/verify
Verify your frontend changes before pushing.
Prerequisites
- Dev server running (e.g.
npm run dev) - Playwright MCP configured in Claude Code (see install section below)
- Auth set up if app requires login (
/verify-setup)
Playwright MCP Install
If Playwright MCP is not available, show this:
/verify requires Playwright MCP for browser interaction.
Install:
claude mcp add playwright -- npx @playwright/mcp@next --storage-state .verify/auth.json --isolated --caps=devtools --output-dir .verify/mcp-output
@nextis required — video + trace tools ship only in the alpha.--caps=devtoolsexposes them;--output-dirpins a known drop zone. Restart Claude Code, then re-run/verify.
Conversation Flow
This skill is turn-based. Each turn has a trigger and a bounded set of actions. Never skip ahead.
Turn 1: Spec Intake
Trigger: User invokes /verify.
Check for arguments first. If the user passed a file path as an argument (e.g. /verify path/to/spec.md), skip this turn entirely — go straight to Turn 2 using that path.
Otherwise, try smart spec discovery first:
find . -maxdepth 3 -name "*.md" \( -name "*spec*" -o -name "*plan*" -o -name "*requirements*" -o -name "*acceptance*" \) -not -path "./.verify/*" -not -path "./node_modules/*" -not -path "./.git/*" 2>/dev/null | head -5
- If exactly 1 file found: suggest it. "Found a likely spec:
path/to/spec.md. Use this? (y/n)" - If multiple files found: show the list and ask the user to pick one.
- If no files found: "What spec are you verifying? Paste the spec content or give a file path."
Do not call any other tools. End your response and wait.
Turn 2: Pre-flight + MCP Check
Trigger: User has provided a spec (pasted content, file path, or confirmed a discovered file).
- If they gave a file path — read the file with the Read tool.
- If they pasted content —
mkdir -p .verifythen write to.verify/spec.md.
MCP preflight: Check if Playwright MCP is available:
Use ListMcpResourcesTool with server="playwright"
- If the server exists → Playwright MCP is available, proceed.
- If "Server not found" → show the install instructions from the Prerequisites section. Stop.
- If MCP is configured but non-responsive (e.g. connection error), show: "Playwright MCP is configured but not responding. Try restarting Claude Code."
Dev server check:
BASE_URL=$(cat .verify/config.json 2>/dev/null | grep -o '"baseUrl"[[:space:]]*:[[:space:]]*"[^"]*"' | grep -o 'http[^"]*' || echo "http://localhost:3000")
curl -sf "$BASE_URL" > /dev/null 2>&1 || { echo "Dev server not running at $BASE_URL"; exit 1; }
Auth check: Navigate to the app and check if you're logged in:
Use mcp__playwright__browser_navigate to go to $BASE_URL
Use mcp__playwright__browser_snapshot to read the page
If the page shows a login/signup form instead of authenticated content, try these sources in order:
1. Credentials file referenced from .verify/config.json. Check for a credentials field:
python3 -c "import json; c=json.load(open('.verify/config.json')); print(c.get('credentials',''))" 2>/dev/null
If the printed path is non-empty AND starts with .verify/ (the skill's allowed file zone — reject anything outside), use the Read tool to read the file directly. Parse the email and password in-head — never echo them to the shell or print them to stdout.
Supported formats:
- key=value (one per line):
email=user@example.com password=secret - JSON:
{"email": "user@example.com", "password": "secret"}
Then log in via Playwright:
mcp__playwright__browser_typeinto the email textbox (use thereffrom the snapshot) — pass the email as thetextargumentmcp__playwright__browser_typeinto the password textbox — pass the password as thetextargumentmcp__playwright__browser_clickon the submit button (usually labeled "Sign In", "Log in", or equivalent)mcp__playwright__browser_snapshot— confirm the page URL no longer matches/signin//login//auth
Tell the user briefly: "Logged in using credentials from {CREDS_FILE}." Never include the email or password in any output, result.json, or report.
2. User-provided credentials in chat. If no credentials field is set (or the file is missing / unreadable), fall back to asking:
"You're not logged in. Either add a
credentialsfield to.verify/config.jsonpointing at a file under.verify/, or paste an email/password here and I'll log in via the browser."
If the user pastes creds, use the same Playwright type+click sequence to log in, then snapshot to confirm.
3. Neither worked. Write verdict auth_expired for each AC and stop — tell the user to set up credentials and retry.
Proceed to Turn 3.
Turn 3: Spec Interpreter
Trigger: Pre-flight passed.
Review the spec inline. For each AC, check:
- Reveal action — does it say "shown/displayed/visible" without saying how? → flag
- Preconditions — requires specific data to exist? → flag
- Target — UI element identifiable by label or text? If too vague → flag
- Success — clear pass/fail? If not → flag
If no ambiguities: skip Turn 4, go directly to Turn 5. If ambiguities found: ask the first flagged question. End response and wait.
Turn 4: Clarification Loop
Trigger: User answered a clarifying question.
Keep a running list of AC annotations, e.g.:
- AC3: expiry date revealed via hover on Pending badge
- AC1: expiration field is inline in the send dialog
Note the answer and add it to the list. If more ambiguities remain — ask the next one and wait.
When all answered — proceed to Turn 5.
Turn 5: Extract ACs + Verify with Playwright MCP
Trigger: All ambiguities resolved (or there were none).
This turn has three phases: AC extraction, verification, and reporting.
Phase 1: Extract Acceptance Criteria
Read the spec content and any clarifications. Also read context files if they exist:
.verify/app.json— known routes, use for specific navigation paths in AC descriptions.verify/seed-data.txt— actual database records, use for specific data references (limit: first 8000 chars).verify/learnings.md— corrections from past verification runs
Extract testable ACs. Each AC must be concrete enough for browser verification:
AC quality standard — this is critical:
- BAD: "The settings form shows an expiration field"
- GOOD: "The team document settings page (/t/{teamUrl}/settings/document) shows a 'Default Envelope Expiration' combobox with options 'Never expires' and 'Custom duration'"
USE THE SEED DATA. If the spec says "a document with expiration set" and seed data shows a recipient "recipient-expiry@test.documenso.com", reference those exact values.
USE THE ROUTES. If app routes show /t/personal_xyz/settings/document, reference that navigation path.
Extraction rules:
- Each AC: one specific testable behavior
- Skip ACs requiring external services (Stripe, email, OAuth)
- Pure UI ACs with multiple checks on the same page should be split into individual ACs (one behavior each)
- NEVER use template variables like {envId}, {orgId} — resolve to actual values from routes or seed data
Present the AC list to the user: "I've extracted N acceptance criteria. Here's the plan: [list ACs]. Starting verification now."
Phase 2: Verify Each AC with Playwright MCP
Set up the evidence directory:
RUN_ID=$(date +%Y%m%d-%H%M%S)
mkdir -p .verify/runs/$RUN_ID/evidence
mkdir -p .verify/mcp-output/traces
# Purge stale artifacts from prior crashed runs so this run's recordings don't collide
rm -f .verify/mcp-output/*.webm 2>/dev/null || true
rm -f .verify/mcp-output/traces/trace-*.trace .verify/mcp-output/traces/trace-*.network .verify/mcp-output/traces/trace-*.stacks 2>/dev/null || true
For EACH acceptance criterion, fo