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.
Automates browser testing and interactions using Playwright. Detects local dev servers automatically and writes test scripts to /tmp for safe execution. Use when you need to test websites, validate forms, check responsive design, or perform any browser-based automation.
/plugin marketplace add DataflightSolutions/claude-plugins/plugin install playwright@dataflight-claude-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
General-purpose browser automation skill. I write custom Playwright code for any automation task and execute it via the universal executor.
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 viewportsFor custom automation beyond these common tasks, I write specialized Playwright code.
IMPORTANT - Path Resolution:
Use ${CLAUDE_PLUGIN_ROOT} for all paths. This resolves to the plugin installation directory.
cd ${CLAUDE_PLUGIN_ROOT} && node -e "require('./lib/helpers').detectDevServers().then(servers => console.log(JSON.stringify(servers, null, 2)))"
Decision tree:
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();
}
})();
cd ${CLAUDE_PLUGIN_ROOT} && node run.js /tmp/playwright-test-{name}.js
ALWAYS use headless: false unless user explicitly requests headless mode. This lets users see what's happening.
cd ${CLAUDE_PLUGIN_ROOT} && npm run setup
Installs Playwright and Chromium browser. Only needed once.
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();
})();
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();
})();
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();
})();
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();
})();
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();
})();
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();
})();
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();
})();
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();
})();
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();
})();
The lib/helpers.js provides 42 utility functions:
Browser & Context:
launchBrowser(browserType?, options?) - Launch browser with defaultscreateContext(browser, options?) - Create context with viewport/localecreatePage(context, options?) - Create page with timeoutsaveStorageState(context, path) - Save session for reuseloadStorageState(browser, path) - Restore saved sessiondetectDevServers(customPorts?) - Scan for running dev serversNavigation & Waiting:
waitForPageReady(page, options?) - Smart page ready detectionnavigateWithRetry(page, url, options?) - Navigate with automatic retrywaitForSPA(page, options?) - Wait for SPA route changeswaitForElement(page, selector, options?) - Wait for element stateSafe Interactions:
safeClick(page, selector, options?) - Click with retry logicsafeType(page, selector, text, options?) - Type with clear optionsafeSelect(page, selector, value, options?) - Safe dropdown selectionsafeCheck(page, selector, checked?, options?) - Safe checkbox/radioscrollPage(page, direction, distance?) - Scroll in any directionscrollToElement(page, selector, options?) - Scroll element into viewauthenticate(page, credentials, selectors?) - Handle login flowhandleCookieBanner(page, timeout?) - Dismiss cookie consentForm Helpers:
getFormFields(page, formSelector?) - Extract form field metadatagetRequiredFields(page, formSelector?) - Get required fieldsgetFieldErrors(page, formSelector?) - Get validation errorsvalidateFieldState(page, selector) - Check field validityfillFormFromData(page, formSelector, data, options?) - Auto-fill formsubmitAndValidate(page, formSelector, options?) - Submit and check errorsAccessibility:
checkAccessibility(page, options?) - Run axe-core auditgetARIAInfo(page, selector) - Extract ARIA attributescheckFocusOrder(page, options?) - Verify tab ordergetFocusableElements(page) - List focusable elementsPerformance:
measurePageLoad(page, url, options?) - Comprehensive load metricsmeasureLCP(page) - Largest Contentful PaintmeasureFCP(page) - First Contentful PaintmeasureCLS(page) - Cumulative Layout ShiftNetwork:
mockAPIResponse(page, urlPattern, response, options?) - Mock APIblockResources(page, resourceTypes) - Block images/fonts/etccaptureRequests(page, urlPattern?) - Capture network requestscaptureResponses(page, urlPattern?) - Capture responseswaitForAPI(page, urlPattern, options?) - Wait for API callVisual:
takeScreenshot(page, name, options?) - Timestamped screenshotcompareScreenshots(baseline, current, options?) - Visual difftakeElementScreenshot(page, selector, name, options?) - Element screenshotMobile:
emulateDevice(browser, deviceName) - Emulate iPhone/Pixel/etcsetGeolocation(context, coords) - Set GPS coordinatessimulateTouchEvent(page, type, coords) - Trigger touch eventsswipe(page, direction, distance?, options?) - Swipe gestureMulti-page:
handlePopup(page, triggerAction, options?) - Handle popup windowshandleNewTab(page, triggerAction, options?) - Handle new tabscloseAllPopups(context) - Close extra pageshandleDialog(page, action, text?) - Handle alert/confirm/promptData Extraction:
extractTexts(page, selector) - Get text from elementsextractTableData(page, tableSelector) - Parse table to JSONextractMetaTags(page) - Get meta tag infoextractOpenGraph(page) - Get OG metadataextractJsonLD(page) - Get structured dataextractLinks(page, options?) - Get all linksConsole Monitoring:
captureConsoleLogs(page, options?) - Capture console outputcapturePageErrors(page) - Capture JS errorsgetConsoleErrors(consoleCapture) - Get collected errorsassertNoConsoleErrors(consoleCapture) - Fail if errors existFiles:
uploadFile(page, selector, filePath, options?) - Upload fileuploadMultipleFiles(page, selector, filePaths) - Upload multipledownloadFile(page, triggerAction, options?) - Download and savewaitForDownload(page, triggerAction) - Wait for downloadUtilities:
retryWithBackoff(fn, maxRetries?, initialDelay?) - Retry with backoffdelay(ms) - Promise-based delayFor 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:
detectDevServers() before localhost testing/tmp/playwright-test-*.js, never plugin directoryTARGET_URL constant at topheadless: false unless explicitly requestedslowMo: 100 to see actionswaitForURL, waitForSelector instead of timeoutsPlaywright 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 })
For comprehensive Playwright API documentation, see API_REFERENCE.md:
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.