Skip to content

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).

  1. 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"
  }
}
  1. 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 the CONFIG block in helpers/auth.ts, add the test:e2e:seed npm script, merge the Playwright projects, copy test-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.


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:doctor script 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 in helpers/auth.ts CONFIG. Those are exercised by the setup project, 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/mismatched data-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.e2e exists, 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.