Skip to main content
ntro workflow test is the design-time inner loop. You author a runbook locally, run the command, and get a per-scenario summary back in under a second. No Temporal cluster, no Docker, no deploy cycle. It catches the same workflow bugs the deployed e2e flow would catch — wrong @runbook.step ordering, malformed activity payloads, signal handling regressions, child-workflow dispatch problems — but in seconds rather than minutes. This is what makes the coding-agent loop tight: the agent generates a change, runs the test, sees the result, iterates.

Prerequisites

This is installed automatically as a dependency of ntro-cli, so if you have the CLI, you have the harness.

Run a single workflow

Output:
The harness runs both built-in scenarios (HAPPY and REJECT_ALL) by default. HAPPY exercises the most code; REJECT_ALL verifies your runbook handles rejection cleanly.

Parent + children

Most production runbooks dispatch child workflows. Register all of them on the same harness invocation:
Each --child is registered alongside the parent so the harness can dispatch them when the parent calls run_child_workflow(slug=...). Without registering a child, the dispatch fails with “child workflow slug not registered”.

Specific scenarios

Run only one scenario:
Run several:
Custom scenario names work too — define them in runbooks/<slug>/tests/scenarios.py and reference by name:

CI / scripting — JSON output

Returns a structured payload of scenario results that’s easy to parse from a CI script:
The non-zero exit code on failure is your CI gate.

What’s auto-mocked vs what’s real

The harness uses your runbook’s real code paths — your NtroWorkflow subclass, your @runbook.step decorators, your Pydantic models. The bits it fakes are:
  • Activity returns — derived from the activity’s return type via Pydantic introspection
  • HITL responsesHAPPY approves, REJECT_ALL rejects, custom scenarios script per-step
  • submit_file signals — synthetic document_refs derived from the workflow’s advertised args
  • Temporal worker — runs in-memory via WorkflowEnvironment instead of a real Temporal cluster
Everything else is your code running for real. See ntro.testing for the harness internals and how to write custom scenarios.

What this catches (and what it doesn’t)

For everything in the right column, deploy to a staging tenant and run there. The local harness is the inner loop; staging is the outer loop. Both are needed.

A typical iteration

Iteration cycle: under 5 seconds. That’s the value prop.

Testing capability (SDK)

Internals: WorkflowHarness, Scenario, custom mocks.

Deploy to production

Once scenarios pass, ship it.