ScreenshotNeo

BlogEngineering

Why Do Website Screenshots Differ Between Headless and Headed Chrome?

Headless and headed Chrome can render different screenshots. Match the browser build, viewport, scale factor, page state, and capture timing to narrow the cause.

By the ScreenshotNeo team4 October 202610 min read

Headless and headed Chrome screenshots can differ when the runs use different browser implementations or visual environments. Start by matching the Chrome version and headless implementation, viewport dimensions, device scale factor, orientation, and capture timing. If the page depends on WebGL, WebGPU, canvas, or GPU-backed compositing, compare graphics settings too. There is no single universal cause, and a mismatch does not by itself prove a Chrome rendering bug.

This guide shows how to make a repeatable comparison with Chrome’s command line, what to record, how to investigate remaining differences, and how to avoid mistaking a page-state or setup difference for a browser rendering issue.

1. What “headless” means in this comparison

Headless Chrome runs without a visible browser window. Chrome’s current headless mode uses Chrome itself; older workflows may use the separate chrome-headless-shell binary. The old shell is described as lighter, while current headless mode is intended to provide more authentic results for high-accuracy end-to-end testing. Treat those implementations as separate configurations rather than assuming their screenshots will match. Chrome’s headless documentation describes the distinction.

“Headed” means Chrome is running with its normal visible browser window. Even if both runs use Chrome, a screenshot comparison is meaningful only when the browser build, page inputs, viewport, scale, and capture state are aligned.

2. The main causes of screenshot differences

Variable How it can change the image What to match or record
Chrome build and implementation Different versions or the old headless shell can have different browser behavior. Exact version, executable path, and whether the run uses current --headless or chrome-headless-shell.
Viewport and screen Page layout can respond to viewport dimensions and screen configuration. Width, height, orientation, and screen settings.
Device scale factor Changes the mapping between CSS pixels and output pixels, affecting screenshot dimensions and rasterization. Device scale factor and resulting image dimensions.
Page state and timing Animations, time-dependent scripts, delayed fonts or images, network responses, and client-side data can alter the captured frame. Same data and browser profile assumptions; an explicit readiness condition and capture delay.
Graphics configuration GPU and graphics backend differences may matter for WebGL, WebGPU, canvas, or compositing. GPU availability and backend when the page uses graphics APIs.

Chrome documents viewport and device scale factor as rendering inputs. Chrome 142 adds documented virtual-screen configuration for headless, including screen size and scale factor. Do not assume those settings are available in older releases. Chrome 142 virtual screens.

3. Reproduce the difference with Chrome’s command line

Use the same Chrome executable for both runs where possible. First record its version, then take a headed screenshot and a current-headless screenshot at the same window size. Replace the example URL and output paths with your own.

# Record the version of the Chrome executable you will use
/path/to/chrome --version

# Headed capture (a browser window opens)
/path/to/chrome --window-size=1440,1000 --screenshot=/tmp/headed.png https://example.com

# Current headless capture
/path/to/chrome --headless --window-size=1440,1000 --screenshot=/tmp/headless.png https://example.com

Chrome documents --screenshot and --window-size in its headless command-line guide. A headed screenshot may require a desktop session and a clean test profile; adapt the executable path and profile handling to your OS and installation.

Make capture timing explicit

A fixed timeout is useful for repeatability, but it does not prove that an application is ready. Chrome’s command-line reference documents --timeout for capture timing and --virtual-time-budget for advancing time-dependent code in headless runs. Use the same timing approach in both modes. For application-specific pages, wait for the actual state you need, such as a known selector, loaded data, fonts, or images, in your automation script. A Chrome flag cannot guarantee that a site’s asynchronous work is complete.

# Example headless capture with a bounded wait and virtual time budget
/path/to/chrome --headless --window-size=1440,1000 \
  --timeout=5000 --virtual-time-budget=5000 \
  --screenshot=/tmp/headless-after-wait.png https://example.com

Consult the Chrome headless documentation for the supported flags and adapt timing to the page. A virtual-time budget is not equivalent to waiting for every external network request.

Compare outputs and metadata

Check both the image dimensions and the pixels. If the dimensions differ, first fix the viewport or scale setup. If dimensions match but pixels differ, inspect the regions that changed and correlate them with page state, fonts, images, animations, or graphics. Keep the original screenshots and the configuration notes together so another run can reproduce the comparison.

# ImageMagick example: report dimensions and compare pixel differences
identify /tmp/headed.png /tmp/headless.png
compare -metric AE /tmp/headed.png /tmp/headless.png /tmp/diff.png

This comparison command requires ImageMagick. The absolute-error metric counts differing pixels; it does not explain why they differ. A diff image is a debugging aid, not a universal pass/fail threshold.

4. A practical comparison checklist

  1. Record the browser. Save the Chrome version, executable used, operating system, and whether the run uses current --headless or the old chrome-headless-shell.
  2. Hold the page inputs steady. Use the same URL, user/session assumptions, data, cookies, and application state. Avoid comparing a warm, authenticated headed profile with a fresh headless profile unless that difference is intentional.
  3. Set visual dimensions deliberately. Match viewport width and height. Record orientation and screen configuration where applicable, plus device scale factor and final screenshot dimensions.
  4. Choose a readiness condition. Decide what must be present before capture. Use the same selector, delay, or other page-specific condition for each run; document it.
  5. Control animation and time. If the page changes over time, use a consistent time budget or wait strategy. Ensure both captures target the same frame or state.
  6. Inspect graphics only when relevant. If the mismatch involves WebGL, WebGPU, canvas, or compositing, record GPU and graphics backend settings and compare like with like.
  7. Repeat the pair. Re-run both captures with the same setup. If the mismatch moves or disappears, investigate nondeterministic page state before attributing it to rendering.
  8. Label the residual carefully. Once these inputs match, describe the remaining pixels as an observed difference. The available Chrome guidance does not establish one universal cause or priority order for every discrepancy.

