Guides
Writing cases
The ID grammar #
<FEATURE>-<SUBFEATURE>-<NNN>, e.g. AUTH-LOGIN-003, CHECKOUT-QTY-011.
Feature and subfeature are uppercase, alphanumeric, stable. The number is zero-padded to three digits and never reused: a deleted case's number retires with it, so an ID in an old task or run report always means the same thing.
Priorities #
| Meaning | Consequence | |
|---|---|---|
| P0 | The feature is broken without it. Core path, security, data integrity. | Any P0 that is not an approved pass makes the feature display "Not Ready" regardless of score. QA watches 100% of P0 evidence. |
| P1 | Important, but the feature is usable if it regresses. | QA samples >=30% of P1 passes. |
| P2 | Polish, rare paths, cosmetic states. | Approved in bulk at matrix level; QA samples >=10%. |
Be honest about P0: everything P0 makes the readiness gate meaningless, nothing P0 makes it toothless.
The scenario mix #
Every feature declares all five slots. Each is covered by at least one case or marked n_a with a reason a reviewer would accept (one that names why, not merely asserts it).
| Slot | What it is | Typical miss |
|---|---|---|
| happy | The intended path, end to end. | Testing only this. |
| negative | The user does something wrong, or the operation legitimately fails. | Asserting the error appears but not that the success state is absent. |
| boundary | Limits: zero, one, maximum, one past maximum, empty, longest allowed. | Testing the middle of the range. |
| permission | A different role, tenant or ownership relationship. | Assuming your own role is the only one. Needs a role account. |
| data-validation | Field-level rules: format, required, length, type. | Marking n_a when the form has free-text input. |
Writing an expected outcome #
An expected outcome is a claim about observable state that a Playwright assertion can check: what a person would see, precisely enough that two developers would write the same assertion.
| Rejected | Why | Write instead |
|---|---|---|
| "Order is placed successfully" | Names no observable thing. | "The order confirmation panel shows an order number." |
| "Page looks correct" | Nothing to assert. | "The heading reads 'Order complete' and the cart badge shows 0." |
| "The POST /orders call returns 201" | Invisible on engine-dispatched hosts; the wait no-ops and the test passes regardless. | "The order row appears at the top of the order history table." |
| "Error is shown" | Which error, where? | "An alert with text 'Your cart is empty' is visible." |
| "The button works" | An action, not an outcome. | "After clicking, the button shows a spinner and becomes disabled." |
Assert absence too. A negative case that only checks the error can pass while the action also went through. Pair "the error is visible" with "the confirmation panel is not rendered".
Steps #
Steps are what the executor does, in order, in the UI, at the level of user intent: "Set the quantity field to 11", not "click the input, select all, type 11". The spec converter fills in the mechanics; over-specified steps age badly and constrain selector strategy.
Preconditions carry state the steps assume: who is signed in, what data exists, which role. Several developers share one deployed environment, so cases must not assume they are alone in it.
Cases that write #
Decide mutation.policy from what the cases do, not where they will run (policies and fixtures: see the cases schema).
| The cases | Policy | Then |
|---|---|---|
| only read, list, open or filter | read-only |
no step may save, delete, submit or upload |
| create and change their own entities | scoped-write with a prefix |
every step naming a new entity starts the name with the prefix: "Create a project named QA_TEST_<run>-alpha" |
| touch shared data on purpose | unrestricted |
never runnable on production |
When many cases work inside one entity, declare it once under fixtures and keep preconditions honest: "Inside the shared project fixture", not a restatement of how it was made.
Linking cases to acceptance criteria #
Declare stated acceptance criteria once under requirements; each case names what it tests with covers (id and quoting rules: see the cases schema).
requirements:
- id: ORDER-1
title: A customer can place an order
criteria:
- id: AC-1
text: Placing an order with one item shows an order number
- id: AC-2
text: An order with an empty cart cannot be placed
cases:
- id: ORDER-PLACE-001
covers: [ORDER-1/AC-1]- One claim per entry: a requirement id, a slash, a criterion id.
- List only what the case's outcomes would actually catch: a criterion marked covered by a case that does not assert it is a false green.
- An uncovered criterion is a warning; QA decides whether to add the case or accept the gap.
- No stated criteria, no keys.
What not to generate #
- Volume. Twenty shallow cases are worse than eight sharp ones: they cost QA review time and dilute the confidence score.
- Cases that restate the same assertion with different inputs, unless the inputs are boundaries.
- Cases for behavior the code does not have. If the task describes something the code does not do yet, that is a question for the user, not a test that fails forever.
- Anything you could not explain to QA in one sentence. If it needs a paragraph, it is probably two cases or none.