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.

Make the invisible session observable before changing selectors or adding retries. Freeze the exact reproduction, run one diagnostic pass with a visible browser or an inspector, pause at the failing action, and preserve a screenshot, DOM, console, network and trace artifact. Then classify the failure as page state or timing, test code or locator, browser and driver, DevTools protocol, or host environment. That sequence explains why headed mode works when headless mode fails without turning every failure into a larger timeout.

1. Freeze the failure before debugging it

Intermittent automation becomes much easier to reason about when every run has the same inputs. Record these values with the failure:

  • Automation framework and browser versions, plus the operating system or container image.
  • The exact URL, viewport dimensions, locale, timezone, authentication state and feature flags.
  • The complete command line and environment variables.
  • The first action that fails, not merely the final assertion.
  • Whether the run is local, CI, headed or headless, and whether it uses a proxy.

Run the smallest reproduction that still fails. If local and CI differ, copy the CI values into the local run where possible. A reduced test distinguishes an application problem from a fixture, parallelism or environment problem.

2. Make the browser visible

Playwright Inspector

Playwright runs headless by default. Start a diagnostic test with:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
npx playwright test --debug

The Inspector pauses actions, displays actionability logs, and lets you pick or edit locators. You can also pause from code:

await page.goto('https://example.com');
await page.pause();
await page.getByRole('button', { name: 'Continue' }).click();

For a one-off visual comparison, launch headed and slow the actions:

const { chromium } = require('playwright');
(async () => {
  const browser = await chromium.launch({ headless: false, slowMo: 250 });
  const page = await browser.newPage({ viewport: { width: 1280, height: 900 } });
  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
  await page.pause();
  await browser.close();
})();

Do not assume that headed and headless render identically. The headed run is an observation tool: compare DOM, viewport, fonts, focus, permissions and timing, then fix the underlying difference.

Puppeteer visibility

Puppeteer can be launched with headless: false for a local diagnostic pass. Add a modest slowMo only while observing; it is not a synchronization strategy.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const puppeteer = require('puppeteer');
(async () => {
  const browser = await puppeteer.launch({ headless: false, slowMo: 200 });
  const page = await browser.newPage();
  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
  await page.screenshot({ path: 'visible.png', fullPage: true });
  await browser.close();
})();

3. Preserve replayable evidence at the failing action

At failure, save enough evidence for another person to replay the state:

  • A screenshot, preferably full page and viewport when both are useful.
  • Current URL, page title and serialized HTML.
  • Console messages, uncaught page errors and failed network requests.
  • A framework trace when available.
  • Browser process standard error and the exact launch arguments.

Playwright trace and failure hook

import { test, expect } from '@playwright/test';

test('diagnostic checkout', async ({ page }, testInfo) => {
  await page.goto('https://example.com');
  try {
    await page.getByRole('button', { name: 'Continue' }).click();
    await expect(page.getByText('Done')).toBeVisible();
  } catch (error) {
    await page.screenshot({ path: testInfo.outputPath('failure.png'), fullPage: true });
    console.error('URL:', page.url());
    console.error('HTML length:', (await page.content()).length);
    throw error;
  }
});

Enable tracing for the diagnostic run and open the resulting trace in Trace Viewer. It shows action timing, snapshots, screenshots and network details in one replayable artifact.

Selenium screenshot and page state

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument('--headless=new')
driver = webdriver.Chrome(options=options)
try:
    driver.get('https://example.com')
    driver.save_screenshot('failure.png')
    print('URL:', driver.current_url)
    print('Title:', driver.title)
    print(driver.page_source[:1000])
finally:
    driver.quit()

Configure Selenium logging at DEBUG and write it to a file in CI. The log should include the command that was sent, its duration and the returned error.

4. Inspect a raw headless Chrome session

Chrome’s headless process is effectively invisible, so attach DevTools when framework-level evidence is insufficient. Launch Chrome with a dynamically allocated remote debugging port:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
google-chrome --headless --remote-debugging-port=0 https://example.com

Copy the WebSocket endpoint printed to standard output. In a separate headed Chrome window, open chrome://inspect, choose Configure…, add the host and port from that endpoint, and inspect the listed target. You can now view DOM, console, network and storage state as if the page were visible.

Capture the launch output. A missing executable, rejected sandbox, certificate error or proxy failure often appears there before the first WebDriver action.

5. Turn on framework and protocol logs

Playwright

Set the API logger for a focused run:

DEBUG=pw:api npx playwright test tests/checkout.spec.ts

Actionability messages reveal whether an element was detached, hidden, covered, disabled or outside the expected state.

Puppeteer

Forward browser-process output and enable Puppeteer’s protocol diagnostics:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
NODE_DEBUG="puppeteer:*" node debug.js
const browser = await puppeteer.launch({ dumpio: true });
console.log(browser.debugInfo.pendingProtocolErrors);

dumpio: true sends the browser’s stdout and stderr to the parent process. Inspect pendingProtocolErrors when calls hang or a target closes unexpectedly.

Selenium

Raise the Selenium logger to DEBUG, send it to a file, and retain the driver service log as a CI artifact. Compare the command sequence with a successful run; the first divergent command is usually more useful than the final stack trace.

6. Synchronize on conditions, not elapsed time

The most common automation failures are synchronization failures: the application is still changing when the command runs. A fixed sleep can be too short on a slow run and wasteful on a fast one.

