How to Fix Selenium Python Screenshots Failing on Simple Webpages
Separate browser capture from file saving, then fix paths, windows, drivers, and viewport issues with a repeatable Selenium diagnostic.
Most Selenium screenshot failures are one of two problems: WebDriver did not return PNG bytes, or Selenium returned bytes but could not write the requested file. Start by testing those layers separately. Use an absolute writable .png path, create its parent directory, check the Boolean result from save_screenshot(), and inspect the current URL and window before diagnosing the browser session.
Selenium documents the operation as saving “the current window to a PNG image file.” The standard call captures the viewport of the active window; it does not promise a full-document image. See the Python WebDriver API for the file and byte-returning methods.
1. Run a minimal diagnostic script
This script removes the common path and session ambiguities. It creates the output directory, resolves an absolute filename, prints the active page, checks the Boolean return, and always quits the driver.
from pathlib import Path
from selenium import webdriver
out = Path("artifacts/selenium-shot.png").resolve()
out.parent.mkdir(parents=True, exist_ok=True)
driver = webdriver.Chrome()
try:
driver.get("https://example.com")
print("url:", driver.current_url)
print("window:", driver.current_window_handle)
saved = driver.save_screenshot(str(out))
print("saved:", saved)
print("path:", out)
print("exists:", out.exists())
if out.exists():
print("bytes:", out.stat().st_size)
finally:
driver.quit()
If saved is False, Selenium reached its file-writing boundary but encountered an operating-system I/O error. If the call raises a WebDriver exception, preserve the complete traceback and investigate the browser session, driver, and selected window. If the file exists but looks empty or incomplete, you have a rendering or viewport problem rather than a simple save failure.
2. Separate capture from file saving
get_screenshot_as_png() returns PNG bytes without asking Selenium to open your destination path. Writing those bytes yourself tells you which layer is broken.
from pathlib import Path
from selenium import webdriver
out = Path("artifacts/bytes-shot.png").resolve()
out.parent.mkdir(parents=True, exist_ok=True)
driver = webdriver.Chrome()
try:
driver.get("https://example.com")
png = driver.get_screenshot_as_png()
print("received bytes:", len(png))
with out.open("wb") as image_file:
image_file.write(png)
print("written:", out, out.exists(), out.stat().st_size)
finally:
driver.quit()
- Byte command fails: inspect the driver session, browser process, active window, and exception.
- Byte command succeeds but manual write fails: inspect the directory, permissions, disk space, filename, and process working directory.
- Both succeed but the image is wrong: check the URL, tab, viewport, page readiness, and whether you expected a full document.
3. Fix path and filesystem errors
Relative paths are resolved from the Python process’s current working directory, which can differ from the directory containing your script, a test runner’s directory, or a service’s working directory. Prefer a resolved absolute path ending in .png.
from pathlib import Path
out = (Path(__file__).parent / "artifacts" / "page.png").resolve()
out.parent.mkdir(parents=True, exist_ok=True)
print("cwd:", Path.cwd())
print("output:", out)
print("parent writable:", out.parent.exists())
| Symptom | Likely cause | Fix |
|---|---|---|
False from save_screenshot |
Filesystem OSError |
Create the parent directory, use an absolute path, check permissions and available space. |
| No file where expected | Relative path resolved from another working directory | Print Path.cwd() and use Path.resolve(). |
| Zero-byte or unreadable file | Interrupted write, storage problem, or later code replaced the file | Write PNG bytes yourself and print the byte count immediately. |
| Permission denied | Container, CI user, sandbox, or read-only mount | Write to a known writable workspace directory and check the process user. |
4. Confirm the current page and window
Screenshot methods operate on the current window. A script that opens a new tab, switches handles, closes a tab, or navigates after your wait can capture a different page than expected.
print("handles:", driver.window_handles)
print("active:", driver.current_window_handle)
print("url:", driver.current_url)
print("title:", driver.title)
# Select a known tab when several are open.
driver.switch_to.window(driver.window_handles[0])
Do this immediately before capture. If the page redirects, print the final URL after get(). If a click opens a tab, wait for the new handle and switch to it explicitly before taking the screenshot.
5. Distinguish viewport screenshots from full-page images
A successful file that omits content below the fold is not a failed save. Selenium's ordinary screenshot methods capture the current window viewport. Full-document capture is a separate browser capability; Firefox exposes a distinct full-page API, and availability differs by browser binding. Do not infer full-page support from the existence of save_screenshot().
For a viewport image, set the window size before navigation or capture:
driver.set_window_size(1440, 1000)
driver.get("https://example.com")
driver.save_screenshot(str(out))
If you need a complete page in a cross-browser workflow, measure the document and use browser-specific scrolling or a full-page feature supported by your chosen driver. Validate the resulting image height; a viewport screenshot and a full-document screenshot answer different questions.
6. Handle WebDriver and browser-session exceptions
When capture raises, keep the complete exception. The title alone cannot identify a root cause. Record:
- Selenium, browser, and driver versions
- Operating system and CPU architecture
- Headless or visible mode and every browser argument
- The exact URL, output path, and complete traceback
- Active window handle and
driver.current_url - Whether
get_screenshot_as_png()behaves differently
Compare the same script in a visible browser. A visible run can reveal a crashed page, unexpected redirect, consent overlay, or browser startup failure that is hidden in headless mode. Selenium's troubleshooting documentation and driver and session guidance are the appropriate references for session errors; without the actual exception, assigning a narrower cause would be speculation.
7. Make page timing explicit
Taking a screenshot immediately after navigation can capture a loading state. Wait for a meaningful condition rather than adding an arbitrary long sleep.
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
# Example: wait for a page element that proves the view is ready.
WebDriverWait(driver, 20).until(
lambda d: d.find_element(By.CSS_SELECTOR, "main").is_displayed()
)
driver.save_screenshot(str(out))
For pages that render content after JavaScript, wait for a stable selector, then use a short, justified delay only when the application has no observable readiness signal. A screenshot can be valid PNG data while still showing a skeleton, login page, or partially loaded content.
8. Common failure checklist
- Classify the symptom: exception,
False, missing file, zero bytes, blank image, wrong tab, or missing below-fold content. - Print and resolve the output path; create its parent directory.
- Check
save_screenshot()'s Boolean result. - Try
get_screenshot_as_png()and write the bytes with Python. - Print the URL, title, active window, and window handles immediately before capture.
- Confirm the browser and driver session is alive and versions are compatible.
- Wait for a page-specific readiness condition.
- Decide whether you need a viewport or full-document image.
- Reproduce in visible mode and retain the complete traceback.
9. Performance, reliability, and cost considerations
Launching a browser and loading a page generally costs more time and resources than writing already-returned PNG bytes. Reuse a driver for a controlled batch of pages, but reset navigation and window state between captures. Always call quit() in a finally block so failed jobs do not leave browser processes behind.
For reliable automation, log the URL, final URL, window handle, viewport dimensions, screenshot method, output path, Boolean result, byte count, and exception text. These fields make capture failures distinguishable from storage failures. Do not report a screenshot as successful solely because no exception was raised.
Selenium itself does not provide a billing layer for screenshots. Your costs come from browser compute, storage, CI minutes, and the pages you load. If you need retries, make them bounded and capture the diagnostic context on the final attempt.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF. Cookie and consent banners are accepted and 60+ known consent platforms, newsletter popups, and chat widgets are removed before capture; each step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status.
See the ScreenshotNeo API documentation for all options. A basic request:
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Relevant options include full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device presets or custom viewports, retina scale, custom CSS and JavaScript, click-before-capture, selector or network-idle waits, blocking ads, trackers, requests or resource types, custom headers, cookies, user agents and Authorization, timezone and geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, usage reporting, and PDF output.
An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. Plans include 1,000 free shots each month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Why does Selenium return False instead of raising?
get_screenshot_as_file() and the corresponding save operation report filesystem I/O failure with a Boolean result. Check the absolute path, parent directory, permissions, and disk space.
Can Selenium save JPEG or WebP with save_screenshot?
The standard Python screenshot file method saves PNG. Convert the returned PNG bytes afterward if your pipeline requires another format.
Does a successful screenshot prove the page finished loading?
No. It proves that the browser returned image data. Wait for an application-specific selector or readiness condition to avoid capturing a loading or partial state.
Why is content below the fold missing?
The ordinary method captures the current window viewport. Use a browser-specific full-page capability or a service that supports full-page capture when you need the complete document.
What information should I include when asking for help?
Include the complete exception or Boolean result, Selenium/browser/driver versions, operating system, headless settings, exact URL, active window handle, output path, and whether byte capture succeeds.


