Playwright Local Browser Automation
Status: Production Ready β
Last Updated: 2026-01-21
Dependencies: Node.js 20+ (Node.js 18 deprecated) or Python 3.9+
Latest Versions: [email protected], [email protected], [email protected]
Browser Versions: Chromium 143.0.7499.4 | Firefox 144.0.2 | WebKit 26.0
β οΈ v1.57 Breaking Change: Playwright now uses Chrome for Testing builds instead of Chromium. This provides more authentic browser behavior but changes the browser icon and title bar.
Quick Start (5 Minutes)
1. Install Playwright
Node.js:
npm install -D playwright
npx playwright install chromium
Python:
pip install playwright
playwright install chromium
Why this matters:
playwright install downloads browser binaries (~400MB for Chromium)
- Install only needed browsers:
chromium, firefox, or webkit
- Binaries stored in
~/.cache/ms-playwright/
2. Basic Page Scrape
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle' });
const title = await page.title();
const content = await page.textContent('body');
await browser.close();
console.log({ title, content });
CRITICAL:
- Always close browser with
await browser.close() to avoid zombie processes
- Use
waitUntil: 'networkidle' for dynamic content (SPAs)
- Default timeout is 30 seconds - adjust with
timeout: 60000 if needed
3. Test Locally
npx tsx scrape.ts
python scrape.py
Why Playwright Local vs Cloudflare Browser Rendering
| Feature |
Playwright Local |
Cloudflare Browser Rendering |
| IP Address |
Residential (your ISP) |
Datacenter (easily detected) |
| Stealth Plugins |
Full support |
Not available |
| Rate Limits |
None |
2,000 requests/day free tier |
| Cost |
Free (your CPU) |
$5/10k requests after free tier |
| Browser Control |
All Playwright features |
Limited API |
| Concurrency |
Your hardware limit |
Account-based limits |
| Session Persistence |
Full cookie/storage control |
Limited session management |
| Use Case |
Bot-protected sites, auth flows |
Simple scraping, serverless |
When to use Cloudflare: Serverless environments, simple scraping, cost-efficient at scale
When to use Local: Anti-bot bypass needed, residential IP required, complex automation
The 7-Step Stealth Setup Process
β οΈ 2025 Reality Check: Stealth plugins work well against basic anti-bot measures, but advanced detection systems (Cloudflare Bot Management, PerimeterX, DataDome) have evolved significantly. The detection landscape now includes:
- Behavioral analysis (mouse patterns, scroll timing, keystroke dynamics)
- TLS fingerprinting (JA3/JA4 signatures)
- Canvas and WebGL fingerprinting
- HTTP/2 fingerprinting
Recommendations:
- Stealth plugins are a good starting point, not a complete solution
- Combine with realistic user behavior simulation (use
steps option)
- Consider residential proxies for heavily protected sites
- "What works today may not work tomorrow" - test regularly
- For advanced scenarios, research alternatives like
nodriver or undetected-chromedriver
Step 1: Install Stealth Plugin (Node.js)
npm install playwright-extra playwright-stealth
For puppeteer-extra compatibility:
npm install puppeteer-extra puppeteer-extra-plugin-stealth
Step 2: Configure Stealth Mode
playwright-extra:
import { chromium } from 'playwright-extra';
import stealth from 'puppeteer-extra-plugin-stealth';
chromium.use(stealth());
const browser = await chromium.launch({
headless: true,
args: [
'--disable-blink-features=AutomationControlled',
'--no-sandbox',
'--disable-setuid-sandbox',
],
});
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
viewport: { width: 1920, height: 1080 },
locale: 'en-US',
timezoneId: 'America/New_York',
});
Key Points:
--disable-blink-features=AutomationControlled removes navigator.webdriver flag
- Randomize viewport sizes to avoid fingerprinting
- Match user agent to browser version (Chrome 120 example above)
Step 3: Mask WebDriver Detection
await page.addInitScript(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
});
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5],
});
Object.defineProperty(navigator, 'languages', {
get: () => ['en-US', 'en'],
});
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
parameters.name === 'notifications' ?
Promise.resolve({ state: Notification.permission }) :
originalQuery(parameters)
);
});
Step 4: Human-Like Mouse Movement
async function humanClick(page, selector) {
const element = await page.locator(selector);
const box = await element.boundingBox();
if (box) {
const x = box.x + box.width * Math.random();
const y = box.y + box.height * Math.random();
await page.mouse.move(x, y, { steps: 10 });
await page.mouse.click(x, y, { delay: 100 });
}
}
Step 5: Rotate User Agents
const userAgents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
]