Free tools Windows power users keep installed
One-click scans. No signup required.
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
When browser automation fails in headless mode, make the run observable before changing selectors or adding retries. Reproduce the failure with the same inputs, pause at the failing action, and save a screenshot, logs, and—if your framework supports it—a trace. Then classify what you see: the page may be in the wrong state, the test may be using the wrong locator, the browser or driver may have failed, the protocol connection may be stuck, or the CI environment may differ from your local machine.
Headless browsers are not inherently impossible to inspect. Playwright has an Inspector and Trace Viewer, Puppeteer can expose Chrome DevTools and browser-process logs, Selenium provides screenshots and logging, and Chromium can be inspected remotely. The useful question is not simply “Why did headless fail?” but “What evidence shows where the run diverged?”
Start with a controlled reproduction
Before changing code, record the conditions under which the failure occurs. Small differences in browser version, viewport, authentication, or host configuration can make a test that passes locally fail in CI.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problems- Record the automation framework and version, browser version, operating system or container image, and the exact command used.
- Record the URL, viewport dimensions, locale, timezone if relevant, and authentication state. Note any test data or feature flags that affect the page.
- Identify the exact action that fails and preserve its error text and elapsed time. “The test timed out” is less useful than “the submit button never became enabled after the form was filled.”
- Run the same inputs locally and in CI when possible. If they differ, change one environmental variable at a time rather than altering the test and the environment together.
Keep the reproduction as small as possible. A single page and the shortest action sequence that still fails are easier to diagnose than a whole suite with several possible failure points.
#1 Best Overall
Make the invisible session observable
Use the debugging facilities of the framework first. A headed run can make a hidden state visible, but it is a diagnostic aid—not proof that headed and headless runs behave identically in every environment.
Playwright: open the Inspector and pause at the failing action
For a Playwright test, run:
npx playwright test --debug
This opens the Playwright Inspector, where you can step through actions, inspect actionability information, and edit or pick locators. If you need to pause a particular script at a known point, put this immediately before the suspect action:
await page.pause();
For a one-off visible browser run, launch with headless: false; an optional slowMo setting can make actions easier to watch. Use this to find the first point where the page differs from expectations, not to conceal a timing problem by slowing every run.
For API-level diagnostics, enable Playwright’s API logging in the shell that runs the test:
DEBUG=pw:api npx playwright test
Playwright’s trace recording and Trace Viewer are useful when a failure is intermittent or happens in CI, where watching a live run is impractical. Preserve the trace as a failure artifact and inspect the actions around the first incorrect state.
Puppeteer: expose browser and protocol activity
Enable Puppeteer’s debug output for a diagnostic run:
NODE_DEBUG="puppeteer:*" node your-script.js
When browser-process output may explain an early exit or launch problem, set dumpio: true in the Puppeteer launch options:
const browser = await puppeteer.launch({ dumpio: true });
If a call hangs or a target closes unexpectedly, inspect browser.debugInfo.pendingProtocolErrors for pending protocol errors. Save the output with the failing run; protocol messages without the matching action and timestamp can be difficult to interpret.
Selenium: capture a screenshot and use explicit waits
Selenium’s WebDriver API supports screenshots. Save one at the failure point, along with the current URL and relevant page or driver logs. Raise Selenium logging to DEBUG and write it to a file when the normal output does not show what happened before the command failed.
For synchronization, wait for the condition the next command actually requires—for example, that a control is visible or enabled—instead of sleeping for an arbitrary duration. Selenium’s documentation identifies poor synchronization as a common source of Selenium-related errors. Do not mix implicit and explicit waits in the same session: Selenium warns that the resulting wait times can be unpredictable.
Rank #3
Save evidence at the point of failure
A screenshot shows what was rendered, but it rarely explains why. Collect a small, replayable evidence bundle so you can compare a passing run with a failing one.
- Screenshot: capture immediately before or after the failed action. This can reveal a dialog, overlay, unexpected navigation, blank page, or element outside the viewport.
- URL and page state: record the current URL and inspect the DOM or page HTML at the failure point. Check whether the application reached the expected route and whether the target element exists.
- Console and page errors: save browser console messages and uncaught page errors. They can point to failed scripts or application errors even when the automation command only reports a timeout.
- Network failures: record failed requests and relevant responses. A missing API response or blocked asset may leave the page in a state where the locator is correct but its expected control is not ready.
- Trace and process output: keep the framework trace where available, API or protocol logs, browser stderr/stdout, and the full command line.
For intermittent CI failures, preserve these artifacts on failure rather than relying on someone to reproduce the issue interactively. Keep credentials, cookies, and other sensitive page data out of artifacts that are accessible beyond the test team.
Inspect raw headless Chrome with DevTools
If the framework’s own view is not enough, Chrome can expose a remote debugging endpoint. Start headless Chrome with --remote-debugging-port=0. Copy the WebSocket endpoint printed to stdout, then open chrome://inspect in a separate headed Chrome window and configure it to use that endpoint. The remote target can then be inspected through Chrome DevTools.
This is especially useful when you need to inspect the browser target independently of the test runner. Treat the debugging endpoint as sensitive: anyone who can reach it may be able to inspect or control the browser session. Do not expose it on a network interface or retain it beyond the diagnostic run unless access is deliberately secured.
Classify the failure before changing the test
Use the evidence to decide which layer is failing. A locator change will not fix a browser that exits before opening a page, and a longer timeout will not fix a blocked network request.
Recommended Free Tools
| Evidence or symptom | Likely class | Next diagnostic step |
|---|---|---|
| The element is absent, hidden, disabled, covered, or in a different frame than expected. | Locator or page-state problem | Inspect the DOM and frame context at failure; use actionability information or wait for the relevant condition. |
| The same action sometimes succeeds, while the page is still rendering or changing. | Timing or race condition | Replace a fixed sleep with a bounded wait for the specific state the next action needs; log elapsed time. |
| The browser exits before the first page action or prints launch errors. | Browser process, sandbox, or host problem | Read launch output and check executable availability, permissions, sandbox support, and container resources. |
| Calls hang, a target closes, or protocol errors remain pending. | Protocol or connection problem | Enable framework or protocol logging; for Puppeteer inspect pending protocol errors, or inspect the raw Chromium target through DevTools. |
| The failure occurs only in CI. | Environment difference | Compare versions, viewport, locale, timezone, fonts, environment, network policy, and resource limits between local and CI runs. |
When a locator or page state is wrong
A correct selector can still fail if its element has not been added to the DOM, is not visible or enabled, belongs to another frame, or is outside the state the test assumes. Inspect the DOM at the exact failure point and verify whether the element is in the expected frame or shadow-root context. In Playwright, use Inspector actionability information; in Selenium, wait for the relevant condition. Increase a timeout only after identifying what state is taking too long.
When timing is the issue
A fixed delay can be too short on a slow run and wasteful on a fast one. Prefer a bounded, condition-based wait and log both the condition and how long it took. For example, if the next step needs a button to be enabled, wait for that state rather than waiting a guessed number of seconds after page load. In Selenium, keep the session on one wait strategy instead of mixing implicit and explicit waits.
When the browser or driver may be at fault
Capture the browser’s launch output and verify the browser executable and driver compatibility. Then try the same minimal reproduction in another supported browser. If it fails only with one browser or driver combination, that points toward the browser/driver layer rather than the page locator, though it does not by itself identify the underlying defect.
Check container and CI conditions
When a browser fails before the test reaches the page, or behaves differently only on a build agent, inspect the host rather than adding retries.
- Sandbox and permissions: verify the browser can run with the sandbox configuration available in the environment. Puppeteer documents Linux “No usable sandbox!” launch failures. Treat
--no-sandboxas an environment-specific emergency workaround only when the execution boundary is trusted and the security impact is understood. - Container resources: inspect shared memory and process limits, and confirm the browser has enough resources to start and load the page.
- Launch policy: check browser stderr for launch conflicts. Puppeteer’s troubleshooting guidance notes that extension policies can block launch.
- GPU mode: Puppeteer’s documentation notes that
chrome-headless-shellneeds--enable-gpufor GPU acceleration. Do not add it as a general-purpose fix without confirming that this browser mode and requirement apply. - System inputs: compare installed fonts, certificates, proxy and DNS settings, filesystem access, environment variables, and display assumptions between local and CI.
Make a single diagnostic headed run in CI if a display server is available. Its purpose is to reveal page state; a pass in headed mode does not establish that the headless run has the same environment or rendering conditions.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Fix synchronization without hiding the cause
Synchronization is one of the most common sources of browser automation errors. The application may still be changing when the test sends a command. A blanket sleep makes the symptom less consistent, not more understood.
- Identify the state the next action requires: an element is attached, visible, enabled, a URL has changed, or a particular response has arrived.
- Wait for that condition with a bounded timeout appropriate to the test.
- On timeout, log the condition, the elapsed time, and the page state. This distinguishes a slow transition from a page that never reached the required state.
- Keep the wait close to the action it protects so future failures identify the missing prerequisite.
Do not turn a targeted fix into a larger global timeout unless evidence shows that many unrelated transitions are legitimately slow. Longer global waits can delay feedback and still leave a test waiting for a condition that will never occur.
Or skip the browser setup
If what you need is a website image or PDF rather than an interactive browser-debugging session, ScreenshotNeo provides a screenshot API and MCP server. It cannot replace a Playwright, Puppeteer, or Selenium trace when you need to diagnose actions, but it can capture a page without you setting up a browser locally. For example, save a WebP shot with cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Equivalent Python:
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)
Or Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
See the ScreenshotNeo API documentation for request options. Cookie banners are accepted and removed before capture, along with known newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies page verdict and billing status in headers. An MCP server offers take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for free.
Common debugging mistakes
- Changing a selector before checking the page: first confirm the expected element exists in the DOM, in the correct frame, and in the expected state.
- Adding retries to a deterministic failure: retries can hide evidence and make a broken test appear intermittent. Find out whether the browser, page, or host failed before deciding to retry.
- Using a headed pass as proof of equivalence: it can expose state, but it does not guarantee the same behavior under headless CI conditions.
- Increasing every timeout: a global increase delays failures without identifying the unmet condition. Wait for the state the next action needs.
- Ignoring browser output: launch stderr, console errors, network failures, and protocol logs can reveal a problem that the final test exception does not describe.
Frequently Asked Questions
Does a passing headed run prove the test is fixed?
No. It shows that the test can pass under the headed run’s conditions. Confirm the fix in the original headless environment and compare the saved failure evidence.
Should I retry a flaky browser test automatically?
Only after distinguishing an intermittent external condition from a repeatable test or environment defect. Preserve failure artifacts so retries do not erase the first useful evidence.
Quick Recap
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.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.

