Playwright Browser Automation
General-purpose browser automation skill. I write custom Playwright code for any automation task and execute it via the universal executor.
Quick Commands Available
For common tasks, these slash commands are faster:
/screenshot - Take a quick screenshot of a webpage
/check-links - Find broken links on a page
/test-page - Basic page health check
/test-responsive - Test across multiple viewports
For custom automation beyond these common tasks, I write specialized Playwright code.
Critical Workflow
IMPORTANT - Path Resolution:
Use ${CLAUDE_PLUGIN_ROOT} for all paths. This resolves to the plugin installation directory.
Step 1: Auto-Detect Dev Servers (ALWAYS FIRST for localhost)
cd ${CLAUDE_PLUGIN_ROOT} && node -e "require('./lib/helpers').detectDevServers().then(servers => console.log(JSON.stringify(servers, null, 2)))"
Decision tree:
- 1 server found: Use it automatically, inform user
- Multiple servers found: Ask user which one to test
- No servers found: Ask for URL or offer to help start dev server
Step 2: Write Scripts to /tmp
NEVER write test files to plugin directory. Always use /tmp/playwright-test-*.js
Script template:
// /tmp/playwright-test-{descriptive-name}.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
// Parameterized URL (auto-detected or user-provided)
const TARGET_URL = 'http://localhost:3847';
(async () => {
const browser = await chromium.launch({ headless: false, slowMo: 100 });
const page = await browser.newPage();
try {
await page.goto(TARGET_URL, { waitUntil: 'networkidle' });
console.log('Page loaded:', await page.title());
// Test code here...
await page.screenshot({ path: '/tmp/screenshot.png', fullPage: true });
console.log('Screenshot saved to /tmp/screenshot.png');
} catch (error) {
console.error('Test failed:', error.message);
await page.screenshot({ path: '/tmp/error-screenshot.png' });
} finally {
await browser.close();
}
})();
Step 3: Execute from Plugin Directory
cd ${CLAUDE_PLUGIN_ROOT} && node run.js /tmp/playwright-test-{name}.js
Step 4: Default to Visible Browser
ALWAYS use headless: false unless user explicitly requests headless mode. This lets users see what's happening.
Setup (First Time)
cd ${CLAUDE_PLUGIN_ROOT} && npm run setup
Installs Playwright and Chromium browser. Only needed once.
Common Patterns
Test a Page (Basic)
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3847';
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto(TARGET_URL);
console.log('Title:', await page.title());
console.log('URL:', page.url());
await page.screenshot({ path: '/tmp/page.png', fullPage: true });
await browser.close();
})();
Test Responsive Design
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3847';
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
const viewports = [
{ name: 'Desktop', width: 1920, height: 1080 },
{ name: 'Tablet', width: 768, height: 1024 },
{ name: 'Mobile', width: 375, height: 667 }
];
for (const viewport of viewports) {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await page.goto(TARGET_URL);
await page.screenshot({ path: `/tmp/${viewport.name.toLowerCase()}.png`, fullPage: true });
console.log(`${viewport.name} screenshot saved`);
}
await browser.close();
})();
Test Login Flow
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3847';
(async () => {
const browser = await chromium.launch({ headless: false, slowMo: 100 });
const page = await browser.newPage();
await page.goto(`${TARGET_URL}/login`);
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await page.waitForURL('**/dashboard');
console.log('Login successful, redirected to dashboard');
await browser.close();
})();
Fill and Submit Form
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3847';
(async () => {
const browser = await chromium.launch({ headless: false, slowMo: 50 });
const page = await browser.newPage();
await page.goto(`${TARGET_URL}/contact`);
await page.fill('input[name="name"]', 'John Doe');
await page.fill('input[name="email"]', 'john@example.com');
await page.fill('textarea[name="message"]', 'Test message');
await page.click('button[type="submit"]');
await page.waitForSelector('.success-message');
console.log('Form submitted successfully');
await browser.close();
})();
Check for Broken Links
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3847';
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto(TARGET_URL);
const links = await page.locator('a[href^="http"]').all();
const results = { working: 0, broken: [] };
for (const link of links) {
const href = await link.getAttribute('href');
try {
const response = await page.request.head(href);
if (response.ok()) {
results.working++;
} else {
results.broken.push({ url: href, status: response.status() });
}
} catch (e) {
results.broken.push({ url: href, error: e.message });
}
}
console.log(`Working links: ${results.working}`);
console.log(`Broken links:`, results.broken);
await browser.close();
})();
Run Accessibility Audit
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3847';
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto(TARGET_URL);
const results = await helpers.checkAccessibility(page);
console.log('Accessibility audit complete');
console.log(`Critical issues: ${results.summary.critical}`);
console.log(`Serious issues: ${results.summary.serious}`);
await browser.close();
})();
Measure Performance
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3847';
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
const metrics = await helpers.measurePageLoad(page, TARGET_URL);
console.log('Load time:', metrics.loadTime, 'ms');
console.log('TTFB:', metrics.metrics.ttfb, 'ms');
console.log('DOM Content Loaded:', metrics.metrics.domContentLoaded, 'ms');
const lcp = await helpers.measureLCP(page);
console.log('LCP:', lcp, 'ms');
await browser.close();
})();
Mock API Response
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3847';
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
// Mock the API before navigating
await helpers.mockAPIResponse(page, '**/api/users', [
{ id: 1, name: 'Mock User 1' },
{ id: 2, name: 'Mock User 2' }
]);
await page.goto(TARGET_URL);
// Page will receive mocked data
await browser.close();
})();
Test Mobile Device
const { chromium, devices } = require('playwright');
const TARGET_URL = 'http://localhost:3847';
(async () => {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext({
...devices['iPhone 12']
});
const page = await context.newPage();
await page.goto(TARGET_URL);
await page.screenshot({ path: '/tmp/iphone12.png' });
await browser.close();
})();
Available Helpers
The lib/helpers.js provides 42 utility functions:
Browser & Context:
launchBrowser(browserType?, options?) - Launch browser with defaults
createContext(browser, options?) - Create context with viewport/locale
createPage(context, options?) - Create page with timeout
saveStorageState(context, path) - Save session for reuse
loadStorageState(browser, path) - Restore saved session
detectDevServers(customPorts?) - Scan for running dev servers
Navigation & Waiting:
waitForPageReady(page, options?) - Smart page ready detection
navigateWithRetry(page, url, options?) - Navigate with automatic retry
waitForSPA(page, options?) - Wait for SPA route changes
waitForElement(page, selector, options?) - Wait for element state
Safe Interactions:
safeClick(page, selector, options?) - Click with retry logic
safeType(page, selector, text, options?) - Type with clear option
safeSelect(page, selector, value, options?) - Safe dropdown selection
safeCheck(page, selector, checked?, options?) - Safe checkbox/radio
scrollPage(page, direction, distance?) - Scroll in any direction
scrollToElement(page, selector, options?) - Scroll element into view
authenticate(page, credentials, selectors?) - Handle login flow
handleCookieBanner(page, timeout?) - Dismiss cookie consent
Form Helpers:
getFormFields(page, formSelector?) - Extract form field metadata
getRequiredFields(page, formSelector?) - Get required fields
getFieldErrors(page, formSelector?) - Get validation errors
validateFieldState(page, selector) - Check field validity
fillFormFromData(page, formSelector, data, options?) - Auto-fill form
submitAndValidate(page, formSelector, options?) - Submit and check errors
Accessibility:
checkAccessibility(page, options?) - Run axe-core audit
getARIAInfo(page, selector) - Extract ARIA attributes
checkFocusOrder(page, options?) - Verify tab order
getFocusableElements(page) - List focusable elements
Performance:
measurePageLoad(page, url, options?) - Comprehensive load metrics
measureLCP(page) - Largest Contentful Paint
measureFCP(page) - First Contentful Paint
measureCLS(page) - Cumulative Layout Shift
Network:
mockAPIResponse(page, urlPattern, response, options?) - Mock API
blockResources(page, resourceTypes) - Block images/fonts/etc
captureRequests(page, urlPattern?) - Capture network requests
captureResponses(page, urlPattern?) - Capture responses
waitForAPI(page, urlPattern, options?) - Wait for API call
Visual:
takeScreenshot(page, name, options?) - Timestamped screenshot
compareScreenshots(baseline, current, options?) - Visual diff
takeElementScreenshot(page, selector, name, options?) - Element screenshot
Mobile:
emulateDevice(browser, deviceName) - Emulate iPhone/Pixel/etc
setGeolocation(context, coords) - Set GPS coordinates
simulateTouchEvent(page, type, coords) - Trigger touch events
swipe(page, direction, distance?, options?) - Swipe gesture
Multi-page:
handlePopup(page, triggerAction, options?) - Handle popup windows
handleNewTab(page, triggerAction, options?) - Handle new tabs
closeAllPopups(context) - Close extra pages
handleDialog(page, action, text?) - Handle alert/confirm/prompt
Data Extraction:
extractTexts(page, selector) - Get text from elements
extractTableData(page, tableSelector) - Parse table to JSON
extractMetaTags(page) - Get meta tag info
extractOpenGraph(page) - Get OG metadata
extractJsonLD(page) - Get structured data
extractLinks(page, options?) - Get all links
Console Monitoring:
captureConsoleLogs(page, options?) - Capture console output
capturePageErrors(page) - Capture JS errors
getConsoleErrors(consoleCapture) - Get collected errors
assertNoConsoleErrors(consoleCapture) - Fail if errors exist
Files:
uploadFile(page, selector, filePath, options?) - Upload file
uploadMultipleFiles(page, selector, filePaths) - Upload multiple
downloadFile(page, triggerAction, options?) - Download and save
waitForDownload(page, triggerAction) - Wait for download
Utilities:
retryWithBackoff(fn, maxRetries?, initialDelay?) - Retry with backoff
delay(ms) - Promise-based delay
Inline Execution
For quick one-off tasks, execute code inline:
cd ${CLAUDE_PLUGIN_ROOT} && node run.js "
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto('http://localhost:3847');
console.log('Title:', await page.title());
await page.screenshot({ path: '/tmp/quick.png' });
await browser.close();
"
When to use:
- Inline: Quick tasks (screenshot, check element, get title)
- Files: Complex tests, responsive design, anything to re-run
Tips
- CRITICAL: Detect servers FIRST - Always run
detectDevServers() before localhost testing
- Use /tmp for scripts - Write to
/tmp/playwright-test-*.js, never plugin directory
- Parameterize URLs - Put URL in
TARGET_URL constant at top
- Visible browser default - Always
headless: false unless explicitly requested
- Slow down for debugging - Use
slowMo: 100 to see actions
- Smart waits - Use
waitForURL, waitForSelector instead of timeouts
- Error handling - Always use try-catch for robust automation
Troubleshooting
Playwright not installed:
cd ${CLAUDE_PLUGIN_ROOT} && npm run setup
Module not found:
Run from plugin directory via run.js wrapper
Browser doesn't open:
Check headless: false and ensure display available
Element not found:
Add wait: await page.waitForSelector('.element', { timeout: 10000 })
Advanced Usage
For comprehensive Playwright API documentation, see API_REFERENCE.md:
- Selectors & Locators best practices
- Network interception & API mocking
- Authentication & session management
- Visual regression testing
- Mobile device emulation
- Performance testing
- CI/CD integration
1---2name: playwright-browser-automation-23description: 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---5
6# Playwright Browser Automation
7
8General-purpose browser automation skill. I write custom Playwright code for any automation task and execute it via the universal executor.
9
10## Quick Commands Available
11
12For common tasks, these slash commands are faster:
13- `/screenshot` - Take a quick screenshot of a webpage
14- `/check-links` - Find broken links on a page
15- `/test-page` - Basic page health check
16- `/test-responsive` - Test across multiple viewports
17
18For custom automation beyond these common tasks, I write specialized Playwright code.
19
20## Critical Workflow
21
22**IMPORTANT - Path Resolution:**
23Use `${CLAUDE_PLUGIN_ROOT}` for all paths. This resolves to the plugin installation directory.
24
25### Step 1: Auto-Detect Dev Servers (ALWAYS FIRST for localhost)
26
27```bash
28cd ${CLAUDE_PLUGIN_ROOT} && node -e "require('./lib/helpers').detectDevServers().then(servers => console.log(JSON.stringify(servers, null, 2)))"
29```
30
31**Decision tree:**
32- **1 server found**: Use it automatically, inform user
33- **Multiple servers found**: Ask user which one to test
34- **No servers found**: Ask for URL or offer to help start dev server
35
36### Step 2: Write Scripts to /tmp
37
38NEVER write test files to plugin directory. Always use `/tmp/playwright-test-*.js`
39
40**Script template:**
41```javascript
42// /tmp/playwright-test-{descriptive-name}.js
43const { chromium } = require('playwright');
44const helpers = require('./lib/helpers');
45
46// Parameterized URL (auto-detected or user-provided)
47const TARGET_URL = 'http://localhost:3847';
48
49(async () => {
50 const browser = await chromium.launch({ headless: false, slowMo: 100 });
51 const page = await browser.newPage();
52
53 try {
54 await page.goto(TARGET_URL, { waitUntil: 'networkidle' });
55 console.log('Page loaded:', await page.title());
56
57 // Test code here...
58
59 await page.screenshot({ path: '/tmp/screenshot.png', fullPage: true });
60 console.log('Screenshot saved to /tmp/screenshot.png');
61 } catch (error) {
62 console.error('Test failed:', error.message);
63 await page.screenshot({ path: '/tmp/error-screenshot.png' });
64 } finally {
65 await browser.close();
66 }
67})();
68```
69
70### Step 3: Execute from Plugin Directory
71
72```bash
73cd ${CLAUDE_PLUGIN_ROOT} && node run.js /tmp/playwright-test-{name}.js
74```
75
76### Step 4: Default to Visible Browser
77
78ALWAYS use `headless: false` unless user explicitly requests headless mode. This lets users see what's happening.
79
80## Setup (First Time)
81
82```bash
83cd ${CLAUDE_PLUGIN_ROOT} && npm run setup
84```
85
86Installs Playwright and Chromium browser. Only needed once.
87
88## Common Patterns
89
90### Test a Page (Basic)
91
92```javascript
93const { chromium } = require('playwright');
94const TARGET_URL = 'http://localhost:3847';
95
96(async () => {
97 const browser = await chromium.launch({ headless: false });
98 const page = await browser.newPage();
99
100 await page.goto(TARGET_URL);
101 console.log('Title:', await page.title());
102 console.log('URL:', page.url());
103
104 await page.screenshot({ path: '/tmp/page.png', fullPage: true });
105 await browser.close();
106})();
107```
108
109### Test Responsive Design
110
111```javascript
112const { chromium } = require('playwright');
113const TARGET_URL = 'http://localhost:3847';
114
115(async () => {
116 const browser = await chromium.launch({ headless: false });
117 const page = await browser.newPage();
118
119 const viewports = [
120 { name: 'Desktop', width: 1920, height: 1080 },
121 { name: 'Tablet', width: 768, height: 1024 },
122 { name: 'Mobile', width: 375, height: 667 }
123 ];
124
125 for (const viewport of viewports) {
126 await page.setViewportSize({ width: viewport.width, height: viewport.height });
127 await page.goto(TARGET_URL);
128 await page.screenshot({ path: `/tmp/${viewport.name.toLowerCase()}.png`, fullPage: true });
129 console.log(`${viewport.name} screenshot saved`);
130 }
131
132 await browser.close();
133})();
134```
135
136### Test Login Flow
137
138```javascript
139const { chromium } = require('playwright');
140const TARGET_URL = 'http://localhost:3847';
141
142(async () => {
143 const browser = await chromium.launch({ headless: false, slowMo: 100 });
144 const page = await browser.newPage();
145
146 await page.goto(`${TARGET_URL}/login`);
147
148 await page.fill('input[name="email"]', 'test@example.com');
149 await page.fill('input[name="password"]', 'password123');
150 await page.click('button[type="submit"]');
151
152 await page.waitForURL('**/dashboard');
153 console.log('Login successful, redirected to dashboard');
154
155 await browser.close();
156})();
157```
158
159### Fill and Submit Form
160
161```javascript
162const { chromium } = require('playwright');
163const TARGET_URL = 'http://localhost:3847';
164
165(async () => {
166 const browser = await chromium.launch({ headless: false, slowMo: 50 });
167 const page = await browser.newPage();
168
169 await page.goto(`${TARGET_URL}/contact`);
170
171 await page.fill('input[name="name"]', 'John Doe');
172 await page.fill('input[name="email"]', 'john@example.com');
173 await page.fill('textarea[name="message"]', 'Test message');
174 await page.click('button[type="submit"]');
175
176 await page.waitForSelector('.success-message');
177 console.log('Form submitted successfully');
178
179 await browser.close();
180})();
181```
182
183### Check for Broken Links
184
185```javascript
186const { chromium } = require('playwright');
187const TARGET_URL = 'http://localhost:3847';
188
189(async () => {
190 const browser = await chromium.launch({ headless: false });
191 const page = await browser.newPage();
192
193 await page.goto(TARGET_URL);
194
195 const links = await page.locator('a[href^="http"]').all();
196 const results = { working: 0, broken: [] };
197
198 for (const link of links) {
199 const href = await link.getAttribute('href');
200 try {
201 const response = await page.request.head(href);
202 if (response.ok()) {
203 results.working++;
204 } else {
205 results.broken.push({ url: href, status: response.status() });
206 }
207 } catch (e) {
208 results.broken.push({ url: href, error: e.message });
209 }
210 }
211
212 console.log(`Working links: ${results.working}`);
213 console.log(`Broken links:`, results.broken);
214
215 await browser.close();
216})();
217```
218
219### Run Accessibility Audit
220
221```javascript
222const { chromium } = require('playwright');
223const helpers = require('./lib/helpers');
224const TARGET_URL = 'http://localhost:3847';
225
226(async () => {
227 const browser = await chromium.launch({ headless: false });
228 const page = await browser.newPage();
229
230 await page.goto(TARGET_URL);
231
232 const results = await helpers.checkAccessibility(page);
233 console.log('Accessibility audit complete');
234 console.log(`Critical issues: ${results.summary.critical}`);
235 console.log(`Serious issues: ${results.summary.serious}`);
236
237 await browser.close();
238})();
239```
240
241### Measure Performance
242
243```javascript
244const { chromium } = require('playwright');
245const helpers = require('./lib/helpers');
246const TARGET_URL = 'http://localhost:3847';
247
248(async () => {
249 const browser = await chromium.launch({ headless: false });
250 const page = await browser.newPage();
251
252 const metrics = await helpers.measurePageLoad(page, TARGET_URL);
253 console.log('Load time:', metrics.loadTime, 'ms');
254 console.log('TTFB:', metrics.metrics.ttfb, 'ms');
255 console.log('DOM Content Loaded:', metrics.metrics.domContentLoaded, 'ms');
256
257 const lcp = await helpers.measureLCP(page);
258 console.log('LCP:', lcp, 'ms');
259
260 await browser.close();
261})();
262```
263
264### Mock API Response
265
266```javascript
267const { chromium } = require('playwright');
268const helpers = require('./lib/helpers');
269const TARGET_URL = 'http://localhost:3847';
270
271(async () => {
272 const browser = await chromium.launch({ headless: false });
273 const page = await browser.newPage();
274
275 // Mock the API before navigating
276 await helpers.mockAPIResponse(page, '**/api/users', [
277 { id: 1, name: 'Mock User 1' },
278 { id: 2, name: 'Mock User 2' }
279 ]);
280
281 await page.goto(TARGET_URL);
282 // Page will receive mocked data
283
284 await browser.close();
285})();
286```
287
288### Test Mobile Device
289
290```javascript
291const { chromium, devices } = require('playwright');
292const TARGET_URL = 'http://localhost:3847';
293
294(async () => {
295 const browser = await chromium.launch({ headless: false });
296 const context = await browser.newContext({
297 ...devices['iPhone 12']
298 });
299 const page = await context.newPage();
300
301 await page.goto(TARGET_URL);
302 await page.screenshot({ path: '/tmp/iphone12.png' });
303
304 await browser.close();
305})();
306```
307
308## Available Helpers
309
310The `lib/helpers.js` provides 42 utility functions:
311
312**Browser & Context:**
313- `launchBrowser(browserType?, options?)` - Launch browser with defaults
314- `createContext(browser, options?)` - Create context with viewport/locale
315- `createPage(context, options?)` - Create page with timeout
316- `saveStorageState(context, path)` - Save session for reuse
317- `loadStorageState(browser, path)` - Restore saved session
318- `detectDevServers(customPorts?)` - Scan for running dev servers
319
320**Navigation & Waiting:**
321- `waitForPageReady(page, options?)` - Smart page ready detection
322- `navigateWithRetry(page, url, options?)` - Navigate with automatic retry
323- `waitForSPA(page, options?)` - Wait for SPA route changes
324- `waitForElement(page, selector, options?)` - Wait for element state
325
326**Safe Interactions:**
327- `safeClick(page, selector, options?)` - Click with retry logic
328- `safeType(page, selector, text, options?)` - Type with clear option
329- `safeSelect(page, selector, value, options?)` - Safe dropdown selection
330- `safeCheck(page, selector, checked?, options?)` - Safe checkbox/radio
331- `scrollPage(page, direction, distance?)` - Scroll in any direction
332- `scrollToElement(page, selector, options?)` - Scroll element into view
333- `authenticate(page, credentials, selectors?)` - Handle login flow
334- `handleCookieBanner(page, timeout?)` - Dismiss cookie consent
335
336**Form Helpers:**
337- `getFormFields(page, formSelector?)` - Extract form field metadata
338- `getRequiredFields(page, formSelector?)` - Get required fields
339- `getFieldErrors(page, formSelector?)` - Get validation errors
340- `validateFieldState(page, selector)` - Check field validity
341- `fillFormFromData(page, formSelector, data, options?)` - Auto-fill form
342- `submitAndValidate(page, formSelector, options?)` - Submit and check errors
343
344**Accessibility:**
345- `checkAccessibility(page, options?)` - Run axe-core audit
346- `getARIAInfo(page, selector)` - Extract ARIA attributes
347- `checkFocusOrder(page, options?)` - Verify tab order
348- `getFocusableElements(page)` - List focusable elements
349
350**Performance:**
351- `measurePageLoad(page, url, options?)` - Comprehensive load metrics
352- `measureLCP(page)` - Largest Contentful Paint
353- `measureFCP(page)` - First Contentful Paint
354- `measureCLS(page)` - Cumulative Layout Shift
355
356**Network:**
357- `mockAPIResponse(page, urlPattern, response, options?)` - Mock API
358- `blockResources(page, resourceTypes)` - Block images/fonts/etc
359- `captureRequests(page, urlPattern?)` - Capture network requests
360- `captureResponses(page, urlPattern?)` - Capture responses
361- `waitForAPI(page, urlPattern, options?)` - Wait for API call
362
363**Visual:**
364- `takeScreenshot(page, name, options?)` - Timestamped screenshot
365- `compareScreenshots(baseline, current, options?)` - Visual diff
366- `takeElementScreenshot(page, selector, name, options?)` - Element screenshot
367
368**Mobile:**
369- `emulateDevice(browser, deviceName)` - Emulate iPhone/Pixel/etc
370- `setGeolocation(context, coords)` - Set GPS coordinates
371- `simulateTouchEvent(page, type, coords)` - Trigger touch events
372- `swipe(page, direction, distance?, options?)` - Swipe gesture
373
374**Multi-page:**
375- `handlePopup(page, triggerAction, options?)` - Handle popup windows
376- `handleNewTab(page, triggerAction, options?)` - Handle new tabs
377- `closeAllPopups(context)` - Close extra pages
378- `handleDialog(page, action, text?)` - Handle alert/confirm/prompt
379
380**Data Extraction:**
381- `extractTexts(page, selector)` - Get text from elements
382- `extractTableData(page, tableSelector)` - Parse table to JSON
383- `extractMetaTags(page)` - Get meta tag info
384- `extractOpenGraph(page)` - Get OG metadata
385- `extractJsonLD(page)` - Get structured data
386- `extractLinks(page, options?)` - Get all links
387
388**Console Monitoring:**
389- `captureConsoleLogs(page, options?)` - Capture console output
390- `capturePageErrors(page)` - Capture JS errors
391- `getConsoleErrors(consoleCapture)` - Get collected errors
392- `assertNoConsoleErrors(consoleCapture)` - Fail if errors exist
393
394**Files:**
395- `uploadFile(page, selector, filePath, options?)` - Upload file
396- `uploadMultipleFiles(page, selector, filePaths)` - Upload multiple
397- `downloadFile(page, triggerAction, options?)` - Download and save
398- `waitForDownload(page, triggerAction)` - Wait for download
399
400**Utilities:**
401- `retryWithBackoff(fn, maxRetries?, initialDelay?)` - Retry with backoff
402- `delay(ms)` - Promise-based delay
403
404## Inline Execution
405
406For quick one-off tasks, execute code inline:
407
408```bash
409cd ${CLAUDE_PLUGIN_ROOT} && node run.js "
410const browser = await chromium.launch({ headless: false });
411const page = await browser.newPage();
412await page.goto('http://localhost:3847');
413console.log('Title:', await page.title());
414await page.screenshot({ path: '/tmp/quick.png' });
415await browser.close();
416"
417```
418
419**When to use:**
420- **Inline**: Quick tasks (screenshot, check element, get title)
421- **Files**: Complex tests, responsive design, anything to re-run
422
423## Tips
424
425- **CRITICAL: Detect servers FIRST** - Always run `detectDevServers()` before localhost testing
426- **Use /tmp for scripts** - Write to `/tmp/playwright-test-*.js`, never plugin directory
427- **Parameterize URLs** - Put URL in `TARGET_URL` constant at top
428- **Visible browser default** - Always `headless: false` unless explicitly requested
429- **Slow down for debugging** - Use `slowMo: 100` to see actions
430- **Smart waits** - Use `waitForURL`, `waitForSelector` instead of timeouts
431- **Error handling** - Always use try-catch for robust automation
432
433## Troubleshooting
434
435**Playwright not installed:**
436```bash
437cd ${CLAUDE_PLUGIN_ROOT} && npm run setup
438```
439
440**Module not found:**
441Run from plugin directory via `run.js` wrapper
442
443**Browser doesn't open:**
444Check `headless: false` and ensure display available
445
446**Element not found:**
447Add wait: `await page.waitForSelector('.element', { timeout: 10000 })`
448
449## Advanced Usage
450
451For comprehensive Playwright API documentation, see [API_REFERENCE.md](../../API_REFERENCE.md):
452
453- Selectors & Locators best practices
454- Network interception & API mocking
455- Authentication & session management
456- Visual regression testing
457- Mobile device emulation
458- Performance testing
459- CI/CD integration