SKILL.md
test-qa — Story-driven CRUD, not a monkey test
Degree of freedom: MIXED — story generation and severity [HIGH freedom]; session, anti-stall, and CRUD verify-after-refresh [LOW freedom — run exactly]. Driver is playwright-cli, never Playwright MCP. Guest-vs-authed wander is test-exploratory — do not reclaim it.
Full QA of a live webapp as a senior QA engineer preparing a production release: user-story-driven coverage, CRUD, data-pipeline integrity, UX, and edge cases. This is not a page-navigation monkey test.
Read protocol-browser-anti-stall before any browser step (Rule 0: manual & headed). Also references/playwright-session-coordination.md in that folder.
This skill vs neighbors
| Skill | Owns |
|---|---|
| test-qa (this) | Story-driven CRUD + smoke; persist after refresh (+ DB) |
test-exploratory |
Unscripted wander + guest vs authed diff |
test-playwright |
This-diff PDCA + fix-as-you-go |
test-red-team |
Hostile feature×dimension matrix |
test-visual-regression |
Pixel baselines |
plan-stub-checker |
Dead buttons / fake components (plan) |
How to reason (every finding)
- Observe — screenshot + console + network (+ DB if available)
- Interpret — persist fail, dead control, UX defect, or expected empty?
- Classify — PASS / FAIL / BLOCKED (auth) — never "sort of works"
- Severity — Critical (blocks ship) / Major (bad UX) / Minor (polish)
Worked example
Observe: POST /api/items 201; list shows "QA-TEST-Widget"; after hard
gotothe row is gone. Console clean.
Interpret: optimistic UI; write never persisted.
Classify: FAIL — data pipeline (ghost create).
Severity: Critical — CRUD not verified E2E.
Critical rules
- Manual & headed, never scripted. One
playwright-cliaction at a time.
eval / run-code inspection-only. No *.spec.ts, no npx playwright test.
- User in the chair, engineer in the head. First-open navigation; inspect
like you know the stack.
- Every mutation verified E2E. Create/update/delete is untested until the
change survives a refresh and (if DB access exists) the row matches.
- Evidence for every finding. Screenshot + console + network + repro.
- Clean up. Delete
QA-TEST-data. Leave auth session intact. - No invented routes. Discover pages/entities from source first.
Phase 0: Codebase Discovery [HIGH freedom]
0a. Detect Tech Stack
Read the dependency manifest (package.json, requirements.txt, pyproject.toml, Cargo.toml, go.mod). Record framework, UI lib, auth, DB/ORM, state, CSS, and scripts.dev port (3000 / 3001 / 5173 / 5174).
0b. Discover Routes
| Framework | Scan Pattern |
|---|---|
| Next.js App Router | app//page.tsx or app//page.js |
| Next.js Pages | pages/**/*.tsx (excluding app, document) |
| Remix | app/routes/**/*.tsx |
| SvelteKit | src/routes/**/+page.svelte |
| Nuxt | pages/**/*.vue |
| React Router (SPA) | Grep for <Route or createBrowserRouter |
| Django | urls.py files |
| Rails | config/routes.rb |
For each route: path, dynamic segments, layout nesting, auth guard.
0c. Discover Data Model
| Source | Where |
|---|---|
| Supabase migrations | supabase/migrations/*.sql — tables, RLS |
| Prisma schema | prisma/schema.prisma |
| Drizzle schema | drizzle/schema.ts or src/db/schema.ts |
| TypeScript types | types/*.ts, **/types.ts |
| API routes | app/api/** — CRUD endpoints |
| Feature files | features/*/ — services, hooks, components |
For each entity: name, key fields, CRUD capabilities, relationships.
0d. Discover Auth Pattern
Provider config (createClient, NextAuth, ClerkProvider, Auth0Provider); login (signIn / login / authenticate); test credentials (.env.local, .env.test, .env.example, README); protected routes (middleware, guards).
0e. Read Feature Documentation
Feature-directory READMEs (@_*-README.md, docs/) for expected behavior.
0f. Record Discovery Results
APP DISCOVERY:
- Framework: [name + version]
- Dev server: http://localhost:[port]
- Auth: [provider + method]
- Test account: [email / password, or "none found — ask user"]
- Routes discovered: [count]
- Public: [list]
- Auth-required: [list]
- Dynamic: [list with param patterns]
- Data entities: [list with CRUD capabilities]
- API endpoints: [count]
Phase 1: Environment Verification [LOW freedom — session exact]
1a. Verify Dev Server Running
Check the terminals folder for npm run dev / pnpm dev / next dev. If none, tell the user and stop.
1b. Load the app (own your session)
- Session name
-s=qa-<app>; never reuse another agent's name. - Isolated browser — add
--persistent --profileif login is required:
PW="npx --yes @playwright/cli@latest"
$PW -s=qa-<app> open --headed http://localhost:3000
sleep 2
$PW -s=qa-<app> snapshot
$PW -s=qa-<app> screenshot --filename .playwright-mcp/qa-baseline.png
$PW -s=qa-<app> console
$PW -s=qa-<app> requests
Blank after 3 incremental waits (6s) → blocker.
1c. Authenticate (reuse session — do not re-login every run)
Follow protocol-browser-anti-stall/references/playwright-session-coordination.md.
- Hit a protected route — if already signed in, skip to 1d.
state-load .playwright-mcp/auth/<host>.jsonif it exists (or reuse the
--persistent --profile directory).
- If still logged out:
- Email/password: credentials from .env.test / README (never paste secrets). - OAuth / SSO: sign in in the visible window; incremental snapshots; then state-save .playwright-mcp/auth/<host>.json. - Google cannot sign in from a Playwright-launched browser — use the one-time real-Chrome profile in the coordination reference.
- Verify auth (avatar, dashboard, protected pages).
- Do not log out at cleanup unless testing logout.
If auth is impossible, mark auth-required pages BLOCKED and test public only.
1d. Capture Baseline
BASELINE:
- URL: [current URL]
- Console errors: [count — list if any]
- Network failures: [count — list if any]
- Visible content: [brief description]
- Screenshot: [reference]
Phase 2: Intelligent Page Crawl [HIGH freedom]
For EVERY route from Phase 0b:
2a. Navigate and Capture
goto → anti-stall (2s → snapshot → verify) → screenshot → console → requests.
2b. Classify the Page
| Classification | Signals |
|---|---|
| CRUD page | Forms, edit/delete, data tables |
| Display page | Data, no mutation (dashboards, profiles) |
| Settings page | Toggles, selects, save for preferences |
| Auth page | Login, register, forgot password |
| Static page | About, terms, privacy |
| Navigation hub | Links to children (home, index) |
2c. Detect findings during crawl
| Check | What to Look For |
|---|---|
| Dead page | 404, error boundary, blank |
| Console errors | JS / React errors, failed assertions |
| Network failures | 4xx/5xx, CORS, timeouts |
| Missing content | "undefined", "null", "NaN", "[object Object]" |
| Mock data | "Lorem ipsum", "TODO", "test", "example@" |
| Dead buttons | No onClick / navigate nowhere (snapshot) |
| Missing metadata | Empty or generic document.title |
| Loading stuck | Spinner/skeleton after 6s |
| Empty state | No data AND no helpful empty message |
| Broken images | Missing/broken src or error fallback |
| Overflow | Text/elements past their containers |
2d. Build Feature Map
FEATURE MAP:
- CRUD pages: [entity + operations]
- Forms found: [page + purpose]
- Data displays: [tables, lists, cards, charts]
- Interactive elements: [buttons, toggles, dropdowns per page]
- Search/filter: [pages]
- Settings: [configurable preferences]
- Dead buttons found: [page + element]
- Pages with errors: [list]
Phase 3: Dynamic User Story Generation [HIGH freedom]
Stories come from the feature map, not a canned list.
| Cat | Story | Drive |
|---|---|---|
| A | First impression | Home: value prop in 3s, CTA, guest-reachable nav, trust |
| B | Core journey | Primary purpose from Phase 0, start to finish |
| C | CRUD per entity | Phase 4 — verify persist after refresh (+ DB) |
| D | Navigation | Every nav link, back/forward, breadcrumbs, no dead ends |
| E | Search/filter | Known hit, empty miss + helpful empty, filters |
| F | Errors | Empty required, invalid input, /nonexistent 404, network fail |
| G | Settings | Change, navigate away, come back — preference persisted |
Phase 4: CRUD Testing [LOW freedom — verify after refresh]
For each CRUD-capable entity, run the lifecycle.
4a. Create
- Open the creation form; identify fields via
snapshot - Fill realistic data: text
QA-TEST-[field]-[timestamp]; domain-sane
numbers/dates; valid select; non-default toggle
screenshotbefore submit- Submit;
requestsmust be 2xx;snapshot+screenshotfor success
feedback (toast, redirect, confirmation)
Record the item's ID, name, and URL.
4b. Read
List view → find the item → displayed fields match → detail view (if any) matches → screenshot.
4c. Update
Edit 1–2 fields → screenshot before save → save → requests 2xx → UI updates → hard-refresh (goto same URL) → changes still there.
4d. Delete
screenshot before → delete (+ confirm if any) → requests 2xx → gone from list → detail URL is 404 or redirect.
4e. Validation Testing
- Empty submission → inline validation
- Invalid data — wrong format, text in number field → specific errors
- Boundary — 500+ chars, zero, negative, past dates
- Special characters —
<script>alert('xss')</script>,'; DROP TABLE,
emoji, Unicode
- Duplicate — create the same item twice → duplicate handling
4f. Data Pipeline Verification
After each mutation:
- Network —
requests: call made? status? - Response — body has expected data?
- UI — reflects mutation without a manual refresh?
- Refresh — after
gotosame page, still visible? - DB (if Supabase MCP or DB access):
supabase:execute_sql
{
"project_id": "<PROJECT_ID>",
"query": "SELECT * FROM <table> WHERE <identifying_column> LIKE 'QA-TEST-%' ORDER BY created_at DESC LIMIT 5"
}
Pipeline failures: optimistic UI but API failed; stale cache; missing invalidation (item appears only after refresh); ghost data (deleted item returns); silent 4xx/5xx.
Phase 5: UX Quality Audit [HIGH freedom]
Assess each page. Layout breakage → audit-responsive. Empty/error chrome → audit-ui-states. Do not re-own those audits.
5a. Visual Quality
| Check | How to Verify |
|---|---|
| Consistent spacing | Screenshot — no irregular gaps or cramped areas |
| Typography | No mixed sizes where they should match; no orphaned headings |
| Truncation | Ellipsis where appropriate, not clipped |
| Image loading | All images render; no broken-image icons |
| Icons | All icons render (no missing squares) |
| Dark mode | If supported: toggle; no white flashes |
| Responsive | 1280 / 768 / 375 — layout adapts without breaking |
5b. Interaction Quality
| Check | How to Verify |
|---|---|
| Dead buttons | Click every button. Does it do something? |
| Form labels | Every input has a visible label or accessible name |
| Loading states | Data fetch shows a loading indicator |
| Success feedback | After mutations — toast, confirmation, or visual change |
| Error feedback | Failures — helpful, visible |
| Disabled states | Visually distinct; reason clear |
| Focus management | After modal close or submit, focus moves |
5c. Information Architecture
| Check | How to Verify |
|---|---|
| Page titles | document.title descriptive and unique |
| Active nav state | Current page highlighted |
| Dead ends | Any page with no way forward or back? |
| Empty states | No data — helpful message? |
| 404 page | /nonexistent-page — helpful 404? |
Self-critique before reporting [LOW freedom — do not skip]
- CRUD E2E — create/update/delete proven after refresh (+ DB if available)
- Evidence — every FAIL has screenshot + console + network + repro
- Cleanup —
QA-TEST-rows gone; auth session left intact - Right owner — guest/authed wander →
test-exploratory; pixel →
test-visual-regression; hostile → test-red-team
- Binary — no "sort of works"
Further reading
- [5d. Data Display Quality and more](references/details.md)