Playwright Stealth: Anti-Detection Setup for 2026
Playwright is Microsoft’s browser automation framework that supports Chromium, Firefox, and WebKit. While it’s more modern than Selenium and faster than Puppeteer for many tasks, it still gets detected by anti-bot systems out of the box.
This guide covers how to configure Playwright for stealth scraping — patching detection vectors, adding behavioral signals, and integrating proxies for maximum evasion.
Why Playwright Gets Detected
Standard Playwright reveals itself through:
navigator.webdriverreturnstrue- Missing or incorrect browser runtime objects
- Playwright-specific properties in the browser context
- Default headless mode fingerprints
- Missing or empty
navigator.plugins - Incorrect
navigator.permissionsresponses - Detectable automation flags in the browser arguments
Stealth Setup with Python
Using playwright-stealth
The playwright-stealth package ports Puppeteer Stealth’s evasions to Playwright:
pip install playwright playwright-stealth
playwright install chromiumfrom playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync
def scrape_stealthily(url):
with sync_playwright() as p:
browser = p.chromium.launch(
headless=False,
args=[
"--disable-blink-features=AutomationControlled",
"--no-sandbox",
]
)
context = browser.new_context(
viewport={"width": 1920, "height": 1080},
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/122.0.0.0 Safari/537.36"
),
locale="en-US",
timezone_id="America/New_York",
)
page = context.new_page()
# Apply stealth patches
stealth_sync(page)
page.goto(url, wait_until="networkidle")
content = page.content()
browser.close()
return content
html = scrape_stealthily("https://bot.sannysoft.com")Manual Stealth Configuration
For more control, apply evasions manually:
from playwright.sync_api import sync_playwright
def create_stealth_context(playwright):
browser = playwright.chromium.launch(
headless=False,
args=[
"--disable-blink-features=AutomationControlled",
"--disable-features=IsolateOrigins,site-per-process",
"--no-sandbox",
]
)
context = browser.new_context(
viewport={"width": 1920, "height": 1080},
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/122.0.0.0 Safari/537.36"
),
locale="en-US",
timezone_id="America/New_York",
color_scheme="light",
java_script_enabled=True,
)
# Apply comprehensive stealth patches
context.add_init_script("""
// Remove webdriver flag
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
// Fix chrome runtime
window.chrome = {
runtime: {
onMessage: { addListener: () => {} },
sendMessage: () => {}
},
loadTimes: () => ({
commitLoadTime: Date.now() / 1000,
connectionInfo: "h2",
finishDocumentLoadTime: Date.now() / 1000 + 0.1,
finishLoadTime: Date.now() / 1000 + 0.2,
firstPaintAfterLoadTime: 0,
firstPaintTime: Date.now() / 1000 + 0.05,
navigationType: "Other",
npnNegotiatedProtocol: "h2",
requestTime: Date.now() / 1000 - 0.5,
startLoadTime: Date.now() / 1000 - 0.3,
wasAlternateProtocolAvailable: false,
wasFetchedViaSpdy: true,
wasNpnNegotiated: true
}),
csi: () => ({
startE: Date.now(),
onloadT: Date.now() + 100,
pageT: Date.now() + 200,
tran: 15
})
};
// Fix plugins
Object.defineProperty(navigator, 'plugins', {
get: () => [
{
name: 'Chrome PDF Plugin',
description: 'Portable Document Format',
filename: 'internal-pdf-viewer',
length: 1
},
{
name: 'Chrome PDF Viewer',
description: '',
filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai',
length: 1
},
{
name: 'Native Client',
description: '',
filename: 'internal-nacl-plugin',
length: 2
}
]
});
// Fix languages
Object.defineProperty(navigator, 'languages', {
get: () => ['en-US', 'en']
});
// Fix permissions
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
parameters.name === 'notifications'
? Promise.resolve({ state: Notification.permission })
: originalQuery(parameters)
);
// Fix hardware concurrency
Object.defineProperty(navigator, 'hardwareConcurrency', {
get: () => 8
});
// Fix device memory
Object.defineProperty(navigator, 'deviceMemory', {
get: () => 8
});
// Fix platform
Object.defineProperty(navigator, 'platform', {
get: () => 'Win32'
});
""")
return browser, context
with sync_playwright() as p:
browser, context = create_stealth_context(p)
page = context.new_page()
page.goto("https://target-site.com")
print(page.title())
browser.close()Node.js Setup
const { chromium } = require('playwright-extra');
const stealth = require('puppeteer-extra-plugin-stealth');
// playwright-extra supports puppeteer-extra plugins
chromium.use(stealth());
(async () => {
const browser = await chromium.launch({
headless: false,
});
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
});
const page = await context.newPage();
await page.goto('https://bot.sannysoft.com');
await page.screenshot({ path: 'test.png' });
await browser.close();
})();Proxy Integration
Basic Proxy
from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync
with sync_playwright() as p:
browser = p.chromium.launch(
headless=False,
proxy={
"server": "http://residential.example.com:7777",
"username": "user",
"password": "pass"
}
)
page = browser.new_page()
stealth_sync(page)
page.goto("https://target-site.com")
print(page.title())
browser.close()Per-Context Proxy Rotation
from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync
proxies = [
{"server": "http://gate.example.com:7777", "username": "user1", "password": "pass1"},
{"server": "http://gate.example.com:7778", "username": "user2", "password": "pass2"},
{"server": "http://gate.example.com:7779", "username": "user3", "password": "pass3"},
]
with sync_playwright() as p:
for i, proxy in enumerate(proxies):
browser = p.chromium.launch(headless=False, proxy=proxy)
page = browser.new_page()
stealth_sync(page)
page.goto("https://httpbin.org/ip")
ip_text = page.inner_text("body")
print(f"Proxy {i}: {ip_text}")
browser.close()Bypassing Cloudflare
from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync
import time
def bypass_cloudflare(url, proxy=None):
with sync_playwright() as p:
launch_args = {"headless": False}
if proxy:
launch_args["proxy"] = proxy
browser = p.chromium.launch(**launch_args)
context = browser.new_context(
viewport={"width": 1920, "height": 1080},
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/122.0.0.0 Safari/537.36"
),
)
page = context.new_page()
stealth_sync(page)
page.goto(url, wait_until="domcontentloaded")
# Wait for Cloudflare to resolve
for _ in range(20):
if "Checking your browser" not in page.content():
break
time.sleep(1)
# Extract session data
cookies = context.cookies()
content = page.content()
cf_clearance = next(
(c for c in cookies if c["name"] == "cf_clearance"),
None
)
browser.close()
return {
"html": content,
"cookies": cookies,
"cf_clearance": cf_clearance["value"] if cf_clearance else None
}
result = bypass_cloudflare(
"https://protected-site.com",
proxy={
"server": "http://residential.example.com:7777",
"username": "user",
"password": "pass"
}
)
print(f"Content length: {len(result['html'])}")Human Behavior Simulation
from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync
import random
import time
def simulate_human(page):
"""Add human-like behavior to a Playwright page."""
# Random mouse movements with natural curves
for _ in range(random.randint(3, 7)):
x = random.randint(100, 1500)
y = random.randint(100, 800)
# Move in steps for more natural movement
page.mouse.move(x, y, steps=random.randint(5, 15))
time.sleep(random.uniform(0.1, 0.4))
# Natural scroll
scroll_amount = random.randint(200, 600)
page.mouse.wheel(0, scroll_amount)
time.sleep(random.uniform(0.5, 1.5))
# Sometimes scroll back up a bit
if random.random() > 0.5:
page.mouse.wheel(0, -random.randint(50, 150))
time.sleep(random.uniform(0.3, 0.8))
def type_humanly(page, selector, text):
"""Type text with human-like delays."""
page.click(selector)
time.sleep(random.uniform(0.2, 0.5))
for char in text:
page.keyboard.type(char)
time.sleep(random.uniform(0.05, 0.15))Using Firefox for Stealth
Playwright’s Firefox support can bypass some detections that specifically target Chrome:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.firefox.launch(headless=False)
context = browser.new_context(
viewport={"width": 1920, "height": 1080},
locale="en-US",
)
page = context.new_page()
# Firefox doesn't need as many patches as Chromium
# But still remove webdriver flag
page.add_init_script("""
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
""")
page.goto("https://target-site.com")
print(page.title())
browser.close()Playwright vs Puppeteer vs Undetected ChromeDriver
| Feature | Playwright + Stealth | Puppeteer + Stealth | Undetected ChromeDriver |
|---|---|---|---|
| Multi-browser | Chromium, Firefox, WebKit | Chromium only | Chrome only |
| Languages | Python, JS, Java, C# | JavaScript | Python |
| Auto-wait | Yes | Manual | Manual |
| Network interception | Excellent | Good | Basic |
| Stealth quality | Good (with patches) | Good (mature plugin) | Excellent (binary patching) |
| Speed | Fast | Fast | Moderate |
| Best for | Multi-language teams | JS developers | Python + Selenium users |
For Python projects, Playwright with stealth patches is typically the best choice. For the toughest anti-bot systems, Undetected ChromeDriver may be more effective due to its binary-level patching.
FAQ
Is Playwright Stealth as good as Puppeteer Stealth?
The playwright-stealth Python package ports most of Puppeteer Stealth’s evasions. For Node.js, you can use playwright-extra with the original Puppeteer stealth plugin. In practice, both achieve similar bypass rates. The main difference is maturity — Puppeteer’s stealth plugin has been around longer and is more battle-tested.
Can Playwright run in headed mode on a server?
Yes. Use Xvfb (X Virtual Framebuffer) to create a virtual display on headless servers:
xvfb-run python your_script.pyOr use pyvirtualdisplay in Python for programmatic control.
Which browser engine is best for stealth?
Chromium has the most anti-detection tooling but is also the most targeted by bot detection. Firefox is less commonly used in automation, so some detection systems have weaker Firefox fingerprinting. WebKit (Safari) is the least detectable but has the smallest feature set and worst proxy support.
Does Playwright support SOCKS5 proxies?
Yes. Set the proxy server to socks5://host:port in the launch options. Playwright handles both SOCKS4 and SOCKS5 natively.
How do I handle CAPTCHAs with Playwright?
For reCAPTCHA and hCaptcha, use CAPTCHA solving services to get tokens, then inject them via page.evaluate(). For Cloudflare Turnstile, browser automation with stealth often resolves it automatically. See our Turnstile bypass guide.
Conclusion
Playwright with stealth configuration is a powerful combination for web scraping in 2026. Its multi-browser support, excellent Python and JavaScript APIs, and built-in auto-waiting make it the most developer-friendly browser automation tool available. Pair it with residential proxies and human-like behavior simulation for maximum evasion success.
Useful Resources
- Playwright Documentation
- playwright-stealth PyPI
- Browser Fingerprinting Guide
- How Websites Detect Bots
- IP Rotation Strategies
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research
Related Reading
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research