All articles Bot detection

How to Detect Playwright-Stealth Evasion in 2026

Playwright-stealth ports the puppeteer-extra evasion catalog to Playwright, flipping navigator.webdriver to false and repainting the properties scrapers check first. The patches look convincing property by property, but they are static overrides layered on a real automation stack, and that mismatch is exactly what a modern detector looks for.

What playwright-stealth actually patches

The stealth bundle is a set of independent evasions, each targeting one known leak:

  • navigator.webdriver forced to false
  • A fake chrome runtime object injected into window
  • navigator.plugins and navigator.mimeTypes populated with plausible fake entries
  • navigator.languages set so it is not empty
  • WebGL UNMASKED_VENDOR and UNMASKED_RENDERER spoofed to a common GPU string
  • Notification.permission and navigator.permissions.query made consistent

Each patch fixes a symptom. None of them changes the underlying Chromium-under-CDP reality, so the evasions have to agree with each other perfectly across every surface, and they usually do not.

Where the evasions still leak

The fastest tells come from consistency checks the stealth author did not anticipate:

  1. Property descriptor tampering. Overriding navigator.webdriver with Object.defineProperty leaves a getter where a native data property should be. Comparing Object.getOwnPropertyDescriptor output against a genuine Chrome build flags the rewrite.
  2. WebGL parameter contradictions. The spoofed renderer string claims a GPU whose supported extensions, max texture size, and precision formats do not match the values the driver actually reports.
  3. toString leakage on patched functions. Stealth wraps native functions; unless every wrapper spoofs Function.prototype.toString, the patched method stringifies to reveal injected code instead of [native code].
  4. Permission and feature mismatches. A headful-claiming profile that still reports headless-only codec support or a missing chrome.csi timing object contradicts itself.

Any single contradiction is weak evidence. A stack of them within one page load is not.

CDP artifacts stealth cannot remove

Playwright drives Chromium over the Chrome DevTools Protocol, and CDP leaves runtime traces stealth patches do not touch. Execution-context creation timing, the presence of an auxiliary DevTools target, and subtle differences in how Runtime.evaluate handles exceptions all persist. These are process-level signals living below the JavaScript layer stealth operates in, which is why we emphasize server-side Smart Signals over any single browser property — a value a script owns can be forged, but the environment that produced it is far harder to fake convincingly.

Behavioral and timing signals

Even a perfectly patched browser still has to act human. Stealth does nothing for behavior:

  • Input entropy. Real pointer paths have jitter, variable velocity, and micro-corrections. Scripted mouse.move calls interpolate cleanly and land pixel-perfect on targets.
  • Event cadence. Human keystrokes vary in dwell and flight time; automated type() calls arrive at machine-regular intervals.
  • Navigation rhythm. Bots skip the read-and-decide pauses humans take between meaningful actions.

Combine behavioral flatness with even one environment contradiction and confidence rises fast.

Why cross-session correlation matters most

The single most reliable signal is not on the page at all. Stealth-patched Playwright farms rotate proxies and clear storage, but they reuse the same code, same launch flags, and same handful of hardware profiles. Prynt issues a stable visitorId that survives incognito, storage clearing, and IP rotation, so a thousand “unique” visitors resolving to a few underlying identities is a pattern no per-page patch can hide. Layer in our cross-site reputation network and an automation stack burned on one property arrives pre-flagged on the next.

Avoiding false positives

Aggressive stealth detection can misfire, and blocking real users is worse than missing a bot. Several legitimate configurations mimic individual stealth tells: privacy browsers strip navigator.plugins, hardened setups spoof WebGL vendor strings, and corporate managed devices ship unusual font sets. That is exactly why no single property should trigger a block. A property-tampering check that fires alone might be a privacy extension; the same check firing alongside CDP artifacts, behavioral flatness, and a collapsed cross-session identity is a stealth-patched bot. Weighting signals into an explainable suspect score, rather than hard-blocking on any one leak, keeps the false-positive rate low while still catching evasion. It also gives your fraud team reason codes to review borderline sessions instead of silently turning away paying customers, which matters most on high-value flows like checkout and account creation where a wrong block has real revenue cost.

Putting it together

Practical detection of stealth-patched Playwright looks like this:

  1. Collect environment signals and run internal consistency checks, not just presence checks.
  2. Score CDP and process-level artifacts that stealth operates too high in the stack to reach.
  3. Weight behavioral flatness across the session.
  4. Correlate the visitorId across sessions and against the reputation network.
  5. Return an explainable score with reason codes rather than a single hard block.

No one of these is decisive. Together they push a stealth session’s suspect score well past a confident threshold.

Playwright-stealth raises the floor for evasion, not the ceiling for detection. Want to see which of your automated flows still leak? Spin up the playground and test a stealth-patched session against live signals, free to start.

Try it free

Prynt is device intelligence with a free tier — visitor IDs, bot & fraud Smart Signals, and behavioral biometrics, powered by a cross-site network. Start free.

Keep reading