- name
- playwright-npx
- description
- Fast browser automation using Node.js scripts with Playwright (run via
node script.mjs). Use for web scraping, screenshots, form automation, and any browser task requiring programmatic control. For simple page fetching without JavaScript execution, use web_fetch first. For interactive CLI browsing without writing code, use browser tool or playwright-cli. This skill is ideal when you need full control, custom logic, or reusable scripts. - metadata
- {"clawdbot":{"emoji":"🎭","requires":{"bins":["node","npx"]}}, "created_by": "Kuba + Mahone", "created_date": "2026-02-04", "is_custom": true}
Playwright Browser Automation
🤝 Developed together by Kuba + Mahone · Feb 2026
Code-first browser automation with Playwright.
When to Use
| Tool | Use When |
|---|---|
| web_fetch | Simple pages, no JavaScript needed |
| This skill | JavaScript-heavy sites, complex interactions, full control |
| stealth-browser | Bot detection / Cloudflare issues |
| browser tool | Visual exploration, last resort |
| playwright-cli | Interactive CLI without writing code |
Setup
# One-time per project
npm init -y
npm install playwright
npx playwright install chromiumpackage.json example:
{
"name": "my-automation",
"type": "module",
"dependencies": {
"playwright": "^1.40.0"
}
}Minimal Example
// tmp/example.mjs
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
console.log('Title:', await page.title());
await browser.close();node tmp/example.mjsQuick Patterns
Screenshot
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.setViewportSize({ width: 1280, height: 800 });
await page.goto('https://example.com');
await page.screenshot({ path: 'tmp/screenshot.png', fullPage: true });
await browser.close();Scrape Data
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://news.ycombinator.com');
const stories = await page.$$eval('.titleline > a', links =>
links.slice(0, 5).map(a => ({ title: a.innerText, url: a.href }))
);
console.log(JSON.stringify(stories, null, 2));
await browser.close();Form Interaction
await page.goto('https://example.com/login');
await page.fill('input[name="email"]', 'user@example.com');
await page.fill('input[name="password"]', 'password');
await page.click('button[type="submit"]');Wait for Dynamic Content
// Wait for network idle (SPA)
await page.goto(url, { waitUntil: 'networkidle' });
// Wait for specific element
await page.waitForSelector('.results', { timeout: 10000 });
// Wait for condition
await page.waitForFunction(() =>
document.querySelectorAll('.item').length > 0
);Persistent Session
import fs from 'fs';
const SESSION_FILE = 'tmp/session.json';
let context;
if (fs.existsSync(SESSION_FILE)) {
context = await browser.newContext({ storageState: SESSION_FILE });
} else {
context = await browser.newContext();
}
const page = await context.newPage();
// ... login ...
await context.storageState({ path: SESSION_FILE });Headless vs Headed
// Headless (default, fastest)
await chromium.launch({ headless: true });
// Headed (see the browser)
await chromium.launch({ headless: false });
// Slow motion (debugging)
await chromium.launch({ headless: false, slowMo: 100 });Selectors Quick Reference
// CSS
await page.click('button.submit');
await page.fill('input#email', 'text');
// Text content
await page.click('text=Submit');
await page.click('text=/log\s*in/i'); // regex
// XPath
await page.click('xpath=//button[@type="submit"]');
// ARIA role
await page.click('role=button[name="Submit"]');
// Test ID (most stable)
await page.click('[data-testid="submit-btn"]');
// Chain selectors
await page.click('nav >> text=Settings');See references/selectors.md for complete selector guide.
Error Handling
try {
await page.goto('https://example.com', { timeout: 30000 });
const hasResults = await page.locator('.results').isVisible().catch(() => false);
if (!hasResults) {
console.log('No results');
process.exit(0);
}
} catch (error) {
console.error('Error:', error.message);
await page.screenshot({ path: 'tmp/error.png' });
process.exit(1);
} finally {
await browser.close();
}Examples & Templates
Working Examples
- examples/screenshot.mjs - Full-page screenshots
- examples/scrape.mjs - Data extraction
- examples/form-interaction.mjs - Form automation
- examples/login-session.mjs - Persistent sessions
Reusable Templates
- scripts/minimal-template.mjs - Clean starting point
- scripts/screenshot-template.mjs - Configurable screenshot
- scripts/scrape-template.mjs - Data scraping scaffold
Copy templates:
cp scripts/minimal-template.mjs tmp/my-task.mjs
# Edit tmp/my-task.mjs, then run:
node tmp/my-task.mjsTooling Commands
# Record interactions to generate code
npx playwright codegen https://example.com
# Debug selectors
npx playwright codegen --target javascript https://example.com
# Show trace
npx playwright show-trace tmp/trace.zipDeep References
- references/selectors.md - Complete selector guide (CSS, text, XPath, ARIA, test-id)
- references/debugging.md - Debugging techniques (headless, slowMo, screenshots)
- references/troubleshooting.md - Common errors and solutions
Tips
- Always put scripts in
tmp/— it's gitignored - Use
.mjsextension for ES modules (notype: moduleneeded) - Add
console.log()liberally for debugging - Use
page.screenshot()when things go wrong - For complex sites, add
await page.waitForLoadState('networkidle') - See references/debugging.md for detailed debugging guide
- See references/troubleshooting.md for common issues