Fix Selenium Screenshots That Save as a Black Image in Headless Chrome
A black PNG can mean the page rendered incorrectly, capture happened too early, or the headless setup differs from your expected browser. Isolate the cause with this step-by-step Selenium and Chrome checklist.
A black screenshot from headless Chrome has no single documented cause or universal flag that fixes every case. First confirm Selenium successfully wrote the file, then compare headless with headful Chrome, check the Chrome and ChromeDriver versions, and make the browser mode, viewport, and page readiness explicit. Change one variable at a time so you can identify which part of your environment changes the result.
This guide uses Python Selenium. The example is a starting point, not a tested universal fix; adapt the page URL and readiness condition to your application.
1. Confirm the screenshot file was saved successfully
Selenium’s Python save_screenshot() saves the current window to a PNG and returns False if an I/O error occurs. A True result confirms the save operation succeeded, but does not confirm the captured pixels show the page correctly. Use an absolute path while diagnosing, make sure its parent directory exists, and open the exact file Selenium wrote.
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
output = Path("capture.png").resolve()
output.parent.mkdir(parents=True, exist_ok=True)
options = Options()
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
try:
driver.set_window_size(1280, 900)
driver.get("https://example.com")
saved = driver.save_screenshot(str(output))
print(f"saved={saved} path={output} bytes={output.stat().st_size if output.exists() else 0}")
finally:
driver.quit()
The method and its return value are documented in the Selenium Python Chrome WebDriver API. If saved is false, investigate permissions, the destination path, available disk space, or whether the destination can be written. If it is true but the image is black, continue with rendering and capture diagnostics.
2. Compare headless with headful Chrome
Run the same script, URL, browser binary, viewport, and readiness checks with the headless argument removed. This is a diagnostic comparison: if both captures are black, focus first on the page state, navigation, or application itself. If only the headless capture is black, focus on the headless mode, browser environment, and rendering differences.
Keep the comparison controlled. Avoid changing the browser version, viewport, user data directory, and page wait at the same time. Note whether the page is blank before the screenshot, whether only particular regions are black, and whether the problem is consistent across repeated captures.
3. Record Chrome, ChromeDriver, and Selenium versions
Before changing flags, record the versions in the failing runtime. Selenium documents that Chrome and ChromeDriver must match in their major version. Selenium 4 is compatible with Chrome 75 and later, according to its current Chrome-specific documentation; compatibility still depends on the actual browser and driver installed in your environment.
python -c "import selenium; print('Selenium', selenium.__version__)"
chrome --version
chromedriver --version
On Windows, the executable names or paths may differ. Record the resolved browser binary and driver paths when multiple installations exist. In containers and CI, run these commands in the same image and job that creates the black screenshot. See Selenium’s Chrome documentation for the major-version requirement and Chrome options.
4. Check which Headless implementation Chrome is using
Chrome’s newer Headless mode arrived in Chrome 112 and uses the regular Chrome browser code while running without a visible UI. Chrome documents --headless=new as the opt-in flag for that mode. Older Headless behavior came from a separate implementation, so do not assume a flag or result transfers across all Chrome releases.
options = Options()
options.add_argument("--headless=new")
Try this only when it is appropriate for the Chrome release you have installed. Verify the actual browser version rather than assuming a container tag or workstation configuration supplies a particular release. Chrome describes the new mode and its relationship to the earlier implementation in its Headless mode documentation.
5. Set the viewport and wait for the page state you need
Selenium captures the current window, so set its dimensions explicitly and verify them before capture. A page can finish its initial navigation before a client-rendered application, fonts, images, or other important content is ready. Wait for an application-specific condition, such as a visible page heading or a completed loading marker, instead of relying on an arbitrary sleep when you can.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
options = Options()
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
try:
driver.set_window_size(1280, 900)
driver.get("https://example.com")
# Replace this with a selector that means your page is ready.
WebDriverWait(driver, 20).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, "h1"))
)
print("URL:", driver.current_url)
print("Viewport:", driver.get_window_size())
if not driver.save_screenshot("capture.png"):
raise OSError("Selenium could not save capture.png")
finally:
driver.quit()
If the page has no stable heading, wait for a known element, application state, or network-independent readiness signal that your own site exposes. A longer timeout alone does not make a broken page render correctly; use it to distinguish a slow application from a capture problem.
Chrome’s command-line screenshot documentation shows --window-size and --timeout as controls for command-line captures. Those flags are useful context, but Selenium’s workflow should explicitly set the WebDriver window size and wait for the page state your test requires. See the Chrome Headless command-line reference.
6. Inspect the invisible browser with remote debugging
If the capture remains black, inspect what Chrome actually rendered. Chrome documents launching Headless Chrome with --remote-debugging-port, then connecting from a headful Chrome instance through chrome://inspect. This can reveal whether the tab is blank, stuck, on an unexpected URL, or rendering content differently from what you expected.
For a standalone Chrome diagnostic, the documented pattern is:
chrome --headless --remote-debugging-port=0 https://developer.chrome.com/
Chrome prints a DevTools WebSocket address to standard output. Use the address and port as described in the Chrome Headless debugging guide. When Chrome is started through Selenium, make sure any debugging configuration is compatible with the installed Chrome and ChromeDriver setup; do not expose a remote debugging port to an untrusted network.
7. Treat GPU flags as a narrow diagnostic
Do not add --disable-gpu as a default fix. The Chrome Headless FAQ that discusses it was last updated on April 27, 2017. That page describes the flag as a temporary workaround for some bugs, says it is needed only on Windows according to that guidance, and says other platforms no longer require it. This is dated, platform-specific advice, not evidence that the flag fixes current black screenshots generally.
If your operating system and Chrome version make that older guidance relevant, test the flag by itself, compare the output, and remove it if it makes no difference. The same FAQ says headless Chrome does not need Xvfb; installing a virtual display is not a universal remedy for headless capture. See the dated Chrome Headless FAQ.
8. Use a controlled troubleshooting sequence
- Check the save return value, output path, file size, and whether the PNG opens.
- Capture the same page in headful mode without changing other variables.
- Log Chrome, ChromeDriver, and Selenium versions; check the browser and driver major versions.
- Check the active Headless implementation and try
--headless=newwhen supported by the installed Chrome. - Set a predictable window size and wait for a page-specific readiness condition.
- Inspect the Headless tab using Chrome remote debugging.
- Only test
--disable-gpuwhen the dated guidance fits your platform and version context.
Write down the outcome at each step. Comparing Headless with headful, new with legacy where both are available, explicit with default dimensions, and early with ready-state capture helps isolate a variable without obscuring the result.
9. Common errors and what to check
| Symptom | Likely area to investigate | Next step |
|---|---|---|
save_screenshot() returns False |
File I/O, path, permissions, or disk space | Use an absolute path, create the output directory, and verify write access. |
| PNG exists but is black | Rendered page state or Headless-specific difference | Compare headful capture and inspect the Headless page with DevTools. |
| Screenshot shows a blank page or loader | Capture happened before application content was ready, or navigation did not reach the expected page | Check current_url, wait for an application-specific element, and inspect the live tab. |
| ChromeDriver session fails to start | Driver unavailable, incompatible versions, or a different browser binary than expected | Record executable paths and versions; check the Chrome/ChromeDriver major-version match. |
--headless=new is rejected or has no effect |
Flag support differs across Chrome releases or a different binary is running | Confirm the browser version and use the Headless mode supported by that release. |
| Capture is unexpectedly small or cropped | Default or mismatched viewport dimensions | Set the window size explicitly and print driver.get_window_size(). |
Adding --disable-gpu changes nothing |
The dated workaround does not apply to this platform/version or this failure | Remove it and continue isolating mode, version, dimensions, and page readiness. |
10. Reliability, performance, and cost considerations
For repeatable screenshots, pin or otherwise control the browser environment used by local development, CI, and production jobs. Record browser and driver versions with failures, choose a fixed viewport, wait on meaningful application state, and retain the output artifact and relevant logs long enough to compare runs. These practices make regressions easier to reproduce; they do not guarantee a site will render identically across operating systems or browser versions.
Headless mode avoids displaying a browser UI, but this documentation does not establish a universal speed advantage or a performance fix for black captures. Avoid adding flags or arbitrary delays without measuring their effect in your own job. If this is a high-volume capture workflow, account for browser startup, page loading, retries, and the cost of maintaining compatible browser and driver installations; the cited sources provide no benchmark or cost estimate for your workload.
11. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request can return an image or PDF without managing a Selenium and Chrome capture environment. The API supports PNG, JPEG, or WebP output; see the ScreenshotNeo API documentation for request options and configuration.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Responses identify the page verdict and billing status in headers.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for Claude, Cursor, and other MCP clients. - The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card required.
12. FAQ
Does a black PNG prove Chrome saved a corrupt file?
No. A successful save only shows that Selenium wrote the screenshot file. The captured page can still be blank or rendered incorrectly.
Should I install Xvfb for headless Chrome?
Chrome’s dated Headless FAQ says headless Chrome does not need Xvfb. Investigate your actual browser environment before adding a virtual display.
What details help diagnose a specific black capture?
Share the Chrome, ChromeDriver, and Selenium versions; operating system or container image; the launch options; whether headful mode differs; the target page’s readiness condition; and a minimal reproduction. Avoid including API keys, cookies, or private page data.


