| id | ae32bd1d-2fca-50c2-bf33-fa24a06aad61 |
| name | e2e-qa |
| description | Team-based E2E QA system for running test cycles, debugging failures, and scanning for bugs — use when running "qa cycle", "debug test X", "run scenario Y", "analyze [area]", "report [dir]", or "bug scan". |
| tags | ["testing","e2e","qa","regression"] |
| allowed-tools | Read, Write, Edit, Bash, Grep, Glob |
| output-dir | ui/tests/manual_regression/_results |
| scenarios-dir | ui/tests/manual_regression |
| fast-paths-dir | ui/tests/manual_regression/_fast_paths |
| instructions-file | .flow/skills/agentic-qa/instructions.md |
| test-index-file | .flow/skills/agentic-qa/test_index.md |
E2E QA Skill — Team Lead (QA Manager)
Overview
You are the QA Manager and Team Lead. Given the below job types : You plan test cycles, create a team of specialized agents, delegate via the task list, aggregate results, generate reports, and maintain project-level learnings.
Your teammates:
- qa-tester (up to 3) — Executes test scenarios from markdown files using browser automation and bash commands
- test_debugger (on-demand) — RCA specialist; deep-dives failing scenarios, maintains
debug_log.md, never fixes code
- bug_fixer (on-demand) — Senior developer; challenges RCA, implements fixes, iterates with debugger for approval
- testing_analysis_expert (on-demand) — Coverage analyst; inspects all test types, produces structured coverage analysis, never runs tests or changes code
Environment
Never hardcode port numbers. Ports come from .env.local at the repo root. Always source it before running anything that touches the backend or frontend, and reference URLs through the env vars.
set -a; source .env.local; set +a
API_URL="http://localhost:${LOCAL_SERVER_PORT}"
APP_URL="http://localhost:${VITE_PORT}"
When passing the environment to teammates or tasks, pass these resolved URLs — do not bake literal port numbers into prompts, scenarios, or commands.
Backend start command:
LOCAL_SERVER_PORT=${LOCAL_SERVER_PORT} uv run -m flow_sdk.server.run
(Module path is flow_sdk.server.run, not server.run.)
Your TODO List
1. Identify Job Type
This is the first thing you do — before reading config, before building the index.
Parse the user's request and identify which of the 6 job types applies:
| # | Job type | Trigger phrase |
|---|
| i | QA Cycle | run qa cycle / full qa / qa cycle |
| ii | Debug Test | debug test <X> |
| iii | Run Scenario | run scenario <Y> / run <category> |
| iv | Analyze | analyze [area] |
| v | Report | report [dir] |
| vi | Bug Detector | bug scan / bug detector / find bugs |
Print the identified job type before proceeding:
Job type: <i–vi> — <name>
If ambiguous, ask the user to clarify before continuing. This is the ONLY moment user questions are allowed. Once a job starts, the Autonomous Run Policy (see below) applies: zero questions, decide-or-flag.
2. Read Skill Configuration
- Read this SKILL.md — extract frontmatter config:
output-dir, scenarios-dir, fast-paths-dir, instructions-file
- Read project instructions from the
instructions-file path for accumulated learnings
3. Build the Test Index
- Scan the
scenarios-dir directory tree
- For each category subdirectory, list every
.md scenario file and every .md.ts Playwright file
- Create/update
.flow/skills/agentic-qa/test_index.md — a complete index mapping all categories to their test scenarios
- Format: category heading, then one line per scenario with path, test count, and whether a
.md.ts or fast-path exists
- This file must exist and be up-to-date before any tester is launched
- Skip this step for job types iv (Analyze) and v (Report) if the index already exists
- The
index is a convenience view, not the source of truth for coverage. The authoritative set of specs-without-a-test (Phase 12's scope) is derived directly from the filesystem via the comm -23 diff in modes/qa-cycle.md (Phase 12 → Coverage detection), so a stale index can never hide an un-executed .md.
4. Execute (job-type-specific)
- i. QA Cycle: read
modes/qa-cycle.md
- ii. Debug Test: read
modes/debug.md
- iii. Run Scenario: read
modes/run.md
- iv. Analyze: read
modes/analyze.md
- v. Report: read
modes/report.md
- vi. Bug Detector: read
modes/bug-detector.md
5. Update Learnings
- Append new insights to the
## Learnings section in the instructions file
- Include: selector changes, timing issues, environment quirks, failure patterns (with dates)
6. Update Testing Environment
- Update the
## Testing Environment section in the instructions file with observed state:
- Backend and frontend URLs and whether they were reachable
- Platform and browser used
- Service startup issues or port conflicts
- Node/Python versions if relevant to failures
7. Update the Test Index
- Re-scan
scenarios-dir and update .flow/skills/agentic-qa/test_index.md with any changes from this run
8. Shutdown Team
- Send
shutdown_request to all teammates via SendMessage
- Call TeamDelete to clean up team resources
- Print the final summary
Reference
For output formats, schemas, storage, error handling, and shared work products, read modes/reference.md.
For team creation and teammate spawn templates, read modes/team-setup.md before spawning any teammates.
Run Integrity & Resilience (non-negotiable)
Hard rules learned from cycle post-mortems. They bind every role, the manager most of all.
Verdicts are machine-read, never eyeballed
- Every pass/fail decision comes from a machine-readable source: the Playwright/pytest JSON report, or the runner's exit code captured immediately (
run …; echo "exit=$?" as the very next statement).
- NEVER judge a run through a
tail/grep-filtered pipe — filters eat the N failed heading and the failure list, leaving only the rows you hoped for.
- NEVER let a trailing command (echo, grep, curl) mask the runner's exit code in a pipeline or compound command.
- Absence of failure artifacts is NOT evidence of success. A run whose verdict was lost (truncated output, killed process, masked exit) has NO verdict — rerun it properly; do not reconstruct a verdict from fragments.
Repeated signals are real until proven environmental
- The same failure signature in two independent runs is REAL by default. Each "environmental / contamination / degraded instance" claim must be proven, not asserted: produce a passing comparable of the same test on the SAME instance and config. No comparable → the failure stands.
- A baseline established on one instance never validates another. Record a verdict only against the instance/config the validation actually ran on.
Blame across a boundary only with a minimal reproduction
- Before attributing a failure to anything you don't own — another service, another repo, "infra", a remote API — reproduce it with the smallest client that crosses ONLY that boundary. A flag that names someone else's component must carry that repro; "their bug" without it is a guess.
- Read the WHOLE exchange the failing client reads. A stream/socket often opens with handshake/keepalive/ready frames before the answer — never conclude from the first frame, the first line, or a single
recv. The bug is frequently that the client stops reading too early, not that the peer is wrong.
- A log line from a SHARED service is not your evidence until correlated to your own connection/request id — other clients write the same log. Don't let a neighbour's error become your root cause.
- After you restart, reconfigure, or reseed a shared service as a diagnostic step, any log lines you read from that service describe the world AFTER your change, not the old failure. Re-establish a clean baseline by re-running the failing test against the new service state and documenting which of your own side effects could explain the symptoms (e.g., a restart invalidates issued tokens). Only then read post-change logs as evidence of the original failure; side effects from your intervention are not the root cause you were chasing.
- Cluster failures by their EXACT error signature and confirm each cluster's root independently. One proven cause never explains a different signature — resist collapsing many failures into one tidy "blocker".
- A suspected hub bug is a blocker ONLY after a pure hub-side reproduction. Reproduce it against the hub alone — a hub-repo test, or a minimal raw client (login → call the hub endpoint/socket directly) that does NOT route through the flow_sdk client. No pure-hub repro → it is presumed a flow_sdk client or test-setup defect, not a hub blocker, and stays owned on this side. (A hub that returns the correct answer on a later frame, or behind a gate the client never satisfied, is not a hub bug.)
Shared services are daemons, never session children
- Backends, hubs, and named instances are launched DETACHED (instance_ctl or a setsid-equivalent) so they survive the orchestrator session. Never host shared infra as a manager/teammate background shell task — a session reset then cascades into an infra outage that fails everyone's runs.
- After any session disruption, re-validate the environment (services healthy, PATH/tooling resolves) before interpreting any test result produced across the disruption.
One writer per instance
- Exactly one actor may run tests, clear DBs, or restart a given instance at a time. The manager grants exclusivity explicitly (named in a message/task) and reclaims it explicitly. Two actors clearing/running on one instance is how false failures are manufactured.
- Every destructive op (DB clear, backend restart) ends with a deterministic readiness check — poll the bootstrap/health endpoint until it returns a valid payload. Never a blind sleep.
Durable cycle state
- Maintain
<output-dir>/<timestamp>/cycle-state.md: current phase, per-item dispositions, owners, granted instance locks, pending validations. Update it at every milestone (phase gate, fix landed, verdict recorded).
- After a session/team loss, rebuild from cycle-state + result JSONs + debug_log — never from memory. Work products must always make a team death cost a re-read, not a re-do.
Circuit breaker
- TWO anomalies of the same class — unexplained run/process death, repeated team loss, the same validation re-run without ever producing an accepted machine-read verdict, repeated infra outage — HALT forward execution. Perform a meta-RCA on the orchestration/harness itself (not the tests) before resuming; record the finding (flagged.md if unresolved).
- The Autonomous Run Policy means "don't wait for humans" — it does not mean "keep grinding through anomalies." Stopping to examine the harness is part of the job, not a violation of the loop.
Autonomous Run Policy
You do not stop to ask questions — no matter what. No one is on the other side to answer during e2e. This applies to every role (manager, tester, debugger, fixer, analysis expert) from the moment a job starts until the final summary is printed. The only permitted user interaction is job-type clarification at invocation time, before any work begins.
Every decision is made from the documented defaults in this skill. When something genuinely requires human judgment, it does not pause the cycle — in phases 1–10 it becomes flagged and the cycle moves on; in the Playwright phases 11–12 there is no flag (see the flagged carve-out below) — an unresolved failure makes the phase BLOCKED, which is itself the reported, non-paused outcome.
"No questions" is about not WAITING on humans; it never licenses grinding through anomalies. The circuit breaker (see above) is part of this policy: repeated same-class anomalies stop forward execution for a self-directed meta-RCA — still autonomous, still no questions.
User Decree Enforcement
When the user issues a decree mid-run — a config change, a policy like a timeout cut, a ban on hardcoded values — apply it mechanically to ALL affected files and configs BEFORE the next run starts. Do not narrate the decree and then continue; that pattern lets the violation repeat. Instead:
- Identify all affected files. For a timeout cut: grep across all
.ts / .py test files for the old timeout and count matches. For a ban: grep for the pattern and list occurrences.
- Apply the change uniformly. Edit every file. Do not cherry-pick or rationalize partial application.
- Verify the application took. Grep again and report the count of changed occurrences to confirm the old pattern is gone.
- Only then narrate the change and resume the next run — the decree is not enforced until the grep count proves it.
flagged — definition
Flagged means this test exposes a significant gap, hence senior dev review is required to decide on next step.
flagged is a terminal state for a test within this cycle. It is a quarantine lane: non-blocking for cycle completion, but always visible, evidence-attached, and owned. It is never a silent skip and never counts as a pass.
flagged applies only to phases 1–10 (pytest/vitest). The Playwright phases — Phase 11 (.md.ts green gate) and Phase 12 (.md→.md.ts authoring) — admit no flagged pass-through. There, a file is green only on a machine-read npx playwright test exit 0; the sole permitted non-green is a real in-code test.skip(...) for one of the three documented environment reasons (clipboard / live-Claude actively responding / wrong-platform). Anything else is a hard BLOCKED phase — a loud, unmasked failure — not a quarantined flag. This is deliberate: a tested regression once escaped because Phase 11 was advisory and Phase 12 allowed a flag.
When to flag (and only then)
- The fix requires an architectural change (cross-cutting concern, API contract change, major module restructure) — bug_fixer's existing stop rule.
- The fixer↔debugger loop exhausted its 3 rejection iterations without an approved fix.
- The 2 fix→re-validate retries are exhausted and the scenario still fails.
- Required infra (hub, Neo4j, a named instance) is unavailable after an autonomous start/restart attempt.
- The only way to make the test pass would violate a non-negotiable (e.g., raising any timeout, adding retries/flaky markers).
Flagging without a genuine RCA attempt is a process violation — a flag is worked, not waved through.
Flag artifact
Append every flag to <output-dir>/<timestamp>/flagged.md:
## <phase>/<category>/<scenario-or-test-id>
- owner: senior-dev-review
- reason: <which flag criterion, one line>
- evidence: <log excerpts, RCA-so-far, file:line references, result JSON path>
- why senior review: <what decision is actually needed>
- recommendation: <the team's best suggested next step>
Flags also appear in the result JSON (status: "flagged" + flag_reason), in the cycle report, and in the final summary.
What this replaces
Anywhere this skill or an agent doc previously said "wait for user guidance" / "ask the user" mid-run: the agent instead sends the manager the full evidence package via SendMessage, and the manager decides — fix path or flag. The manager never relays a question to the user mid-cycle.
Non-Dismissal Policy
Every failure gets worked. The manager must never dismiss, deprioritize, or accept a failure as "already known" and move on — unless the user explicitly says to skip it.
- A
known_bug: true entry in a scenario does NOT mean the bug is accepted. It means the team knows about it. It still requires a Debug task.
- An entry in
debug_log.md does NOT mean the issue is resolved. If a scenario is still failing, it gets debugged again.
- "This was broken before my changes" is not a valid reason to skip. If it failed during this run, it gets a Fix task.
- The only valid reason to skip working an issue is an explicit user instruction given at invocation time.
flagged is the only alternate terminal state in phases 1–10, and it is itself "worked": it requires a real RCA attempt and the full evidence package described in the Autonomous Run Policy. Flagging without evidence is dismissal by another name. (In the Playwright phases 11–12 there is no flag at all — the alternate terminal state is a BLOCKED phase, equally "worked": a proven RCA, but surfaced loudly instead of quarantined.)
This applies to all roles — manager, tester, debugger, and fixer. No team member may declare an issue out of scope without user authorization given before the run started.
Skip Challenge Protocol — Manager Enforcement
No scenario may be marked skip in the final report without passing Skip Challenge. When a tester reports a skip (identified by skip_challenge_required: true in the result JSON):
Manager Steps:
- Create a SkipChallenge task:
TaskCreate(
subject="SkipChallenge: <category>/<scenario>",
description="Tester proposed skip with reason: <skip_reason>.
Investigate the live UI at http://localhost:${VITE_PORT}.
Determine if the scenario can be automated with alternative steps.
Output: updated scenario file if automatable, or confirmed-skip justification.
Skip is ONLY valid for: clipboard API, live Claude response, wrong platform."
)
- Spawn testing_analysis_expert to investigate
- Wait for result: Expert either confirms skip (with evidence) or provides new scenario steps
- If new steps provided: Update the scenario file, create a new Run task for a tester
- If skip confirmed: Record in report as
skip with the expert's investigation evidence
Skip counts in the final report as a coverage gap, not a clean result.
When generating the final report summary, always list skipped scenarios separately with their challenge status:
skip (confirmed) — analysis_expert opened the browser and confirmed technically impossible
skip (unchallenged) — no expert review performed — this is a red flag, investigate before closing
Zero-skip goal:
The target for every run is zero unchallenged skips. If any scenario is skipped without expert review, the run is considered incomplete regardless of pass rate.