| name | playwright-skill |
| description | Browser automation for web projects using playwright-cli |
| version | 2.0.0 |
| allowed-tools | Bash(playwright-cli:*), Bash(bin/open), Bash(bin/close), Bash(bin/click-css), Bash(bin/fill-css), Bash(bin/upload), Bash(bin/auth-save), Bash(bin/auth-load), Bash(bin/context), Bash(bin/inject-capture), Bash(bin/cache-query) |
Playwright Skill v2
Browser automation using playwright-cli (Microsoft's official Playwright CLI). No daemon management required — the browser starts and stops automatically.
1. Session Startup & Isolation
Always start with bin/open (not bare playwright-cli open):
bin/open --headed
playwright-cli goto <url>
bin/open derives a session name from the current directory (e.g. my-project), appends a short agent identifier when running under Claude Code, and starts the browser under that named session. All other bin/ scripts resolve the session automatically.
Session isolation: When CLAUDE_SESSION_ID is set (automatic in Claude Code), each agent gets its own session name (e.g. my-project-a1b2c3d4) and session file. Parallel agents never interfere with each other. If a session is already open for this agent, bin/open closes it first to prevent orphaned Chrome processes — other agents' sessions are unaffected.
Before opening a new session, check if you already have one:
playwright-cli list
If your session is already listed and open, skip bin/open and use it directly.
At the end of a task, clean up with:
bin/close
This closes the browser and removes the session tracking file. If the browser is stuck use playwright-cli kill-all instead.
Overriding the session name:
bin/open --headed --session my-custom-name
2. Reading Snapshots
After every command, stdout contains a link to the current accessibility tree:
### Snapshot
- [Snapshot](.playwright-cli/page-<timestamp>.yml)
Always read the snapshot file to see what's on the page:
Read .playwright-cli/page-<timestamp>.yml
The snapshot contains all interactive elements with [ref=eN] identifiers used for clicking, filling, and hovering.
3. Core Commands
Navigation
playwright-cli goto <url>
playwright-cli go-back
playwright-cli go-forward
playwright-cli reload
Interacting with Elements (use ref from snapshot)
playwright-cli click <ref>
playwright-cli fill <ref> <value>
playwright-cli type <text>
playwright-cli press <key>
playwright-cli hover <ref>
playwright-cli select <ref> <val>
Observation
playwright-cli snapshot
playwright-cli screenshot
Dialogs
playwright-cli dialog-accept
playwright-cli dialog-dismiss
Session Management
bin/close
playwright-cli kill-all
playwright-cli list
Escape Hatch
playwright-cli run-code '<async page => { }>'
4. CSS Selector Fallbacks
Use these when ARIA-ref commands fail — for example, when framework-rendered elements are outside the main ARIA tree.
Modal dialogs — often rendered as DOM portals:
bin/click-css "[data-modal-confirm]"
bin/click-css "button[data-dismiss]"
Hidden file inputs — often hidden from accessibility tree:
bin/upload "input[type=file]" /path/to/file.pdf
Framework event targets — ARIA-ref clicks don't always fire framework event handlers:
bin/fill-css "[data-field=email]" "user@example.com"
bin/click-css "[data-action=save]"
See skill/CONFIG.md for project-specific selector patterns.
5. Auth Profiles
Auth profile names and base URLs are defined in skill/worktrees.json (gitignored).
See skill/CONFIG.md for setup instructions.
Save after authenticating:
bin/auth-save <profile-name>
Restore on a new session:
bin/open --headed
playwright-cli goto <baseUrl>
bin/auth-load <profile-name>
playwright-cli goto <baseUrl>/dashboard
Profiles are stored in .playwright-skill/auth/ and gitignored.
6. Recovery
| Problem | Solution |
|---|
| Browser stuck / daemon hung | playwright-cli kill-all then bin/open --headed |
| No session open (bin/context error) | bin/open --headed |
| Auth expired | bin/auth-load <profile> then reload |
| Element ref not found | playwright-cli snapshot then read the new snapshot file |
| Modal / framework component not clickable | Use bin/click-css with a CSS selector |
| Action has no visible effect | Tail your server log for a swallowed exception (see §9) |
| Page blank or error after action | Tail your server log for a swallowed exception (see §9) |
| End of task cleanup | bin/close |
7. run-code Examples
playwright-cli run-code 'await page.waitForSelector(".loading-complete")'
playwright-cli run-code 'await page.evaluate(() => window.dispatchEvent(new CustomEvent("refresh")))'
playwright-cli run-code 'await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight))'
playwright-cli run-code 'console.log(await page.locator("table tbody tr").count())'
8. Context Capture & QA Workflow
Three tools integrate ResponseBuilder and CacheStore into the v2 skill, enabling rich page diagnostics.
Tools
| Command | Description |
|---|
bin/context [--screenshot] | Capture full page context → compact TrimmedResponse JSON on stdout |
bin/inject-capture | Inject browser-side console + fetch/XHR patching |
bin/cache-query <cmd> [args] | Query cached data by ID and type |
bin/context
Collects context from three sources and stores it in .playwright-skill/cache/:
playwright-cli snapshot — accessibility tree (YAML)
playwright-cli console / playwright-cli network — native browser logs
playwright-cli run-code — page URL, title, HTML, cookies, injected captures
Outputs a TrimmedResponse (~200 tokens) with:
pageState — URL, title, auth status, error flag
cacheRef.id — reference ID to retrieve full context later
cacheRef.available — list of available data types
bin/context
bin/context --screenshot
bin/inject-capture
Patches console.*, window.fetch, and XMLHttpRequest in the browser to capture events in window.__capturedConsole and window.__capturedRequests. These are merged with playwright-layer captures by bin/context.
Idempotent — safe to run multiple times. Only captures events after injection.
bin/inject-capture
bin/cache-query
Retrieves full context stored by bin/context.
bin/cache-query query <id> snapshot
bin/cache-query query <id> html
bin/cache-query query <id> console
bin/cache-query query <id> network
bin/cache-query query <id> screenshot
bin/cache-query list
bin/cache-query stats
bin/cache-query clear
bin/cache-query clear <id>
Typical QA Workflow
bin/open --headed
playwright-cli goto http://localhost:8000
bin/auth-load <profile>
bin/inject-capture
playwright-cli click e5
playwright-cli fill e7 "search term"
playwright-cli press Enter
bin/context --screenshot
bin/cache-query query <id> console
bin/cache-query query <id> network
bin/cache-query query <id> snapshot
bin/cache-query query <id> screenshot
bin/close
Multi-project / parallel agent note: When running under Claude Code, each agent automatically gets an isolated session via CLAUDE_SESSION_ID. Direct playwright-cli commands (goto, click, fill, etc.) work as-is when only one session is open. If multiple sessions are active, prefix with -s=<session> — e.g. playwright-cli -s=my-project-a1b2c3d4 goto <url>. The session name is printed when you run bin/open.
Reading Console Errors
bin/cache-query query <id> console | python3 -c "
import sys, json
msgs = json.load(sys.stdin)
errors = [m for m in msgs if m['type'] == 'error']
for e in errors:
print(e['text'])
"
9. Diagnosing Server-Side Exceptions
When a page action fails silently (form doesn't submit, page goes blank, modal doesn't respond), the cause is often a server-side exception that Playwright can't see. The framework's error page often renders HTML that the accessibility tree can't meaningfully parse.
Set PLAYWRIGHT_SERVER_LOG in your environment (or per-project worktrees.json notes) to the path of your server log, then use the patterns below. The default examples assume $PLAYWRIGHT_SERVER_LOG.
When to Check the Server Log
- A form submission or action doesn't produce the expected result (e.g., status doesn't change, modal stays open).
- The page shows an unexpected state after clicking a button.
- A navigation results in a blank or error-like page.
- A framework-driven component stops responding after an action.
How to Check
Quick check — last 30 lines of the log:
tail -30 "$PLAYWRIGHT_SERVER_LOG"
Search for recent exceptions today:
grep -A 5 "$(date +'%Y-%m-%d')" "$PLAYWRIGHT_SERVER_LOG" \
| grep -A 5 "Exception\|Error\|Traceback\|SQLSTATE" \
| tail -40
Read only new lines since a known size:
wc -l "$PLAYWRIGHT_SERVER_LOG"
tail -n +1501 "$PLAYWRIGHT_SERVER_LOG"
Marker Pattern for Test Runs
Drop a marker into the log so you can easily find new exceptions afterward:
echo "[$(date +'%Y-%m-%d %H:%M:%S')] QA-TEST-START" >> "$PLAYWRIGHT_SERVER_LOG"
grep -A 9999 "QA-TEST-START" "$PLAYWRIGHT_SERVER_LOG" | tail -50
Common Exception Patterns
The patterns vary by framework — recognize the family rather than the exact string. Some recurring ones:
| Log Pattern | Likely Cause |
|---|
SQLSTATE[23505] / UNIQUE constraint failed | Unique constraint violation (duplicate insert) |
SQLSTATE[42703] / column ... does not exist | Missing migration |
SQLSTATE[23502] / NOT NULL constraint | Missing required field |
undefined method / NoMethodError / AttributeError | Method/attribute doesn't exist on model/class |
Class "..." not found / NameError / ImportError | Missing import or wrong namespace |
null / None / nil property access | Missing related record on an optional relationship |
Method Not Allowed / 405 | Wrong HTTP verb (GET vs POST) |
Default log locations by framework
If PLAYWRIGHT_SERVER_LOG is unset, try the standard path for your stack:
| Framework | Path |
|---|
| Laravel | storage/logs/laravel.log |
| Rails | log/development.log |
| Django (DEBUG) | console — pipe it to a file |
| Express / Node | stdout — pipe to a file or use pino/winston sink |
| Symfony | var/log/dev.log |
| Phoenix | _build/dev/lib/<app>/ebin console — pipe to a file |