Test Case Steps — Low-Code Flow
Mockarty test cases are built from steps. A step is one click in your test:
“open the login page”, “POST /auth”, “verify the welcome email arrives”.
This page is the practical guide to building steps the low-code way — pointing
and clicking instead of writing code — so a non-developer can author an
end-to-end test that calls real APIs, chains values from one call to the next,
debugs itself step by step, and lights up green or red on every run.
About URLs in examples: all examples use
localhost:5770as the default
Mockarty address. If your instance runs on a remote server, replace
localhost:5770with its actual address (e.g.https://mockarty.company.com).
See Tips & Useful Features for details.
Related pages: Test Case Management ·
Runtime Flow View ·
Test Plans ·
Test Plan Run View ·
Override Surfaces ·
Secrets Storage ·
JsonPath Guide
To run a case inside a Test Plan and watch it expand inline alongside
other plan items (load, fuzz, chaos, contract, nested plans), see the
Test Plan Run View guide. For how
{{plan.X}}values flow between plan-level overrides and case-step
extracts, see Override Surfaces.
1. What a step is — manual or automatic
Every step in a test case is either manual or automatic. The choice is
a small two-icon switch at the top of each step row in the builder.
| Mode | Use when | What runs at execution time |
|---|---|---|
| Manual (hand icon) | A tester needs to look at the screen and decide pass/fail — UI inspection, visual diff, real-device checks, on-call escalation drills. | Nothing automatic. The runner pauses, the step execution modal opens, the tester records the verdict. |
| Automatic (robot icon) | The check can be expressed as an HTTP call (or chain of calls) whose response decides pass/fail. | The runner makes the request, evaluates expectations, and records the result without any human in the loop. |
You can mix both in the same case. A common pattern is automatic for
plumbing, manual for the user-visible result: log in via API, fetch a user
profile via API, then ask a human “open the email client and confirm the
welcome message landed”.
To switch a step, click the hand or robot icon in the step header. The body
of the step morphs to show the right inputs for the mode you picked.
[Step row with mode switch — screenshot pending]
2. Linking an API Tester request to a step
For automatic steps, the work happens inside the API Tester — Mockarty’s
HTTP / gRPC / GraphQL client. You build and save a request there, then link
it from the step. The runner re-fetches the linked request on every run, so
edits in API Tester apply to existing cases instantly — no copy-paste, no
snapshot drift.
To link a request:
- Open the test case in the builder (
/ui/test-cases). - Make sure the step is in Automated mode (robot icon active).
- Inside the step body, click Pick endpoint.
- A tree picker opens, showing your saved API Tester requests grouped by
collection. Type to filter by name, method, or path. Pick the one you want. - The step body collapses to a single chip:
POST /api/v1/auth — Login. The
chip name follows the request name in API Tester — rename there and the
chip follows.
[Linking an API Tester request to a step — screenshot pending]
To unlink, click the request chip and choose Clear. The step body returns
to the empty (none — pick a request) state.
What a linked request brings with it
When the case runs the linked step, Mockarty pulls the request definition
fresh from API Tester (method, URL, headers, body, expectations) and fires it
through the same engine that powers API Tester itself. The response is
captured against the step run so the runtime flow view
can show you exactly what came back.
3. Binding an environment to a step
API Tester organises hosts, base paths, tokens and per-team values into
environments (think “staging”, “production”, “regional-eu”). A step can
pick its own environment so the same logical test can target different
deployments without rewriting requests.
A step’s environment binding has three layers:
- Environment — one named environment from API Tester. Provides every
{{env.X}}placeholder its variables. - Overrides — a per-step list of key/value pairs that take precedence
over the environment for this step only. Useful for “use staging for the
whole case, but for this one step talk to staging-eu”. - Secrets — pointers into the Secrets Storage
with a per-step alias. The secret value is fetched and substituted at
execution time; it is never written into the case definition.
To set the environment on a step:
- In the automatic step body, click Environment.
- Pick an environment from the dropdown (search by name). Leave blank to
inherit the case-level default. - Click Add override to add
key = valuerows. Each row provides one
{{env.<key>}}value that wins over the environment’s own definition for
this step. - Click Add secret to bind a secret. The secret picker lists every
secret the current user can read in this namespace. Choose the source
secret and an alias — the alias is the name you use in the step
({{secret.<alias>}}), so you can reference{{secret.API_TOKEN}}
regardless of whether the underlying secret is namedprod_api_tokenor
staging_api_token.
Realistic example: staging-eu login that uses a prod secret
A login step that hits the staging environment, but talks to the European
region by overriding host, and pulls its API token from the production
secrets store:
| Layer | Value |
|---|---|
| Environment | Staging (provides host=https://api.staging.example.com, tenant=acme) |
| Override | host = https://api.staging-eu.example.com |
| Secret | source prod_api_token, alias API_TOKEN |
The linked API Tester request URL is:
{{env.host}}/v1/auth
with header Authorization: Bearer {{secret.API_TOKEN}}. At execution time
the runner resolves the URL to https://api.staging-eu.example.com/v1/auth
(the override wins over the environment) and substitutes the token from the
prod secret store. The secret value is masked in the runtime view and in the
audit log — it is never visible after the run finishes.
4. Variable references inside a step
Anywhere a step accepts text — URL, header value, body, JSON path,
expectation — you can drop in a placeholder of the form {{namespace.key}}.
There are four namespaces:
| Namespace | Source | Filled when |
|---|---|---|
{{plan.X}} |
Values harvested by earlier steps in the same run (see §5). | At the moment the earlier step’s extract rule fires. |
{{override.X}} (implicit) |
The step’s per-step overrides list. | When the step’s bindings are resolved at run start. |
{{env.X}} |
The bound environment’s variables. | When the step’s bindings are resolved at run start. |
{{secret.X}} |
The Secrets Storage entry bound to the alias X. |
At the moment the request is built — never persisted. |
Precedence — most specific wins
When the same name lives in multiple namespaces, Mockarty picks the most
specific value. Reading left to right, that order is:
plan > override > env > secret
So {{env.host}} for the step in §3 resolves to the override
https://api.staging-eu.example.com (the override beats the environment),
and a later step that extracted host into plan.host would beat the
override in turn.
The step row shows a small conflict badge next to any key that has
multiple sources active, and a tooltip lists which one will win at run time.
If the precedence is wrong for your case, the simplest fix is to rename one
of the keys (e.g. give the override a more specific name like host_eu).
5. Extracting data from one step for the next
A login step returns a token; the next step needs that token. Extracts
are the rules that copy values out of one step’s response and put them into
the run’s plan context ({{plan.X}}) so the next step can use them.
Open the step’s Extract section to add a rule. There are four kinds:
| Kind | What it pulls | Example |
|---|---|---|
| JSONPath | A value from the JSON response body. | $.access_token |
| Regex | A capture group from a header, the raw header block, or the body. | Bearer\s+(?P<t>[A-Za-z0-9._-]+) against header.authorization |
| Header | A single response header. | X-Request-Id |
| Status | The HTTP status code as an integer. | (no input — just pick this kind) |
Each rule has a To field — the name under plan. that downstream steps
will use to read the value back. So a JSONPath rule with From $.access_token
and To plan.token means “after this step passes, {{plan.token}}
contains the response body’s access token”.
For regex rules, the Source dropdown picks what to run the pattern
against:
- body (default) — the raw response body.
- header.
— one header’s value (e.g. header.authorization). - header_raw — every response header concatenated as
Name: valuelines.
The named capture group t (or, if you don’t name it, capture group 1)
becomes the harvested value.
Realistic chain: login → fetch profile
| Step | Method | Extract | Next step uses |
|---|---|---|---|
| 1. Login | POST /auth |
JSONPath $.access_token → plan.token |
— |
| 2. Fetch profile | GET /me with Authorization: Bearer {{plan.token}} |
JSONPath $.id → plan.userId |
— |
| 3. Audit log query | GET /audit?user={{plan.userId}} |
— | — |
When step 1 passes, plan.token is written. Step 2 starts with that value
already substituted into the Authorization header. Step 2 in turn writes
plan.userId, and step 3 uses it in the query string.
Test on last response
The extract editor has a Test on last response button. After you’ve run
the step at least once in a debug run (see §8), this button replays your
extract rule against the cached response without touching the server, so
you can iterate on the JSONPath / regex without re-firing the API. The
button shows the resolved value next to the rule, or an error message if
the path doesn’t match.
If the cached response is on the server only — for example you ran the case
on a different machine — the button falls back to a tiny server-side
preview call that does the same evaluation and returns the value.
5a. Asserting on the response — structured pass/fail checks
Extracts harvest values for the next step; assertions decide whether
this step passed at all. A step that calls POST /auth and gets back
503 Service Unavailable is technically a “completed request” — without
an assertion, the dispatcher accepts it as a PASS and dutifully writes a
poisoned token into plan.token. Adding status == 200 to the step’s
assertions list closes that hole: the step fails closed, the harvester
is skipped, and downstream steps never see the bad data.
Open the step’s Assertions section to add a check. There are six kinds:
| Kind | What it checks | Operators |
|---|---|---|
| status | The HTTP status code | = ≠ > ≥ < ≤ |
| header | A response header by name (case-insensitive) | = ≠ contains not_contain regex_match exists not_exists |
| jsonpath | A JSONPath leaf in the response body | All of the above plus > ≥ < ≤ when the leaf is numeric |
| body_contains | The raw body as a string | contains not_contain regex_match |
| duration_ms | The step’s wall-clock in milliseconds | = ≠ > ≥ < ≤ |
| body_size | The response body byte count | = ≠ > ≥ < ≤ |
Each assertion has:
- Kind — picks what to check against.
- Source — required for
header(header name) andjsonpath(a path
like$.user.id). The other four kinds don’t use it. - Operator — comparison op. Numeric ops work on numeric leaves; the
string ops compare as strings;regex_matchcompiles Expected as a
regular expression and tests the actual value against it. - Expected — the value to compare to. For
regex_matchit’s the
pattern; forexists/not_existsit’s ignored. - Note — optional human-readable note shown next to the failure in
the runtime view, so a teammate reviewing a failed run sees why the
assertion was important, not just that it failed.
When to use which
| You want to check that… | Use |
|---|---|
| The API responded with a success code | status = 200 (or >= 200 + < 300) |
| A specific user came back in the body | jsonpath $.user.email = ${env.email} |
| The token shape looks like a JWT | jsonpath $.token regex_match ^eyJ[A-Za-z0-9._-]+$ |
| The trace id header was set | header X-Request-Id exists |
| The response did NOT echo a debug payload | body_contains not_contain DEBUG_TRACE |
| The endpoint stayed under the SLA budget | duration_ms < 500 |
| The body wasn’t suspiciously empty | body_size > 0 |
Test on last response
The assertions editor mirrors the Extracts editor: a Test on last
response button lets you paste a sample payload (or pick up the one
cached from a previous debug run) and runs every assertion in-browser.
Each row shows pass or the rendered failure message — useful when you’re
authoring a regex or refining a JSONPath.
What happens at runtime
Assertions run after the executor returns and before the
harvester writes into the plan context. The flow per step is:
- Executor sends the request, captures the response.
- Assertions evaluate against the response. ANY failure → step fails
closed, with a multi-line error listing every failure. - Harvest runs only when the step passes — so a failed assertion
never produces aplan.Xvalue.
The runtime view surfaces the failures inline: each step that failed on
assertions gets a red “Assertions” chip in its card header and an
“Assertions” section in the detail panel listing every failed check with
the kind, op, expected, actual, and your optional Note.
6. Step dependencies
By default, steps run in order: step 2 only starts after step 1 has
ended. That works for most cases but means a slow step blocks faster ones
even when there’s no real data dependency between them.
The Depends on picker — a dropdown inside the step body, listing every
other step in the current case — lets you make this explicit. The runner
treats dependsOn as the only real ordering constraint:
- When you add a brand-new step, its
dependsOnis pre-filled with the
step immediately above (so the legacy “run sequentially” behaviour is
preserved by default). - Remove the dependency to let the step run as soon as the case starts —
in parallel with its siblings. - Add multiple dependencies to fan in: “this step needs both Login and
Seed data to finish first”. - Branch from one step into many: “after Login is done, fetch profile,
fetch orders, fetch settings — all three in parallel”.
The header of every step that has at least one dependency shows a small
link badge with the dependency count. Hover for the list of upstream
step names.
When a dependency ends in failed or cancelled, every downstream
step is marked skipped with the reason dependency failed: <step name>.
The runtime view colours these chips grey so it’s obvious which branch was
abandoned.
When to fan out
- Independent setup work — seeding fixture data, warming caches,
pre-creating test users — runs in parallel and shortens overall case
time. - Independent assertions — three different endpoints all need to
succeed after a deployment — run in parallel for the same reason.
When to keep things linear
- Token-chain — step N needs
{{plan.X}}written by step N-1. Keep the
defaultdependsOn: [previous]and the runner serialises automatically. - Stateful operations — “create order” must finish before “cancel
order”. Express the order withdependsOn.
7. Watching a run live
Click Run on a case, or run a Test Plan item that
references the case, and the Runtime Flow View opens.
Every step is a card in a tree. Expand a card to see, inline:
- the request that was fired (URL, headers, body — fully resolved, with
secrets masked); - the response (status, headers, body, duration);
- the env summary chip — which environment, how many overrides, how
many secrets were active; - every value the extract rules harvested (e.g.
plan.token = "ey…").
A small chip on each step card carries the live status: pending,
waiting (gated on a dependency), running, passed, failed,
skipped, awaiting manual.
The runtime view updates as the run progresses — there is no need to
refresh. The full reference for the view is on the
Runtime Flow View page.
8. Sequential debug
A debug run runs the case the same way as a normal run except it pauses
after every step. You inspect what just happened, decide what to do next,
and click Continue to move on.
To start a debug run:
- Open the case.
- In the run menu, pick Debug run instead of plain Run.
- The first step starts, finishes, and the runtime view shows a yellow
paused bar at the bottom with three buttons:
| Button | What it does |
|---|---|
| Continue | Releases the pause. The next step (or next layer of parallel steps) starts. |
| Continue & edit | Lets you tweak the harvested plan values, the step’s overrides, or the next step’s request before resuming. The change is recorded in the run’s history. |
| Stop | Cancels the run. Already-completed steps keep their results; the case run ends in cancelled. |
Use Continue & edit to fix a flaky token mid-debug: edit
plan.token by hand to a known-good value, click Resume, see the next
step succeed.
Debug runs are the same kind of run as normal runs — they show up in the
case’s history, count against retention quotas, and feed the same Allure
report when bundled into a Test Plan. The only difference is the per-step
pause.
9. Common pitfalls
The runner is opinionated about half-finished configuration. When a step
can’t run, the runtime view marks it red and the error chip in the step
header is the verbatim message — copy it back here to find the fix.
“unresolved placeholder: {{plan.X}}”
The step’s text contains {{plan.X}} but no earlier step wrote a value
under that name.
Fix: open the upstream step (the one that should produce X), add an
extract rule with To plan.X. If you renamed it, fix the downstream
references too — the conflict badge in the step row catches most of them.
“secret not found: <alias>”
The step references {{secret.<alias>}} but no secret is bound to that
alias on the step.
Fix: open the step’s environment binding, click Add secret, pick
the source secret from the picker, and set the alias to <alias>. If the
secret picker is empty, you don’t have read access to any secret in this
namespace — ask a namespace admin to grant it, or use the
Secrets Storage page to create one.
“dependency cycle detected”
You set dependsOn on two or more steps such that following the arrows
brings you back where you started (A → B → A).
Fix: open the Depends on picker on the steps in the cycle and remove
one of the back-edges. The runtime view lists the offending step names in
the error message — start there.
“conflict — env override beats plan value”
The same key is being written by both an upstream step’s extract (into
plan.X) and the current step’s overrides. The runtime view shows a
conflict badge to warn that the plan-context value won the race.
Fix: decide which source you actually want. If the upstream step’s
value should win, delete the override row on the downstream step. If the
override should win, rename either the override key or the upstream
extract’s To so the two never collide.
“environment not found”
The bound environment was deleted or moved between namespaces after the
step was authored. The step run fails immediately on resolve.
Fix: open the step’s environment dropdown and re-pick. If the
environment really did go away, recreate it in API Tester or pick a
different one with the same variable names.
Continue on failure
By default, when ANY step in a test case fails, the runner cascades the
failure: every remaining step is skipped and the case ends with status
Failed. That matches how a smoke test usually behaves — the first
broken thing tells you the whole flow is broken.
But not every step is load-bearing. Maybe step 3 is “post a metric to
the analytics endpoint” — useful to record, not worth aborting the
release flow for. The Continue case if this step fails toggle on
each step row marks the step as non-blocking:
- A Fail on a non-blocking step records the failure (counts toward
the failed-step counter, lights the step row red in the runtime view)
but the runner activates the next pending step normally. - The case ends with the dedicated status Passed (with soft failures)
when every other step passed. That’s amber — distinct from green
Passed and from red Failed so dashboards and CI gates can
decide how strict they want to be. - If a later blocking step fails (or the user picks “stop the case”
in the resolve modal — see below), the case still ends Failed and
remaining steps cascade-skip — hard failures dominate amber.
Per-attempt override (manual resolve modal)
The step’s authoring flag is the default, but the user can override it
for a single resolution. In the manual-resolve modal the Continue on
fail checkbox next to the Fail button is pre-populated from the step’s
authoring flag:
- Leave it as-is to honour the case author’s intent.
- Check it (when authoring says blocking) to continue the case despite
this one failure — useful when you know the failure is environmental
and not worth re-running. - Uncheck it (when authoring says non-blocking) to force the cascade —
useful when you spot a deeper problem and want to stop the line.
The override is recorded against the attempt row alongside the
attachments and note, so the audit trail shows who explicitly continued
or stopped a case beyond what the author had configured.
When to use it
- Use it on: telemetry posts, optional cleanup steps, “nice to
have” assertions, follow-up calls that don’t gate the core flow. - Don’t use it on: authentication, the primary action under test,
setup that downstream steps depend on (the case keeps going but
downstream steps will fail too — and at that point you’ve lost the
signal a hard fail would have given you).
Audit trail
The audit log captures every step resolution:
- Step resolved — logged on every resolve, regardless of outcome.
- Fail continued — logged specifically when the user explicitly picked “continue” on a failing step. This makes it clear when the cascade was suppressed by a human choice versus the step’s authoring default.
Admins can review this trail in Admin → Audit.
Next: read Runtime Flow View for the
full reference on the live execution view, and
Test Case Management for the broader case / folder /
version model.