---
name: yad-test-cases
description: 'Shape step 9 of the gated SDLC — a PARALLEL, non-blocking track. Opens when the stories gate passes (the epic is already ready-for-build, so Build can start at the same time) and runs alongside implementation. With the test architect, author test-cases.md for the approved stories, and — when a testing tool is connected — generate/link the actual automation tests in it; otherwise produce the test-case artifact only. Reads epic + architecture + contract + UI + stories as input. Never auto-advances — hands off to the team review gate. Use when the user says "author the test cases" or after the stories gate passes.'
---

# SDLC — Author Test Cases (Shape step 9 — parallel, non-blocking)

**Goal:** Produce a human-authored, AI-assisted `test-cases.md` for an approved epic — the risk-based
test cases that cover the stories' acceptance criteria — **and**, when a testing tool is connected, the
**actual automation tests** inside the connected code repo(s), linked back from the artifact. This is a
**Shape step**: human-authored with AI assist, **never auto-advances**. When the test cases are
drafted, control passes to `yad-review-gate` (base rule: 1 distinct approver, who should not be the author).

**This step does NOT block Build.** It opens when the **stories** gate passes — at which point
the epic is already `ready-for-build`, so implementation (`yad-spec` → `yad-implement` → …) can start
**at the same time** the tester works here. The test-cases track is driven by its own step `status`
(it opens to `in_progress` when `stories-review` passes) and its review **never moves `currentStep`
away from `ready-for-build`**, so the two run in parallel.

Test work is shaped by the **test architect** lens, which designs the cases and writes the automation. The
automation is materialized in the **testing tool connected via `yad-connect-testing`**
(`.sdlc/testing.json`), reached through its MCP. When a tool is connected the lens **generates** tests
into it (or **links** an existing suite and reads it back); when none is connected, the step degrades to
the Markdown artifact only — the testing tool is additive, exactly like the design tool.

## When a tool is missing

<!-- Written from yadflow's cli/toolbox.mjs (E87). Change the words there: a test holds this section to it. -->

No tool below is required. When one is missing, this skill still finishes, using the fallback written
beside it.

Tell the person which tools you used and which fallbacks, and why.

- **Playwright MCP** (`playwright`, a connector). Used when `.sdlc/testing.json` connects it. yad-connect-testing writes that file; follow it, not yad's toolbox answer.
  - Fallback — artifacts-only: yad-test-cases writes test-cases.md only.
- **Cypress** (`cypress`, a connector). Used when `.sdlc/testing.json` connects it. yad-connect-testing writes that file; follow it, not yad's toolbox answer.
  - Fallback — artifacts-only: yad-test-cases writes test-cases.md only.
- **pytest** (`pytest`, a connector). Used when `.sdlc/testing.json` connects it. yad-connect-testing writes that file; follow it, not yad's toolbox answer.
  - Fallback — artifacts-only: yad-test-cases writes test-cases.md only.
- **Maestro** (`maestro`, a connector). Used when `.sdlc/testing.json` connects it. yad-connect-testing writes that file; follow it, not yad's toolbox answer.
  - Fallback — artifacts-only: yad-test-cases writes test-cases.md only.

<!-- end: When a tool is missing -->

## Conventions

- `{project-root}` resolves from the project working directory.
- Artifacts live under `{project-root}/epics/EP-<slug>/` (build plan §6).
- The connected testing tool is recorded in `{project-root}/.sdlc/testing.json` (`config.yaml`
  `testing`), written by `yad-connect-testing`. The per-epic case→test map is `test-links.json`
  (Step 4b).
- Speak in the `communication_language` set in `{project-root}/.sdlc/config.yaml`; write documents in `document_output_language`.

## On Activation

### Step 1 — Resolve the epic and check the track
Resolve the `EP-<slug>` (ask if not provided).

**Precondition gate (rail):** run `yad next EP-<slug> --check test-cases` first. If it exits non-zero,
**STOP** — surface the blocker it prints and point the user at `yad next EP-<slug>`. Do not author until
it passes. The check is track-aware: it keys off the `test-cases` step's predecessors, **not**
`currentStep` (this is a parallel track — `currentStep` stays at `ready-for-build`).

This passes once the `test-cases` step is runnable — its predecessor `stories-review` is `done` (so the
step has opened to `in_progress`) and `test-cases` is not already `done`. While it is still `todo` (or
`blocked`, in a file written before shape 7), the stories review has not passed.

### Step 1b — Open the authoring branch
Open the test-cases authoring branch `test-cases/EP-<slug>` per the shared procedure
(`../yad-epic/references/state-schema.md` → "Authoring branches"): git-safe (skip with a note if
`{project-root}` is not a git work tree), check out the branch if it exists, else create it from the
Product's default branch. Author `test-cases.md` on it, and end the step with `yad fold EP-<slug> test-cases` — the one commit the
step keeps (E44). This is **distinct** from the verified ledger's
`review/…` branch.

