Skip to content

Steps and snippets

A manual case is a list of steps, and a project’s manual cases repeat the same steps a hundred times in slightly different words: Open the cart, open the Cart, Open the cart page. Two things on this page keep that vocabulary in one place, the way Testomat.io’s Steps Database and snippets do — the model taken as it is, the form Plune’s own.

Every action of a manual case is a row of the project’s Steps Database, written when the case is saved: each * line under ## Steps — each steps[].action on the API — becomes one row, one per exact text (trimmed; inside, verbatim, so Open the cart with two spaces is another step). The row counts the live cases carrying it: a deleted case drops out of the count and a restored one comes back. A case that is not manual contributes nothing.

GET /v1/settings/steps
GET /v1/settings/steps?q=open
{
"steps": [
{ "id": "", "step": "Open the cart", "description": "From the shop's header.", "count": 12 },
{ "id": "", "step": "open the Cart", "count": 1 },
{ "id": "", "step": "Pay by card", "count": 3 }
]
}

Ordered case-insensitively by text, so the duplicates sit next to each other; q narrows to steps that start with it, case-insensitively — the way the case form’s suggestions ask.

On the dashboard the action field of every step offers the Steps Database as you type — the browser’s own completion list, the step’s note as its label. Pick one and the case carries that exact text, which is what keeps the vocabulary from drifting; write a new one and it joins the database on save.

PATCH /v1/settings/steps/{id}
{ "step": "Open the cart page" }

The action is rewritten in every live case that carries the step, in one transaction. Each of those cases gets a new version — the step is its content, not its metadata — and its own audit entry with the reason (step renamed: Open the cart → Open the cart page), so the change reads in the case’s history like any edit. The answer is the surviving row and how many cases followed:

{ "step": { "id": "", "step": "Open the cart page", "count": 12 }, "affected": 12 }

When the new text already exists, the rename is a merge into that row: "merged": true, and step.id is the survivor’s, not the one you patched. A description in the same body — or alone — is the note under the step in Settings and the label of the suggestion; it touches no case, and null removes it.

POST /v1/settings/steps/merge
{ "ids": ["…", "…"], "into": "Open the cart" }

Every live case carrying any step in ids has that action rewritten to into — the same per-case write as a rename, one audit entry per case. into need not exist yet: an existing row of that text is the survivor and keeps its note; otherwise the first of ids is renamed in place. The other rows go with their links. An unknown id is a 404 before anything is written.

POST /v1/settings/steps with { "step": "Sign in as the shop owner.", "description": "…" } adds a row no case carries yet (count: 0), so the suggestions offer it before anyone writes it; the same text again is a 409 that carries the existing row.

Deleting is only for what nothing carries. DELETE /v1/settings/steps/{id} answers 204 for a step with count: 0 and 409the step is used by cases — rename or merge it instead — for one that cases carry: deleting it would mean rewriting those cases, which is what rename and merge are for. DELETE /v1/settings/steps?unused=true sweeps every row with a count of 0 — steps made by hand and never used, steps orphaned when a case was edited — and answers { "deleted": n }. Without unused=true the request is refused.

On the dashboard all of this is Settings → Steps: the list with counts and notes, Rename and Merge into… on each row through the same Was → Becomes dialog as tags, with the number of cases the change rewrites; Note; Delete only on unused rows; + step; Delete unused in the header.

A snippet is a titled piece of Markdown a case is written from — preconditions you repeat, a checklist of steps, a whole case to start from. It is inserted into a case as a copy: editing the snippet later changes no case, and deleting it leaves every case as it was. There is no bin for a snippet.

GET /v1/snippets
GET /v1/snippets/{id}
POST /v1/snippets { "title": "Refund — checklist", "text": "## Steps\n* …", "template": true }
PATCH /v1/snippets/{id} { "title"?, "text"?, "template"? }
DELETE /v1/snippets/{id}

The title is unique within the project (409 otherwise); the text is Markdown up to 16 KiB; template marks the snippet as one a new case may start from. All three writes are in the audit feed under the entity snippet.

The case form has Insert snippet ▾ beside its Form / Markdown tabs:

  • on the Markdown tab the text goes in at the caret, as text;
  • on the Form tab the text is read by the same parser the Markdown tab uses: a ## Steps section — or bare * / 1. items with no heading — joins the case’s steps at the end, with their Expected: lines; anything else joins the description. A line under the header says which happened.

On a new case, when the project has snippets flagged as templates, Start from ▾ lists them: pick one and the form is filled from it the same way, once.

On the dashboard the snippets themselves are Settings → Snippets: the list with a template badge and the first line of each, one form for adding and editing (title, Markdown text, the template checkbox), and Delete with a confirmation that says the cases keep their copy.