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:
- Viewport size. Set it explicitly and record
innerWidthandinnerHeight. Playwright's new-context reference documents a default of 1280×720. Withviewport: null, dimensions depend on the host window. Neither default is a reason to assume your two runs match. - Rendering environment. Record fonts, graphics configuration and browser arguments when investigating canvas, video or screenshot differences. Headless mode alone doesn't prove that GPU rendering is disabled.
- Permissions and dialogs. Configure supported permissions in the test context and handle application dialogs explicitly. Test downloads through the framework's download API; don't treat every download as an OS permission prompt.
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.
navigator.webdriver reports automation. That's the spec.
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.
- Use a test account with known credentials, stored outside the repository (environment variables or a secrets manager).
- Assert on application outcomes. For an order fixture, verify its saved identifier and expected state; a confirmation-page heading alone doesn't prove that persistence succeeded.
- Set explicit timeouts and viewport sizes. Don't rely on framework defaults staying consistent across versions.
- Run the same test headed and headless. Keep application logic, fixture state and environment differences in the investigation until the evidence isolates a cause.
Keep a failure record
For each test run, record:
- Environment: OS, browser binary + version, driver version, framework version, headed/headless, viewport size.
- Request: URL, method, authentication method, timestamp.
- Result: HTTP status, response content type, screenshot, console errors.
- Classification: startup failure, navigation failure, authentication failure, actionability timeout, application-level rejection.
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:
| Check | Headed | Headless | Pass? |
|---|---|---|---|
| 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.