Playwright has overtaken Puppeteer as the default browser automation tool for both legitimate testing and bot operations. From a detection standpoint, the two look similar at first glance, but Playwright introduces specific globals, exposes its own function-binding mechanism, and supports three browsers instead of one. This article covers the Playwright-specific tells and the cross-browser detection picture.
The companion posts: Puppeteer bot detection for the Puppeteer detail, Selenium bot detection for the WebDriver case, headless browser detection for what the frameworks share, and bot detection for the broader framing.
What “Playwright bot” means
Playwright is Microsoft’s cross-browser automation library, with bindings for Node.js, Python, .NET and Java. It drives Chromium, Firefox and WebKit through their respective inspection protocols. Chromium uses the same Chrome DevTools Protocol that Puppeteer uses; WebKit uses a Playwright-specific protocol that exists because Safari does not expose CDP; Firefox is driven through a customized version of the Firefox remote protocol.
The cross-browser angle matters for detection because a single Playwright operator can target three different browsers, each with its own automation tells. A defender that only checks for Chromium-specific signals will miss Playwright sessions running against Firefox or WebKit.
The library is the default choice for new automation projects in 2026 for a few reasons:
- The Node.js Puppeteer stealth ecosystem has stagnated since puppeteer-extra-plugin-stealth was effectively deprecated by its maintainer in early 2025 and, as of mid-2026, has not been picked back up (Scrapewise: Playwright vs Puppeteer 2026).
- Playwright’s Python ecosystem is active and has multiple maintained stealth packages.
- The auto-wait API is more forgiving than Puppeteer’s, which reduces script brittleness against modern SPAs.
- The browser context isolation model fits scraping workloads where each task wants a clean browser state.
All four of those reasons also make Playwright a target for bot detection vendors. It is also the engine under most cloud-hosted browser services and agentic browsing products. AI agent detection covers that population specifically.
Playwright-specific tells
In addition to all the general headless and CDP tells covered in the Puppeteer post, Playwright leaves traces that are specific to its own runtime.
1. window.playwright__binding
Playwright exposes a binding mechanism for communicating between the browser context and the test driver. The binding is implemented as a function on the window object:
typeof window.__playwright__binding__ === 'function'
This is set by default and is one of the cheapest checks available for catching unpatched Playwright sessions.
2. window.__pwInitScripts
A second Playwright-specific global, related to the way Playwright injects its initialisation scripts into the page context. If present, it is a clear automation signal.
3. Exposed-function fingerprint
When Playwright code uses page.exposeFunction() or page.exposeBinding() to make a Node function callable from inside the browser, the resulting browser-side wrapper has specific characteristics:
- The wrapper has an
__installedproperty that returns a boolean. - Calling
toString()on the wrapper returns code containing the stringnew Error(\exposeBindingHandle supports a single argument`)`.
A detection script iterates window properties, finds functions, and inspects their toString() output. Any match against the Playwright wrapper signature is a high-confidence detection. Patched stealth implementations can suppress these specific strings, but the underlying pattern (a function whose toString contains code that does not match standard browser internals) is harder to defeat generically.
4. CDP detection (when Chromium is the target)
Playwright on Chromium uses CDP and is detectable through the same Runtime.enable side effects as Puppeteer. The standard detection pattern:
const e = new Error();
let detected = false;
Object.defineProperty(e, 'stack', {
get() { detected = true; return 'fake'; }
});
console.log(e);
If the stack getter fires, CDP is attached. This works the same for Playwright on Chromium as for Puppeteer on Chromium.
5. WebKit and Firefox specifics
Playwright on WebKit and Firefox uses its own protocols, which leave their own observable artefacts:
- The WebKit Playwright build is a custom-compiled WebKit that differs in subtle ways from production Safari (slightly different feature flags, different ANGLE/SwiftShader presence, different JIT timing). Detection at the JS-environment layer can distinguish Playwright-WebKit from real Safari.
- The Firefox Playwright driver requires a specific Firefox build with additional remote-debugging capabilities. The presence of those capabilities is detectable.
In practice, most Playwright bot operations target Chromium because the ecosystem of stealth tooling and proxy compatibility is biggest there. WebKit and Firefox Playwright bots exist but are rarer.
What playwright-stealth and playwright-extra patch
Two stealth packages dominate the Playwright ecosystem.
playwright-stealth (Python). Actively maintained, modern API, integrates with Playwright’s browser_context lifecycle. Patches the standard set of fingerprint tells: navigator.webdriver, plugin list, language array, WebGL renderer, AudioContext fingerprint adjustments, chrome runtime presence, permission state.
playwright-extra with stealth plugin (Node.js). A port of the puppeteer-extra-plugin-stealth approach to Playwright. Less actively maintained than the Python version. Patches roughly the same set of tells.
What neither package patches:
window.__playwright__binding__orwindow.__pwInitScripts(these are set after stealth scripts run in some cases).- The exposed-function
toString()fingerprint. - CDP attachment side effects.
- TLS fingerprint (because the TLS layer is the browser’s, not Playwright’s).
- Behavioral absence.
Browser support changes the detection surface
Because Playwright targets three browsers, a detector that wants comprehensive coverage has to implement checks for each:
Chromium target. All the standard headless and CDP tells from the Puppeteer post apply. Plus the Playwright-specific globals.
Firefox target. Firefox’s navigator.webdriver behavior is slightly different from Chrome’s. The plugin and language checks work the same. The Playwright-Firefox build has specific timing fingerprints in performance.now() that distinguish it from production Firefox.
WebKit target. Real Safari runs on macOS, iOS, iPadOS or in WKWebView. Playwright-WebKit on Linux is essentially Safari running outside its normal environment. The user-agent claims Safari, but the TLS fingerprint is closer to Linux Chromium because the underlying TLS stack differs from real Apple Safari. Cross-checks between TLS and claimed browser are very effective here.
For a defender, the practical guidance is to detect each of the three target browsers separately. A check that only handles Chromium will miss the smaller but growing population of Firefox and WebKit Playwright bots.
Practical detection
The signal hierarchy for catching Playwright in production:
-
Cheap server signals.
- JA4 fingerprint vs claimed browser. Linux Playwright-WebKit claiming Safari is the easiest catch.
- HTTP/2 SETTINGS frame.
- IP from a hosting ASN paired with a desktop UA.
-
Playwright-specific globals.
window.__playwright__binding__window.__pwInitScripts- Iterate functions looking for the exposed-function fingerprint.
-
Standard headless and CDP tells.
- All the Puppeteer-applicable checks.
Runtime.enableside-effect detection.
-
Browser-specific tells.
- Chromium: WebGL renderer, plugins, languages, permission state.
- Firefox: webdriver flag behavior, performance.now precision.
- WebKit: TLS vs UA mismatch, timing patterns, absence of iOS/macOS-specific APIs.
-
Behavioral absence.
- Same as Puppeteer and Selenium. Zero-interaction sessions are easy to flag.
-
Cross-checks.
- Joint distribution of claimed browser, TLS, environment, behavioral and infrastructure signals. A single contradiction is enough.
Playwright’s auto-wait makes some detection easier
Playwright’s auto-wait API means scripts do not need to insert explicit wait(500) calls between actions. The framework waits for elements to be visible, enabled and stable before clicking. This makes scripts cleaner but introduces a detectable timing fingerprint: actions chain together with characteristic short intervals (a few tens of milliseconds) that humans rarely produce.
Behavioral analysis that measures the inter-action timing distribution can catch this directly. Real users produce a long-tail distribution of action intervals. Playwright scripts with auto-wait produce a much tighter distribution centered on small values.
How Foil detects Playwright
Foil’s classification surface includes framework.playwright.chromium, framework.playwright.firefox and framework.playwright.webkit, with sub-variants for stealth-patched versions. The classification combines:
- Server-side TLS and HTTP signals (especially powerful for WebKit-target sessions on Linux).
- Playwright-specific JS environment checks.
- General headless and CDP detection.
- Per-browser timing and feature-presence checks.
- Behavioral snapshot evaluation including auto-wait timing patterns.
A session classified as Playwright arrives at the application with a named verdict and the underlying signals enumerated, so policy decisions can be made with full context.
Further reading
- DataDome, Will Playwright replace Puppeteer for bad bot play-acting?: datadome.co/bot-management-protection
- AlterLab, Playwright Anti-Bot Detection: What Works (2026): alterlab.io/blog
- Scrapewise, Best Playwright Stealth 2026: Tested vs Cloudflare & Akamai: scrapewise.ai/blogs