Playwright Browser Automation
Overview
This skill enables general-purpose browser automation using Playwright. It's designed for one-off automation tasks, interactive testing, and ad-hoc browser scripting. The skill includes intelligent utilities for session persistence, error handling, server detection, and common automation patterns extracted from production bots.
When to Use This Skill
Invoke this skill when the user requests:
-
"Automate logging into this website"
-
"Take screenshots of this page"
-
"Fill out this form automatically"
-
"Test this login flow"
-
"Extract data from this website"
-
"Click through this multi-step process"
-
Any interactive browser automation task
DO NOT use for scheduled monitoring - use the web-monitor-bot skill instead for cron-based periodic checks with analytics.
Quick Start
- Installation
Install dependencies in your project or test directory:
npm install playwright playwright-extra puppeteer-extra-plugin-stealth dotenv npx playwright install chromium
- Run a Script
Execute automation scripts using the provided runner:
node run.js path/to/your-script.js
Or run inline code:
node run.js "await page.goto('https://example.com'); await page.screenshot({ path: 'screenshot.png' });"
Or pipe code via stdin:
cat script.js | node run.js
- Use Helper Utilities
Import the utilities library for advanced patterns:
const utils = require('./lib/utils.js');
// Safe click with retry await utils.safeClick(page, 'button.submit', { retries: 3 });
// Type with human-like delays await utils.humanType(page, '#email', 'user@example.com');
// Session management await utils.saveCookies(context, 'session.json'); await utils.loadCookies(context, 'session.json');
// Screenshot with error handling await utils.captureScreenshot(page, 'step-1.png');
Core Features
Session Persistence
Save and restore browser sessions to avoid repeated logins:
const { saveCookies, loadCookies } = require('./lib/utils.js');
// After successful login await saveCookies(context, 'my-session.json');
// On subsequent runs const hasCookies = await loadCookies(context, 'my-session.json'); if (hasCookies) { console.log('Session restored!'); await page.goto(protectedUrl); } else { // Perform login }
Benefits:
-
Skip login flows on repeated runs
-
Maintain authentication state
-
Avoid rate limiting from frequent logins
-
Faster execution times
Development Server Detection
Automatically detect running development servers before executing scripts:
const { detectDevServers } = require('./lib/utils.js');
const servers = await detectDevServers(); if (servers.length > 0) { console.log('Found servers:', servers); // Use detected server URLs in your automation }
Stealth Mode
Built-in stealth plugin to bypass basic bot detection:
const { chromium } = require('playwright-extra'); const stealth = require('puppeteer-extra-plugin-stealth')();
chromium.use(stealth);
const browser = await chromium.launch({ headless: false, args: ['--no-sandbox', '--disable-setuid-sandbox'] });
Error Handling & Retries
Robust error handling with automatic retries:
const { retryWithBackoff } = require('./lib/utils.js');
// Retry an operation up to 3 times with exponential backoff const result = await retryWithBackoff(async () => { await page.waitForSelector('.dynamic-content', { timeout: 10000 }); return await page.textContent('.dynamic-content'); }, { maxRetries: 3, initialDelay: 1000 });
Common Automation Patterns
Pattern 1: Login Flow Automation
const { chromium } = require('playwright-extra'); const stealth = require('puppeteer-extra-plugin-stealth')(); const { saveCookies, loadCookies, humanType, safeClick } = require('./lib/utils.js');
chromium.use(stealth);
const browser = await chromium.launch({ headless: false }); const context = await browser.newContext(); const page = await context.newPage();
// Try to load saved session const hasCookies = await loadCookies(context, 'session.json');
if (!hasCookies) { // Perform login await page.goto('https://example.com/login'); await humanType(page, '#email', 'user@example.com'); await humanType(page, '#password', 'password123'); await safeClick(page, 'button[type="submit"]');
await page.waitForNavigation();
// Save session for next time await saveCookies(context, 'session.json'); console.log('Login successful, session saved!'); } else { console.log('Using saved session'); await page.goto('https://example.com/dashboard'); }
await browser.close();
Pattern 2: Form Filling & Submission
const { humanType, safeClick, captureScreenshot } = require('./lib/utils.js');
await page.goto('https://example.com/form');
// Fill form fields with human-like typing await humanType(page, '#name', 'John Doe'); await humanType(page, '#email', 'john@example.com', { delay: 50 }); await page.selectOption('#country', 'US'); await page.check('#terms');
// Screenshot before submission await captureScreenshot(page, 'before-submit.png');
// Submit with retry logic await safeClick(page, 'button#submit', { retries: 3 });
// Wait for success await page.waitForSelector('.success-message', { timeout: 15000 }); await captureScreenshot(page, 'after-submit.png');
Pattern 3: Data Extraction
const { retryWithBackoff } = require('./lib/utils.js');
await page.goto('https://example.com/data');
// Extract data with retry logic const data = await retryWithBackoff(async () => { await page.waitForSelector('.data-table', { timeout: 10000 });
const rows = await page.$$('.data-table tr'); const results = [];
for (const row of rows) { const cells = await row.$$('td'); const rowData = await Promise.all(cells.map(cell => cell.textContent())); results.push(rowData); }
return results; }, { maxRetries: 3 });
console.log('Extracted data:', data);
Pattern 4: Multi-Step Workflow
const { safeClick, humanType, captureScreenshot } = require('./lib/utils.js');
// Step 1: Search await page.goto('https://example.com'); await humanType(page, '#search', 'playwright automation'); await safeClick(page, 'button.search'); await page.waitForTimeout(2000); await captureScreenshot(page, 'step-1-search.png');
// Step 2: Filter results await safeClick(page, '.filter-option[data-filter="recent"]'); await page.waitForTimeout(1000); await captureScreenshot(page, 'step-2-filtered.png');
// Step 3: Select item await safeClick(page, '.result-item:first-child'); await page.waitForNavigation(); await captureScreenshot(page, 'step-3-details.png');
// Step 4: Complete action await safeClick(page, 'button.add-to-cart'); await page.waitForSelector('.cart-notification', { timeout: 5000 }); await captureScreenshot(page, 'step-4-added.png');
Pattern 5: Screenshot Capture with Responsive Testing
const { captureScreenshot } = require('./lib/utils.js');
const viewports = [ { width: 1920, height: 1080, name: 'desktop' }, { width: 768, height: 1024, name: 'tablet' }, { width: 375, height: 667, name: 'mobile' } ];
for (const viewport of viewports) {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await page.goto('https://example.com');
await captureScreenshot(page, screenshot-${viewport.name}.png);
}
Pattern 6: Cloudflare Challenge Handling (Production-Ready)
const { detectCloudflare, saveCookies } = require('./lib/utils.js');
await page.goto('https://protected-site.com');
// Intelligent Cloudflare detection with 90s polling try { const wasBlocked = await detectCloudflare(page, context, 'initial page load', { maxWaitSeconds: 90, // Poll for up to 90 seconds pollIntervalSeconds: 10, // Check every 10 seconds cookiePath: 'session.json' // Save cookies immediately after solve });
if (wasBlocked) { console.log('Cloudflare challenge auto-solved! Proceeding...'); } else { console.log('No Cloudflare challenge detected'); }
// Continue with automation await page.click('.protected-button');
} catch (error) { // Challenge not solved after 90s console.error('Cloudflare blocked:', error.message); // Screenshots auto-saved: cloudflare-detected.png, cloudflare-timeout.png process.exit(1); }
Features:
-
Smart polling: Checks every 10s for up to 90s (not single wait)
-
Block tracking: Tracks consecutive/total blocks in cloudflare-blocks.json
-
Auto-screenshots: cloudflare-detected.png (when detected), cloudflare-cleared.png (when solved), cloudflare-timeout.png (if fails)
-
Cookie persistence: Saves session immediately after challenge clears
-
Progress updates: Console logs every 30s during wait
Block Tracking:
const { getCloudflareBlocks } = require('./lib/utils.js');
const blocks = getCloudflareBlocks();
console.log(Consecutive blocks: ${blocks.consecutive});
console.log(Total blocks: ${blocks.total});
console.log(Last 10 events:, blocks.history.slice(-10));
Utility Functions Reference
Session Management
// Save browser cookies to file await saveCookies(context, filepath);
// Load browser cookies from file const restored = await loadCookies(context, filepath); // Returns: true if cookies were loaded, false if file doesn't exist
Safe Interactions
// Click with automatic retry and error handling await safeClick(page, selector, { retries: 3, timeout: 10000 });
// Click with human-like mouse movement (curved path + random position) await humanClick(page, selector, { timeout: 10000 });
// Type with human-like delays (randomized between chars) await humanType(page, selector, text, { delay: 100 });
// Generate random delay for timing variation const delay = randomDelay(1000, 2500); // Returns random ms between 1000-2500 await page.waitForTimeout(delay);
Screenshots
// Capture screenshot with automatic error handling await captureScreenshot(page, filepath);
Retry Logic
// Retry async operation with exponential backoff const result = await retryWithBackoff(asyncFunction, { maxRetries: 3, initialDelay: 1000, backoffMultiplier: 2 });
Server Detection
// Detect running development servers on localhost const servers = await detectDevServers(); // Returns: Array of { port, url } objects
Advanced Configuration
Timeout Strategies
Implement dynamic timeouts based on conditions:
const isPeakTime = new Date().getHours() === 17; // 5 PM const timeout = isPeakTime ? 60000 : 30000;
await page.goto(url, { timeout }); await page.waitForSelector(selector, { timeout });
Headless vs. Headed Mode
// Headed (visible browser) - useful for debugging const browser = await chromium.launch({ headless: false });
// Headless (background) - useful for production/CI const browser = await chromium.launch({ headless: true });
Custom User Agents
const context = await browser.newContext({ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' });
Browser Arguments
const browser = await chromium.launch({ headless: false, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-blink-features=AutomationControlled' ] });
Best Practices
- Use Session Persistence
Always save cookies after authentication to speed up subsequent runs:
// After login await saveCookies(context, 'session.json');
// Before automation const hasCookies = await loadCookies(context, 'session.json');
- Implement Retry Logic
Use retries for flaky network requests or dynamic content:
await retryWithBackoff(async () => { await page.waitForSelector('.dynamic-element', { timeout: 10000 }); return await page.textContent('.dynamic-element'); }, { maxRetries: 3 });
- Capture Screenshots on Errors
Always save evidence when things go wrong:
try { await page.click('.submit-button'); } catch (error) { await captureScreenshot(page, 'error.png'); throw error; }
- Use Human-Like Interactions
Avoid detection by simulating natural human behavior:
const { humanClick, humanType, randomDelay } = require('./lib/utils.js');
// Bad: Instant, robotic interactions await page.fill('#input', text); await page.click('button');
// Good: Human-like behavior await humanType(page, '#input', text, { delay: 50 }); await page.waitForTimeout(randomDelay(500, 1500)); // Random pause await humanClick(page, 'button'); // Mouse movement + random click position
Anti-Detection Checklist:
-
✅ Use humanClick() instead of .click() (adds mouse movement)
-
✅ Use humanType() instead of .fill() (adds typing delays)
-
✅ Use randomDelay() for all waitForTimeout() calls (avoid fixed timing)
-
✅ Add random pauses between actions (humans don't act instantly)
-
✅ Handle Cloudflare with detectCloudflare() (smart polling + tracking)
- Clean Up Resources
Always close browsers and remove temporary files:
try { // ... automation code ... } finally { await browser.close(); // Clean up lock files, temp files, etc. }
File Organization
Recommended structure for automation projects:
my-automation/ ├── run.js # Script runner ├── lib/ │ └── utils.js # Utility functions ├── scripts/ │ ├── login.js # Login automation │ ├── scrape.js # Data extraction │ └── test-flow.js # Test workflows ├── sessions/ │ └── cookies.json # Saved sessions ├── screenshots/ # Captured images ├── .env # Environment variables └── package.json
Environment Variables
Use .env files for configuration:
.env
TARGET_URL=https://example.com LOGIN_EMAIL=user@example.com LOGIN_PASSWORD=secretpass HEADLESS=false TIMEOUT=30000
Load in scripts:
require('dotenv').config();
const targetUrl = process.env.TARGET_URL; const headless = process.env.HEADLESS === 'true'; const timeout = parseInt(process.env.TIMEOUT) || 30000;
Troubleshooting
Script not finding elements
-
Use page.waitForSelector() before interactions
-
Increase timeout values for slow-loading pages
-
Verify selectors in browser DevTools
-
Use captureScreenshot() to see page state
Bot detection issues
-
Enable stealth mode with playwright-extra
-
Use humanType() instead of fill()
-
Add random delays: await page.waitForTimeout(Math.random() * 2000 + 1000)
-
Use realistic user agents
-
Handle Cloudflare challenges with manual intervention pattern
Session not persisting
-
Ensure cookies are saved after successful login
-
Check file permissions on cookie file
-
Verify cookie expiration times
-
Re-login and save fresh cookies
Timeouts on slow pages
-
Increase timeout values: { timeout: 60000 }
-
Use waitUntil: 'domcontentloaded' instead of 'load'
-
Implement retry logic with backoff
-
Check network tab for slow requests
Comparison with web-monitor-bot
Feature playwright (this skill) web-monitor-bot
Use Case One-off automation tasks Scheduled monitoring
Execution Manual/on-demand Cron-based periodic
Analytics No Yes (built-in dashboard)
Notifications Manual implementation Slack/webhook integration
Session Management Yes (utilities) Yes (built-in)
Concurrency Control Manual Lock files (built-in)
Best For Testing, scraping, workflows Availability tracking, alerts
Examples
See the examples/ directory for complete working scripts:
-
examples/login-flow.js
-
Full login automation with session persistence
-
examples/form-submission.js
-
Multi-step form filling
-
examples/data-extraction.js
-
Scraping with retry logic
-
examples/screenshot-tool.js
-
Responsive screenshot capture
-
examples/cloudflare-handler.js
-
Handling bot protection
Resources
Dependencies
-
Playwright - Browser automation framework
-
playwright-extra - Plugin support
-
puppeteer-extra-plugin-stealth - Bot detection bypass
Documentation
-
Playwright API Docs
-
Selectors Guide
-
Best Practices
License
MIT