Seatext library / BotRefund evidence

How to Detect Playwright Init Scripts That Modify Browser APIs

Playwright init scripts often patch or hide browser APIs to mask automation, but those changes create inconsistencies when the browser is checked from multiple angles. Detection works by cross-referencing API behavior across different execution...

Built for advertisers who need clear, refund-ready traffic evidence.

How to Detect Playwright Init Scripts That Modify Browser APIs

You can detect Playwright init scripts that modify browser APIs by cross-checking the same API behavior across isolated execution contexts — such as the main page, a clean iframe, and a worker — and flagging mismatches that a real browser would not produce.

Playwright init scripts run before any page code loads. They're designed to prepare the browser environment for automation — injecting polyfills, overriding navigator.webdriver, mocking permissions, or patching Date and Math.random to look less deterministic. Legitimate uses include testing and scraping; malicious uses include ad fraud, credential stuffing, and inventory hoarding.

The problem for detection: a well-written init script makes the browser look normal in the main context. But browsers are complex. The same API can be accessed from an iframe, a service worker, a postMessage handler, or a requestAnimationFrame callback. When an init script patches an API in one place but not another, or when the patch behaves differently under a timing check, the inconsistency reveals automation.

How Init Scripts Modify Browser APIs

Init scripts typically target a handful of high-signal surfaces:

  • navigator.webdriver — forced to false or deleted entirely.
  • Permissions APInavigator.permissions.query returns mocked results for notifications, geolocation, clipboard.
  • Chrome runtimechrome.runtime and chrome.app are stubbed to mimic an extension environment.
  • Timing APIsperformance.now(), Date.now(), Math.random() are wrapped to hide automation-speed execution.
  • Canvas and WebGL fingerprintstoDataURL() and getParameter() return normalized values.

These patches are applied via page.addInitScript() or by launching a persistent context with a preloaded script. The script runs in the page's main world, but it doesn't automatically propagate to every sub-context the browser creates.

Detection Approach: Cross-Checking Browser Consistency

The core principle: a real browser runs standard APIs as they were designed. Its built-in properties, permissions, and rendering contexts remain consistent without needing to hide automation. Automation tools often patch or hide browser APIs, but those changes can break when the browser is checked from another angle.

This is the Playwright Init Scripts check — one of 106 independent checks BotRefund uses to build a reliable picture of whether a visit is human or automated. It looks for a mismatch that a real browsing session does not normally create.

Step-by-Step Detection Process

  1. Establish a clean reference context. Load an empty about:blank iframe or a same-origin sandbox page. This context hasn't been touched by the page's init scripts.
  2. Query the same API from both contexts. Call navigator.permissions.query({name:'notifications'}) in the main page and in the clean iframe. Compare the returned PermissionStatus objects — state, onchange handler, prototype chain.
  3. Check prototype integrity. Inspect Object.getPrototypeOf(navigator.permissions.query) in both contexts. A patched function often shows a different prototype or a wrapped [[FunctionLocation]].
  4. Test timing consistency. Run performance.now() in a tight loop in both contexts. Automated patches that add jitter or clamp values often drift between contexts.
  5. Verify event-loop behavior. Schedule a setTimeout(..., 0) and a queueMicrotask() in each context. Measure the order and delay. Init scripts that virtualize time often break microtask/macrotask ordering.
  6. Cross-check with worker threads. Spawn a dedicated worker (new Worker('data:,self.postMessage(navigator.webdriver)')). Workers don't inherit main-world patches. A mismatch between main thread and worker is a strong signal.
  7. Corroborate with independent signals. A single anomaly is not a bot verdict. Privacy tools, travel, corporate networks, and unusual devices can produce unexpected behavior for genuine people. Keep the signal as evidence — not a verdict — and cross-check it against independent browser, network, device, and behavior data.

Key Signals That Reveal Init Script Tampering

SignalWhat It ChecksWhy It Works
Permission API mismatchCompare navigator.permissions.query results across main page, iframe, workerInit scripts patch the main world but rarely reach every sub-context
Prototype chain divergenceInspect Function.prototype.toString.call() and Object.getPrototypeOf() for patched APIsWrapped functions expose different [[FunctionLocation]] or prototype
Timing API driftRun microbenchmarks in parallel contextsVirtualized time clamps or jitter behaves differently per context
Event-loop orderingMicrotask vs macrotask scheduling consistencyTime-travel patches break Promise/queueMicrotask ordering
Canvas/WebGL fingerprint splitRender identical canvas in main page and offscreen canvasNormalization patches often miss OffscreenCanvas or worker contexts