### Step 2 — Read inputs
Read `epic.md` (user-level acceptance signals, scope), `architecture.md` (flows, components by repo),
`contract.md` (the shared surface under test), `ui-design.md` (screens/states the tests exercise), and
**all** approved `stories/EP-<slug>-S0N.md`. Each story's **acceptance criteria are the source of truth**
for the cases — the test cases must collectively verify every story's criteria.

- **UI-optional-safe:** `ui-design.md` may be absent — the `ui-design` step is optional and can be
  marked N/A for a UI-less epic (state.json shows it `skipped`). When there is no UI design, there are
  simply no screens to exercise; the stories' acceptance criteria remain the source of truth. If it
  shows `deferred`, the screens are still to be designed: cover the stories' criteria now, and leave the
  screen-level cases for when the UI design lands.

### Step 2b — Load existing-code context (make the brain code-aware)
Read the registry `{project-root}/.sdlc/repos.json` (`config.yaml` `code_context`). For **each repo in
`epic.repos`**, load the code-map `{project-root}/.sdlc/code-context/<repo>/code-map.md` so the
automation **targets real existing endpoints/components and reuses existing fixtures** rather than
inventing parallel ones.

- **Greenfield-safe:** if `repos.json` is absent/empty, note "no repos connected" and proceed.
- **Staleness:** if a repo's current HEAD ≠ its registry `syncedHead`, warn and suggest
  `yad repo refresh <repo>` (a human decision — flag and stop, never auto-refresh); stamp
  `code-context: stale` in the frontmatter.
- **Traceability:** record the loaded maps in the `test-cases.md` `code-context:` frontmatter field.

### Step 3 — Author the test cases (assist: test architect)
Adopt the **test architect** lens. For each
story / user flow shape risk-based test cases:

- **Risk assessment** — categorize and score what can fail (probability × impact); depth scales with
  impact (risk-based testing).
- **Coverage plan** — assign each case a priority **P0 (critical) / P1 (high) / P2 (medium) / P3 (low)**
  and a level, preferring the lowest useful level (unit > integration > E2E).
- **Entry/exit criteria** and any **NFR** thresholds (security, performance, reliability) in scope.
- Each case: a stable label, the story it covers, preconditions, steps, and expected result.

### Step 3b — Materialize the automation in the connected tool (generate or link)
Read `{project-root}/.sdlc/testing.json` (`config.yaml` `testing.registry`). Decide the path:

- **No tool / `tool: "none"` / `source: "unavailable"`** (or the file is absent): **degrade** — author
  the Markdown artifact only and record `testing: none` in the frontmatter with a one-line note
  (mirrors the `design: none` degrade). Skip to Step 4.
- **A tool is connected and its MCP is available:** adopt the `test architect` lens and, using the
  provider recorded in `testing.json` (Playwright via a Playwright MCP, Cypress/pytest/Maestro via
  theirs — Maestro authors mobile flows where Playwright has no reach) write the
  automation:
  - **Generate** — when the provider is write-capable, author one automation test per high-priority
    (P0/P1) case into the connected code repo(s) for the repos in `epic.repos`, reusing the code-maps
    (Step 2b) and existing fixtures so tests target built endpoints/components, then run them via the MCP
    to confirm they execute.
  - **Link** — when the user points at an existing suite (or the provider is read-only), reference it and
    **read the tests back** so `test-cases.md` reflects the real suite.

  Capture each test's `name`, `repo`, `level`, `path`, and `url`. Record which direction was used
  (`generated | linked`). Honest-capability rule: a read-only MCP supports **link** only — never claim a
  test was generated that the provider cannot produce. See
  `../yad-connect-testing/references/testing-context.md`.

### Step 4 — Write the test-cases artifact
Write `{project-root}/epics/EP-<slug>/test-cases.md` using EXACTLY this template:

```markdown
---
id: EP-<slug>
epic: EP-<slug>
artifact: test-cases
status: draft
owner: <inherit from epic.md owner>   # the epic owner carries through; not retyped
repos: [<inherit from epic>]
code-context: { repos: [], loaded: <YYYY-MM-DD or none> }   # code-maps that informed the tests (Step 2b)
testing: <none | { tool: <playwright|cypress|pytest|maestro|…>, direction: <generated|linked>, suite: <url/path>, tests: <N> }>   # the connected testing tool (Step 3b)
---

## Test strategy & risk
<!-- risk assessment (category, probability, impact, score), entry/exit criteria, NFR thresholds in scope -->

## Test cases
<!-- one row/subsection per case: id, story covered, priority (P0–P3), level, preconditions, steps, expected -->

## Coverage & traceability
<!-- story -> case map; confirm every story's acceptance criteria are covered -->

## Automation (<tool>)
<!-- omit this section when testing: none. one row per automation test, linking to it in the repo.
     mirrors test-links.json (Step 4b). -->
<!-- - <Case id> -> <repo>:<test path> (<level>) — <url> -->
```

