| name | playwright |
| description | General-purpose browser automation skill for Playwright. Use this skill when the user wants to automate browser tasks like testing login flows, scraping data, taking screenshots, filling forms, clicking elements, or any interactive web automation. This skill provides smart utilities for session management, error handling, and development server detection. NOT for scheduled monitoring (use web-monitor-bot instead). |
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
1. Installation
Install dependencies in your project or test directory:
npm install playwright playwright-extra puppeteer-extra-plugin-stealth dotenv
npx playwright install chromium
2. 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
3. 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
1. 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');
2. 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 });
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;
}
4. 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 allwaitForTimeout()calls (avoid fixed timing) - ✅ Add random pauses between actions (humans don't act instantly)
- ✅ Handle Cloudflare with
detectCloudflare()(smart polling + tracking)
5. 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 offill() - 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 persistenceexamples/form-submission.js- Multi-step form fillingexamples/data-extraction.js- Scraping with retry logicexamples/screenshot-tool.js- Responsive screenshot captureexamples/cloudflare-handler.js- Handling bot protection
Resources
Dependencies
- Playwright - Browser automation framework
- playwright-extra - Plugin support
- puppeteer-extra-plugin-stealth - Bot detection bypass
Documentation
License
MIT