# Test runs and the QA record (https://docs.vitrinka.ai/qa/runs)



## The QA plan [#plan]

A feature's test plan is a `qa` task under its epic, and each user path under it is a `journey` task. A journey's **flow** is a list of steps. Each step has a kind — `act`, `verify` or `wait` — and says what to do; optionally where (`at`), what proves it (`see`), a locator, and `next`. `next` points at another journey, or at one of its steps (`<journey-key>#<step>`): that is how a flow branches or loops back. The checklist a tester ticks is derived from the flow, so nobody keeps two lists.

The server draws the plan as one living diagram, top-down: a lane per role, the journeys of a role side by side, each journey's steps left to right, and only the edges `next` and `blocks` links make. Past a size cap, journeys collapse back to single nodes so a large plan still renders.

* **Edit it in place** — click a step node and the journey opens with that step focused in the flow editor (kind · do · at · see · next). Agents change the same thing with `edit_journey_node`, or `update {kind: "task"}` on the journey's fields.
* **The plan is its board** — a `qa` task's panel shows its board read-only, fitted to the plan; `vitrinka task qa-board <id>` creates or returns that board, a Plan section plus one section per journey.
* **Every plan in one place** — the project's **Record › QA** lists every plan with its verdict, its coverage (journeys passed over journeys), its journey count and whether an agent is walking it right now, above the sessions, runs and bug reports that fed it.

## Vitrinka reads, your runner runs [#law]

Teams keep Playwright, Appium, Selenium, Cypress, Maestro or their own harness untouched. Vitrinka reads what a run leaves behind and never starts a browser or drives a device; a runner integration is a file format, not a plugin, and nothing in your suite has to change to use it.

## One command [#run]

```bash
vitrinka qa run -- bun x playwright test
vitrinka qa run --task 412 --pr acme/shop#42 -- bun x wdio run wdio.conf.ts
```

`vitrinka qa run -- <test command>` runs your command verbatim, with a few variables any reporter may honour: `VITRINKA_RUN_ID`, `VITRINKA_RUN_DIR`, `VITRINKA_RUN_STARTED_AT` and `VITRINKA_SHOTS=always`. When it exits, the CLI finds what the run left behind — the repository's declared runners first (`vitrinka setup --runners` writes them into `vitrinka.config.json`), else the newest results written since the start — folds them into one run manifest and publishes it. The test command's exit code is the CLI's exit code: a failed publish never hides a failing test, and the other way round.

The run lands on `--task` (a qa task, a journey, a story or an epic), else on the feature of the checkout's `vt-<id>` branch, else on the project's rolling **Unplanned runs** epic. `--results <path>` skips the search; `--dry-run` runs and folds, prints the manifest and publishes nothing.

## What it folds [#formats]

| Format          | What vitrinka reads                                                                                                                                                                                                                                  |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| JUnit XML       | The JUnit reporter of Playwright, Jest, wdio, Go and most others — alone it covers Selenium, Cypress, Maestro, Detox, TestCafe and a home-grown harness. Screenshots ride as `[[ATTACHMENT\|…]]` lines or as files written under `VITRINKA_RUN_DIR`. |
| Playwright JSON | The JSON reporter's file, with its attachments.                                                                                                                                                                                                      |
| allure          | An `allure-results` directory.                                                                                                                                                                                                                       |
| wdio            | The `@wdio/json-reporter` files.                                                                                                                                                                                                                     |
| Run manifest    | A v1 manifest a runner, or `vitrinka qa usertest`, already wrote.                                                                                                                                                                                    |

## The run manifest [#manifest]

Whatever the format, a run becomes one v1 manifest. A runner can also write it itself and publish it with `vitrinka qa run publish <manifest.json>`:

```json
{ "version": 1, "runId": "20260909-093000-ab12", "runner": "playwright", "app": "web",
  "branch": "vt-410-checkout", "sha": "7a3da39", "pr": "acme/shop#42",
  "specs": [ { "key": "checkout", "file": "e2e/checkout.spec.ts", "title": "Checkout",
    "cases": [ { "title": "pays with a saved card", "verdict": "fail", "ms": 4210,
                 "shots": ["shots/checkout-1.png"], "error": "Expected total 42.00" } ] } ] }
```

A case's verdict is `pass`, `fail` or `skip`. Shot paths are relative to the manifest's directory, and a manifest holds at most 200 specs and 500 cases per spec; a case carries any number of shots.

## What lands on the record [#record]

One door, `POST /api/v1/tasks/{id}/run` (MCP `publish_run`), turns the manifest into the feature's QA record — no publisher agent, no hand-written comments.

* **The plan exists by construction** — without a `qa` task, one is created under the feature's epic with a journey per spec. Journeys match by key, then by title; new ones are appended.
* **Verdicts fold per journey** — every case passed is `pass`, any failure is `fail`, passes with skips are `partial`, all skipped or no cases is `untested`. The plan's coverage rolls up from its journeys.
* **Evidence on the qa board** — each journey's section carries a run callout, the cases as a checklist and a media card per screenshot. Publishing the same run again replaces its cards; a new run appends beneath.
* **Refs where they belong** — the run on the qa task and the epic, the board section and the shots on each journey, the log on the qa task, the pull request on the epic.
* **A bug per failing case** — `--bugs intake` (the default) files a bug draft through [intake](/pm/intake) that blocks its journey; `direct` files a live bug under the journey's story or epic; `none` files nothing.

## Exploratory testing as a run [#usertest]

Testing by hand produces the same record. A person, or an agent running `/vitrinka:usertest` ([capture skills](/dev/skills/capture#usertest)), opens a session, opens a case per thing tried, snaps screens into it and closes it with a verdict; `finish` folds the session into a manifest — one journey per case — and publishes it through the same door.

```bash
vitrinka qa usertest start --task 412 --platform web
vitrinka qa usertest case "Pays with a saved card"
vitrinka board capture web --file cart.png --viewport 1440x900 --route /cart --note "Coupon applied"
vitrinka qa usertest verdict fail --note "Total ignores the coupon"
vitrinka qa usertest finish
```

Every `vitrinka board capture` between `case` and `verdict` attaches to the open case, and a failing case's note becomes the first line of its bug draft.
