Test plans
A plan is a titled collection of case filters that a run is started from — the model Testomat.io’s test plans use, taken as it is. Each collection is one filter of the cases list, written the way the list’s URL writes it: inside a collection the dimensions narrow together, between collections the cases add up, and a collection with nothing set is every case.
A plan is a query, not a list of ids. Nothing is copied when it is saved: what it collects is resolved when it is read or launched, so a case written under the filter tomorrow is in tomorrow’s run without anybody editing the plan — and a case deleted today leaves every plan today.
The plan
Section titled “The plan”POST /v1/plans{ "title": "Release regression", "description": "Everything under checkout, plus every critical case wherever it lives.", "kind": "mixed", "collections": ["suiteId=6f1c2b3a-0000-4000-8000-0000000000a1", "priority=critical"]}| field | |
|---|---|
title | Unique within the project — a second plan of the same title is a 409. |
description | Optional, up to 16 384 bytes. |
kind | manual, automated or mixed — who executes it and which cases it may collect (below). |
asManual | manual only: take automated cases too, to be walked by a person. |
collections | One to twenty query strings. Each is read through the filter contract, so an unknown value of an enumerated dimension is refused with the filter’s own sentence (400 · unknown priority “urgent” — priorities are low, normal, important, high, critical), and written back canonical: two spellings of one filter are one string. |
GET /v1/plans lists the project’s plans, ordered by title, each with count — how many cases it
collects right now. GET /v1/plans/{id} is the plan itself; PATCH changes any field
(collections replaces the whole list); DELETE is for good — there is no bin for a plan, it is a
saved query, and the runs it started stay where they are.
What it collects
Section titled “What it collects”GET /v1/plans/{id}/casesThe union of the collections, in the order of the tree, each case once, live cases only — then the kind’s rule:
| kind | collects |
|---|---|
manual | manual cases; with asManual, automated ones too |
automated | automated cases |
mixed | manual and automated cases |
Evals belong to no plan: their run is plune eval. Every case comes with its externalKeys, which
is what a reporter greps by — see the road for CI below.
Launching a run
Section titled “Launching a run”POST /v1/plans/{id}/launch{ "environment": "staging", "labels": ["release"] }The body is optional; title, environment and labels say what the run is called and marked,
and the plan says everything else. The answer is 201 { "run": … } — a run of the plan’s kind,
started through the same road a reporter takes, with:
configuration.expected— every case the plan collects at this moment, each with its first external key, so a reporter that joins the run later matches by it;meta.planId— where the run came from;title— the plan’s, unless the body names another.
A plan that collects nothing is not launched: 409 · the plan matches no case — nothing to run.
The runs of one plan are GET /v1/runs?planId={id}.
On the dashboard
Section titled “On the dashboard”Plans in the sidebar is the list — the kind, the live count, and Edit · Launch run · Delete
behind each row’s menu. The form builds a collection with the same filter bar the cases screen has
(suite, tags, priority, type, state, search), one bar per collection, and shows Matched cases
under them as you edit. The plan’s page names each collection in words as a link into the cases
screen with that filter, lists the matched cases with their state, and the runs launched from it.
Launch run asks for a title (the plan’s, by default) and an environment, then opens the run —
for a manual or mixed plan, with the To run section a person marks the cases in
(Manual runs). On the Runs screen, New run ▾ › From a plan… leads
here, and Manual run… beside it starts a manual run without a plan.
The road for CI
Section titled “The road for CI”A plan is also a way to run a subset in CI without maintaining a list of test names by hand. Since
CLI 0.13.0, plune plan grep <id> prints the plan as one --grep pattern — the platform decides
which tests run, the runner runs only those:
npx playwright test --grep "$(plune plan grep $PLAN)"npx vitest run -t "$(plune plan grep $PLAN)"The command reads GET /v1/plans/{id}/cases with PLUNE_TOKEN (or the saved login) and builds
the pattern from each case’s path-title key — the title path with the file left out, each
segment escaped, [ >#]+ between segments so the same pattern fits Playwright, jest, mocha and
vitest, whichever way they spell the boundary. A case without a key goes in by title. Only the
pattern reaches stdout; the count of cases goes to stderr, so $(…) stays clean. An empty plan
prints (?!), a pattern that matches nothing, because an empty --grep would run everything.
Exit codes are those of plune pull: not logged in or a rejected token → 2, a plan that does not
exist → 1.
Without the CLI, the same answer is GET /v1/plans/{id}/cases — the cases with their external
keys, one join away from a filter.