Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

A Puppeteer test that fails only in headless mode is not automatically a React bug. First determine whether Chrome fails to launch, the page fails to load, or the app renders but the test assertion fails. Then compare Puppeteer’s headless modes, browser versions, and the environment that runs the test. Those checks often identify the cause before any React code needs to change.

First identify where the failure happens

“Headless failure” can describe several different problems. A browser launch exception, a navigation timeout, a JavaScript page error, and a failed assertion happen at different stages and need different fixes. Preserve the exact error and logs rather than treating all four as a rendering issue.

  • Chrome fails before Puppeteer connects: investigate whether the browser was installed, whether the executable path is correct, and whether the host can start Chrome.
  • Chrome connects, but navigation or loading fails: inspect the URL, network access, page errors, and the test’s waiting condition.
  • The page loads, but an assertion fails: compare what the test expects with what the page actually rendered, including timing and browser-mode differences.
  • Only CI fails or failures are intermittent: check the runner’s libraries, permissions, memory and process limits, and test-worker count.

In Puppeteer, the dumpio launch option sends browser stdout and stderr to the parent process, which can expose a Chrome startup or runtime error that is otherwise easy to miss. Capture that output alongside the Puppeteer exception, page errors, navigation result, and failing assertion. A minimal diagnostic launch looks like this:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const puppeteer = require('puppeteer');

(async () => {
  let browser;
  try {
    browser = await puppeteer.launch({
      headless: true,
      dumpio: true,
    });

    const page = await browser.newPage();
    page.on('pageerror', error => console.error('Page error:', error));
    page.on('console', message => {
      if (message.type() === 'error') console.error('Page console:', message.text());
    });

    const response = await page.goto('http://localhost:3000', {
      waitUntil: 'networkidle0',
      timeout: 30000,
    });
    console.log('HTTP status:', response?.status());
    console.log('Page title:', await page.title());
  } catch (error) {
    console.error('Puppeteer failure:', error);
    process.exitCode = 1;
  } finally {
    if (browser) await browser.close();
  }
})();

Use the actual URL and waiting condition from the failing test when reproducing it. A diagnostic script is useful for separating launch, navigation, and page failures; it does not prove that a particular React component or test assertion is correct.

Make the headless mode explicit

Puppeteer currently documents two headless choices, and they are not interchangeable labels for the same browser. Its default headless: true uses new headless Chrome. headless: 'shell' runs the separate chrome-headless-shell binary associated with old headless mode. Puppeteer says shell can be more performant for automation that does not need the full regular Chrome feature set, but it does not completely match regular Chrome behavior. The default changed in Puppeteer v22, so a test that was written or configured for an earlier release may not be exercising the same mode after an upgrade.

Setting What it runs How to use it in diagnosis
headless: true New headless Chrome; the current documented default. Use as the baseline for current Puppeteer behavior.
headless: 'shell' A separate chrome-headless-shell binary; not fully behavior-matched to regular Chrome. Compare only if the test does not depend on Chrome features shell lacks.
headless: false Regular visible browser mode. Useful for comparison when a display is available. On a headless CI runner, a display server such as Xvfb may be required.

Run the same test and browser version under each relevant mode, changing one variable at a time. If visible mode passes while headless mode fails, that narrows the investigation; it does not by itself establish a React defect. Record the chosen mode in the test configuration so local runs and CI do not silently compare different defaults.

const browser = await puppeteer.launch({
  headless: true, // Compare explicitly with 'shell' or false when appropriate
});

Check Puppeteer and Chrome installation and compatibility

The full puppeteer package normally downloads a compatible Chrome for Testing. If a modern package manager blocks the package’s install script, the JavaScript package can be present while its expected browser is missing. The documented recovery is to allow Puppeteer’s install script or install the browser explicitly with npx puppeteer browsers install.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
npx puppeteer browsers install

puppeteer-core is different: it intentionally does not download Chrome. When using it, provide a browser executable path or channel rather than expecting a bundled browser to appear. Also check that the path points to the binary you intend the test to use.

const puppeteer = require('puppeteer-core');

const browser = await puppeteer.launch({
  executablePath: '/path/to/chrome',
  headless: true,
});

Puppeteer works best with its downloaded Chrome for Testing and does not guarantee compatibility with an arbitrary installed Chrome. Record the installed Puppeteer version, browser version, selected mode, package manager, and any executablePath or channel. If CI or a local setup manages Chrome separately, verify that the selected browser is compatible with the Puppeteer release instead of assuming that any installed Chrome will work.

Inspect Linux and container launch conditions

When Chrome fails before Puppeteer connects on Linux or in a container, inspect the host before changing the React app. Puppeteer’s troubleshooting guidance calls out missing shared libraries, sandbox and user-namespace restrictions, Ubuntu AppArmor conditions, and locations that Chrome cannot write. A read-only environment can prevent Chrome from creating its profile or using its cache.

  • Shared libraries: check the Chrome stderr and runtime image for missing system libraries; add the required dependencies to the image rather than masking the launch error.
  • Sandbox or user namespaces: inspect the runner’s policy and relevant browser diagnostics. The right fix depends on the host’s security configuration.
  • Writable paths: make sure the process can create and write its browser profile and cache in the environment where the test runs.
  • AppArmor: on affected Ubuntu setups, check whether the policy is blocking the browser’s expected user-namespace behavior.