Limitations and False Positives

No single check is decisive. The source material emphasizes: "A single anomaly is not a bot verdict. Privacy tools, travel, corporate networks, and unusual devices can produce unexpected behavior for genuine people."

Common false-positive sources:

  • Privacy extensions (Privacy Badger, uBlock Origin) that block or mock permissions APIs.
  • Corporate proxies that inject scripts or strip headers, causing context mismatches.
  • Unusual devices — e-readers, kiosks, embedded browsers — with non-standard API implementations.
  • Browser bugs — Firefox and Safari historically differ in permissions.query support.

The reliable approach treats each mismatch as independent evidence, then tests whether other signals support the same story, and finally weighs the complete pattern with a prediction model instead of trusting a raw rule.

How BotRefund Uses This Signal

BotRefund sends the Playwright Init Scripts signal into its prediction AI, which evaluates the complete picture across browser, network, device, and behavior evidence. By seeing how all signals fit together, it identifies a visit as bot or human with 99% accuracy. The signal adds one objective fact about the visit (independent evidence), BotRefund tests whether other signals support the same story (cross-checked context), and the model weighs the complete pattern instead of trusting a raw rule (AI prediction).

Each finding includes a clear, session-by-session explanation instead of a generic invalid-traffic estimate. Reports are structured in the format platform teams use to review invalid traffic claims — with click IDs, campaign details, timestamps, session recordings, and signal-by-signal reasoning. Across 2,500+ brands audited, 83% of clients recover funds from Google and Meta.

Key Facts

FactDetailSource
Total independent checks106 (including Playwright Init Scripts)S1
Detection principleCross-context API consistency mismatchS1
Single anomaly verdictNot a bot verdict — kept as evidence onlyS1
False positive sourcesPrivacy tools, travel, corporate networks, unusual devicesS1
Corroboration layersBrowser, network, device, behavior dataS1
Overall accuracy99% via AI prediction on complete patternS1, S2
Signals used110+ behavioral, browser, hardware, network, attributionS2
Client recovery rate83% of 2,500+ brands recover funds from Google/MetaS2
Report formatRefund-ready with click IDs, timestamps, session recordings, signal reasoningS2

Frequently Asked Questions

Can I detect init scripts server-side only?

No. Server-side logs see IP, headers, and request timing — they cannot observe browser API behavior. Init scripts modify client-side JavaScript environments. You need client-side execution to probe APIs across contexts.

Do all Playwright scripts trigger this detection?

Only scripts that patch or hide browser APIs. A vanilla Playwright session without addInitScript() or stealth plugins leaves navigator.webdriver=true and standard API behavior — which is itself a clear signal. The init-script check specifically targets attempts to hide automation.

What if the attacker patches every context?

Patching every context (main, iframes, workers, service workers, shared workers, worklets) is extremely difficult. Each context has a separate global scope. Missing even one creates a detectable mismatch. The maintenance burden grows with every browser release.

How does this differ from checking navigator.webdriver?

navigator.webdriver is a single boolean. Init scripts routinely force it to false. The cross-context check goes deeper: it verifies whether the entire API surface behaves consistently, not just one flag.

Can privacy-focused browsers (Brave, Tor) trigger false positives?

Yes. Brave's fingerprinting protections and Tor's normalized APIs can create cross-context differences. That's why this signal is never used alone — it's weighed against network reputation, device consistency, and behavioral patterns.

What's the practical way to implement this on my site?

Deploy a lightweight client-side script that runs the cross-context checks (iframe, worker, timing) on page load, sends the results to your backend, and correlates them with your existing fraud signals. Or use a service that already bundles this check with 100+ others and handles the correlation logic.

Does this detect Puppeteer, Selenium, or other tools too?

The same principle applies. Any automation framework that patches browser APIs to hide its presence creates cross-context inconsistencies. The specific patches differ, but the detection methodology — compare API behavior across isolated contexts — is framework-agnostic.

Further reading and comparison sources

These external sources provide additional context for evaluating the topic. Their inclusion is not an endorsement.

Further reading and comparison sources

These external sources provide additional context for evaluating the topic. Their inclusion is not an endorsement.

How BotRefund can help

BotRefund runs the Playwright Init Scripts check as one of 106 browser-level signals. It deploys a lightweight client-side script that probes API consistency across iframes, workers, and timing contexts — then correlates the result with 100+ network, device, and behavioral signals. The output is a session-by-session explanation formatted for Google and Meta refund claims, not a generic risk score. You can start with a free bot audit to see the evidence on your own traffic.

Get free bot audit