Builds the application test plan from the estimate, data model, mockup and brief. Produces test_plan.json with cases traced to the estimate epics, exportable to Excel.
Installs into .claude/skills of the current project.
Are you the author of Test Plan?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/federico-anastasi-test-plan-dyla-ai)
---
name: test-plan
description: "Builds the application test plan from the estimate, data model, mockup and brief. Produces test_plan.json with cases traced to the estimate epics, exportable to Excel."
user-invocable: true
pack: delivery
---
# /test-plan — Test plan
The test plan is the list of cases the team runs before delivery. It does two jobs: it
verifies the application does what the brief promised, and it proves it to the client.
The material is all there already: the estimate says **what was built**, the mockup says
**how it is used**, the brief says **what should happen**.
---
**UX: chat stays conversational.** Never paste the test cases into chat: the user reads
them in the viewer. In chat you talk about coverage and areas, citing cases by id **and**
title (e.g. "TC12, the import with rejected rows").
## PROTOCOL
1. **Check dependencies** — a confirmed estimate is required
2. **Read** the estimate, mockup, data model, brief and context.md
3. **If the test plan exists:** ask whether to update or regenerate it
4. **Write test_plan.json straight away** with `meta.status: "draft"`
5. **Say in words** what is covered and what is not (BLOCKING)
6. **On confirmation:** `meta.status: "confirmed"`
7. **Update context.md**
---
## PHASE 1 — CHECK DEPENDENCIES
Read `projects/{project}/estimate.json`. If it does not exist, **stop**:
```
Writing the test plan needs the estimate: the epics it measures coverage against come
from there. Produce it first with /estimate.
```
If it exists but `meta.status` is `"draft"`, say the cases will need revisiting once the
estimate settles, then carry on: waiting gains nothing, most of the cases will not change.
---
## PHASE 2 — READING
Read ALL of these before you go on:
- `projects/{project}/estimate.json` — the epics and tasks: the skeleton of the coverage
- `projects/{project}/mockup.json` — if it exists: pages, fields, actions, dialogs
- `projects/{project}/data_model.json` — if it exists: the entities and constraints to verify
- the brief: `projects/{project}/brief.json`, or the input document (or its extract under
`.extracted/`)
- `projects/{project}/context.md` — decisions and assumptions
- `projects/{project}/test_plan.json` — if it exists
- `knowledge/` — if the folder exists: house test conventions, client requirements
If `test_plan.json` exists, ask in a ```questions block (format in
`.claude/prompts/questions_format.md`):
````
```questions
[
{
"id": 1,
"q": "The test plan already exists ({N} cases, {M} already run). Update it or regenerate it from scratch?",
"options": ["Update it — add missing cases, flag the ones to revisit, KEEP the outcomes", "Regenerate it from scratch — the recorded outcomes are lost"]
}
]
```
````
Updating is nearly always the right answer: the outcomes are the tester's work, and they
do not get thrown away for a regeneration.
---
## PHASE 3 — WRITE THE DRAFT test_plan.json (before you talk in chat)
**Living-documents architecture: this skill writes ONLY JSON.** The Excel export
(`test_plan.xlsx`) is generated by the backend on demand from `test_plan.json`.
Build the JSON following `schemas/test_plan.schema.json`. `meta` requires `project`,
`title` and `date` — `title` is easy to forget since chat never shows it, but the schema
rejects the document without it.
**Write it in pieces, not in one call.** A finished test plan runs to tens of thousands of
characters, and emitting all of it in a single tool call is where this breaks: observed on
a local model, the `Write` arrived with an EMPTY payload and the document was never
created at all. So: `Write` the skeleton first — `meta` (with `status: "draft"`) and an
empty list (`cases` has no `minItems`, so an empty list is a valid document) — then add
one epic's cases per `Edit`. Anchor `old_string` on the last case already written — its
unique `id` (e.g. `"id": "TC7"`) makes the match unambiguous — and insert the new cases
right after it. Every call stays small, the file is valid JSON throughout, and an
interruption costs the last item instead of the whole document.
### How many cases, and which
- **Every epic must have at least one case.** That is the rule that makes coverage
measurable, and the first thing anyone looks at in the "Coverage" sheet of the export
- As an order of magnitude, expect roughly 1-3 cases per estimate task, more where an
epic has approval branches or file imports (see below) — a 20-task estimate typically
lands 25-45 cases, not 6 and not 150
- One case per **complete user flow**, not per technical object: "submit a new request for
approval" is a case, "the request table" is not
- For every flow with an approval: one case for the happy path and one for the rejection.
They are different paths, not variants of one
- For every file import: one case with a valid file and one with rejected rows. The second
is the one that finds bugs
- `type`: `functional` by default; `integration` when the case crosses an external system;
`negative` for cases verifying that errors are handled; `regression` for what has to be
re-run at every release
### How a case is written
- `title`: what it verifies, in one line, from the user's point of view
- `epic` (and `task` when the case maps to exactly one): the link that makes coverage
measurable. Without it the case does not count
- `preconditions`: the state of the system and the data needed. A tester who did not build
the application has to be able to start from here. If nothing is needed, say so
explicitly rather than leaving it blank
- `steps`: numbered from 1 with no gaps (the backend checks). The `action` cites the
mockup page when one exists ("from the Requests list, open the detail and click
Approve"). `expected` is filled in when that step produces something verifiable; on pure
navigation steps it can be absent
- `expected_result`: the overall outcome — what the tester writes in the report
- `brief_ref`: the chapter that defines the behaviour being verified. This is what lets
you trace back to the requirement when a result is disputed
- `outcome`: always `"to_run"` on new cases. Outcomes are recorded by the tester in the
viewer
### When updating
- **Do not touch** `outcome`, `tester`, `run_at` or `notes` on existing cases
- Cases whose requirement changed: update the steps and flag them in chat, so the tester
knows they need re-running
- New cases take ids continuing after the last existing one
Check validity:
```
python -c "import json; from jsonschema import validate; validate(json.load(open(r'projects/{project}/test_plan.json', encoding='utf-8')), json.load(open(r'schemas/test_plan.schema.json', encoding='utf-8'))); print('valid')"
```
---
## PHASE 4 — CONVERSATIONAL SUMMARY (BLOCKING)
The file already exists and the user can see it in the viewer. In chat: how many cases,
how they spread across the epics, **which epics are uncovered or covered by a single
case** (the most useful thing you can say), and which areas needed the most negative cases
and why.
An example of the tone (not a fixed template):
```
34 cases, spread across all six epics. Most of them sit on the import (9 cases): half are
negative, because that is where the file arrives malformed and the rejected rows have to
be handled by hand.
Two epics have a single case: "Reporting" and "Notifications". For the first that is
fine, it is one view. For the second it is not — I think it needs at least the
failed-delivery cases. Want me to add them?
```
**STOP. Wait for confirmation or change requests.**
---
## PHASE 5 — CONFIRMATION
On confirmation: `meta.status: "confirmed"` in `test_plan.json` (`Edit` just that line — do not rewrite the file: it is tens of thousands of
characters, and regenerating all of them to change one word is both slow and a
chance to alter something by accident), re-validate. Confirm briefly in chat, mentioning that the Excel downloads from the
"download" button in the viewer and that outcomes are recorded in the Test plan tab.
---
## PHASE 6 — UPDATE context.md
`## Test plan` section: status, number of cases, coverage per epic, and the gaps that are
known and accepted. Update `## Deliverables`.
---
## RULES
- Every epic of the estimate has at least one case: if one is left uncovered, say so in
chat explicitly, do not bury it in the JSON
- One case = one complete user flow, not one technical object
- Steps numbered from 1 with no gaps
- Negative cases are not optional: imports, approvals and integrations always have them
- When updating, recorded outcomes are never touched
- Chat stays conversational: never the list of cases in the message