onionwright · guide
Your Playwright script keeps getting detected.
You added the stealth plugin, maybe switched to undetected-chromedriver, and it still gets flagged. That is not a bug in your setup — it is that every one of those fixes reaches the same layer, the driver, and the tells that catch you live in two other layers the driver cannot touch. Here is the whole picture, in the order it bites.
The fixes you have probably tried
Each of these helps, and each stops at the driver. That is the ceiling of anything that runs from inside the page or patches the automation library:
- puppeteer-extra-plugin-stealth / playwright-stealth
Overrides a list of known properties from inside the page. Clears the obvious ones, but an override is itself detectable — a getter sitting on a prototype where the real browser has none — and the list is always a step behind the detectors. - undetected-chromedriver
Removes the Selenium-specific tells (the cdc_ properties injected into the document). Real help against Selenium fingerprinting, but scoped to the driver; navigator.webdriver, the client-hint brand and the WebGL renderer are decided above it. - patchright
A patched Playwright that avoids the CDP tells its own automation leaves. The furthest the driver layer goes — and still the driver layer. It cannot change what the browser reports about itself before any script runs, or how the input is timed.
Where you are actually caught
Detection is layered, and clearing one layer just moves the check to the next. The three, in the order a detector reaches them:
1. Driver tells
Caught by: The automation framework leaves traces — cdc_ properties, a main-world execution frame, CDP artefacts. This is the layer the stealth libraries above address.
Closed by: Use a driver that does not leave them, or a browser that does not expose them. Necessary, not sufficient — clearing this just moves the detector to the next layer.
2. Engine coherence
Caught by: navigator.webdriver reads true; the Sec-CH-UA client-hint brand and the WebGL renderer string do not match a real Chrome on real hardware. These are set by the browser before your script can touch them, so a page-side override arrives too late and leaves its own trace.
Closed by: Fix them in the engine, not the page — delete the branch that sets navigator.webdriver rather than reassigning the property, so there is nothing left to find.
3. Behaviour
Caught by: Even a clean fingerprint moves like a machine: the mouse jumps in straight lines to exact pixels, clicks land with zero dwell, keystrokes are perfectly even, and events synthesised over CDP are marked isTrusted=false. A behavioural detector reads this in seconds.
Closed by: Drive the input with human motor timing — curved paths that overshoot and correct, dwell before a click, keystroke intervals from a real biometrics dataset — and deliver events the page sees as trusted.
The full mechanics of each signal — taken from reverse-engineering a real detection engine — are in how websites detect automation.
What closes all three at once
The engine layer needs a patched browser, not a patched driver; the behaviour layer needs input driven with human motor timing; and a coherent fingerprint comes for free from running on a real host with a real GPU, real fonts and the IP you bring. That combination is what onionwright is — a patched Chromium plus a humanized-input layer, driven by your existing Playwright code unchanged.
It is not magic, and this is the honest part: no tool clears every detector, including the ones that say they do. onionwright flips the common engine and behaviour tells on the same run where stock Chrome is caught, and two checks still catch it — measured, and shown with the method to reproduce.
See what it passes, and what it doesn’tCommon questions
Does undetected-chromedriver still work?
It removes the cdc_ properties Selenium injects, so it helps against Selenium fingerprinting — but it is scoped to the driver. navigator.webdriver, the client-hint brand and the WebGL renderer are decided above it, so a site checking those still catches it.
Why does playwright-stealth (or puppeteer-stealth) get detected?
It overrides a list of known properties from inside the page. An override is itself detectable — a getter sitting on a prototype where the real browser has none — and the list is always a step behind the detectors. It clears the obvious tells, not the engine or behaviour layers.
Can you make Playwright undetectable?
Not completely, and anyone claiming 'undetectable' is the one to distrust. You can close the driver, engine and behaviour layers a long way — your Playwright code unchanged, running under a patched browser with human input timing — but two checks still catch it, measured and shown on the proof page.
Keep reading
- onionwright vs patchright — patching the driver versus patching the browser, in full
- Pricing — $0.10 per browser-hour, one meter, no per-seat or per-profile fee