From release
Interact with and test web applications using Playwright. Supports ad-hoc browser tasks (screenshots, form filling, deploy checks), Python and TypeScript E2E testing, visual debugging, and browser automation.
npx claudepluginhub fairchild/dotclaude --plugin skill-creatorThis skill uses the workspace's default tool permissions.
Test and interact with web applications using Playwright. Supports ad-hoc browser tasks, Python test scripts, and TypeScript E2E patterns.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Test and interact with web applications using Playwright. Supports ad-hoc browser tasks, Python test scripts, and TypeScript E2E patterns.
For quick one-off tasks — no test framework or codebase required.
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto('https://example.com')
page.wait_for_load_state('networkidle')
# Screenshot
page.screenshot(path='/tmp/screenshot.png', full_page=True)
# Mobile viewport
page.set_viewport_size({"width": 390, "height": 844})
page.screenshot(path='/tmp/mobile.png', full_page=True)
# Read content
title = page.title()
text = page.locator('main').inner_text()
# Interact
page.fill('[name="email"]', 'test@example.com')
page.click('button[type="submit"]')
browser.close()
Common ad-hoc tasks:
page.screenshot(path='/tmp/shot.png', full_page=True)page.fill() + page.click(), screenshot resultpage.locator('selector').inner_text()Save scripts to /tmp/ and run with python /tmp/script.py. No project setup needed — just pip install playwright && playwright install chromium.
Project has pyproject.toml / requirements.txt → Python (below)
Project has package.json / bun.lock / pnpm-lock.yaml → TypeScript (below)
Both → Prefer TypeScript; use Python if existing tests are Python
Helper Scripts Available:
scripts/with_server.py - Manages server lifecycle (supports multiple servers)Always run scripts with --help first to see usage. DO NOT read the source until you try running the script first and find that a customized solution is absolutely necessary. These scripts can be very large and thus pollute your context window. They exist to be called directly as black-box scripts rather than ingested into your context window.
User task → Is it static HTML?
├─ Yes → Read HTML file directly to identify selectors
│ ├─ Success → Write Playwright script using selectors
│ └─ Fails/Incomplete → Treat as dynamic (below)
│
└─ No (dynamic webapp) → Is the server already running?
├─ No → Run: python scripts/with_server.py --help
│ Then use the helper + write simplified Playwright script
│
└─ Yes → Reconnaissance-then-action:
1. Navigate and wait for networkidle
2. Take screenshot or inspect DOM
3. Identify selectors from rendered state
4. Execute actions with discovered selectors
Single server:
python scripts/with_server.py --server "npm run dev" --port 5173 -- python your_automation.py
Multiple servers (e.g., backend + frontend):
python scripts/with_server.py \
--server "cd backend && python server.py" --port 3000 \
--server "cd frontend && npm run dev" --port 5173 \
-- python your_automation.py
Automation script (servers managed automatically):
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto('http://localhost:5173')
page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute
# ... your automation logic
browser.close()
element_discovery.py - Discovering buttons, links, and inputs on a pagestatic_html_automation.py - Using file:// URLs for local HTMLconsole_logging.py - Capturing console logs during automationFor projects using npm/pnpm/bun with @playwright/test.
Detect test command from package.json:
grep -E '"test"|"test:e2e"|"test:playwright"' package.json
Common patterns:
bun test / npm test / pnpm test — default testsbun run test:e2e — E2E specificbun run test:headed — visible browser for debuggingMost projects require the dev server running first:
# Terminal 1: Start dev server
bun run dev # detect from lockfiles
# Terminal 2: Run tests
bun test
Check wrangler.jsonc or dev script for port (default Wrangler: 8787):
const BASE_URL = process.env.BASE_URL || 'http://localhost:8787';
await page.goto(BASE_URL);
Tests live in e2e/, tests/, or tests/e2e/:
import { test, expect } from '@playwright/test';
test('user can complete flow', async ({ page }) => {
await page.goto('http://localhost:8787');
await page.waitForLoadState('networkidle');
await page.click('text=Get Started');
await page.fill('[name="email"]', 'test@example.com');
await expect(page.locator('.success')).toBeVisible();
});
await page.screenshot({ path: 'screenshots/step-1.png' });
await page.screenshot({ path: 'screenshots/full.png', fullPage: true });
await page.locator('.component').screenshot({ path: 'screenshots/component.png' });
npx playwright test --headed # visible browser
npx playwright test --ui # interactive UI mode
npx playwright test --debug tests/auth.spec.ts # debug specific test
Prefer semantic selectors:
page.getByRole('button', { name: 'Submit' })
page.getByLabel('Email')
page.getByText('Welcome')
page.getByTestId('submit-btn') // fallback
page.locator('.submit-button') // last resort
await page.waitForLoadState('networkidle');
await page.waitForSelector('.loaded');
await page.waitForResponse(resp => resp.url().includes('/api/'));
await expect(page.locator('.result')).toBeVisible({ timeout: 10000 });
networkidleDon't inspect the DOM before waiting for networkidle on dynamic apps.
--help on bundled scripts before reading sourcetext=, role=, CSS selectors, or IDsWhen building UI components, follow these patterns so Playwright tests stay stable across refactors.
Selector priority (most to least resilient):
getByRole / getByLabel — semantic, survives styling changesgetByTestId — stable, explicit contract between code and testsgetByText — readable, but breaks on copy changesAuthoring guidelines:
<button>, <nav>, <input>) over generic <div> with click handlersaria-label to interactive elements that lack visible textdata-testid to elements that are hard to select semantically (dynamic lists, generated UIs)data-testid values stable — they're a contract with tests, not implementation detail<label htmlFor="..."> so getByLabel worksExample — testable form:
<form data-testid="login-form">
<label for="email">Email</label>
<input id="email" type="email" aria-label="Email" />
<button type="submit">Sign In</button>
</form>
Tests can use: getByLabel('Email'), getByRole('button', { name: 'Sign In' }), or getByTestId('login-form').
For dispatched visual analysis of test screenshots and UI/UX quality assessment:
Task(
subagent_type: "general-purpose",
model: "sonnet",
prompt: "Read ~/.claude/skills/webapp-testing/SKILL.md. You are a Playwright
test engineer and UI/UX analyst. Run the E2E tests, capture screenshots at
key interaction points, then analyze for:
- Functionality: Does the UI reflect expected state?
- Usability: Are interactive elements accessible?
- Visual hierarchy: Is information organized logically?
- Consistency: Do elements follow design patterns?
- Accessibility: Contrast ratios, focus states
Project: {project}
Tests: {test command or path}
Return a structured report: test summary, visual findings, UX observations,
prioritized recommendations, and screenshot inventory."
)