How to troubleshoot blank screenshots in headless Chrome visual tests
Diagnose blank screenshots by checking page content, capture timing, viewport dimensions, browser mode and DevTools Protocol behavior.
A blank screenshot in a headless Chrome visual test can mean the page never populated, the capture happened too early, the viewport differed from the test layout, or the browser mode or automation path behaved differently than expected. Start by checking the rendered DOM, then isolate timing, dimensions, browser mode and capture commands one at a time.
This guide uses Chrome’s documented command-line controls and distinguishes evidence from diagnosis: a nonempty DOM shows that content was serialized after scripts ran, but it does not prove that the content painted successfully.
1. Confirm which page Chrome loaded
Before changing screenshot flags, verify the target URL and inspect the DOM Chrome produced. The --dump-dom option serializes the DOM after parsing and script execution. It helps answer whether the app populated the page at all.
google-chrome --headless --dump-dom 'https://example.com' > page.html
On systems where the executable is named chromium or chromium-browser, substitute that binary. Inspect page.html for the expected application content, title, and any error or loading state.
- Expected content is absent: investigate the URL, navigation, app startup, data loading, authentication, and runtime errors before debugging the image file.
- Expected content is present: the page populated in the serialized DOM. Continue to rendering and screenshot timing checks; this alone does not establish that pixels were painted.
Chrome’s Headless CLI reference documents --dump-dom and the screenshot controls below.
2. Check when capture happens
A page can have DOM content before its visual state is ready. Animations, timers, client-side rendering, image loading, and asynchronous data can make a screenshot taken too soon look empty or incomplete.
Chrome’s --timeout sets the maximum wait before a screenshot is captured, even if loading continues. --virtual-time-budget is a separate control that fast-forwards time-dependent page code. They address different timing questions; choose values based on the application’s readiness requirements.
google-chrome --headless \
--timeout=5000 \
--screenshot=shot.png \
'https://example.com'
For a page that depends on timers, compare with a run using a virtual time budget:
google-chrome --headless \
--virtual-time-budget=5000 \
--screenshot=shot-virtual-time.png \
'https://example.com'
These values are examples, not universal settings. Increasing a timeout can help test a timing hypothesis, but it does not fix a page that never reaches its intended state. Likewise, virtual time is useful for time-dependent code and should not be treated as a generic substitute for understanding readiness.
3. Make the viewport explicit
Viewport-dependent layouts can render differently when Chrome uses dimensions other than the visual test’s intended size. Set --window-size to match the dimensions used by the test.
google-chrome --headless \
--window-size=1440,900 \
--screenshot=shot.png \
'https://example.com'
Use the same width and height when comparing local output with CI. A responsive app may hide content, move it off-screen, or switch layouts at a breakpoint. If the image exists but appears blank, check its dimensions and inspect the captured viewport before concluding that the page failed to load.
4. Record the exact Headless Chrome mode
“Headless Chrome” can refer to different implementations. Chrome 112 updated Headless so Chrome creates platform windows without displaying them. Chrome also documents a separate chrome-headless-shell: it is lighter, while current Headless runs the real Chrome browser and is suited to higher-fidelity end-to-end testing.
When a screenshot differs between a developer machine and CI, record the executable, version, and mode in both environments. Compare the same mode first, then test the other mode if available. This helps distinguish a browser-mode difference from an application or timing difference.
| Choice | Useful when | Tradeoff to consider |
|---|---|---|
| Current Chrome Headless | You need the real Chrome browser path and high-fidelity end-to-end testing. | Use the same Chrome version and environment as the browser path you want to match. |
chrome-headless-shell |
You want a lighter option for screenshotting or scraping. | It is a separate implementation, so its output may not match current Chrome Headless exactly. |
See Chrome’s documentation on Headless mode and the Headless shell for the distinction and platform guidance.
5. Inspect scripted browser communication
If a test drives Chrome through automation, inspect whether navigation and screenshot commands actually happened and in what order. Chrome’s Headless shell documentation points to DevTools Protocol debugging for programmatic Headless use.
- Capture browser logs and the automation tool’s navigation and screenshot errors.
- Use protocol-level inspection to confirm that the intended target was navigated and that the screenshot command ran after the expected page state.
- Compare the scripted run with a direct CLI screenshot using the same binary, URL, viewport, and timing controls.
A mismatch between direct CLI output and the automation result narrows the investigation toward the automation path or its command sequence. It does not by itself identify the cause; use logs and protocol events to locate the failing step.
6. Remove obsolete flags from the diagnosis
Do not assume that every Linux Headless run needs Xvfb or that every blank capture needs --disable-gpu. Chrome says Headless does not require Xvfb, and documents --disable-gpu only as a temporary workaround needed on Windows. Treat flags as version- and platform-specific, and verify them against the Chrome documentation for the environment in question.
7. Isolate the failing variable
Chrome’s documented controls provide diagnostic levers, not a universal root cause. If the DOM is populated but the capture remains blank, compare a minimal page against the application while changing one variable at a time.
- Run a minimal page through the same browser binary, mode, viewport, and screenshot path.
- Run the application with those same settings.
- Change only one factor, such as timing or viewport, and compare the outputs.
- Repeat with the other Headless implementation only if the environment offers both.
If the minimal page is also blank, focus on the browser binary, mode, environment, or capture path. If it works while the application does not, focus on the app’s content, rendering, or readiness behavior. This comparison is a diagnostic recommendation based on Chrome’s documented controls, not a guaranteed diagnosis.
Troubleshooting common symptoms
| Symptom | Likely area to inspect | Next step |
|---|---|---|
| DOM dump lacks the expected app content | Wrong URL, navigation failure, app startup, or content population | Verify the URL and inspect the serialized DOM and browser logs before debugging the image. |
| DOM has content, but the screenshot is blank | Paint, capture timing, automation sequence, or environment | Check capture timing and protocol activity; compare a minimal page using the same path. |
| Screenshot differs between local and CI | Browser version, Headless mode, viewport, or platform flags | Record and align the binary, version, mode, dimensions, and relevant flags. |
| Content appears only after a delay | Page readiness or timer-dependent behavior | Use --timeout to investigate capture wait, or --virtual-time-budget for time-dependent code; tune to the app’s actual requirements. |
| Layout is missing or unexpectedly rearranged | Viewport breakpoint or dimensions | Set --window-size to the visual test’s intended viewport. |
| A legacy recipe requires Xvfb or disables GPU | Version-specific or platform-specific assumptions | Check Chrome’s current platform guidance; Headless does not require Xvfb, and the GPU flag is not universal. |
Performance, reliability, and cost considerations
Longer waits can improve captures of slow pages but also increase the time a visual test spends waiting. Use timing controls to match the application’s real readiness behavior, rather than raising all timeouts indiscriminately. Explicit dimensions and a recorded browser mode make runs easier to compare. For reliability, capture enough diagnostic evidence to distinguish missing DOM content from a later rendering or screenshot problem.
The Chrome documentation cited here describes browser controls, not a universal blank-screenshot failure rate or benchmark. The appropriate wait, viewport, and browser mode depend on the app and test environment.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. Its API can return a screenshot or PDF with one GET request. For the self-hosted Chrome checks above, keep the browser binary, viewport, and timing under your control; for a managed capture, use this request instead:
See the ScreenshotNeo API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo accepts cookie and consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets before the capture; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Does a nonempty DOM dump prove the page rendered?
No. It confirms that the serialized DOM contains content after parsing and script execution. It does not prove the content painted successfully.
Should I always use --virtual-time-budget?
No. It is a diagnostic control for time-dependent page code. Use it when the page’s behavior depends on timers and choose a budget based on that behavior.
Does Headless Chrome need Xvfb?
Chrome’s documentation says Headless does not require Xvfb.
Is --disable-gpu required in CI?
It is not a universal requirement. Chrome documents it as a temporary workaround for Windows.


