How-To

A Headless Browser Compatibility Checklist for Playwright and Selenium

Your headless test works locally but fails against the real application. Before changing random settings, walk through these checks: engine pairing, viewport, authentication state, and what navigator.webdriver actually means.

Match the engine, channel and driver

Start by recording the browser binary, automation driver and framework version. A compatibility problem at startup needs a different investigation from an application failure after navigation.

Playwright bundles its own Chromium, Firefox and WebKit builds. The default headless Chromium is actually chrome-headless-shell, a stripped-down binary optimized for speed. If you need behavior closer to a real Chrome install, pass channel: 'chromium' (or 'chrome' for branded Chrome) to get the full browser in its new headless mode. These aren't interchangeable. The headless shell lacks features the full browser has, and branded Chrome may lag behind Playwright's bundled Chromium by several versions. See the Playwright browsers reference.

Selenium includes Selenium Manager for driver and browser management. Its operation depends on the supplied configuration and available network/cache. If you supply your own driver, check compatibility with the selected browser. Keep the complete session-creation error: it can identify an unsupported browser version directly.

Record the exact versions you're running. When something breaks, "headless Chrome" isn't specific enough. You need the binary path, its --version output, the driver version, and the framework version.

Headed versus headless: what actually differs

Chrome's current headless implementation shares browser code with headed Chrome. That doesn't establish identical results across different machines, browser channels, settings or application timing.

Compare these inputs before drawing a conclusion:

Run the same workload in both modes. A difference narrows the reproduction, but application timing bugs, external services and test assumptions remain possible causes. Change one input at a time.

The W3C WebDriver specification defines an automation-state flag exposed through navigator.webdriver. Applications can use that signal to choose behavior during automation. Record what your actual browser reports rather than treating it as a complete detector of every automation mechanism.

Hiding or overriding this property through JavaScript injection doesn't change the browser's actual automation state. It doesn't establish compatibility with a target application, and it doesn't grant permission to access a service. If an application checks navigator.webdriver and acts on it, that's the application's policy decision. The correct response is to work within the application's terms of access, not to mask the flag.

Separate authentication state from bot-policy decisions

A test that fails after login might have an authentication problem or an access-policy problem. These need different fixes.

Authentication state means cookies, local storage and session data that identify a logged-in user. Playwright can persist this with storageState, saving cookies and local storage to a JSON file and restoring them in later sessions. Note that sessionStorage is domain-specific and not included in storageState; Playwright's docs describe a workaround using addInitScript if you need it.

Keep auth state files out of version control. They contain cookies that can impersonate your test account.

Bot-policy decisions are a different layer. An application can accept your authentication cookies and still restrict automated access based on request patterns, rate, TLS fingerprint or the navigator.webdriver flag. A valid session token doesn't override a bot-management rule. If your authenticated request fails with a challenge or block, check whether the same request succeeds in a headed browser with the same cookies before assuming your auth is broken.

Actionability checks and why headless tests click nothing

Playwright applies action-specific checks. A normal locator click waits for one matching element that is visible, stable, enabled and able to receive events. Filling a field has a different checklist, including editability. Don't assume every action performs every check.

A responsive layout can hide the intended control or put an overlay above it. Inspect the failing locator, screenshot and trace before forcing a click or extending a timeout. Timing differences can expose application defects as well as test defects.

If your test times out on an action, check whether the element is actually visible at the headless viewport size. Add a screenshot before the failing step. That single screenshot has more diagnostic value than twenty retries.

Build isolated, nonsecret test fixtures

Don't test against production with real customer data. Build a fixture that exercises the same application paths with synthetic data in an environment you control.

Keep a failure record

For each test run, record:

Separating failure categories matters. A DNS resolution error needs a different fix than a bot-management block, and both look like "the test failed" in a CI log. Redact credentials, cookies and customer data from your records.

An acceptance matrix before you scale

Before running a hundred concurrent headless sessions, confirm these in a single run:

CheckHeadedHeadlessPass?
Browser launches at specified version
Driver/framework version match confirmed
Target page loads without challenge
Auth state restores correctly
Primary action completes (click, form, navigation)
Application-level assertion passes
Cleanup runs without orphaned processes

Fill in both columns using actual observations. Classify the first failed stage, then repeat that stage with one controlled change. Neither the number of green rows nor a comparison between modes establishes a success rate on another workload.

Get a scoped assessment

This checklist covers documented behavior in Playwright, Selenium, Chrome and the WebDriver spec. It doesn't claim a measured success rate, and it doesn't promise that any particular site will accept automated traffic.

If you need help connecting a managed browser profile through CDP, the managed-profile API guide covers that topic without duplicating the engine-pairing steps here.

Describe your browser, framework version and the failure you're seeing for a scoped compatibility assessment. The starter kit uses Firefox and Camoufox for its browser template. It isn't a Chromium CDP adapter and doesn't guarantee compatibility with every target application.