How headless Chrome, Puppeteer and Playwright get detected
Automation frameworks no longer announce themselves — but they still leave a consistent trail across the environment, the rendering stack and the input timeline. Here is the full detection surface, in the order a real anti-bot stack evaluates it.
Updated 23 August 2026
Run the free live testTier 1 — declared flags
navigator.webdriver === true, set by the W3C WebDriver spec.HeadlessChromein the user-agent, or a missingSec-CH-UAbrand list.- Automation-only globals such as
window.cdc_*(ChromeDriver) or__playwrightbindings.
Every stealth toolkit removes these first, so on their own they only catch naive scripts.
Tier 2 — environment gaps
- Software rendering — WebGL reports "Google SwiftShader" or "llvmpipe" instead of a real GPU.
- Font poverty — a container image resolves 5-15 fonts where a real desktop resolves 80+.
- Missing media stack — no audio output device, no codecs for proprietary formats, empty
mediaDevices. - Permissions anomalies —
Notification.permissionis "denied" while the Permissions API reports "prompt". - Screen mismatch —
outerHeightequal toinnerHeight, no window chrome, exact 1920x1080 with zero taskbar offset.
Tier 3 — contradiction checks
This is where patched browsers fail. A stack cross-references claims against each other: a Windows user-agent with a Linux GPU renderer, an iPhone user-agent with a mouse-move event, deviceMemory of 8 with hardwareConcurrency of 1, a toString() on a spoofed getter that no longer reads[native code], or a timezone offset that disagrees with the IP's country. Each contradiction is worth more than any single flag.
Tier 4 — behavioural biometrics
Human pointer paths curve, overshoot and jitter; typing has variable dwell and flight times; scrolling has inertia and micro-corrections. Automation produces straight lines, constant velocity, uniform key intervals and instant field population. Our analyzer measures path curvature, velocity variance, jitter entropy, click dwell and keystroke rhythm live.
Tier 5 — network and protocol
- TLS/JA3 and HTTP/2 frame-order fingerprints that do not match the claimed browser.
- Datacentre ASN, hosting-provider ranges, and proxy or VPN attribution.
- Header order and casing differing from real Chrome's exact sequence.
Test your own session
Run the live analyzer — it reports an automation verdict with the exact signals that fired, or read the deeper bot & automation detection guide.
Frequently asked questions
How do websites detect headless Chrome?
They combine cheap flags such as navigator.webdriver and the HeadlessChrome user-agent token with harder signals: a SwiftShader software renderer instead of a real GPU, a tiny font list, missing permission prompts, empty plugin arrays, and Chrome DevTools Protocol side effects like an unusually fast Runtime.enable response.
Does puppeteer-extra-plugin-stealth still work?
It defeats the obvious checks, but patching a property leaves traces: overridden getters have the wrong toString output, spoofed values contradict each other (a Windows user-agent with a Linux GPU string), and function prototype chains change. Detection stacks look for those contradictions rather than the original flag.
Is headless Chrome always malicious?
No. It powers screenshot services, uptime monitors, PDF generation, accessibility testing and legitimate crawlers. Good detection scores automation as one input to a risk decision instead of blocking outright.
Can behaviour alone reveal automation?
Often, yes. Scripted input produces perfectly linear mouse paths, zero jitter, constant inter-key timing, instant form fills and no scroll inertia. Human input is noisy, curved and irregular, and that difference survives every user-agent patch.