Do not reflexively add --no-sandbox. Puppeteer strongly discourages disabling Chrome’s sandbox and limits its example to trusted content. Removing a security boundary is not a general-purpose remedy for a container that has not been configured to run Chrome. Understand the runner and its trust model first.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Separate React rendering from timing and test assumptions

The fact that the test targets a React application does not identify React as the cause. The documented Puppeteer environment guidance does not establish a React-specific explanation for headless-only failures, including claims about hydration, effects, or rendering. Those are hypotheses to test against the particular app, not conclusions to draw from the symptom alone.

Once Chrome starts and the page loads, inspect the actual rendered state and the assertion that fails. Check that the test waits for the condition it needs rather than assuming that a fixed delay always corresponds to a completed render. Add targeted logging or a page error listener, then compare the same app and test in the different browser modes with the same browser version and runner where possible. This isolates whether the difference follows the mode, the host, or the test’s own expectation.

A screenshot can help you inspect what was visible at the failing moment, but it is evidence about that capture, not a substitute for checking the browser logs, response, and assertion. For a repeatable capture from a React URL, ScreenshotNeo is a separate screenshot API rather than a fix for Puppeteer or a replacement for diagnosing its launch environment.

Account for CI worker and resource limits

Browser tests consume processes and memory. Puppeteer’s troubleshooting guide describes a Jest case where the runner starts more workers than the container can support, making resource pressure look like a browser or test failure. If failures are intermittent under CI load, compare worker count with the runner’s actual limits and inspect memory and process errors.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
npx jest --maxWorkers=2

The value 2 is the documentation’s example for a particular environment, not a universal optimum. Start with a worker count your runner can sustain, then adjust based on its capacity and observed failures. A visible browser in CI also needs a display server; Xvfb is one option noted in Puppeteer’s troubleshooting material. Avoid treating either a worker setting or a display server as a fix for a browser that is simply missing or incompatible.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

A practical isolation sequence

  1. Reproduce and preserve evidence. Save the exact Puppeteer exception, Chrome stderr, page errors, navigation status, and failing assertion. Turn on dumpio if browser output is not visible.
  2. Establish the browser identity. Record Puppeteer’s installed version, the browser binary and version, the headless value, the executable path or channel, and the package manager.
  3. Confirm installation. Check that the expected browser exists and that package install scripts ran. For the full package, install with npx puppeteer browsers install if necessary; for puppeteer-core, configure the path or channel.
  4. Compare modes on the same host. Test true and, where relevant, 'shell'. Try false only with display support. Keep the browser version and test unchanged during the comparison.
  5. Inspect the runner. On Linux and containers, investigate libraries, sandbox or AppArmor policy, and writable profile/cache locations. In CI, check memory, process limits, and worker count.
  6. Investigate app behavior last. Once launch and host problems are ruled out, examine the page state, React behavior, waits, and assertion in the specific failing test.

Or skip the browser setup

If the immediate need is a screenshot of a React page rather than a Puppeteer test run, ScreenshotNeo provides a one-request capture API. It does not replace browser automation or diagnose a failing assertion. It can be useful when you need a screenshot without setting up and maintaining a browser on the machine making the request. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
  • Before the capture, it accepts the cookie or consent banner as a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets; each step can be turned off.
  • Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Responses identify the page verdict and billing status in X-Page-Verdict and X-Billed headers.
  • An MCP server offers take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
  • The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan.

Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.

Common failure patterns and fixes

Symptom Likely area to inspect Next step
Chrome executable missing or launch cannot find browser Install script blocked, browser not installed, or wrong path Install with npx puppeteer browsers install when using puppeteer; configure an executable path or channel for puppeteer-core.
Chrome exits before Puppeteer connects on Linux Missing libraries, sandbox policy, AppArmor, or unwritable profile/cache Use stderr to identify the host failure and fix the runner configuration or permissions.
Works locally, fails only in CI Different browser version, no display for visible mode, host restrictions, or constrained resources Compare versions and mode; check display support, runner policies, and resource limits.
Intermittent failure under load Too many test workers or process/memory pressure Inspect CI limits and reduce concurrency to a sustainable value.
Page loads but an assertion fails only in one mode Mode-specific browser behavior, page state, timing, or assertion assumptions Compare modes explicitly and inspect the rendered state, page errors, and exact assertion.

Frequently Asked Questions

Does Puppeteer’s default headless mode still mean chrome-headless-shell?

No. The current documented default, headless: true, uses new headless Chrome; headless: 'shell' selects the separate shell binary.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can I use headless: false on a CI runner?

Yes, if the runner provides a display server. Puppeteer’s troubleshooting guidance notes Xvfb as an option for non-headless CI tests.

Does a headless-only failure prove that React hydration is broken?

No. The symptom alone does not establish a React cause; first rule out browser mode, installation, compatibility, host, and CI resource issues.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.