Marketplace playwright-browser-automation
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.
git clone https://github.com/aiskillstore/marketplace
T=$(mktemp -d) && git clone --depth=1 https://github.com/aiskillstore/marketplace "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/dataflightsolutions/playwright-browser-automation" ~/.claude/skills/aiskillstore-marketplace-playwright-browser-automation-7486ba && rm -rf "$T"
skills/dataflightsolutions/playwright-browser-automation/SKILL.mdPlaywright 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:
- Take a quick screenshot of a webpage/screenshot
- Find broken links on a page/check-links
- Basic page health check/test-page
- Test across multiple viewports/test-responsive
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:
- Launch browser with defaultslaunchBrowser(browserType?, options?)
- Create context with viewport/localecreateContext(browser, options?)
- Create page with timeoutcreatePage(context, options?)
- Save session for reusesaveStorageState(context, path)
- Restore saved sessionloadStorageState(browser, path)
- Scan for running dev serversdetectDevServers(customPorts?)
Navigation & Waiting:
- Smart page ready detectionwaitForPageReady(page, options?)
- Navigate with automatic retrynavigateWithRetry(page, url, options?)
- Wait for SPA route changeswaitForSPA(page, options?)
- Wait for element statewaitForElement(page, selector, options?)
Safe Interactions:
- Click with retry logicsafeClick(page, selector, options?)
- Type with clear optionsafeType(page, selector, text, options?)
- Safe dropdown selectionsafeSelect(page, selector, value, options?)
- Safe checkbox/radiosafeCheck(page, selector, checked?, options?)
- Scroll in any directionscrollPage(page, direction, distance?)
- Scroll element into viewscrollToElement(page, selector, options?)
- Handle login flowauthenticate(page, credentials, selectors?)
- Dismiss cookie consenthandleCookieBanner(page, timeout?)
Form Helpers:
- Extract form field metadatagetFormFields(page, formSelector?)
- Get required fieldsgetRequiredFields(page, formSelector?)
- Get validation errorsgetFieldErrors(page, formSelector?)
- Check field validityvalidateFieldState(page, selector)
- Auto-fill formfillFormFromData(page, formSelector, data, options?)
- Submit and check errorssubmitAndValidate(page, formSelector, options?)
Accessibility:
- Run axe-core auditcheckAccessibility(page, options?)
- Extract ARIA attributesgetARIAInfo(page, selector)
- Verify tab ordercheckFocusOrder(page, options?)
- List focusable elementsgetFocusableElements(page)
Performance:
- Comprehensive load metricsmeasurePageLoad(page, url, options?)
- Largest Contentful PaintmeasureLCP(page)
- First Contentful PaintmeasureFCP(page)
- Cumulative Layout ShiftmeasureCLS(page)
Network:
- Mock APImockAPIResponse(page, urlPattern, response, options?)
- Block images/fonts/etcblockResources(page, resourceTypes)
- Capture network requestscaptureRequests(page, urlPattern?)
- Capture responsescaptureResponses(page, urlPattern?)
- Wait for API callwaitForAPI(page, urlPattern, options?)
Visual:
- Timestamped screenshottakeScreenshot(page, name, options?)
- Visual diffcompareScreenshots(baseline, current, options?)
- Element screenshottakeElementScreenshot(page, selector, name, options?)
Mobile:
- Emulate iPhone/Pixel/etcemulateDevice(browser, deviceName)
- Set GPS coordinatessetGeolocation(context, coords)
- Trigger touch eventssimulateTouchEvent(page, type, coords)
- Swipe gestureswipe(page, direction, distance?, options?)
Multi-page:
- Handle popup windowshandlePopup(page, triggerAction, options?)
- Handle new tabshandleNewTab(page, triggerAction, options?)
- Close extra pagescloseAllPopups(context)
- Handle alert/confirm/prompthandleDialog(page, action, text?)
Data Extraction:
- Get text from elementsextractTexts(page, selector)
- Parse table to JSONextractTableData(page, tableSelector)
- Get meta tag infoextractMetaTags(page)
- Get OG metadataextractOpenGraph(page)
- Get structured dataextractJsonLD(page)
- Get all linksextractLinks(page, options?)
Console Monitoring:
- Capture console outputcaptureConsoleLogs(page, options?)
- Capture JS errorscapturePageErrors(page)
- Get collected errorsgetConsoleErrors(consoleCapture)
- Fail if errors existassertNoConsoleErrors(consoleCapture)
Files:
- Upload fileuploadFile(page, selector, filePath, options?)
- Upload multipleuploadMultipleFiles(page, selector, filePaths)
- Download and savedownloadFile(page, triggerAction, options?)
- Wait for downloadwaitForDownload(page, triggerAction)
Utilities:
- Retry with backoffretryWithBackoff(fn, maxRetries?, initialDelay?)
- Promise-based delaydelay(ms)
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
before localhost testingdetectDevServers() - Use /tmp for scripts - Write to
, never plugin directory/tmp/playwright-test-*.js - Parameterize URLs - Put URL in
constant at topTARGET_URL - Visible browser default - Always
unless explicitly requestedheadless: false - Slow down for debugging - Use
to see actionsslowMo: 100 - Smart waits - Use
,waitForURL
instead of timeoutswaitForSelector - 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