Selenium Scrolling Screenshot Misses Content at the Bottom: How to Fix It
A Selenium screenshot may capture only the current viewport. Diagnose the crop, choose a full-document method, and handle content that loads on scroll.
If a Selenium screenshot misses the bottom of a page, first check whether your screenshot call captures the current window or the full document. Selenium’s ordinary screenshot methods capture the current window; they do not automatically guarantee a full-page image. Use Firefox’s full-document screenshot method when running Selenium Python with Firefox, WebDriver BiDi’s document capture where your browser and driver support it, or a validated scroll-and-stitch method when scrolling is needed to load the content.
The fix depends on what is missing: content beyond the document viewport, content that loads only after scrolling, or content inside a separately scrollable element. This guide helps you distinguish those cases and choose the capture method.
1. Identify what your screenshot captures
Start by recording the browser, browser version, Selenium language binding and version, driver version, and the exact screenshot method. Selenium’s common Python screenshot API describes a screenshot of the current window. Firefox’s Python API separately documents full-document screenshot methods. These are different capture jobs.
- Viewport/current-window capture: captures what is visible in the current browsing context.
- Full-document capture: captures the document beyond the visible viewport, if the browser and API support it.
- Scroll-and-stitch: scrolls through the page, waits for content, captures segments, and combines them. This can help when scrolling triggers lazy loading, but it requires layout-specific handling.
Before changing code, open the saved image and compare its dimensions with the viewport and the page’s expected content. If the image is about one viewport tall, you are likely using a viewport capture. If it is taller but still lacks content, check loading behavior, nested scroll containers, horizontal overflow, and capture limits.
2. Use Firefox’s full-document screenshot API with Selenium Python
When using Selenium Python with Firefox, call the explicit full-document method. The Selenium Firefox API documents both get_full_page_screenshot_as_file() and save_full_page_screenshot() as saving a full-document screenshot to PNG. Use a full output path ending in .png and check the returned boolean.
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.firefox.options import Options
output = Path("/tmp/page-full.png").resolve()
options = Options()
# Uncomment to run without opening a visible browser window.
# options.add_argument("-headless")
driver = webdriver.Firefox(options=options)
try:
driver.get("https://example.com")
saved = driver.get_full_page_screenshot_as_file(str(output))
if not saved:
raise RuntimeError(f"Selenium did not save the screenshot to {output}")
print(f"Saved full-page screenshot: {output}")
finally:
driver.quit()
If your Selenium Python installation exposes save_full_page_screenshot() instead, use it with the same full path:
saved = driver.save_full_page_screenshot("/tmp/page-full.png")
if not saved:
raise RuntimeError("Full-page screenshot could not be saved")
Check the installed API if either method is missing. Your binding or installed Selenium version may not expose the method shown in the current Firefox API documentation. This Firefox-specific Python example is not a general cross-browser WebDriver guarantee. See the Selenium Firefox WebDriver API documentation.
3. Try WebDriver BiDi document capture where supported
WebDriver BiDi’s browsingContext.captureScreenshot command has an origin option. The default is viewport; set it to document to capture the entire scrollable document beyond the viewport. Browser and driver support varies, so confirm that your actual session exposes the command before building a workflow around it. The example below shows the BiDi command parameters; how you send a BiDi command depends on the client binding and its supported API.
{
"method": "browsingContext.captureScreenshot",
"params": {
"context": "YOUR_BROWSING_CONTEXT_ID",
"origin": "document"
}
}
The command returns screenshot data that your client must decode and write as an image. Check your binding’s BiDi documentation for session setup, command dispatch, and response decoding. Do not assume that an ordinary viewport screenshot becomes a document screenshot just because the page is scrolled to the top. See MDN’s WebDriver BiDi captureScreenshot reference.
4. Load lazy content with a scroll-and-stitch approach
A full-document screenshot cannot include content that has not been loaded or rendered yet. Lazy-loaded images, infinite lists, and scroll-triggered sections may need actual scrolling before capture. A scroll-and-stitch workflow captures successive views after scrolling and combines them. WebdriverIO describes this strategy for lazy-loaded content and complex layouts; Selenium users need to implement and validate an equivalent approach for their own browser and page.
- Navigate to the page and wait for its initial content.
- Measure the document height and viewport height.
- Scroll down by a measured step, leaving overlap between captures.
- Wait for the content expected at that position to appear or for the layout to settle.
- Capture each segment, then stitch the images and inspect the seams.
- Recheck document height during the process; lazy loading may extend the page.
Here is a runnable Selenium Python capture loop that saves viewport segments for later stitching. It scrolls in increments with overlap, waits briefly after each move, and checks for page growth. It does not stitch the output images: stitching and seam correction depend on the page layout and image tooling you use.
from pathlib import Path
import time
from selenium import webdriver
from selenium.webdriver.firefox.options import Options
out = Path("/tmp/selenium-segments")
out.mkdir(parents=True, exist_ok=True)
options = Options()
# options.add_argument("-headless")
driver = webdriver.Firefox(options=options)
try:
driver.set_window_size(1440, 900)
driver.get("https://example.com")
time.sleep(1) # Replace with an explicit wait for your page when possible.
viewport = driver.execute_script("return window.innerHeight")
step = max(1, int(viewport * 0.8)) # 20% overlap
y = 0
index = 0
previous_height = 0
stable_checks = 0
while stable_checks < 3:
height = driver.execute_script(
"return Math.max(document.documentElement.scrollHeight, "
"document.body ? document.body.scrollHeight : 0)"
)
driver.execute_script("window.scrollTo(0, arguments[0])", y)
time.sleep(0.5) # Allow scroll-triggered content to load.
driver.save_screenshot(str(out / f"segment-{index:04d}.png"))
index += 1
new_height = driver.execute_script(
"return Math.max(document.documentElement.scrollHeight, "
"document.body ? document.body.scrollHeight : 0)"
)
if new_height <= previous_height and y + viewport >= new_height:
stable_checks += 1
else:
stable_checks = 0
previous_height = new_height
if y + viewport >= new_height:
y = max(0, new_height - viewport)
else:
y += step
print(f"Saved {index} viewport segments in {out}")
finally:
driver.quit()
For production use, replace fixed sleeps with waits for a page-specific condition, such as the next card appearing or a loading indicator disappearing. The loop above is a starting point, not a universal stitching algorithm: page height can keep growing, and fixed headers, sticky elements, animations, overlapping captures, or inner scrollers can create duplicated or missing pixels.
See the WebdriverIO visual testing method options for its documented scroll-and-stitch approach. That source describes WebdriverIO behavior, not native Selenium support.
5. Check inner scroll containers and horizontal overflow
The missing content may not belong to the document’s main scroll. Applications often put a table, chat panel, or results list inside an element with overflow: auto or overflow: scroll. Scrolling the window will not reveal the bottom of that inner element. Inspect its scrollHeight, clientHeight, and scroll position, then scroll the element itself before taking the screenshot.
details = driver.execute_script("""
const root = document.documentElement;
const body = document.body;
const candidates = [...document.querySelectorAll('*')]
.filter(el => {
const style = getComputedStyle(el);
const scrollsY = /(auto|scroll|overlay)/.test(style.overflowY);
return scrollsY && el.scrollHeight > el.clientHeight + 2;
})
.map(el => ({
tag: el.tagName,
id: el.id,
className: String(el.className),
clientHeight: el.clientHeight,
scrollHeight: el.scrollHeight,
overflowY: getComputedStyle(el).overflowY
}));
return {
document: {
clientWidth: root.clientWidth,
scrollWidth: root.scrollWidth,
clientHeight: root.clientHeight,
scrollHeight: Math.max(root.scrollHeight, body ? body.scrollHeight : 0)
},
scrollContainers: candidates
};
""")
print(details)
If the document’s scrollWidth exceeds its clientWidth, there is horizontal overflow. A historical geckodriver issue reported a particular full-page capture case returning only the viewport when horizontal overflow was present. Treat that report as a clue to investigate, not evidence that current Firefox or all full-page methods always fail this way. See geckodriver issue 1580.
6. Choose the capture method
| Method | Use it when | Check |
|---|---|---|
| ScreenshotNeo full-page capture | You want a website screenshot without maintaining browser setup. | Set full-page capture, and check response headers for the page verdict and billing status. |
| Firefox Python full-document API | Your stack is Selenium Python with Firefox. | Method availability, absolute PNG path, and the boolean save result. |
| WebDriver BiDi with document origin | Your browser, driver, and client expose the BiDi screenshot command. | Use origin: "document" and verify support in the actual session. |
| Scroll-and-stitch | Scrolling is required to load content or the layout needs controlled segments. | Handle overlap, page growth, fixed elements, and nested scrollers. |
| Ordinary WebDriver screenshot | You need the currently visible window or viewport. | It is not, by itself, a full-document capture method. |
For the full-page methods, compare browser and binding support, whether scrolling must trigger loading, whether content lives in an inner scroller, and how much capture and stitching logic you want to maintain. Selenium’s common screenshot API documents a current-window screenshot; see the Selenium common WebDriver API documentation.
7. Troubleshoot common failures
| Symptom | Likely cause | What to do |
|---|---|---|
| Image ends exactly at the viewport boundary | The selected method captures the current window. | Use a documented full-document method for your browser or BiDi document origin where supported. |
| Firefox full-page method raises an attribute error | Your installed binding does not expose that method. | Check the installed Selenium Firefox API and version; use a supported capture route for your stack. |
| Screenshot call returns false or file is absent | Output path, permissions, or file I/O failed. | Use an absolute path with a .png extension, ensure the directory exists, and check the return value. |
| Bottom section is blank or images are missing | Content has not loaded yet, or requires scrolling to trigger lazy loading. | Wait for page-specific content and scroll through the relevant areas before capture. |
| Some content remains missing despite full-document capture | It may be inside a nested scroll container rather than the document. | Inspect overflow and scroll dimensions, then scroll the container itself. |
| Capture is clipped or oddly sized with wide content | Horizontal overflow or browser-specific capture behavior may be involved. | Inspect document width and reproduce with a minimal page. The historical geckodriver report is one configuration, not a universal rule. |
| Stitched image has bands or repeated content | Segments overlap incorrectly, page layout moved, or sticky elements were captured at different positions. | Use stable page conditions, calculate overlap, and validate seams; consider hiding or accounting for sticky elements in your own capture process. |
| Last segment omits content after scrolling | Page height grew after lazy content loaded, or the loop stopped too early. | Re-measure height after each scroll and wait for a page-specific loading condition before deciding the page is complete. |
8. Performance, reliability, and cost considerations
A full-document screenshot is a single capture operation, but a very tall page can still produce a large image and consume browser memory. Scroll-and-stitch adds browser interactions, waits, image files, and post-processing; overlap improves seam tolerance but increases work and output size. Use a bounded wait and a page-specific completion condition so an infinite feed or continuously changing page does not keep extending the capture.
For repeatable results, hold the viewport size constant, wait for fonts and key content, disable or wait out animations where appropriate, and capture only after the layout settles. Record the browser and driver versions with visual regression artifacts. If captures run in parallel, keep browser memory and the number of active sessions within the capacity of the machine or worker.
ScreenshotNeo is a managed website screenshot API, so it avoids setting up and maintaining a Selenium browser for this capture. It accepts full-page capture with lazy images loaded. Its stated billing policy charges only for clean shots: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; response headers report the page verdict and billing status. Pricing is free for 1,000 shots per month with no card, then Starter is $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. See ScreenshotNeo for the service and the API documentation for request options.
Or skip the browser setup
Make one GET request for a full-page WebP screenshot. See the ScreenshotNeo API docs for the request parameters and other formats.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com --data-urlencode full_page=true -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com", "full_page": "true"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com',
full_page: 'true'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot; each removal step can be turned off. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000.
Sign up for ScreenshotNeo and get 1,000 screenshots a month free, with no card.
FAQ
Why does my Selenium screenshot stop at the bottom of the browser window?
The method may capture only the current window or viewport. Use a full-document capture API supported by your browser and binding.
Will a full-page screenshot include lazy-loaded images?
Only if they have loaded by capture time. Scroll to trigger them and wait for the relevant images or content to appear.
Can I fix this by scrolling to the bottom before taking a screenshot?
That may trigger lazy loading, but an ordinary screenshot taken afterward still captures the current viewport. Use a full-document method or capture and stitch multiple views.
Does Selenium support the same full-page screenshot call in every browser?
No single cross-browser guarantee is established here. Check the API for your browser and binding; the explicit method described above is for Selenium Python with Firefox.
What if the missing area is inside a page panel?
Scroll the panel’s element rather than the window, then capture that state or its contents using a method suited to the task.


