test-playwright — Develop → Test → Fix (PDCA)
Degree of freedom: MIXED — blast-radius and UX judgment [HIGH freedom];
session, anti-stall, and live re-test after each fix [LOW freedom — run exactly].
Driver is playwright-cli, never Playwright MCP.
The job is not done when the code compiles. It is done when you have driven the live app as a user, found what's broken or clunky, and fixed it. Agents skip Check and Act — you will not.
Plan = the change you just made. Do = already in the code. Check = drive the live app (this skill). Act = fix every pain point in the same turn.
Read protocol-browser-anti-stall before any browser action — Rule 0
(manual & headed), navigation guard, ≤3s waits, fresh snapshot after every
state change, max-4-attempts-per-goal, tab discipline, and persisted auth
(references/playwright-session-coordination.md).
This skill vs neighbors
| Skill | Owns |
|---|---|
| test-playwright (this) | This-diff + blast radius; fix as you go |
test-qa |
Full-app story/CRUD smoke — not this session's diff |
test-exploratory |
Unscripted guest vs authed wander (do not reclaim) |
test-red-team |
Hostile feature×dimension matrix; report first |
test-visual-regression |
Pixel baselines |
How to reason (each surface)
- Observe — screenshot + console + network vs this session's diff
- Interpret — broken / data-wrong / pipeline / friction / visual / a11y
- Classify — PASS / PAIN (fix now) / BLOCKED (needs decision)
- Severity — ship-blocker vs polish; Act now unless risky/out-of-scope
Worked example
Observe: create item → toast "Saved";
requestsshows POST 500; list empty afterreload. Diff touched the create action. Interpret: optimistic UI over a failed write. Classify: PAIN — pipeline lie. Fix the action, then re-drive. Severity: ship-blocker. Act: fix the handler; re-test until 2xx + persist + clean console.
Core principles
- Drive a visible browser by hand, never a script. Headed (never
--headless). One real action at a time.eval/run-codeinspection-only; no*.spec.ts, nonpx playwright test. - Test only what this session changed — plus its blast radius. Not a
full-app crawl (
test-qa). Not guest-vs-logged-in wander (test-exploratory). - Fix as you go — full-stack. Bug, 500, dead button, ugly layout → root cause now, then re-test. Don't batch "later".
- Evidence or it didn't happen. Finding = screenshot + console + network. Fix = live re-test against the real backend.
- Red-team your own work. Assume the change is subtly wrong. Try to break it.
Workflow checklist
PDCA Progress:
- [ ] Phase 1: Scope — what did this session change? (blast radius)
- [ ] Phase 2: Environment — dev server up, app loads, authenticated
- [ ] Phase 3: Walk the changed flows as a real user
- [ ] Phase 4: Fix pain points + errors as you find them (full-stack)
- [ ] Phase 5: Backend truth-check (Sentry / Supabase / logs)
- [ ] Phase 6: Red-team + critique + enhancement ideas
- [ ] Phase 7: Re-test everything you fixed; report
Phase 1: Scope the session changes [HIGH freedom]
Do NOT test the whole app.
- Get the diff (each workspace repo root if needed):
git status --short
git diff --stat HEAD
git diff HEAD --name-only
Map files → user-facing surfaces.
- Page/route → test that page.
- Shared component/hook/util → every importer (
grep -rl "ComponentName" src/) — the blast radius. - API / controller / service → every UI flow that calls it.
- Migration / schema / RLS → read AND write paths, as the client's role.
- Config / env / pricing / prompt → the feature it drives.
Stack & dev URL (only what you need):
scripts.devport; auth + test credentials (.env.local,.env.test, README — ask once if none); backend MCPs this session: Supabase, Sentry, Firecrawl.Write the test plan before opening the browser:
SESSION SCOPE:
- Repos touched: [list]
- Changed surfaces (pages/flows): [list]
- Blast radius (shared code → consumers): [list]
- Backend paths touched (APIs/tables/RPCs): [list]
- Dev URL: http://localhost:[port] Auth: [method / test account]
- User journeys to drive: [ordered list of 2–6 real flows]
Phase 2: Environment verification [LOW freedom — session exact]
Read protocol-browser-anti-stall/references/playwright-session-coordination.md
before opening the browser.
PW="npx --yes @playwright/cli@latest"
- Check
terminals/for a running dev server. If none, start it (block_until_mssized to startup) or tell the user and stop. - Name the session after the task (
-s=qa-<feature>). Never reuse another agent's name. $PW -s=qa-<feature> open --headed <dev-url>— add--persistent --profile "$HOME/.playwright-cli-profiles/<app>"when login is needed.- Anti-stall:
sleep 2→snapshot→ verify content rendered. console+requests→ baseline before the changed feature.- Auth (log in once, by hand — it persists):
- Protected route → already signed in? continue.
--persistent --profilesurvives turns. - Else complete login in the visible window. Verify a protected route.
- Google accounts cannot sign in from a Playwright-launched browser — one-time real-Chrome login in the coordination reference.
- Lighter alternative:
state-save/state-load.playwright-mcp/auth/<host>.json. - Do not log out unless testing logout.
- Protected route → already signed in? continue.
$PW -s=qa-<feature> closewhen the run is done.
Phase 3: Walk the changed flows as a real user [HIGH freedom; cycle = LOW]
For each Phase 1 journey, live it. Per step (anti-stall throughout):
S="-s=qa-<feature>"
$PW $S goto "<url>"
sleep 2 && $PW $S snapshot
$PW $S screenshot --filename ".playwright-mcp/<step>.png"
$PW $S click <ref> # one action: click / type / fill / select / …
$PW $S snapshot # FRESH refs after every interaction
$PW $S console
$PW $S requests
Then judge: WORK + feel GOOD? PASS or PAIN POINT.
| Category | Look for |
|---|---|
| Broken | Blank, error boundary, 404/500, stuck spinner, dead button, no-op submit |
| Data wrong | undefined / null / NaN / [object Object] / Invalid Date, bad totals, stale after mutation |
| Pipeline | UI shows change but API failed; create missing until refresh; deleted item returns; optimistic never confirms |
| Validation | Empty/invalid submit silent; no inline errors; silent backend reject |
| UX friction | Confusing copy, no loading/success/error, hidden primary, too many clicks |
| Visual | Overflow, cramped/wasted space, broken images/icons, dark-mode, layout shift |
| A11y basics | Unlabeled inputs, unnamed controls, invisible focus, low contrast |
Mutations E2E: after create/update/delete confirm (a) network 2xx, (b) UI
reflects it, (c) survives hard reload, (d) if Supabase MCP — the row
changed. Prefix test data QA-TEST- and clean it up.
Phase 4: Fix pain points and errors — as you go [HIGH freedom; re-test = LOW]
The Act phase agents skip. Fix the root cause before moving on.
- Diagnose to root cause — don't patch symptoms.
- Frontend → component/hook/state.
- 4xx/5xx → payload + backend log; controller/service/validation.
FE↔BE mismatch →
debug-fe-be-integrationmindset. relation does not exist/ missing column → migration not deployed. Deploy via Supabase MCP (apply_migrationfor DDL,execute_sqlfor data) AND keep the versioned file on disk. (full-stack-ship-discipline— requested schema ships;DELETE/UPDATE/TRUNCATEon real rows asks.)- RLS → verify as the client's role (
SET ROLE anon;/authenticated;), fix policy, re-verify. - Config/env/CORS → fix and note other environments.
- Apply surgically.
ReadLintson files you edited. - Re-drive the same flow — green console, 2xx, correct UI, persisted data. A fix is not done until re-tested live.
- Genuinely out of scope or risky → STOP and surface it; don't silently ship a broken flow.
FIX LOG:
- [surface] [symptom] → root cause: [...] → fix: [file(s)] → re-test: PASS/▢
Phase 5: Backend truth-check (full-stack) [HIGH freedom]
Don't trust the UI alone. Look up MCP schemas first.
Sentry — new/related production errors on touched surfaces:
sentry:search_issues
{
"organizationSlug": "<ORG>", "query": "unresolved issues in the last 7 days",
"projectSlugOrId": "<PROJECT>", "regionUrl": "<REGION_URL>", "limit": 25
}
analyze_issue_with_seer on anything that maps to your change. Resolve
(update_issue) only AFTER a verified fix.
Supabase — list_tables, execute_sql, get_logs(service: 'api'|'postgres'),
get_advisors. New ERROR advisors from your change are in scope. Confirm
deployed migrations on the remote (information_schema / pg_proc / pg_policies).
App logs / terminal — server stack traces that never reached the browser.
Phase 6: Red-team and critique [HIGH freedom]
Skeptical reviewer + demanding user, on the changed surfaces only.
Full-app hostile matrix → test-red-team. Guest wander → test-exploratory.
- Break it: double-submit, rapid toggle, back/forward, deep links, empty
states, huge inputs, special chars (
<script>,'; DROP TABLE, emoji), slow/failed network. - Question the UX: primary action obvious in 3s? Feedback immediate?
Would a real user get stuck? Deep polish →
enhance-web-ux/enhance-web-ui. - Question the design: match existing tokens/patterns, or drift?
- Research when unsure: Firecrawl
firecrawl_searchfor current pattern/feature best practices; map back to concrete changes.
Capture enhancement ideas — concrete: what, why, effort. Distinguish "fix now" (Phase 4) from "suggested next" (report).
Phase 7: Re-test and report [LOW freedom — do not skip]
- Re-drive every fixed flow end to end. Confirm green.
- Clean up
QA-TEST-data; reset settings; verify cleanup in DB if applicable. - Report:
## PDCA Test Report — [feature / session summary]
### Scope (what this session changed)
- Repos: [...] Surfaces tested: [...] Backend paths: [...]
- Dev URL: [...] Auth: [...]
### Flows driven (as a user)
| # | Journey | Result | Evidence |
|---|---------|--------|----------|
| 1 | [...] | PASS / FIXED / BLOCKED | [screenshot/console/network] |
### Fixed this turn (Act)
| # | Surface | Symptom | Root cause | Fix (files) | Re-tested |
|---|---------|---------|-----------|-------------|-----------|
| 1 | [...] | [...] | [FE/BE/DB/config] | [...] | ✅ |
### Still broken / out of scope (needs decision)
| # | Surface | Finding | Why not fixed | Recommendation |
|---|---------|-------|---------------|----------------|
### Backend truth-check
- Sentry: [new/related issues + status]
- Supabase: [schema/data/logs/advisors — migration deployed? Y/N]
### Red-team findings
| # | Surface | Severity | Finding | Evidence |
|---|---------|----------|---------|----------|
### Enhancement suggestions (Plan the next cycle)
1. [concrete idea] — why it helps — rough effort
### Verdict
**Ship / Ship after fixes / Not ready** — [1–2 sentence justification]
Console clean: [Y/N] · All flows green on re-test: [Y/N] · Test data cleaned: [Y/N]
Self-critique before reporting [LOW freedom — do not skip]
- Scope — this-diff + blast radius, not full-app (
test-qa) or guest wander - Driver — playwright-cli, headed, named session; never Playwright MCP
- Act — every PAIN fixed or explicitly blocked; live re-test after each fix
- Evidence — finding = screenshot+console+network; fix = green re-drive
- Honest verdict — red console or unfixed PAIN ≠ Ship
playwright-cli commands
PW="npx --yes @playwright/cli@latest", then $PW -s=<session> <command>.
Snapshot/ref-based; sessions are isolated — re-snapshot after each state
change. Headless by default — pass --headed on open.
Drive: open --headed, goto, go-back, click, type, fill,
select, check, uncheck, hover, drag, drop, press, upload,
dialog-accept, resize.
Observe: snapshot, find, screenshot --filename .playwright-mcp/<name>.png,
console, requests, request <n>.
Wait: sleep N (shell, ≤3s) or
run-code "async (page) => { await page.getByText('X').first().waitFor({ timeout: 5000 }); }".
Inspection-only: eval, run-code.
Old MCP → CLI map: protocol-browser-anti-stall/references/mcp-to-cli-map.md.
Guardrails
- Manual & headed, never scripted — visible browser; no
*.spec.ts, nonpx playwright test. Anti-stall Rule 0. - Scope discipline — session changes + blast radius. Full-app →
test-qa. - Own your session — every command
-s=<task>; neverclose-all/kill-allsessions you didn't open. - Auth reuse — log in once into
--persistent --profile; don't log out unless testing logout. - Anti-stall always — never block >3s; max 4 attempts;
[TIMEOUT]and skip. - Fix the root cause, full-stack — UI, API, DB, config; re-test live.
- Schema in sync — MCP changes get a versioned migration file; verify remote.
- Ask before mutating real data — requested DDL ships; prod row
DELETE/UPDATE/TRUNCATEasks first. - No secrets in chat —
.env*by name only. - Evidence — screenshot + console + network + a green re-test.
- Honest verdict — don't declare done with a red console or unfixed PAIN.