5. Viewport, scale factor, and virtual screens

--window-size=WIDTH,HEIGHT sets the headless window dimensions in the CLI examples. It is important to distinguish the browser’s viewport in CSS pixels from the screenshot’s output dimensions in device pixels: device scale factor affects that relationship. When using browser automation, set both viewport size and device scale factor explicitly if the tool supports them, and report the actual screenshot dimensions.

For current headless Chrome, virtual-screen configuration is documented in stable Chrome beginning with version 142. It allows screen size and scale factor to be configured for headless. Check your version before depending on it, and do not silently compare a virtual-screen run with a run using different screen assumptions. See Chrome’s virtual-screen configuration guidance.

6. Page readiness and timing

Two screenshots taken at different stages of page loading can differ even when Chrome is configured identically. A navigation event or fixed delay may be too early for an app whose data, fonts, lazy images, or widgets load later. Define readiness in terms of the page you need to capture:

  • Wait for a stable element that indicates the relevant UI has rendered.
  • Wait for required data to appear, rather than relying only on elapsed time.
  • Make font and image loading part of the capture setup if they affect the region under comparison.
  • Disable or wait for animation when a specific frame matters.
  • Use the same state and wait logic in headed and headless runs.

Chrome’s --timeout and --virtual-time-budget flags provide CLI timing controls, but the application determines what “ready” means. Treat readiness as part of your test, not as a guarantee from a browser flag. Flag details are in the Chrome headless guide.

7. When GPU settings are worth investigating

Look at GPU and graphics backend settings when the page uses WebGL, WebGPU, canvas rendering, or effects that depend on compositing. Chrome’s documented Linux guidance for a particular WebGPU/WebGL setup says GPU is disabled by default there and describes settings for enabling it. That guidance is scoped to that configuration; it does not establish that GPU differences explain ordinary screenshot mismatches across all operating systems, Chrome versions, or pages. Chrome’s WebGPU troubleshooting guidance.

For a graphics-dependent discrepancy, record the environment and compare the same backend and GPU availability in both runs where possible. If you cannot make the environments equivalent, report the difference as a limitation of the comparison.

8. Troubleshooting common mismatches

Symptom Likely setup issue What to try
Whole page layout shifts or wraps differently Viewport width, screen size, or responsive state differs. Set the same width and height; verify the rendered viewport and screen assumptions.
Screenshot dimensions differ Device scale factor or output scaling differs. Set and record scale factor; compare actual image dimensions before pixel diffs.
Fonts or text edges differ Different Chrome build, platform font availability, font loading state, or scale factor. Use the same build and environment where possible; wait for fonts; record OS and scale.
Images or content are missing in one capture Capture happened before loading, the page state differs, or the profile/network inputs differ. Wait for the relevant image or content; align data, cookies, profile assumptions, and timing.
Animated content differs between runs The captures target different points in time. Freeze or disable animation in test setup, or synchronize the capture state.
WebGL/WebGPU/canvas region differs GPU availability or graphics backend may differ. Record GPU configuration and compare like with like; keep platform-specific guidance in scope.
Difference appears only with the old headless binary The runs use different headless implementations. Repeat with current --headless and document which binary is used.
CLI screenshot captures an unexpected moment Default navigation/capture timing does not match the app’s readiness needs. Use documented timeout/virtual-time options and a page-specific readiness condition where needed.

9. Reliability, performance, and cost considerations

A repeatable screenshot workflow depends more on controlling inputs than on adding arbitrary wait time. Explicit browser versions and page readiness conditions make failures easier to reproduce. Longer waits can reduce captures taken too early, but they also increase job time and still do not guarantee that a page reached the intended state. Prefer a specific readiness condition and bounded timeout, then capture failure details such as browser version, URL, viewport, timing, and output dimensions.

Headless mode is often convenient for automated capture because it does not need a visible window, while headed mode can help inspect what the browser displayed. The Chrome documentation describes the old headless shell as lighter and current headless as more authentic for high-accuracy end-to-end testing; do not infer a general performance ratio from that description. The reviewed sources provide no controlled benchmark comparing all modes or environments.

For self-hosted capture, account for the engineering time to install and update Chrome, manage browser processes and profiles, and diagnose page-specific readiness. If screenshots are an occasional task, a managed API can avoid maintaining that browser setup; costs then depend on the service’s published plan and what it bills.

10. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request can return a PNG, JPEG, WebP, or PDF. For example, this cURL request captures a page:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for the request options. The Python and Node.js equivalents are:

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}`);

ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets 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, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf 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, and every feature is on every plan.

Create a free ScreenshotNeo account and get 1,000 screenshots a month with no card.

11. Frequently asked questions

Does a different screenshot prove there is a Chrome bug?

No. First align the browser build, mode, viewport, scale, page inputs, and capture state. A remaining difference is an observation to investigate, not proof of a particular cause.

Should I use current headless or the old headless shell?

Choose the implementation that matches your testing goal and production setup, then keep it consistent. Chrome describes current headless as more authentic for high-accuracy end-to-end testing and the old shell as lighter.

Does --virtual-time-budget wait for every request?

No. It controls virtual time for time-dependent code in headless Chrome. Use application-specific readiness checks for content that depends on asynchronous work.

When can headless configure a virtual screen?

Chrome documents virtual-screen configuration in stable Chrome starting with version 142. Confirm the version and supported options in the Chrome guidance before relying on it.

Do I need to investigate GPU settings for every mismatch?

No. Prioritize that investigation when the page uses graphics APIs or GPU-backed rendering. The cited Chrome guidance is scoped to a Linux WebGPU/WebGL setup.