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 = awa