Why Playwright Detection Matters
Playwright, developed by Microsoft, is a powerful browser automation framework used for testing, scraping, and AI agent workflows. While it has legitimate uses in development, it is also widely used by scrapers, credential stuffers, and AI agents to automate interactions with websites that were not designed for automated access.
Unlike older tools like Selenium, Playwright is designed with stealth in mind — it patches many of the obvious detection signals. However, it still leaves detectable traces that a well-designed detection system can identify.
Signal 1: navigator.webdriver
The most fundamental check. The WebDriver specification requires browsers controlled by automation to set navigator.webdriver = true. Playwright respects this by default, though it can be patched:
if (navigator.webdriver === true) {
// Definitely automated — Playwright, Selenium, or similar
}
Note: Playwright's stealth mode patches this to return undefined. Do not rely on this check alone.
Signal 2: Playwright-Specific Global Variables
Playwright injects several global variables into the browser context that are not present in real browsers:
// Check for Playwright-specific globals
const hasPlaywright = !!(
window.__playwright ||
window.__pw_manual ||
window.playwright ||
window.__playwrightScriptContent
);
Signal 3: Missing or Anomalous Browser APIs
Real browsers implement a complete set of browser APIs. Playwright's Chromium build often has gaps:
- Missing plugins:
navigator.plugins.length === 0in headless mode - Missing mimeTypes:
navigator.mimeTypes.length === 0 - Incomplete chrome object:
window.chromemay be missing or incomplete - Unusual screen dimensions: Default viewport of 1280×720 with no screen chrome
Signal 4: CDP Runtime Artifacts
Playwright communicates with the browser via the Chrome DevTools Protocol. This communication can leave artifacts detectable through timing analysis and error stack inspection:
// Error stacks from CDP-injected scripts contain unusual frames
const err = new Error();
const stack = err.stack || '';
const hasCDPFrames = stack.includes('__playwright') ||
stack.includes('evaluateHandle');
Signal 5: Canvas and WebGL Fingerprinting
Playwright's headless Chromium renders canvas and WebGL differently from a real browser with a GPU. The pixel-level output of canvas operations can be used as a fingerprint to identify headless environments:
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.fillText('Shlumi', 10, 10);
const dataURL = canvas.toDataURL();
// Hash the dataURL and compare against known headless fingerprints
Signal 6: Timing and Behavioral Analysis
Playwright scripts interact with pages programmatically, which creates distinctive timing patterns:
- Zero time between page load and first interaction
- Perfectly uniform click timing (no human variance)
- Mouse movements that follow straight-line paths
- Scroll events that jump in exact pixel increments
Combining Signals for High-Confidence Detection
No single signal is sufficient for confident Playwright detection. The key is to combine multiple weak signals into a composite score. Shlumi's scoring engine assigns weights to each signal and computes a 0–100 confidence score:
navigator.webdriver === true: +60 points (definitive)- Playwright globals present: +50 points (definitive)
- Missing plugins: +15 points (soft)
- CDP timing artifacts: +20 points (soft)
- Canvas fingerprint match: +25 points (medium)
A session scoring above 50 receives a suspicious verdict; above 75 receives bot.
Implementing Detection with Shlumi
Rather than implementing each of these checks manually, Shlumi's detection script handles all 30+ signals automatically. Add one script tag to your site and get real-time Playwright detection with configurable enforcement actions (block, challenge, flag, or throttle).