E2E Test Runner (/test-e2e)¶
In plain terms
Runs the end-to-end (Playwright) tests in the background and reports what passed or failed.
What this skill does
Runs Playwright end-to-end tests headless and reports a structured pass/fail summary with diagnosis of any failures. Use when the user asks to run E2E or Playwright tests, or when /pr-ready calls for them.
Runs all Playwright E2E tests headless and reports a structured pass/fail summary with diagnosis of any failures.
[!NOTE] This single skill is project-portable: it reads an optional per-project config and otherwise auto-detects layout, backend health URL, and start command — so it works for FastAPI and Express backends (and frontend-only apps) without separate flavors.
Trigger Conditions¶
- Keywords: "run e2e", "run end-to-end tests", "playwright tests", "test-e2e", "e2e tests"
- Called automatically by
/pr-ready
Step 0 — Load config & discover layout¶
Resolve a few values once here, then substitute them literally into every step below (shell state does not persist between commands, so capture the values and reuse them).
- Read the optional per-project config at
.claude/test-e2e.config.json(repo root):
cat .claude/test-e2e.config.json 2>/dev/null || echo "NO_CONFIG"
Schema — every key optional (a documented copy is at
templates/test-e2e.config.sample.json):
{
"frontendDir": "frontend",
"seedCommand": "npm run test:e2e:seed",
"backend": {
"healthUrl": "http://localhost:8000/api/system/health",
"startCommand": "cd backend && uvicorn main:app --reload --port 8000"
}
}
- Auto-detect whatever the config omits:
FE=$(dirname "$(find . -maxdepth 3 -name 'playwright.config.*' -not -path '*/node_modules/*' | head -1)")
echo "frontendDir: ${FE:-NOT_FOUND}"
grep -q '"test:e2e:seed"' "$FE/package.json" 2>/dev/null && echo "seedCommand: npm run test:e2e:seed" || echo "seedCommand: (none)"
Resolved values (config wins; else detection; use these literally below):
- <frontendDir> — the directory holding playwright.config.*. If not found, STOP and ask the user.
- <seedCommand> — the MFA seed command, or none → skip Step 1.5.
- <healthUrl> / <startCommand> — backend health URL + start command. For FastAPI this is
typically http://localhost:8000/api/system/health + cd backend && uvicorn main:app --reload --port 8000;
for Express typically http://localhost:5001/health + cd backend && npm run dev. If neither a
config backend block nor a backend/ dir exists, the project is frontend-only → skip Step 1.
Step 0.5 — Scaffold MFA helpers if missing¶
This skill bundles reusable, parameterized helpers for apps where login requires mandatory TOTP
MFA backed by Supabase Auth (see templates/ and templates/SETUP.md).
Only relevant when <seedCommand> is set (or the app uses Supabase MFA but isn't wired up yet):
ls <frontendDir>/e2e/seed-mfa.mjs <frontendDir>/e2e/helpers/auth.ts 2>/dev/null
- If both exist: already wired up — continue.
- If missing AND the app uses Supabase Auth with mandatory MFA: offer to scaffold, then follow
templates/SETUP.md(edit theCONFIGblock inhelpers/auth.ts, add thetest:e2e:seednpm script, merge the Playwright projects, copytest-e2e.config.sample.json→.claude/test-e2e.config.json, gitignore.env.e2e+e2e/.auth/):
cp -R "$CLAUDE_SKILL_DIR/templates/e2e/." <frontendDir>/e2e/
Do NOT blindly overwrite existing files — if only some are present, diff before copying. - If the app does not use MFA: skip this step and Step 1.5.
Step 1 — Backend Reachability Check¶
Skip if the project is frontend-only (no <healthUrl>). Otherwise verify the backend is reachable:
curl -s -o /dev/null -w "%{http_code}" <healthUrl> || echo "unreachable"
- If reachable (200): continue to Step 1.4.
- If not reachable: start it in the background with
<startCommand> &, then poll up to 15s:
for i in {1..15}; do
sleep 1
STATUS=$(curl -s -o /dev/null -w "%{http_code}" <healthUrl>)
[ "$STATUS" = "200" ] && echo "Backend ready" && break
echo "Waiting... ($i/15)"
done
If it comes up, tell the user it was auto-started and continue. If still unreachable after 15s,
STOP and ask the user to start it manually with <startCommand> and re-run.
Step 1.4 — Preflight the config (recommended)¶
If the project has a doctor script, run it to validate every parameter against reality first — it's read-only and exits non-zero on the first problem with a pinpointed message:
cd <frontendDir> && npm run test:e2e:doctor 2>&1
- If the
test:e2e:doctorscript doesn't exist: skip — the per-step checks below still apply. - All ✅: continue.
- Any ❌: STOP and report the failing check verbatim (it names the exact parameter to fix).
The doctor does NOT validate the login-form
data-testids inhelpers/auth.tsCONFIG. Those are exercised by thesetupproject, which logs in first — so a wrong selector fails setup (and Step 2) immediately, and Step 4's diagnosis table maps it to a missing/mismatcheddata-testid.
Step 1.5 — Seed the mandatory MFA factor¶
Skip if <seedCommand> is none. Otherwise login goes through mandatory TOTP MFA, and the suite
derives codes from E2E_TOTP_SECRET, provisioned by seeding a verified factor on the E2E user
(a single factor only allows one verification per 30s window, so this must run before the tests):
cd <frontendDir> && <seedCommand> 2>&1
- On success (
✓ Wrote E2E_TOTP_SECRET to …): continue to Step 2. - If it fails: the E2E user may not exist in Supabase, or the local Supabase stack isn't running.
Tell the user to start local Supabase and ensure the user in
<frontendDir>/.env.e2eexists, then re-run.
Step 2 — Run Tests¶
cd <frontendDir> && npx playwright test --reporter=line 2>&1
Tests run headless (Chromium only). Capture stdout for analysis.
Step 3 — Parse Results¶
Extract: total run, passed, failed, skipped, names of failed tests (✘ <test name>), and the error
message for each failure.
Step 4 — Diagnose Failures¶
For each failed test, classify it using the table below and provide a diagnosis:
| Error pattern | Diagnosis |
|---|---|
locator.click: Element not found with data-testid |
Missing data-testid attribute in source component — needs to be added |
locator.click: Element not found with getByRole |
Component not rendered or dialog not open |
strict mode violation: resolved to N elements |
Selector too broad — needs more specific locator |
Timeout 30000ms exceeded on login or navigation |
Backend not responding or login credentials wrong |
Expected URL to match /pattern/ |
Route changed or redirect not working |
Expected: visible / Received: hidden after action |
Async operation not awaited, missing waitFor |
net::ERR_CONNECTION_REFUSED |
Backend or frontend dev server not running |
| Test fails in suite but passes alone | Race condition — test file needs test.describe.configure({ mode: "serial" }) |
Step 5 — Report¶
Present a structured summary:
E2E Test Results
================
✅ Passed: N
❌ Failed: N
⏭ Skipped: N
Total: N
Spec files:
✅ e2e/auth.spec.ts (N tests)
❌ e2e/roles.spec.ts (N/N failed)
For each failure, show file:line, error, last step, one-line diagnosis, and suggested fix. If all
tests pass: ✅ All N E2E tests passed.
Step 5b — Write Results File (only if the CI deploy gate uses it)¶
Some projects' deploy workflow verifies an E2E results file. Detect it:
grep -rl ".results.json" .github/workflows/ 2>/dev/null || echo "no results-file gate"
If such a gate exists (commonly tests/e2e/.results.json), write it after parsing results:
cat > tests/e2e/.results.json << EOF
{
"commit": "$(git rev-parse HEAD)",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"status": "passed|failed",
"total": N, "passed": N, "failed": N, "skipped": N
}
EOF
Set status to "passed" only if failed == 0. This file MUST be committed alongside the code —
the deploy workflow verifies it matches the deployed commit.
Step 6 — Auto-Fix (Optional)¶
If failures are simple and contained (missing data-testid, wrong selector, changed button text):
1. Read the affected source file and/or E2E spec
2. Apply the targeted fix using Edit tool
3. Re-run the failing spec: cd <frontendDir> && npx playwright test e2e/<spec>.spec.ts --reporter=line
4. Confirm the fix resolved the failure before reporting
Do NOT auto-fix if the failure indicates broken application logic, a missing backend endpoint, or a UI regression — report these to the user instead.
Max 1 fix iteration. If the test still fails after fixing, report the remaining issue to the user.