undetected-chromedriver vs playwright-stealth
Choosing a stealth toolkit usually comes down to the automation stack you already run and how much maintenance you can absorb; this comparison sits under Browser Fingerprint & Stealth Configuration and weighs the two most common options side by side.
The quick answer: pick undetected-chromedriver if you already have a Selenium codebase and want the strongest out-of-the-box masking with minimal code changes, and pick playwright-stealth if you are building new, need async concurrency and reliable auto-waiting, or want a toolkit that tracks browser updates with less breakage. undetected-chromedriver patches the ChromeDriver and browser binary for deep coverage but is fragile across Chrome releases; playwright-stealth layers JavaScript patches onto Playwright for a faster, more maintainable, async-native workflow at slightly lower default coverage.
How Each Tool Approaches Stealth
undetected-chromedriver intercepts the ChromeDriver startup, removes the variables and CDP artifacts that expose automation, and launches a Chrome that never advertises the WebDriver flags in the first place. Because it operates at the binary and driver level, it neutralizes several signals — including navigator.webdriver and automation switches — before any page script runs, which is why it often clears heuristic checks with almost no configuration.
playwright-stealth takes the opposite path: it keeps standard Playwright and injects a curated set of JavaScript patches into each page, redefining navigator.webdriver, spoofing WebGL vendor and renderer strings, populating plugins and languages, and smoothing canvas differences. It rides on Playwright's architecture, so you keep async execution, auto-waiting, and network interception. The trade-off is that JavaScript-level patches can lag the newest detection scripts until the patch set is updated.
Detection Coverage
On a fresh install, undetected-chromedriver tends to pass more red rows on fingerprint test pages because it removes automation traces at the source rather than papering over them in script. That depth matters against strict enterprise WAFs. playwright-stealth covers the same well-known surfaces and is usually sufficient, but its coverage depends on how current the patch set is; a brand-new detection technique may slip through until the library catches up. Either way, coverage is a moving target — the same underlying signals are described in how to configure Selenium stealth to avoid detection, and neither tool solves interactive CAPTCHAs.
Maintenance and Speed
Maintenance is where the two diverge most. undetected-chromedriver binds tightly to a specific Chrome major version; when Chrome auto-updates, sessions can fail with a driver-version mismatch until you pin version_main or upgrade the package. playwright-stealth inherits Playwright's managed browser downloads, so pinning a Playwright version pins a matching browser, which makes deployments more reproducible. On speed, undetected-chromedriver adds a patching step and a subprocess launch that slows cold starts, while Playwright reuses one browser process across many contexts, giving it an edge under repeated launches and high concurrency.
A Runnable Comparison
The two scripts below fetch the same fingerprint test page with each tool, so you can observe coverage and startup behavior on your own machine. Both send an explicit, consistent User-Agent.
import undetected_chromedriver as uc
def run_uc(url: str) -> str:
"""Load a page with undetected-chromedriver and return the document title."""
options = uc.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument("--disable-gpu")
options.add_argument("--window-size=1920,1080")
options.add_argument(
"--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
)
driver = uc.Chrome(options=options, use_subprocess=True)
try:
driver.get(url)
return driver.title
finally:
driver.quit()
if __name__ == "__main__":
print(run_uc("https://bot.sannysoft.com"))
from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync
def run_playwright_stealth(url: str) -> str:
"""Load the same page with playwright-stealth and return the document title."""
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
viewport={"width": 1920, "height": 1080},
locale="en-US",
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
),
)
page = context.new_page()
stealth_sync(page)
page.goto(url, wait_until="networkidle", timeout=30000)
title = page.title()
browser.close()
return title
if __name__ == "__main__":
print(run_playwright_stealth("https://bot.sannysoft.com"))
Run both, then inspect the rendered test rows: undetected-chromedriver typically clears the automation-flag checks with no extra work, while the Playwright version gives you the async model and network interception once you scale beyond a single page. For modern projects, Using Playwright for Modern Web Automation shows the broader workflow the stealth layer plugs into.
Edge Cases and Caveats
- Chrome auto-updates break undetected-chromedriver. Pin
version_mainto your installed Chrome major version or disable background Chrome updates on servers to avoid sudden session failures. - playwright-stealth API changes across releases. The stealth entry point has moved between versions; check the installed package's exported names rather than copying an import from an old tutorial.
- Neither defeats cryptographic challenges. Turnstile, hCaptcha, and reCAPTCHA v3 need genuine challenge execution or a solving service — stealth only lowers heuristic suspicion.
- Coverage without IP hygiene is wasted. A perfectly patched browser on a flagged datacenter IP is still blocked; pair either tool with Rotating Proxies and Managing IP Blocks.
- You can combine, not just choose. Some teams use
undetected-chromedriverfor the hardest targets and Playwright for high-volume routine crawling, selecting per site rather than standardizing on one.
Frequently Asked Questions
Which tool has better detection coverage out of the box?undetected-chromedriver generally clears more checks on a fresh install because it removes automation traces at the driver and binary level before page scripts run. playwright-stealth covers the same surfaces through JavaScript patches and is usually enough, but its edge against brand-new detection depends on how current the patch set is.
Which is easier to maintain over time?playwright-stealth tends to be lower maintenance because Playwright manages matching browser downloads, so pinning one version pins a compatible browser. undetected-chromedriver is tied to your locally installed Chrome major version and can break when Chrome auto-updates unless you pin version_main.
Can I use undetected-chromedriver with async code?
Not natively — it is built on synchronous Selenium, so scaling means threads, each with its own browser. If you need true async concurrency on a single event loop, playwright-stealth is the better fit.
Should I ever run both in the same project?
Yes. It is common to route the hardest, most aggressively defended targets through undetected-chromedriver for its deeper masking while handling high-volume routine crawling with Playwright for speed and concurrency, choosing per target rather than committing to one tool everywhere.