aboutsummaryrefslogtreecommitdiff
path: root/playwright-js/SKILL.md
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-04-19 15:24:51 -0500
committerCraig Jennings <c@cjennings.net>2026-04-19 15:24:51 -0500
commit4ffa7417a359ef4eae09f61d7da4de06539462ca (patch)
treeb8eeb8aa5ec2344216c0f0cdcdcc82d0df307ce3 /playwright-js/SKILL.md
parent11f5f003eef12bff9633ca8190e3c43c7dab6708 (diff)
downloadrulesets-4ffa7417a359ef4eae09f61d7da4de06539462ca.tar.gz
rulesets-4ffa7417a359ef4eae09f61d7da4de06539462ca.zip
refactor(playwright): split into playwright-js + playwright-py variants
Rename `playwright-skill/` → `playwright-js/` and add `playwright-py/` as a verbatim fork of Anthropic's official `webapp-testing` skill (Apache-2.0). Cross-pollinate: each skill gains patterns and helpers inspired by the other's strengths, with upstream semantics preserved. ## playwright-js (JS/TS stack) Renamed from playwright-skill; upstream lackeyjb MIT content untouched. New sections added (clearly marked, preserving upstream semantics): - Static HTML vs Dynamic Webapp decision tree (core Anthropic methodology) - Reconnaissance-Then-Action pattern (navigate → networkidle → inspect → act) - Console Log Capture snippet (page.on console/pageerror/requestfailed) Description updated to clarify JS/TS stack fit (React/Next/Vue/Svelte/Node) and reference `/playwright-py` as the Python sibling. ## playwright-py (Python stack) Verbatim fork of anthropics/skills/skills/webapp-testing; upstream SKILL.md and bundled `scripts/with_server.py` + examples kept intact. New scripts and examples added (all lackeyjb-style conveniences in Python): Scripts: scripts/detect_dev_servers.py Probe common localhost ports for HTTP servers; outputs JSON of found services. scripts/safe_actions.py safe_click, safe_type (retry-wrapped), handle_cookie_banner (common selectors), build_context_with_headers (env-var- driven: PW_HEADER_NAME / PW_HEADER_VALUE / PW_EXTRA_HEADERS='{…json…}'). Examples: examples/login_flow.py Login form + wait_for_url. examples/broken_links.py Scan visible external hrefs via HEAD. examples/responsive_sweep.py Multi-viewport screenshots to /tmp. SKILL.md gains 5 "Added:" sections documenting the new scripts, retry helpers, env-header injection, and /tmp script discipline. Attribution notes explicitly mark upstream vs local additions. ## Makefile SKILLS: playwright-skill → playwright-js + playwright-py deps target: extended Playwright step to install Python package + Chromium via `python3 -m pip install --user playwright && python3 -m playwright install chromium` when playwright-py/ is present. Idempotent (detected via `python3 -c "import playwright"`). ## Usage Both skills symlinked globally via `make install`. Invoke whichever matches the project stack — cross-references in descriptions route you to the right one. Run `make deps` once to install both runtimes.
Diffstat (limited to 'playwright-js/SKILL.md')
-rw-r--r--playwright-js/SKILL.md513
1 files changed, 513 insertions, 0 deletions
diff --git a/playwright-js/SKILL.md b/playwright-js/SKILL.md
new file mode 100644
index 0000000..7c5f10c
--- /dev/null
+++ b/playwright-js/SKILL.md
@@ -0,0 +1,513 @@
+---
+name: playwright-js
+description: Browser automation and UI testing with Playwright using the JavaScript bindings. Auto-detects dev servers, writes clean test scripts to /tmp, runs visible Chromium by default for interactive debugging, ships a helper library (safe click/type retries, cookie banner handler, table extraction, dev-server detection, env-driven header injection). Use when testing a web app with a JavaScript or TypeScript stack (React, Next.js, Vue, Svelte, Express, Node frontends generally), automating browser interactions, validating UX, testing login flows, or checking links. Prefer this over playwright-py when the project is JS/TS-native. See also `/playwright-py` for Python-based variant (Django, FastAPI backend smoke tests, pytest integration).
+---
+
+**IMPORTANT - Path Resolution:**
+This skill can be installed in different locations (plugin system, manual installation, global, or project-specific). Before executing any commands, determine the skill directory based on where you loaded this SKILL.md file, and use that path in all commands below. Replace `$SKILL_DIR` with the actual discovered path.
+
+Common installation paths:
+
+- Plugin system: `~/.claude/plugins/marketplaces/playwright-skill/skills/playwright-skill`
+- Manual global: `~/.claude/skills/playwright-skill`
+- Project-specific: `<project>/.claude/skills/playwright-skill`
+
+# Playwright Browser Automation
+
+General-purpose browser automation skill. I'll write custom Playwright code for any automation task you request and execute it via the universal executor.
+
+**CRITICAL WORKFLOW - Follow these steps in order:**
+
+1. **Auto-detect dev servers** - For localhost testing, ALWAYS run server detection FIRST:
+
+ ```bash
+ cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(servers => console.log(JSON.stringify(servers)))"
+ ```
+
+ - If **1 server found**: Use it automatically, inform user
+ - If **multiple servers found**: Ask user which one to test
+ - If **no servers found**: Ask for URL or offer to help start dev server
+
+2. **Write scripts to /tmp** - NEVER write test files to skill directory; always use `/tmp/playwright-test-*.js`
+
+3. **Use visible browser by default** - Always use `headless: false` unless user specifically requests headless mode
+
+4. **Parameterize URLs** - Always make URLs configurable via environment variable or constant at top of script
+
+## How It Works
+
+1. You describe what you want to test/automate
+2. I auto-detect running dev servers (or ask for URL if testing external site)
+3. I write custom Playwright code in `/tmp/playwright-test-*.js` (won't clutter your project)
+4. I execute it via: `cd $SKILL_DIR && node run.js /tmp/playwright-test-*.js`
+5. Results displayed in real-time, browser window visible for debugging
+6. Test files auto-cleaned from /tmp by your OS
+
+## Setup (First Time)
+
+```bash
+cd $SKILL_DIR
+npm run setup
+```
+
+This installs Playwright and Chromium browser. Only needed once.
+
+## Execution Pattern
+
+**Step 1: Detect dev servers (for localhost testing)**
+
+```bash
+cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(s => console.log(JSON.stringify(s)))"
+```
+
+**Step 2: Write test script to /tmp with URL parameter**
+
+```javascript
+// /tmp/playwright-test-page.js
+const { chromium } = require('playwright');
+
+// Parameterized URL (detected or user-provided)
+const TARGET_URL = 'http://localhost:3001'; // <-- Auto-detected or from user
+
+(async () => {
+ const browser = await chromium.launch({ headless: false });
+ const page = await browser.newPage();
+
+ await page.goto(TARGET_URL);
+ console.log('Page loaded:', await page.title());
+
+ await page.screenshot({ path: '/tmp/screenshot.png', fullPage: true });
+ console.log('📸 Screenshot saved to /tmp/screenshot.png');
+
+ await browser.close();
+})();
+```
+
+**Step 3: Execute from skill directory**
+
+```bash
+cd $SKILL_DIR && node run.js /tmp/playwright-test-page.js
+```
+
+## Common Patterns
+
+### Test a Page (Multiple Viewports)
+
+```javascript
+// /tmp/playwright-test-responsive.js
+const { chromium } = require('playwright');
+
+const TARGET_URL = 'http://localhost:3001'; // Auto-detected
+
+(async () => {
+ const browser = await chromium.launch({ headless: false, slowMo: 100 });
+ const page = await browser.newPage();
+
+ // Desktop test
+ await page.setViewportSize({ width: 1920, height: 1080 });
+ await page.goto(TARGET_URL);
+ console.log('Desktop - Title:', await page.title());
+ await page.screenshot({ path: '/tmp/desktop.png', fullPage: true });
+
+ // Mobile test
+ await page.setViewportSize({ width: 375, height: 667 });
+ await page.screenshot({ path: '/tmp/mobile.png', fullPage: true });
+
+ await browser.close();
+})();
+```
+
+### Test Login Flow
+
+```javascript
+// /tmp/playwright-test-login.js
+const { chromium } = require('playwright');
+
+const TARGET_URL = 'http://localhost:3001'; // Auto-detected
+
+(async () => {
+ const browser = await chromium.launch({ headless: false });
+ 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"]');
+
+ // Wait for redirect
+ await page.waitForURL('**/dashboard');
+ console.log('✅ Login successful, redirected to dashboard');
+
+ await browser.close();
+})();
+```
+
+### Fill and Submit Form
+
+```javascript
+// /tmp/playwright-test-form.js
+const { chromium } = require('playwright');
+
+const TARGET_URL = 'http://localhost:3001'; // Auto-detected
+
+(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"]');
+
+ // Verify submission
+ await page.waitForSelector('.success-message');
+ console.log('✅ Form submitted successfully');
+
+ await browser.close();
+})();
+```
+
+### Check for Broken Links
+
+```javascript
+const { chromium } = require('playwright');
+
+(async () => {
+ const browser = await chromium.launch({ headless: false });
+ const page = await browser.newPage();
+
+ await page.goto('http://localhost:3000');
+
+ 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();
+})();
+```
+
+### Take Screenshot with Error Handling
+
+```javascript
+const { chromium } = require('playwright');
+
+(async () => {
+ const browser = await chromium.launch({ headless: false });
+ const page = await browser.newPage();
+
+ try {
+ await page.goto('http://localhost:3000', {
+ waitUntil: 'networkidle',
+ timeout: 10000,
+ });
+
+ await page.screenshot({
+ path: '/tmp/screenshot.png',
+ fullPage: true,
+ });
+
+ console.log('📸 Screenshot saved to /tmp/screenshot.png');
+ } catch (error) {
+ console.error('❌ Error:', error.message);
+ } finally {
+ await browser.close();
+ }
+})();
+```
+
+### Test Responsive Design
+
+```javascript
+// /tmp/playwright-test-responsive-full.js
+const { chromium } = require('playwright');
+
+const TARGET_URL = 'http://localhost:3001'; // Auto-detected
+
+(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) {
+ console.log(
+ `Testing ${viewport.name} (${viewport.width}x${viewport.height})`,
+ );
+
+ await page.setViewportSize({
+ width: viewport.width,
+ height: viewport.height,
+ });
+
+ await page.goto(TARGET_URL);
+ await page.waitForTimeout(1000);
+
+ await page.screenshot({
+ path: `/tmp/${viewport.name.toLowerCase()}.png`,
+ fullPage: true,
+ });
+ }
+
+ console.log('✅ All viewports tested');
+ await browser.close();
+})();
+```
+
+## Inline Execution (Simple Tasks)
+
+For quick one-off tasks, you can execute code inline without creating files:
+
+```bash
+# Take a quick screenshot
+cd $SKILL_DIR && node run.js "
+const browser = await chromium.launch({ headless: false });
+const page = await browser.newPage();
+await page.goto('http://localhost:3001');
+await page.screenshot({ path: '/tmp/quick-screenshot.png', fullPage: true });
+console.log('Screenshot saved');
+await browser.close();
+"
+```
+
+**When to use inline vs files:**
+
+- **Inline**: Quick one-off tasks (screenshot, check if element exists, get page title)
+- **Files**: Complex tests, responsive design checks, anything user might want to re-run
+
+## Available Helpers
+
+Optional utility functions in `lib/helpers.js`:
+
+```javascript
+const helpers = require('./lib/helpers');
+
+// Detect running dev servers (CRITICAL - use this first!)
+const servers = await helpers.detectDevServers();
+console.log('Found servers:', servers);
+
+// Safe click with retry
+await helpers.safeClick(page, 'button.submit', { retries: 3 });
+
+// Safe type with clear
+await helpers.safeType(page, '#username', 'testuser');
+
+// Take timestamped screenshot
+await helpers.takeScreenshot(page, 'test-result');
+
+// Handle cookie banners
+await helpers.handleCookieBanner(page);
+
+// Extract table data
+const data = await helpers.extractTableData(page, 'table.results');
+```
+
+See `lib/helpers.js` for full list.
+
+## Custom HTTP Headers
+
+Configure custom headers for all HTTP requests via environment variables. Useful for:
+
+- Identifying automated traffic to your backend
+- Getting LLM-optimized responses (e.g., plain text errors instead of styled HTML)
+- Adding authentication tokens globally
+
+### Configuration
+
+**Single header (common case):**
+
+```bash
+PW_HEADER_NAME=X-Automated-By PW_HEADER_VALUE=playwright-skill \
+ cd $SKILL_DIR && node run.js /tmp/my-script.js
+```
+
+**Multiple headers (JSON format):**
+
+```bash
+PW_EXTRA_HEADERS='{"X-Automated-By":"playwright-skill","X-Debug":"true"}' \
+ cd $SKILL_DIR && node run.js /tmp/my-script.js
+```
+
+### How It Works
+
+Headers are automatically applied when using `helpers.createContext()`:
+
+```javascript
+const context = await helpers.createContext(browser);
+const page = await context.newPage();
+// All requests from this page include your custom headers
+```
+
+For scripts using raw Playwright API, use the injected `getContextOptionsWithHeaders()`:
+
+```javascript
+const context = await browser.newContext(
+ getContextOptionsWithHeaders({ viewport: { width: 1920, height: 1080 } }),
+);
+```
+
+## Advanced Usage
+
+For comprehensive Playwright API documentation, see [API_REFERENCE.md](API_REFERENCE.md):
+
+- Selectors & Locators best practices
+- Network interception & API mocking
+- Authentication & session management
+- Visual regression testing
+- Mobile device emulation
+- Performance testing
+- Debugging techniques
+- CI/CD integration
+
+## Tips
+
+- **CRITICAL: Detect servers FIRST** - Always run `detectDevServers()` before writing test code for localhost testing
+- **Custom headers** - Use `PW_HEADER_NAME`/`PW_HEADER_VALUE` env vars to identify automated traffic to your backend
+- **Use /tmp for test files** - Write to `/tmp/playwright-test-*.js`, never to skill directory or user's project
+- **Parameterize URLs** - Put detected/provided URL in a `TARGET_URL` constant at the top of every script
+- **DEFAULT: Visible browser** - Always use `headless: false` unless user explicitly asks for headless mode
+- **Headless mode** - Only use `headless: true` when user specifically requests "headless" or "background" execution
+- **Slow down:** Use `slowMo: 100` to make actions visible and easier to follow
+- **Wait strategies:** Use `waitForURL`, `waitForSelector`, `waitForLoadState` instead of fixed timeouts
+- **Error handling:** Always use try-catch for robust automation
+- **Console output:** Use `console.log()` to track progress and show what's happening
+
+## Troubleshooting
+
+**Playwright not installed:**
+
+```bash
+cd $SKILL_DIR && npm run setup
+```
+
+**Module not found:**
+Ensure running from skill 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 })`
+
+## Example Usage
+
+```
+User: "Test if the marketing page looks good"
+
+Claude: I'll test the marketing page across multiple viewports. Let me first detect running servers...
+[Runs: detectDevServers()]
+[Output: Found server on port 3001]
+I found your dev server running on http://localhost:3001
+
+[Writes custom automation script to /tmp/playwright-test-marketing.js with URL parameterized]
+[Runs: cd $SKILL_DIR && node run.js /tmp/playwright-test-marketing.js]
+[Shows results with screenshots from /tmp/]
+```
+
+```
+User: "Check if login redirects correctly"
+
+Claude: I'll test the login flow. First, let me check for running servers...
+[Runs: detectDevServers()]
+[Output: Found servers on ports 3000 and 3001]
+I found 2 dev servers. Which one should I test?
+- http://localhost:3000
+- http://localhost:3001
+
+User: "Use 3001"
+
+[Writes login automation to /tmp/playwright-test-login.js]
+[Runs: cd $SKILL_DIR && node run.js /tmp/playwright-test-login.js]
+[Reports: ✅ Login successful, redirected to /dashboard]
+```
+
+## Notes
+
+- Each automation is custom-written for your specific request
+- Not limited to pre-built scripts - any browser task possible
+- Auto-detects running dev servers to eliminate hardcoded URLs
+- Test scripts written to `/tmp` for automatic cleanup (no clutter)
+- Code executes reliably with proper module resolution via `run.js`
+- Progressive disclosure - API_REFERENCE.md loaded only when advanced features needed
+
+---
+
+## Added: Static HTML vs Dynamic Webapp Decision
+
+Before writing any test, decide which path the target needs. Missing this step causes the most common failure: inspecting a dynamic page before JS has populated it.
+
+```
+User task → Is it static HTML (file:// or plain server-rendered)?
+ ├─ Yes → Read the HTML source directly, identify selectors from the raw markup,
+ │ write a Playwright script using those selectors.
+ │
+ └─ No (dynamic webapp) →
+ 1. Navigate to the page
+ 2. Wait for networkidle: await page.waitForLoadState('networkidle');
+ 3. Inspect rendered DOM (screenshot, page.content(), or locator().all())
+ 4. Identify selectors from the rendered state, not the source
+ 5. Execute actions with those selectors
+```
+
+**Common pitfall:** inspecting the DOM before `networkidle` on a dynamic app returns stale content or an empty skeleton. Every "element not found" bug on dynamic pages should trigger a "did I wait for networkidle?" check first.
+
+## Added: Reconnaissance-Then-Action Pattern
+
+For any non-trivial interaction on a dynamic page:
+
+1. **Reconnoiter.** Navigate, wait for load, capture state:
+ ```javascript
+ await page.goto(TARGET_URL);
+ await page.waitForLoadState('networkidle');
+ await page.screenshot({ path: '/tmp/inspect.png', fullPage: true });
+ const html = await page.content();
+ const buttons = await page.locator('button').all();
+ ```
+
+2. **Decide.** From the screenshot + content + locator list, pick the selectors you'll use. Don't guess from source.
+
+3. **Act.** Execute the interaction with the discovered selectors.
+
+This beats "write what you think the page looks like, run it, fix the selectors when it breaks." Especially valuable for first-time automation on an unfamiliar app.
+
+## Added: Console Log Capture
+
+Frontend errors often don't surface in the Playwright output unless captured explicitly. For flaky tests or "works in my browser, fails in Playwright" symptoms:
+
+```javascript
+page.on('console', msg => console.log(`[browser.${msg.type()}] ${msg.text()}`));
+page.on('pageerror', err => console.log(`[browser.pageerror] ${err.message}`));
+page.on('requestfailed', req => console.log(`[browser.requestfailed] ${req.url()} ${req.failure()?.errorText}`));
+```
+
+Attach these before `page.goto()`. Messages stream to stdout during the test run, giving you the same signal the browser devtools console would.
+
+---
+
+## Attribution
+
+Forked from [lackeyjb/playwright-skill](https://github.com/lackeyjb/playwright-skill) — MIT licensed. See `LICENSE` in this directory for the original copyright and terms.
+
+**Local additions** (not upstream): the three *Added:* sections above (Static HTML vs Dynamic Webapp Decision, Reconnaissance-Then-Action Pattern, Console Log Capture) were added in this fork, informed by patterns from Anthropic's `webapp-testing` skill (the sibling `playwright-py` in this rulesets repo). The upstream skill is self-contained; these additions pair well with it but are not required.