Use an explicit condition

Wait for the state required by the next action: a specific role and name, visibility, enabled state, a URL transition, a response, a frame, or a stable application signal. Playwright’s locators perform actionability checks; Selenium provides explicit waits:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

button = WebDriverWait(driver, 20).until(
    EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-testid="continue"]'))
)
button.click()

Do not mix Selenium implicit and explicit waits. Selenium warns that the combination can produce unpredictable wait times. Log the condition and elapsed time so a timeout identifies what never became true.

7. Classify the evidence and apply the narrow fix

Locator or page-state failure

A correct selector can still fail when the element is not present, visible, enabled, in the expected frame or inside a shadow root. Use the Inspector or saved DOM to verify the context. Switch into the correct iframe, pierce the appropriate shadow root, or wait for the actual condition. Avoid raising a global timeout before identifying the missing state.

Timing and race condition

Look for changing text, loading indicators, late network responses and elements that are replaced after rendering. Replace sleeps with a bounded condition wait. If a response drives the UI, wait for that response or for the resulting state rather than an arbitrary number of milliseconds.

Browser, driver or process failure

If Chrome exits before the first page action, inspect launch stderr and verify the executable path, browser-driver compatibility, file permissions, certificates, proxy and available memory. Run the smallest page in another supported browser. A failure that follows one browser points toward its binary, driver or launch flags; a failure across browsers points toward the host or application.

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

Sandbox and container problems

Check Linux sandbox support, shared memory, process and file-descriptor limits, fonts, DNS and display assumptions. Puppeteer documents a “No usable sandbox!” launch failure and extension-policy conflicts. If using chrome-headless-shell, verify the GPU setting required for GPU acceleration. Treat --no-sandbox as an emergency, environment-specific workaround only when the execution boundary is trusted and its security impact is understood.

Protocol or connection failure

A closed target, hung command or pending callback calls for protocol evidence. Enable Puppeteer protocol logging, inspect pending protocol errors, or attach Chrome DevTools through the WebSocket endpoint. Check whether the browser process was killed by a container limit or whether a proxy interrupted the connection.

CI-only failure

Compare local and CI browser and framework versions, viewport, locale, timezone, fonts, environment variables, network policy and resource limits. Preserve screenshot, trace, console, browser stderr and the exact command line on every failure. A diagnostic CI job can rerun once in headed mode if a display server is available, but use that run to expose state rather than to assume headed behavior is authoritative.

8. A compact decision table

Symptom Most likely class First evidence or action
Element exists in source but click fails Actionability, frame, shadow DOM or timing Inspector logs, DOM snapshot and condition wait
Works after a long sleep Race condition Wait for the exact UI or network state
Browser exits at launch Binary, sandbox or host resources Launch stderr, executable check and container limits
Target closes or calls hang DevTools protocol or killed process Protocol logs, pending errors and process logs
Only CI fails Environment drift Compare versions, fonts, locale, proxy and viewport

9. Make diagnostics reliable and affordable

Keep normal test runs fast and collect heavy artifacts only on retry or failure. Use bounded waits, not unbounded polling. Limit parallel workers when the browser or container is resource-starved, then increase concurrency after measuring CPU, memory and shared-memory headroom. Pin the browser and framework versions used by CI so a silent browser update does not create a new variable.

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

Artifacts should have the test name, timestamp, browser and commit in their filenames. Redact cookies, authorization headers and personal data before uploading them. A screenshot is evidence, not proof of cause: pair it with URL, DOM, console, network and timing data.

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

Or skip the browser setup

When you only need a reproducible website image for a bug report, regression artifact or documentation page, ScreenshotNeo provides a single HTTP request instead of maintaining a browser harness. Its API accepts the URL and returns PNG, JPEG, WebP or PDF; the feature set includes full-page capture with lazy images loaded, CSS-selector element capture, device and viewport controls, retina scale, waits, custom JavaScript and CSS, click-before-capture, hidden selectors, blocked requests, headers, cookies, user agents, timezone, geolocation, transparent backgrounds, resizing, caching, signed links, asynchronous webhooks, bulk capture and a usage API.

Use the API documentation at https://screenshotneo.com/docs/ for parameters and response headers. A minimal call is:

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

Equivalent clients:

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

Before capture, ScreenshotNeo accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and each response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

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

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, and yearly billing gives two months free. Sign up for the free plan to run the first diagnostic captures without installing a browser.

FAQ

Should I always reproduce a failure in headed mode?

No. Use a headed or Inspector-assisted pass to expose state, then validate the fix in the same headless configuration used by CI.

Is increasing the timeout ever the right fix?

Only after you have identified a legitimate slow condition and bounded the wait around it. A larger global timeout can hide a missing frame, selector, response or host failure.

What artifact is most valuable when a CI job fails?

The combination of a failure screenshot, trace, console and network errors, browser stderr, current URL and exact command line is more diagnostic than any single artifact.

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

Frequently Asked Questions

Should I always reproduce a failure in headed mode?

No. Use headed mode or an Inspector-assisted pass to expose state, then validate the fix in the headless configuration used by CI.

Is increasing the timeout ever the right fix?

Only after identifying a legitimate slow condition and bounding the wait around it; a larger global timeout can hide the real cause.

What artifact is most valuable when a CI job fails?

Collect the failure screenshot, trace, console and network errors, browser stderr, current URL and exact command line together.

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.

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