Skip to content

Manual runs

A manual run is a run a person executes in the dashboard — the model Testomat.io’s manual test runs use, taken as it is: the run lists the cases it expects, each gets Passed · Failed · Skipped with a message and, for a case with steps, a mark per step; Finish run closes it. A mixed run is the same report with both halves in it: the manual cases are marked in the dashboard, the automated ones are reported by CI through the reporter, and the verdict counts both.

Nothing new was invented for it. A mark is a result — the same POST /v1/runs/{id}/results a reporter sends — and a finish is the same finish event. What the feature adds is one right the reporter does not have: a person who pressed the wrong button corrects the mark in place instead of sending a second result that would count twice.

Two roads, both ending on the run’s page:

  • From a plan. A plan of kind manual or mixedTest plans — launched with Launch run expects every case the plan collects at that moment.
  • Without a plan. On Runs, New run ▾ › Manual run…: all manual cases, or a selection made with the same filter bar the cases screen has; Run automated cases as manual too widens it the way a plan’s asManual does; a title («Manual · date» by default) and an environment. The dialog counts what the run will expect as you narrow it, and will not start a run of nothing.

On the wire the second road is the start a reporter sends:

POST /v1/runs
{
"schemaVersion": 2,
"kind": "manual",
"title": "Manual · 2026-09-17",
"environment": "staging",
"configuration": { "expected": [{ "testCaseId": "6f1c2b3a-0000-4000-8000-0000000000c1" }] },
"assignment": "prefer-assignee"
}

Both dialogs end with an Assignment row — None or Prefer case assignee — and on the wire that is assignment on POST /v1/runs and on POST /v1/plans/{id}/launch. With prefer-assignee, the launch copies each expected case’s assignee into the run:

"configuration": { "expected": [ ], "assignees": { "6f1c…c1": "6f1c…a2" } }

A copy, on purpose: the run holds who was to run what at launch, and a case that changes hands afterwards does not rewrite a run already under way. Cases nobody held are absent from the map; a launch with the strategy and nobody assigned anywhere records an empty map — the strategy was used, there was nobody to copy. none (the default) stores the configuration as it was sent, which for a reporter’s run is without the map at all.

On the run page the copy is what you see: the person on each «To run» row, prefer case assignee in the head, and a Mine · n / All · n toggle above the list — «Mine» keeps the rows assigned to the signed-in person, and the «x of n done» count follows the toggle. The toggle is there only when the launch made a copy; without one every row would be nobody’s and it would have nothing to say. GET /v1/runs/{id}/summary carries the same map as run.assignees, null when there is none.

While the run is launched, its page has a To run section: every expected case that has no result yet, in the order the run expected it, as a row —

  • the case’s steps (a manual case with steps): a toggle per step that turns with each click — passed → failed → skipped → not marked — as Testomat.io has it;
  • a Message, one line that grows;
  • Passed · Failed · Skipped — the button is the request. There is no separate Save: a mark with nothing else said is the common case.
POST /v1/runs/{id}/results
{
"results": [
{
"resultKey": "manual:6f1c2b3a-0000-4000-8000-0000000000c1",
"testCaseId": "6f1c2b3a-0000-4000-8000-0000000000c1",
"source": "manual",
"status": "failed",
"errorContext": "Step 3: the coupon field accepted an expired code.",
"steps": [
{ "index": 0, "status": "passed" },
{ "index": 1, "status": "passed" },
{ "index": 2, "status": "failed" }
]
}
]
}
field
resultKeymanual:<testCaseId> — unique within the run, so the run holds one mark per case. A second identical mark is a duplicate; a different one is a conflict that names the result already holding the key, and the dashboard patches that one instead.
sourcemanual — what tells the platform a person wrote this. A reporter names its runner here.
statusThe three a person gives: passed, failed, skipped. (broken and blocked are the reporter’s.)
errorContextThe message. Redacted and truncated like a reporter’s error text.
stepsUp to 200 { index, status } — the step’s position in the case’s steps, from 0, and passed, failed or skipped. Steps left unmarked are absent.

Accepted, the row collapses: the badge, the step marks as glyphs (✓ ✕ ○), the message, and Change. The counter in the section’s head — «3 of 12 done» — and the run’s own counts come from the platform after each mark, not from the page’s guess.

Change opens the same editor with the mark as it stands, and the new verdict goes in place:

PATCH /v1/results/{id}
{ "status": "passed", "errorContext": null, "steps": [{ "index": 0, "status": "passed" }] }

Any of status, errorContext (null clears it) and steps (null clears them), at least one. The result keeps its id and its place in the run; outcome is recomputed from the new status; the change is audited under the run with the status and the message before and after.

Finish run sends POST /v1/runs/{id}/events with { "event": "finish" }. With cases still to run, the dashboard asks first and says how many will stand as not run — «3 of 12 done → finished · 9 cases not run». Once closed, the To run section is gone, the head is the verdict of the counts, and the page names what never arrived: «9 expected cases produced no result» — the run’s notRun, which a reporter’s crashed shard leaves behind in exactly the same way.

A mixed run — from a plan of that kind — has both halves on one page. The manual cases are in To run; the automated ones wait below under Waiting for a reporter, with the run’s external key: a reporter that joins the run by that key (plune run start --key …, or the Playwright reporter’s own) reports them, and its results take those cases out of both lists. The counts, the verdict and notRun are one set of numbers over both sources, and Finish closes the run for both — a reporter that reports afterwards is refused as it is today.

A random distribution of cases among members (only «prefer case assignee» exists), run groups, several environments per run, custom statuses, file attachments (the contract has attachments; the dashboard does not upload yet), adding tests or plans to a run already started, and the case page’s history — which counts an eval result’s assertions and does not yet show a manual mark’s status or steps.