Complete browser automation with Playwright. Auto-detects dev servers, writes clean test scripts to /tmp. Test pages, fill forms, take screenshots, check responsive design, validate UX, test login flows, check links, automate any browser task. Use when user wants to test websites, automate browser interactions, validate web functionality, or perform any browser-based testing.
Add this skill
npx mdskills install lackeyjb/playwright-skillWell-structured browser automation with clear workflow, good examples, and proper file hygiene
1---2name: playwright-skill3description: Complete browser automation with Playwright. Auto-detects dev servers, writes clean test scripts to /tmp. Test pages, fill forms, take screenshots, check responsive design, validate UX, test login flows, check links, automate any browser task. Use when user wants to test websites, automate browser interactions, validate web functionality, or perform any browser-based testing.4---56**IMPORTANT - Path Resolution:**7This skill can be installed in different locations (plugin system, manual installation, global, or project-specific). Before executing any commands, determine the skill directory based on where you loaded this SKILL.md file, and use that path in all commands below. Replace `$SKILL_DIR` with the actual discovered path.89Common installation paths:1011- Plugin system: `~/.claude/plugins/marketplaces/playwright-skill/skills/playwright-skill`12- Manual global: `~/.claude/skills/playwright-skill`13- Project-specific: `<project>/.claude/skills/playwright-skill`1415# Playwright Browser Automation1617General-purpose browser automation skill. I'll write custom Playwright code for any automation task you request and execute it via the universal executor.1819**CRITICAL WORKFLOW - Follow these steps in order:**20211. **Auto-detect dev servers** - For localhost testing, ALWAYS run server detection FIRST:2223 ```bash24 cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(servers => console.log(JSON.stringify(servers)))"25 ```2627 - If **1 server found**: Use it automatically, inform user28 - If **multiple servers found**: Ask user which one to test29 - If **no servers found**: Ask for URL or offer to help start dev server30312. **Write scripts to /tmp** - NEVER write test files to skill directory; always use `/tmp/playwright-test-*.js`32333. **Use visible browser by default** - Always use `headless: false` unless user specifically requests headless mode34354. **Parameterize URLs** - Always make URLs configurable via environment variable or constant at top of script3637## How It Works38391. You describe what you want to test/automate402. I auto-detect running dev servers (or ask for URL if testing external site)413. I write custom Playwright code in `/tmp/playwright-test-*.js` (won't clutter your project)424. I execute it via: `cd $SKILL_DIR && node run.js /tmp/playwright-test-*.js`435. Results displayed in real-time, browser window visible for debugging446. Test files auto-cleaned from /tmp by your OS4546## Setup (First Time)4748```bash49cd $SKILL_DIR50npm run setup51```5253This installs Playwright and Chromium browser. Only needed once.5455## Execution Pattern5657**Step 1: Detect dev servers (for localhost testing)**5859```bash60cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(s => console.log(JSON.stringify(s)))"61```6263**Step 2: Write test script to /tmp with URL parameter**6465```javascript66// /tmp/playwright-test-page.js67const { chromium } = require('playwright');6869// Parameterized URL (detected or user-provided)70const TARGET_URL = 'http://localhost:3001'; // <-- Auto-detected or from user7172(async () => {73 const browser = await chromium.launch({ headless: false });74 const page = await browser.newPage();7576 await page.goto(TARGET_URL);77 console.log('Page loaded:', await page.title());7879 await page.screenshot({ path: '/tmp/screenshot.png', fullPage: true });80 console.log('๐ธ Screenshot saved to /tmp/screenshot.png');8182 await browser.close();83})();84```8586**Step 3: Execute from skill directory**8788```bash89cd $SKILL_DIR && node run.js /tmp/playwright-test-page.js90```9192## Common Patterns9394### Test a Page (Multiple Viewports)9596```javascript97// /tmp/playwright-test-responsive.js98const { chromium } = require('playwright');99100const TARGET_URL = 'http://localhost:3001'; // Auto-detected101102(async () => {103 const browser = await chromium.launch({ headless: false, slowMo: 100 });104 const page = await browser.newPage();105106 // Desktop test107 await page.setViewportSize({ width: 1920, height: 1080 });108 await page.goto(TARGET_URL);109 console.log('Desktop - Title:', await page.title());110 await page.screenshot({ path: '/tmp/desktop.png', fullPage: true });111112 // Mobile test113 await page.setViewportSize({ width: 375, height: 667 });114 await page.screenshot({ path: '/tmp/mobile.png', fullPage: true });115116 await browser.close();117})();118```119120### Test Login Flow121122```javascript123// /tmp/playwright-test-login.js124const { chromium } = require('playwright');125126const TARGET_URL = 'http://localhost:3001'; // Auto-detected127128(async () => {129 const browser = await chromium.launch({ headless: false });130 const page = await browser.newPage();131132 await page.goto(`${TARGET_URL}/login`);133134 await page.fill('input[name="email"]', 'test@example.com');135 await page.fill('input[name="password"]', 'password123');136 await page.click('button[type="submit"]');137138 // Wait for redirect139 await page.waitForURL('**/dashboard');140 console.log('โ Login successful, redirected to dashboard');141142 await browser.close();143})();144```145146### Fill and Submit Form147148```javascript149// /tmp/playwright-test-form.js150const { chromium } = require('playwright');151152const TARGET_URL = 'http://localhost:3001'; // Auto-detected153154(async () => {155 const browser = await chromium.launch({ headless: false, slowMo: 50 });156 const page = await browser.newPage();157158 await page.goto(`${TARGET_URL}/contact`);159160 await page.fill('input[name="name"]', 'John Doe');161 await page.fill('input[name="email"]', 'john@example.com');162 await page.fill('textarea[name="message"]', 'Test message');163 await page.click('button[type="submit"]');164165 // Verify submission166 await page.waitForSelector('.success-message');167 console.log('โ Form submitted successfully');168169 await browser.close();170})();171```172173### Check for Broken Links174175```javascript176const { chromium } = require('playwright');177178(async () => {179 const browser = await chromium.launch({ headless: false });180 const page = await browser.newPage();181182 await page.goto('http://localhost:3000');183184 const links = await page.locator('a[href^="http"]').all();185 const results = { working: 0, broken: [] };186187 for (const link of links) {188 const href = await link.getAttribute('href');189 try {190 const response = await page.request.head(href);191 if (response.ok()) {192 results.working++;193 } else {194 results.broken.push({ url: href, status: response.status() });195 }196 } catch (e) {197 results.broken.push({ url: href, error: e.message });198 }199 }200201 console.log(`โ Working links: ${results.working}`);202 console.log(`โ Broken links:`, results.broken);203204 await browser.close();205})();206```207208### Take Screenshot with Error Handling209210```javascript211const { chromium } = require('playwright');212213(async () => {214 const browser = await chromium.launch({ headless: false });215 const page = await browser.newPage();216217 try {218 await page.goto('http://localhost:3000', {219 waitUntil: 'networkidle',220 timeout: 10000,221 });222223 await page.screenshot({224 path: '/tmp/screenshot.png',225 fullPage: true,226 });227228 console.log('๐ธ Screenshot saved to /tmp/screenshot.png');229 } catch (error) {230 console.error('โ Error:', error.message);231 } finally {232 await browser.close();233 }234})();235```236237### Test Responsive Design238239```javascript240// /tmp/playwright-test-responsive-full.js241const { chromium } = require('playwright');242243const TARGET_URL = 'http://localhost:3001'; // Auto-detected244245(async () => {246 const browser = await chromium.launch({ headless: false });247 const page = await browser.newPage();248249 const viewports = [250 { name: 'Desktop', width: 1920, height: 1080 },251 { name: 'Tablet', width: 768, height: 1024 },252 { name: 'Mobile', width: 375, height: 667 },253 ];254255 for (const viewport of viewports) {256 console.log(257 `Testing ${viewport.name} (${viewport.width}x${viewport.height})`,258 );259260 await page.setViewportSize({261 width: viewport.width,262 height: viewport.height,263 });264265 await page.goto(TARGET_URL);266 await page.waitForTimeout(1000);267268 await page.screenshot({269 path: `/tmp/${viewport.name.toLowerCase()}.png`,270 fullPage: true,271 });272 }273274 console.log('โ All viewports tested');275 await browser.close();276})();277```278279## Inline Execution (Simple Tasks)280281For quick one-off tasks, you can execute code inline without creating files:282283```bash284# Take a quick screenshot285cd $SKILL_DIR && node run.js "286const browser = await chromium.launch({ headless: false });287const page = await browser.newPage();288await page.goto('http://localhost:3001');289await page.screenshot({ path: '/tmp/quick-screenshot.png', fullPage: true });290console.log('Screenshot saved');291await browser.close();292"293```294295**When to use inline vs files:**296297- **Inline**: Quick one-off tasks (screenshot, check if element exists, get page title)298- **Files**: Complex tests, responsive design checks, anything user might want to re-run299300## Available Helpers301302Optional utility functions in `lib/helpers.js`:303304```javascript305const helpers = require('./lib/helpers');306307// Detect running dev servers (CRITICAL - use this first!)308const servers = await helpers.detectDevServers();309console.log('Found servers:', servers);310311// Safe click with retry312await helpers.safeClick(page, 'button.submit', { retries: 3 });313314// Safe type with clear315await helpers.safeType(page, '#username', 'testuser');316317// Take timestamped screenshot318await helpers.takeScreenshot(page, 'test-result');319320// Handle cookie banners321await helpers.handleCookieBanner(page);322323// Extract table data324const data = await helpers.extractTableData(page, 'table.results');325```326327See `lib/helpers.js` for full list.328329## Custom HTTP Headers330331Configure custom headers for all HTTP requests via environment variables. Useful for:332333- Identifying automated traffic to your backend334- Getting LLM-optimized responses (e.g., plain text errors instead of styled HTML)335- Adding authentication tokens globally336337### Configuration338339**Single header (common case):**340341```bash342PW_HEADER_NAME=X-Automated-By PW_HEADER_VALUE=playwright-skill \343 cd $SKILL_DIR && node run.js /tmp/my-script.js344```345346**Multiple headers (JSON format):**347348```bash349PW_EXTRA_HEADERS='{"X-Automated-By":"playwright-skill","X-Debug":"true"}' \350 cd $SKILL_DIR && node run.js /tmp/my-script.js351```352353### How It Works354355Headers are automatically applied when using `helpers.createContext()`:356357```javascript358const context = await helpers.createContext(browser);359const page = await context.newPage();360// All requests from this page include your custom headers361```362363For scripts using raw Playwright API, use the injected `getContextOptionsWithHeaders()`:364365```javascript366const context = await browser.newContext(367 getContextOptionsWithHeaders({ viewport: { width: 1920, height: 1080 } }),368);369```370371## Advanced Usage372373For comprehensive Playwright API documentation, see [API_REFERENCE.md](API_REFERENCE.md):374375- Selectors & Locators best practices376- Network interception & API mocking377- Authentication & session management378- Visual regression testing379- Mobile device emulation380- Performance testing381- Debugging techniques382- CI/CD integration383384## Tips385386- **CRITICAL: Detect servers FIRST** - Always run `detectDevServers()` before writing test code for localhost testing387- **Custom headers** - Use `PW_HEADER_NAME`/`PW_HEADER_VALUE` env vars to identify automated traffic to your backend388- **Use /tmp for test files** - Write to `/tmp/playwright-test-*.js`, never to skill directory or user's project389- **Parameterize URLs** - Put detected/provided URL in a `TARGET_URL` constant at the top of every script390- **DEFAULT: Visible browser** - Always use `headless: false` unless user explicitly asks for headless mode391- **Headless mode** - Only use `headless: true` when user specifically requests "headless" or "background" execution392- **Slow down:** Use `slowMo: 100` to make actions visible and easier to follow393- **Wait strategies:** Use `waitForURL`, `waitForSelector`, `waitForLoadState` instead of fixed timeouts394- **Error handling:** Always use try-catch for robust automation395- **Console output:** Use `console.log()` to track progress and show what's happening396397## Troubleshooting398399**Playwright not installed:**400401```bash402cd $SKILL_DIR && npm run setup403```404405**Module not found:**406Ensure running from skill directory via `run.js` wrapper407408**Browser doesn't open:**409Check `headless: false` and ensure display available410411**Element not found:**412Add wait: `await page.waitForSelector('.element', { timeout: 10000 })`413414## Example Usage415416```417User: "Test if the marketing page looks good"418419Claude: I'll test the marketing page across multiple viewports. Let me first detect running servers...420[Runs: detectDevServers()]421[Output: Found server on port 3001]422I found your dev server running on http://localhost:3001423424[Writes custom automation script to /tmp/playwright-test-marketing.js with URL parameterized]425[Runs: cd $SKILL_DIR && node run.js /tmp/playwright-test-marketing.js]426[Shows results with screenshots from /tmp/]427```428429```430User: "Check if login redirects correctly"431432Claude: I'll test the login flow. First, let me check for running servers...433[Runs: detectDevServers()]434[Output: Found servers on ports 3000 and 3001]435I found 2 dev servers. Which one should I test?436- http://localhost:3000437- http://localhost:3001438439User: "Use 3001"440441[Writes login automation to /tmp/playwright-test-login.js]442[Runs: cd $SKILL_DIR && node run.js /tmp/playwright-test-login.js]443[Reports: โ Login successful, redirected to /dashboard]444```445446## Notes447448- Each automation is custom-written for your specific request449- Not limited to pre-built scripts - any browser task possible450- Auto-detects running dev servers to eliminate hardcoded URLs451- Test scripts written to `/tmp` for automatic cleanup (no clutter)452- Code executes reliably with proper module resolution via `run.js`453- Progressive disclosure - API_REFERENCE.md loaded only when advanced features needed454
Full transparency โ inspect the skill content before installing.