Chrome Headless Screenshot Works Locally but Fails in GitHub Actions: Fix
Diagnose why a Chrome Headless screenshot succeeds locally but fails in GitHub Actions. Identify the failure type, then fix browser installation, Linux dependencies, sandboxing, timeouts, or rendering differences.
A Chrome Headless screenshot that works on your machine but fails in GitHub Actions usually points to a difference between the local and CI environments. The browser binary, its Linux libraries, sandbox support, runner image, fonts, or execution timing may differ. There is no single fix that applies to every failure.
Start by classifying what failed: browser launch, page navigation, screenshot timeout, or visual mismatch. Then record the framework, versions, runner OS, exact command, and full error before changing the workflow.
This guide covers Puppeteer and Playwright. Their browser installation and CI setup differ, so use the section for your framework. For the most reliable diagnosis, change one thing at a time and rerun the same capture.
1. Classify the failure before changing the workflow
Find the first failing operation in the logs. A screenshot error may be a downstream symptom of a browser that never launched or a page that never finished navigating.
| Symptom | Likely area to inspect | First useful check |
|---|---|---|
Failed to launch browser, missing executable, or process exits immediately |
Browser installation, binary path, required shared libraries, or sandbox setup | Confirm the expected browser exists; retain the complete launch error and stderr |
| Browser starts, but navigation rejects or never completes | URL, network access, redirects, TLS, application readiness, or navigation wait condition | Log the requested URL, navigation error, response status, and elapsed time |
| Page opens, but screenshot times out | Screenshot wait conditions, slow page resources, selector never appearing, or a case-specific browser issue | Separate navigation from screenshot; log which operation timed out |
| Screenshot succeeds but looks different | Different browser version, OS, fonts, viewport, device scale factor, or page state | Compare capture settings and runtime versions; make local and CI environments alike |
Keep the actual error text. For example, No usable sandbox is a specific launch diagnosis; it does not explain a navigation timeout or a visual difference.
2. Record the CI and local environments
Before changing configuration, collect the same details in both places. This makes it possible to tell whether a change fixed the cause or merely changed timing.
- Automation framework and exact package version: Puppeteer or Playwright.
- Node.js version and package manager.
- GitHub Actions runner label and operating system.
- Installed Chrome or Chromium version, and how it was installed.
- The failing command and complete error output, including browser stderr.
- Whether the failure happens at launch, navigation, screenshot, or image comparison.
- Capture settings that affect output: URL, viewport, device scale factor, wait condition, and any extensions or custom browser arguments.
Do not assume local Chrome is the same binary used by the automation package. Puppeteer normally downloads Chrome for Testing; an install policy that blocks package scripts can prevent that download. Playwright has its own browser installation commands and CI guidance.
3. Puppeteer: check the browser installation and Linux libraries
First verify that Puppeteer installed the browser expected by the installed package version. Its installation guide documents browser installation and explains what to do if the browser download was skipped. If your package manager blocks install scripts, either allow Puppeteer’s install script according to your project policy or install the browser explicitly using the current Puppeteer instructions.
Do not guess a browser path or copy an old binary path from another workflow. Installation behavior and browser versions can change. Consult the Puppeteer installation guide for the package version in your project.
On Linux, a present browser can still fail because a shared library is missing. Puppeteer’s troubleshooting guide suggests using ldd on the Chrome executable to identify missing dependencies. Run it against the actual executable path found in CI:
ldd /path/to/chrome | grep "not found"
If the command reports missing libraries, install the required operating-system packages in the runner or use an environment that already includes them. Use the dependency list in the Puppeteer troubleshooting guide; the exact packages depend on the runner image and browser build.
4. Puppeteer: handle sandbox errors as a security configuration issue
If the error explicitly says No usable sandbox, investigate how the runner or container is configured to support Chrome’s sandbox. Do not treat that message as a generic reason to append a launch flag. Puppeteer’s troubleshooting documentation says: “Running without a sandbox is strongly discouraged. Consider configuring a sandbox instead.”
In particular, avoid making --no-sandbox the routine fix. It disables a browser security boundary. Check the runner’s sandbox and container configuration and follow the guidance for your execution environment in the Puppeteer troubleshooting documentation. A report of this error in one GitHub Actions Linux runner context does not establish that every CI failure has the same cause.
5. Playwright: start with its documented GitHub Actions setup
For Playwright, use the official CI setup as the baseline. Its GitHub Actions guide installs the project dependencies and the browsers plus operating-system dependencies with npx playwright install --with-deps. Keep the Playwright package and browser installation aligned; do not install an unrelated system Chrome and assume it is an equivalent substitute.
A minimal workflow shape is shown below. Use the current action versions and workflow recommended by the Playwright CI documentation, since the documentation example and action versions can change.
name: Playwright screenshots
on: [push, pull_request]
jobs:
screenshots:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run test
Set node-version and the test command to match your project. If the browser still fails to launch, enable Playwright’s documented browser launch logging:
DEBUG=pw:browser npx playwright test
Review the output around process startup for the executable, launch arguments, and browser stderr. The Playwright CI guide also describes running in a container as a way to make screenshot and visual regression environments more consistent across operating systems. If you use its container example, match the image tag to your installed Playwright version; tags such as mcr.microsoft.com/playwright:v1.63.0-noble are version-specific examples, not a permanent default.
6. If launch works, diagnose navigation and screenshot timeouts separately
A screenshot timeout does not necessarily mean Chrome failed to start. Add logs around each operation so the failing stage is clear:
console.log('Opening page');
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
console.log('Page navigation completed');
await page.screenshot({ path: 'screenshot.png', fullPage: true, timeout: 30000 });
console.log('Screenshot saved');
This example uses a DOM-ready navigation condition to make the boundary between navigation and capture visible. Choose a readiness condition that matches the page you need to capture; changing from a stricter wait can alter what appears in the image.
- Confirm the URL is reachable from the GitHub-hosted runner and record navigation failures or response status.
- If waiting for a selector, verify it exists in CI and that the selector is not dependent on local data or a local service.
- If the page relies on a server started by the workflow, ensure the server is ready before navigation.
- Distinguish a slow page from a browser process that exited. The logs and process error differ.
- Increase a timeout only when the page legitimately needs more time. A larger timeout does not fix a missing executable, library, or sandbox.
A Playwright issue documented a screenshot timeout associated with an extension in a particular Linux and Playwright setup. That report is a reminder to isolate extensions and other custom launch settings when basic startup succeeds; it is not evidence that extensions are the cause of an unspecified failure.
7. If the screenshot succeeds but differs from local output
Visual differences often come from environmental variation rather than a failed screenshot API call. Use the same framework version, browser build, viewport, device scale factor, and page state in local and CI runs. Fonts and operating-system rendering can also affect pixels.
- Log the browser and framework versions in both environments.
- Use the same viewport dimensions and device scale factor.
- Wait for the specific page content needed in the capture, rather than relying on an arbitrary delay alone.
- Check whether fonts, images, animations, or other resources have loaded before capture.
- Run the screenshot in a container matching the CI environment if you need consistent output across operating systems. Playwright documents this approach for screenshot and visual regression testing.
Do not compare images from different operating systems or browser versions as if they were guaranteed to be pixel-identical. First make the execution environment and capture settings comparable, then investigate the remaining difference.
8. Make one controlled change at a time
Use this sequence to avoid masking the original cause:
- Save the full failure log and classify the failing stage.
- Confirm the intended browser binary is installed and compatible with the framework version.
- Resolve missing operating-system dependencies if the launch error identifies them.
- For an explicit sandbox error, investigate sandbox support in the runner or container.
- For Playwright, compare your workflow with the official CI instructions and capture
DEBUG=pw:browseroutput. - For navigation or screenshot timeouts, log those stages independently and validate the page’s readiness conditions.
- For rendering differences, align browser, OS/container, fonts, viewport, and page state.
- Rerun after each change and keep the result. If a change has no effect, revert it before trying the next hypothesis.
9. Common errors and fixes
| Error or symptom | Likely cause | What to do |
|---|---|---|
Failed to launch browser or executable missing |
Browser download was skipped, installation did not run, or configured path is wrong | Check the package install policy and browser path; follow the framework’s current installation guide |
| Chrome binary exists but exits on Linux | One or more shared libraries required by the browser are missing | Run ldd on the actual Chrome executable and install the missing dependencies |
No usable sandbox |
Runner or container sandbox support is incompatible with the launch environment | Investigate sandbox configuration using Puppeteer’s guidance; do not make --no-sandbox a general workaround |
| Playwright browser launch fails in Actions | Browser or OS dependencies are not installed for the Playwright version | Use npx playwright install --with-deps and compare with the official CI workflow |
| Navigation times out | Page is unreachable, the application is not ready, or the chosen wait condition is too strict | Log navigation separately, check network and server readiness, then select the appropriate wait condition |
| Screenshot times out after navigation succeeded | Capture wait, selector, resource, or case-specific browser behavior | Log the screenshot step, inspect custom launch settings such as extensions, and vary one setting at a time |
| Image differs from local | Browser, OS, fonts, viewport, device scale factor, or page readiness differs | Align environments and capture settings; consider the same container for both runs |
10. Performance and reliability considerations
CI screenshot time depends on runner startup, dependency installation, browser startup, page load, and capture. Avoid repeatedly downloading or installing browsers in separate steps when your workflow can reuse a consistent environment, but keep browser and framework versions aligned. A version-matched container can reduce differences between runs and between operating systems.
For reliability, retain enough diagnostic output to identify the stage and preserve the full browser error. Avoid solving intermittent timeouts by adding an unbounded delay: it slows every run and may still capture an incomplete page. Prefer a meaningful readiness condition, a bounded timeout, and logs that distinguish navigation from screenshot capture.
There is no benchmark or universal timeout value that fits every page and runner. Measure your own workflow and choose limits based on the page’s expected load behavior. Likewise, do not infer that an issue report for one Ubuntu image, framework version, or extension applies to all GitHub Actions environments.
11. Or skip the browser setup
If your goal is to obtain a website screenshot rather than maintain a browser in CI, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for parameters and response details.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month with no card.
12. FAQ
Does a successful local run prove the GitHub Actions runner has Chrome installed?
No. Local and CI installations are separate. Confirm the browser binary and its version in the runner itself.
Should I use --no-sandbox to fix every GitHub Actions launch error?
No. It is not a general fix, and Puppeteer strongly discourages running without a sandbox. Use it only if you have made an informed environment-specific security decision; first investigate sandbox support.
Is a longer timeout the right fix for every screenshot failure?
No. It can help with genuinely slow pages, but cannot install a missing browser or library, configure a sandbox, or make an unavailable page reachable.
Can I expect identical screenshot pixels on different operating systems?
Not automatically. Browser builds, fonts, and operating-system rendering can differ. Use a consistent environment when pixel-level comparison matters.