### Step 4b — Write the test-links map (when a tool was used)
When Step 3b generated or linked automation, write the machine-readable case→test map to
`{project-root}/epics/EP-<slug>/.sdlc/test-links.json` (sibling of `contract-lock.json` /
`design-links.json`):

```json
{
  "tool": "playwright",
  "suite": "tests/playwright.config.ts",
  "generatedAt": "<YYYY-MM-DD>",
  "direction": "generated | linked",
  "tests": [
    { "case": "TC-01", "story": "EP-<slug>-S01", "repo": "backend", "level": "e2e",
      "path": "tests/order.spec.ts", "url": "<repo url to the test>" }
  ],
  "source": "playwright-mcp"
}
```

Keep the `## Automation (<tool>)` section of `test-cases.md` in step with this file. When Step 3b
degraded (`testing: none`), do **not** write `test-links.json`.

### Step 5 — Advance the authoring step (NOT the gate)
**Check the mode first — the two modes have opposite instructions here.** Read `.sdlc/product.json` (`.sdlc/hub.json` on an older Product that has only that name):
**verified mode** is `platform` set AND `ledger: "verified"` — or, on a project that has not run `yad migrate` yet, `bridge_enabled` (or legacy `bridge`) `true`. `ledger` wins whenever it is present.

**verified mode — do NOT write `state.json`.** The ledger is CI-owned: the `ledger-guard` check rejects
any non-bot commit touching `epics/*/.sdlc/{state,approvals,comments,product-prs,hub-prs}.json` or
`epics/*/reviews/*.md`, `yad gate open` deliberately skips this write for the same reason, and
`yad gate ci --merged` performs the whole transition when the review PR merges. Making the edit here
fails the gate if it rides the review PR, and desynchronises the ledger CI is about to rewrite if it
is pushed around the gate. Commit the artifact set — **`test-cases.md` and, when a testing
tool was used, `.sdlc/test-links.json`** (artifact-side, not ledger; `yad fold EP-<slug> test-cases` takes exactly these; generated tests live in their
own code repo, not here) — then hand off to `yad-review-gate`.

**Otherwise — local, or a platform with no gate-sync CI — the engine makes this edit, not you.**
`yad gate open <epic> test-cases.md` marks `test-cases-review` `in_review`, closes `test-cases` as `done`, and **leaves `currentStep` at `ready-for-build`** — this is the parallel track, and moving it would pull the epic back from Build
— the same transition, from the one function that owns it (`markInReview`, `cli/epic-state.mjs`).
`yad-review-gate action: open` runs that command; hand off to it rather than editing the ledger here.

**With no platform configured** it writes the ledger and simply opens no PR, so this works offline.
**With a platform** the `review/EP-<slug>/test-cases` branch must already be **on origin** — the command
refuses and writes nothing otherwise. Cut it from the authoring branch and push it before handing off
(or run `yad open-pr` from that branch: it pushes the branch, then delegates).

Do **not** hand-edit `state.json`, and do **not** touch `approvals.json` — only real reviewers approve,
through the gate.

### Step 6 — Stop at the gate (do NOT advance)
Report: the path to `test-cases.md`, the connected testing tool and what it produced (e.g. "Playwright —
6 tests generated", the suite path + `test-links.json` path, or "no testing tool — artifacts-only"), that
Build may already be underway in parallel, and that the next action is **review** via
`yad-review-gate` (base rule: 1 distinct approver, who should not be the author). **Never record approval here.** Shape steps do not
auto-advance. When the Product has a platform, the gate opens a review
PR on the Product (via `yad-product-bridge`) and `yad-review-gate action: sync` pulls platform approvals/comments
into the ledger; otherwise the review is recorded local.

## Reference
- Test-cases frontmatter, body template, and the `test-links.json` schema: `references/test-cases-schema.md`.
- State schema and field meanings: `../yad-epic/references/state-schema.md`.
- Connecting a testing tool (Playwright, pluggable) + generate-vs-link recipes and the degrade path:
  `../yad-connect-testing/SKILL.md` (+ `references/testing-context.md`).
- Connecting code repos + the code-context the brain reads: `../yad-connect-repos/SKILL.md`.
