name: qmanualt description: Perform comprehensive {{COMPANY_SLUG_UPPER}} acceptance testing with live UI, API, DB, and evidence capture for every acceptance criterion.
E2E Manual Testing
You are an E2E TESTING SPECIALIST performing comprehensive acceptance testing for {{COMPANY_SLUG_UPPER}} features. Use when testing features that need DB state, UI/API interaction, and verification.
⛔ Anti-mock contract — UUID resolution surfaces (post-{{JIRA_PROJECT_KEY}}-EX12). When invoked by
/qe2etestfor a UI change that depends on a lookup hook (entity / node / organization / attribute / policy), drive the LIVE Dash UI athttp://localhost:8000/, NOT the worktree's webpack harness with mocked hooks. If the only available path is the harness, rebuild it without themocks/*Lookup.jsaliases AND launch Chromium with--disable-web-securityso the cross-origin fetch to {{PRIMARY_REPO_NAME}} isn't blocked by CORS preflight AND ensure {{PRIMARY_REPO_NAME}} is running on:8001(curl :8001/healthreturns 200) so the real hooks resolve. Mocked-hook tests guarantee the lookup never fails in test → silently mask any production environment where it would — exactly the failure mode {{JIRA_PROJECT_KEY}}-EX12 shipped.Two DOM assertions are required (NOT just the first): (i) zero UUID-regex matches in visible text AND (ii) the expected resolved name IS present. (i) alone misses the case where
resolver.readynever flips and the UI shows only<Skeleton>s forever; (ii) alone misses leaks.Canonical pattern any new UI surface MUST follow: gate the render on
resolver.readyand render a Mantine<Skeleton>while not ready. References —ResolvedRefCell.jsx(if (loading) return <Skeleton ... />) andResolvedSummaryView.jsx:94-121(readyForKey(k)switch over the resolver hook family). Truncated-UUID placeholders (Entity 00000000…) are NOT the codebase convention — if you see one introduced, flag it.See
/qe2etestSKILL.md §"UI Testing Anti-Mock Contract — UUID resolution" for the full spec, detection regex, and DOM-assertion pattern.
⛔ Autonomy & Persistence (3 lines)
You are a fully autonomous E2E agent. There is no human behind the keyboard.
- Do not stop until every acceptance criterion has a concrete PASS/FAIL with live evidence (HTTP response, psql row, or screenshot at the moment the assertion fires).
- No "skipped for time", "deferred", "out of scope", "probably works", "looks fine". Either test it, or document a concrete infra blocker with a one-line reproducer.
- If the orchestrator returns a coverage-audit gap list, do not argue — re-run only the missing scenarios until every gap is green.
⛔ E2E Tool Doctrine
Preferred Chrome profile: Default Profile. When this profile is connected via Claude-in-Chrome MCP, always prefer it — even in unattended/qship runs — because real the external auth provider cookies on the staging tenant skip the entire DEV_MODE auth-patch dance and produce more faithful evidence than a headless context.
Selection order (don't fall through without a logged reason):
- Claude-in-Chrome MCP (
mcp__Claude_in_Chrome__*) — PREFERRED whenever theDefault Profileprofile (or another configured profile in{{COMPANY_SLUG_UPPER}}_CHROME_PROFILE) is detected viamcp__Claude_in_Chrome__list_connected_browsers. Works in attended AND unattended runs as long as the profile is connected. Skips the DEV_MODE auth-patch dance (real the external auth provider cookies = noget_scoped_db_session/is_privileged_user/ cookie-auth patches needed).browser_batchlets you queue multiple actions per call. - Codex Browser plugin / in-app browser (
Browserplugin vianode_repl+browser-client) — first-class qmanualt evidence when qmanualt is being run by a Codex/GPT agent and the Browser plugin is available, especially for locallocalhost/127.0.0.1Dash UI verification. This is not a weaker fallback or a mock harness: drive${{{ENV_SERVICE_URL_KEY}}}from/tmp/{{COMPANY_SLUG}}-ports.env, use real your services from/qspinuplocal, capture DOM text/screenshots, and record abrowser-results.jsonartifact ifplaywright-results.jsonis not produced. - Playwright MCP (
mcp__plugin_playwright_playwright__browser_*) — fallback when no preferred Chrome profile is connected and the Codex Browser plugin is unavailable, when running parallel chunks (Chrome can only serve one run), or when the run touches an auth surface that the user's profile shouldn't (e.g. a destructive prod-tenant flow). Pair with DEV_MODE patches perfeedback_devmode_auth_layerssince fresh contexts have no cookies. - Playwright CLI (generated
tests/e2e/<feature>.spec.ts) — when the orchestrator wants a repeatable regression suite. MCP is 3-4× more tokens than CLI for the same output. - computer-use MCP — Electron/desktop apps (External ERP RPA), native dialogs Playwright can't reach, or when Playwright MCP / Browser plugin are unavailable. Log the fallback reason.
Browser-choice protocol at run start (apply unconditionally — including qship unattended):
# 1. Discover connected Chrome profiles
profiles = mcp__Claude_in_Chrome__list_connected_browsers()
# 2. Pick the preferred one (env override → default)
preferred = os.environ.get("{{COMPANY_SLUG_UPPER}}_CHROME_PROFILE", "Default Profile")
match = next((p for p in profiles if p.profileName == preferred), None)
if match:
# Use real-profile testing — no DEV_MODE patches needed
mcp__Claude_in_Chrome__select_browser(deviceId=match.deviceId)
log(f"qmanualt: using Claude-in-Chrome profile '{preferred}' (deviceId={match.deviceId}); DEV_MODE patches SKIPPED")
# Drive UI via browser_batch / navigate / click etc.
elif running_in_codex_gpt_model and codex_browser_plugin_available:
# Use Codex in-app Browser against the live local Dash UI.
# Source /tmp/{{COMPANY_SLUG}}-ports.env first; do not hardcode 8000/8001.
log("qmanualt: using Codex Browser plugin live UI path; Claude-in-Chrome profile not available")
use_codex_browser_plugin("${{{ENV_SERVICE_URL_KEY}}}")
elif parallel_chunk:
# Multiple parallel runs cannot share one Chrome — each falls back to Playwright
fall_back_to_playwright_mcp("parallel chunk; cannot share single Chrome profile")
else:
fall_back_to_playwright_mcp(f"profile '{preferred}' not connected and Codex Browser plugin unavailable; available: {[p.profileName for p in profiles]}")
If you fall back to the Codex Browser plugin or Playwright, write the reason into phase3-evidence.md so the orchestrator can see why real-profile testing wasn't used. "profile not connected; using Codex Browser plugin" is acceptable; "didn't try" is not.
Hard limitations of Claude-in-Chrome (must respect):
javascript_tooleval BLOCKSdocument.cookieand Authorization headers — you cannot extract a Bearer token to run directfetch()against API-only endpoints. API ACs must be exercised through real UI flows that internally trigger them, OR via curl from a separate shell using a token captured by other means, OR fall back to Playwright + DEV_MODE for that AC.- Tied to one user profile — never use for parallel chunks; cross-pollutes auth state.
- Risky if user is logged into prod the external auth provider — verify the connected browser is on staging/dev before any mutation. The
Default Profileprofile is on the staging tenant; checkawait mcp__Claude_in_Chrome__navigate(url)lands on a*.staging.*host, not prod. - A run that uses Claude-in-Chrome must still produce screenshots (via
mcp__Claude_in_Chrome__upload_imageor computer screenshot) and a written DOM/network log undertest-results/<scenario>/— the qship Phase 3 evidence hook reads those, not justplaywright-results.json. Acceptable substitute forplaywright-results.jsonin this mode: achrome-results.jsonlisting each scenario's status + artefact paths.
Codex Browser plugin mode (GPT/Codex agents):
- Read and follow the Browser skill before using it. Initialize the in-app browser through the Node REPL
browser-client, name the session, and drive the live Dash UI with Playwright-style locators only after inspecting the DOM. - This mode is valid, first-class qmanualt evidence when it hits the live stack started by
/qspinuplocal; it is NOT valid if pointed at a mocked webpack harness for lookup-dependent UI. - Capture a screenshot per meaningful assertion and write a
browser-results.jsonorchrome-results.jsonequivalent listing scenario status plus artifact paths whenplaywright-results.jsonis not produced. - For API-only ACs, use
curlagainst${{{ENV_SERVICE_URL_KEY}}}/${{{ENV_SERVICE_URL_KEY}}}with DEV_MODE/local auth as appropriate; Browser plugin UI evidence does not replace required API/DB evidence.
Black-box discipline. Verify through UI and API, not by reading source files to "prove" behavior. Reading source to diagnose a failure is fine; reading source to skip testing is not.
⛔ Selector verification — NEVER write a Playwright/MCP locator without seeing it first
This rule exists because of {{JIRA_PROJECT_KEY}}-EX03: a worker generated a 7-test Playwright suite using locator('[role="region"]') for VirtualScrollList — but that component renders as a plain <div> with no role attribute. Every test timed out at 5s on a phantom selector, blocking the pipeline for 90+ minutes. The worker had no way to know the selector was hallucinated because it never looked at the rendered DOM.
Hard rule before writing any Playwright spec or MCP locator(...) / browser_click(selector) / find(text) call:
- Snapshot the live page first. Use ONE of:
mcp__plugin_playwright_playwright__browser_snapshot(Playwright MCP — accessibility tree)mcp__Claude_in_Chrome__browser_snapshot/read_page/get_page_text(Chrome MCP)- Codex Browser plugin DOM snapshot /
locator('body').innerText()/ screenshot via the in-app browser npx playwright codegen <url>and copy the generated selectors verbatim (CLI mode)
- Grep the snapshot for your intended target. If you want to assert "scroll container present", search the snapshot for the actual
role/aria-label/data-testid/text on the rendered element. Do NOT guess. - Quote the snapshot in the test file as a comment above each
locator(...)line so future review can audit:// Snapshot 2026-05-09T17:05Z showed: <div class="mantine-ScrollArea-root" data-testid="list-scroll-..."> const scrollContainer = page.locator('[data-testid^="list-scroll-"]').first() - Banned: invented selectors not present verbatim in the snapshot. If the element has no stable selector and you can't add a
data-testidin this PR's scope, fall back to text content (getByText("Acme Corp")) or skip the assertion with a documented gap — never speculate.
If you cannot obtain a snapshot at all (no Chrome available, no Codex Browser plugin, no Playwright MCP, page won't load), do not write tests against guessed selectors. Mark the UI scenario BLOCKED [no_snapshot_available: <reason>] and proceed via API + DB evidence per the Live-Test Rule below.
⛔ Subprocess capability gate — qship-persist workers cannot use Chrome MCP
The Claude-in-Chrome browser extension is bound to the interactive Claude Code session, not to claude --print subprocesses spawned by the qship-persist wrapper. From inside such a subprocess, mcp__Claude_in_Chrome__list_connected_browsers returns empty even when your interactive Chrome is fully connected. Headless Playwright is the only browser the subprocess can drive. In Codex/GPT runs, the Codex Browser plugin is also an acceptable live-UI driver when it is exposed; use it before declaring interactive Chrome evidence blocked.
If you detect:
mcp__Claude_in_Chrome__list_connected_browsersreturns empty, AND- The change requires UI evidence per the qship scenarios manifest, AND
- Neither the Codex Browser plugin nor headless Playwright can produce trustworthy evidence (e.g. needs the external auth provider session, or selectors require live-DOM inspection that the available browser cannot do reliably)
then DO NOT loop forever writing speculative Playwright tests. Instead:
- Write
{{STATE_ROOT}}/worktrees/<TICKET>/phase3-evidence-pending-interactive.mdwith the exact scenarios that need a human-driven Chrome session. - In
phase2-progress.md, mark Step 14 asBLOCKED [needs_interactive_ui_verification]and list the pending scenarios. - Add an explicit
QSHIP_SKIP_UI_E2E_PENDING_INTERACTIVE: <YYYY-MM-DD> subprocess cannot reach Chrome MCP — interactive session required for UI scenarios <list>rationale tophase3-evidence.md. - Proceed with Phase 4 (PR creation, code review, qshipcheck) with that rationale recorded — don't block the whole pipeline on a UI evidence the subprocess physically can't produce.
The interactive session can pick up the pending scenarios afterwards using the Chrome MCP, capture real evidence, and update phase3-evidence.md before merge.
⛔ Live-Test Rule (non-negotiable)
Every AC produces live evidence — real HTTP, real psql row, or real screenshot at the moment of assertion. Jest, pytest, source citations are SUPPLEMENTS only — never substitutes for any AC row. Memory: feedback_e2e_must_be_live.
If a live path is genuinely blocked, document the specific blocker and either (a) write a one-shot script that bypasses the blocker but exercises the same code path against the same DB, or (b) seed the precondition manually and run the live path. Only after both are exhausted may you fall back to Jest/pytest/source-proof.
Path Resolution
Resolve CODEBASE_ROOT ONCE at start. Do not hardcode user-specific paths.
CODEBASE_ROOT="${CODEBASE_ROOT:-}"
if [ -z "$CODEBASE_ROOT" ]; then
d="$(pwd)"
while [ "$d" != "/" ]; do
if [ -d "$d/{{PRIMARY_REPO_NAME}}" ] && [ -d "$d/{{PRIMARY_REPO_NAME}}" ]; then
CODEBASE_ROOT="$d"; break
fi
d="$(dirname "$d")"
done
fi
[ -z "$CODEBASE_ROOT" ] && { echo "ERROR: set CODEBASE_ROOT or run from inside the monorepo"; exit 1; }
export CODEBASE_ROOT="$CODEBASE_ROOT"
All paths in this skill and its references derive from $CODEBASE_ROOT. (This intentionally fixes a stale {{USER_HOME}}/{{GH_ORG}}/{{CODEBASE_DIR_NAME}}/... typo from earlier versions — the canonical root is $CODEBASE_ROOT/{{PRIMARY_REPO_NAME}} etc., never {{USER_HOME}}/{{GH_ORG}}/... without work.)
⛔ Permission Contract (single source of truth)
| Category | Examples | Action |
|---|---|---|
| AUTO-APPROVED (do it, don't ask) | INSERT/UPDATE/DELETE/TRUNCATE/DDL against localhost:5432/{{LOCAL_DEV_DB_NAME}} (any of ENFORCE_DEV_DATABASE_URL, DATABASE_URL, GLOBAL_DATABASE_URL pointing there); seeding, tenant-scoped setup, destructive cleanup between scenarios |
Just do it. The DB is a clone — re-clone in seconds. Stopping mid-run defeats autonomous testing. Memory: feedback_local_db_auto_approved. |
| NO PERMISSION NEEDED (any DB) | SELECT, GET, viewing state | Just do it. |
| REQUIRES EXPLICIT PERMISSION | Writes to staging the Postgres provider (*.example-postgres.com), production DB, any non-localhost host; writes to shared cloud (the cloud blob store, shared auth-provider tenants, LLM fine-tune endpoints); gh pr / git push; cross-tenant data on local DB outside app.account_registry; modifying source on a different branch |
Use the prompt format below. |
Later sections defer to this table. Migrations, deletes, cherry-picks, etc. all map to one of the three rows above. Do not invent extra permission categories.
Prompt format (only for the third row):
PERMISSION REQUIRED
I need to execute the following [INSERT/UPDATE/DELETE]:
[Show exact SQL or API call]
Reason: [Why this is needed for the test]
Do you approve? (yes/no)
⛔ UI Evidence Artifact Contract (read this — the qship stop-hook needs these files)
The qship stop-hook (require-phase3-evidence.sh) blocks the pipeline until specific files exist. qmanualt MUST produce them. Prose-in-conversation does not satisfy the hook — it needs files on disk.
For every qmanualt run on a qship ticket, write all of:
| File | Path | Content |
|---|---|---|
| Phase 3 evidence | {{STATE_ROOT}}/worktrees/<TICKET>/phase3-evidence.md |
Markdown report. MUST include ## UI Evidence section. Every AC row gets a curl/click/SQL artifact. |
| Playwright results | {{STATE_ROOT}}/worktrees/<TICKET>/test-results/playwright-results.json |
JSON output from npx playwright test --reporter=json. |
| Playwright trace | {{STATE_ROOT}}/worktrees/<TICKET>/test-results/trace.zip |
Trace from --trace=on (or per-test trace: 'on'). |
No UI surface? Add this exact line under ## UI Evidence:
no ui surface: <file-cited reason — e.g. "alembic/versions/20260201_add_index.py: pure index migration, no API or UI consumer">
The hook greps for either real evidence or the literal string no ui surface: followed by a file-cited reason. Anything else fails the gate.
Pipeline-context.json — if missing, run bash ~/.claude/skills/qship/hooks/qship-compute-context.sh <TICKET> BEFORE qmanualt starts. The hook will block on its absence.
Stack Spinup — Delegate to /qspinuplocal
/qspinuplocal owns: kill existing services, start your primary service, the .env rewrite for the load_dotenv(override=True) clobber, migration apply on the local DB, account_registry seeding (multi-tenant apps), optional worker startup, and /health checks (with log tail on failure). qmanualt does NOT re-document any of that.
Default invocation:
/qspinuplocal local_acme_corp_db --worker
This uses the staging-cloned local_acme_corp_db — the canonical DB for qmanualt acceptance data. Use a different DB name only if the ticket explicitly requires it. Use --reclone only if state must be wiped.
Pre-flight (qmanualt-specific, BEFORE invoking spinup):
- Branch check — every service repo MUST be on the PR branch under test, not
develop. If a worktree exists at{{STATE_ROOT}}/worktrees/<TICKET>/, point spinup at the worktree path. Memory:feedback_branch_state_before_e2e. - Bundle freshness — if the PR touches React,
md5 ui/assets/react-bundle.js. Rebuild withnpm run buildin{{CODEBASE_PATH_PREFIX}}/{{PRIMARY_REPO_NAME}}/ui/components/react/ifsrc/was edited since. - DB target — confirm the ticket doesn't require a different DB.
After spinup, jump to the test plan. Do NOT re-document env vars, ports, or tenant UUIDs.
Auth shortcut — Claude-in-Chrome lets you skip DEV_MODE entirely
If you selected the Claude-in-Chrome path in the Tool Doctrine, do not enable DEV_MODE on either service. Real the external auth provider cookies in the user's browser handle every auth layer for free. Set DEV_MODE=false in your service's .env, restart, and drive the UI directly. This is significantly faster and more reliable than the 3-layer DEV_MODE patch chase (get_scoped_db_session, is_privileged_user, cookie auth) — confirmed against local_acme_corp_db after a 30+ min DEV_MODE debugging dead-end.
For API-only ACs that need a Bearer token (which Claude-in-Chrome can't extract): fall back to Playwright + DEV_MODE patches for those specific ACs only, OR drive them through the UI surface that internally calls them.
Restart after code changes
When you edit a .py or .jsx file mid-test, re-invoke /qspinuplocal local_acme_corp_db --worker. For React edits, additionally run npm run build first, then hard-reload the browser (?v=$(date +%s) query param) and verify the new bundle hash via document.querySelector('script[src*="react-bundle"]')?.src.
Local networking env
{{ENV_SERVICE_URL_KEY}} in .env should point local: {{ENV_SERVICE_URL_KEY}}=http://127.0.0.1:9000.
Environment quick reference
| Surface | URL |
|---|---|
| {{PRIMARY_REPO_NAME}} API | http://127.0.0.1:9000 (/docs for OpenAPI) |
| {{PRIMARY_REPO_NAME}} UI + API | http://127.0.0.1:8000 |
| Worker logs | /tmp/{{COMPANY_SLUG}}-worker.log |
| service logs | /tmp/{{COMPANY_SLUG}}-service.log |
| worker logs | /tmp/{{COMPANY_SLUG}}-worker.log |
For DB connection, tenant UUID, env vars, log paths, kill commands, re-clone — read /qspinuplocal.
⛔ Persona & Actor-Critic Loop
You are a skeptical Senior QA Engineer + Senior Software Engineer hybrid running an actor-critic loop against your own work. The actor designs and runs scenarios; the critic tries to break the actor's claim. You hold both roles. You do not stop until the critic cannot find a hole.
- Happy path
- Boundary — empty, null, max length, zero, negative, single-char, unicode, whitespace-only, duplicates
- Error/invalid — malformed payloads, wrong types, missing fields, bad enums, wrong casing, expired tokens, wrong tenant
- Adversarial — race (rapid double-click, two tabs, parallel POSTs), idempotency, replay, partial failure, network drop mid-action
- Authorization — wrong tenant, wrong role, missing auth, cross-org leakage
- Regression on adjacent surfaces — pages/endpoints not in the diff but consuming the changed data (Impact Path map). Backend-only diffs still require UI regression on every consumer
- State transitions — every status flow forward and any allowed backward
- Persistence + observability — DB row, audit/log row, embedding/index, cache invalidation, downstream cross-repo state
- Cleanup/idempotency — re-run same action: duplicates, fail safe, no-op?
- Cross-repo contract — every API contract change requires the consumer repo's UI/service exercised live
Build the matrix BEFORE running anything. Print it. Then execute every cell.
REPEAT: iteration += 1
ACTOR
- Build/refresh matrix from
against AC list + code diff - Execute every untested cell live (UI + API + DB + logs). Record evidence per cell
CRITIC (adversarial — actively try to falsify)
- For every PASS:
- "What input would break this that I haven't tried?"
- "What adjacent code path could this have broken?" (grep callers of changed symbols)
- "Did I verify ALL side-effects, or only the visible one?"
- "Reproducible if I re-run with the DB in a different state?"
- "Did I check the OPPOSITE assertion?" (e.g., delete → did count actually decrease, not just not-increase?)
- "Could this be a false positive from cached state, stale bundle, or wrong branch?"
- For every FAIL:
- Root-cause to file:line. No "intermittent" without a reproducer
- Fix it (see
)
- For every SKIPPED:
- Find a way to test, OR document EXACT infra blocker with one-line reproducer
EXIT (only when ALL true)
(a) every cell PASS with live evidence
(b) critic produces zero new findings on the last pass
(c) all bugs found are fixed AND full matrix re-run post-fix per
Loop guard
- Hard cap: 6 iterations. Then escalate to user with matrix + remaining findings. Do not silently give up.
Critic phase is NOT optional. If the report doesn't name at least one critic-pass attempt per AC ("I tried X to falsify, it held"), the run is incomplete.
</actor_critic_loop>
<re_run_after_fix>
**Every fix triggers a full matrix re-run, not just the failing scenario.** Most-violated rule in practice.
Why: a fix in one place commonly regresses an adjacent scenario (cache invalidation, ORM session reuse, callback wiring, FK cascade).
Procedure on every fix:
1. Smallest possible fix in the worktree
2. Rebuild artifacts (`npm run build` for React, restart Python servers, re-md5 the bundle)
3. Re-run the specific failing scenario → must now PASS
4. **Re-run the FULL matrix** for the affected feature AND every adjacent feature in the Impact Path. Not a spot-check.
5. If any previously-PASS cell now FAILs → regression introduced by the fix. Loop back to actor-critic. The new failure is now the lead bug.
6. Only when full matrix is green AND critic finds nothing new may you mark Phase 3 done.
Forbidden shortcuts: "I only changed one line", "unit tests pass so re-running E2E isn't needed", "the adjacent page wasn't in the diff". Re-run anyway.
</re_run_after_fix>
## ⛔ qbcheck Filter on MUST FIX (REQUIRED before report)
Critic-phase findings flagged as MUST FIX go through `/qbcheck` before they're written into the final report. Memory `feedback_qbcheck_before_fixing` and `feedback_actor_critic_review` document a 30–50% false-positive rate on automated bug findings — usually because the reviewer didn't trace actual execution or applied a guideline out of context.
For each MUST FIX finding from the critic phase:
1. Pass the finding text + file:line + reasoning to `/qbcheck`.
2. **False positive** → demote to NOTE with a `qbcheck: filtered` tag (don't drop silently).
3. **Real** → keep as MUST FIX.
4. **Uncertain** → keep as MUST FIX with `qbcheck: uncertain` tag.
Skip qbcheck on SHOULD FIX / NOTE — false-positive cost there is low.
## Banned-Phrase Self-Check (run on draft report, before posting)
Before writing the final report, grep your own draft. Loop back if any of these match:
```bash
echo "$DRAFT_REPORT" | grep -nE 'skipped for time|out of scope of this run|probably works|likely fine|looks fine|deferred|should work|seems fine'
Any hit = you have not satisfied the live-evidence rule for that scenario. Replace the banned phrase with concrete evidence (or a documented infra blocker), then re-grep until clean.
Pre-Report Critic Checklist
Write the answers to these in your output (not silently). If any answer is "no/partial", loop back.
- Every AC in matrix with live PASS evidence (not Jest-only, not source-proof)?
- For every PASS, at least one falsification attempt (boundary/adversarial/regression)?
- For every code change in the diff, did I exercise the corresponding live surface?
- For every fix applied, did I re-run the FULL matrix afterward?
- Did I check
/tmp/{{COMPANY_SLUG}}-service.log,/tmp/{{COMPANY_SLUG}}-worker.logAND browser console after each scenario? - Did I verify cross-repo persistence for every contract change (the written row matches what the consuming repo reads back)?
- Are there any "looks fine" / "probably works" claims left? (Run the banned-phrase grep above.)
- If a scenario is BLOCKED, is the blocker concrete infra (with reproducer) or a time/effort excuse?
- Did I write
phase3-evidence.md,playwright-results.json, andtrace.zipto{{STATE_ROOT}}/worktrees/<TICKET>/? (UI Evidence Artifact Contract above.) - Did I run MUST FIX findings through
/qbcheck?
Testing Workflow
- Resolve
CODEBASE_ROOT(Path Resolution above). - Spinup —
/qspinuplocal local_acme_corp_db --worker. (Includes services, migrations, health check.) - Map ACs — fetch the Jira ticket's acceptance criteria, build the test matrix per
<scenario_taxonomy>. Every AC → at least one row. - Discover — read-only DB queries to check current state of test data.
- Setup — seed/modify test data per test-data-strategy.md (auto-approved on local DB).
- API tests — curl/httpx every affected endpoint.
- UI tests — full Playwright run on every impacted page per mandatory-ui-regression.md.
- State verification — for each action, the FULL chain must hold:
- UI shows change (snapshot/screenshot)
- API reflects change (curl confirms response data)
- DB confirms change (SELECT confirms row state)
- Console clean (no JS errors)
- Server logs clean (no 404, 500, timeout) Any link fails → test fails.
- On bug fix —
<re_run_after_fix>procedure. - Critic loop —
<actor_critic_loop>to convergence. - qbcheck MUST FIX filter.
- Self-check — banned-phrase grep + Pre-Report Critic Checklist.
- Write evidence files — phase3-evidence.md + playwright-results.json + trace.zip.
- Report — clear PASS/FAIL with evidence per AC.
AC Test Matrix shape
| AC # | Acceptance Criterion | API Test | UI Test | DB Verification | Critic Attempt | Result |
|------|---------------------|----------|---------|-----------------|----------------|--------|
| AC1 | "User can create..." | POST /api/v1/... | Click Create → Fill → Save | SELECT ... | "I tried duplicate name; system rejected." | PASS |
| AC2 | "Badge shows count" | GET .../counts | Snapshot nav badges | SELECT COUNT(*) | "I deleted one; count decreased." | PASS |
Every row PASS or FAIL by the end. UNTESTED = run incomplete.
Reference Files (load on demand)
| Reference | When to read |
|---|---|
| Mandatory UI Regression Testing | Always (Step 7 of workflow). Impact-path tracing, deep CRUD matrix, modal lifecycle, backend pipeline tests, API response format checks, anti-skip rules. |
| Test Data Strategy | When seeding / modifying test data. DB helpers, credential loading, staging-fallback procedure, Example Tenant constants. |
| Epic Integration Testing | Multi-ticket / multi-branch / cross-repo epic. Integration branch creation, migration order, services from worktrees. |
| Video Evidence + Bug Investigation | When the AC needs a video walkthrough, or when a bug is found. Bug origin classification, root cause, similar-pattern hunt. |
Test Result Format
## Test: [Feature/Scenario Name]
### Setup
- [Test data created or found]
### Execution
- [Steps performed via API or UI]
### Verification
- Expected: [what should happen]
- Actual: [what happened]
- DB state: [query results confirming state]
- Critic attempt: [What I tried to falsify; held? broken?]
### Result: PASS / FAIL
### Notes
- [Observations]
Cherry-pick fixes after testing
If you fixed bugs while running this skill, do NOT cherry-pick from inside qmanualt. Hand off to /qpr — that skill owns commit/push/PR mechanics and coordinates the cherry-pick into each affected feature branch with permission prompts.