Skip to content
SQA Cockpit

Add a scenario

BuildUpdated 3 min readEdit on GitHub ↗

Add a scenario #

Diátaxis form: how-to. A scenario drives the SUT through its public API to exercise a business workflow. For probing a dependency (component) of the SUT, see add-a-component.md. For composing probes and scenarios into a run, see add-a-segment.md. For the vocabulary, see docs/concepts/glossary.md.

When to use this guide #

You want SQA to verify that the SUT behaves correctly when a real client exercises one of its workflows.

Examples (snappy):

  • "Verify that creating a domain with activate: true transitions

it through the state machine to a terminal state" → the domain-activation scenario (already shipped).

  • "Verify that bulk-importing 50 domains via POST /domains/batch

succeeds and respects rate limits" → a future bulk-import scenario.

A scenario is not a probe: probes ask "is X up?"; scenarios ask "does the SUT do Y when poked?" If your check runs against a single endpoint with no follow-up state, it's a probe. If it issues a request, then polls or queries to observe what the SUT did next, it's a scenario.

Layout #

One scenario = one file. The phases (drive / observe / verify / cleanup) live as private helpers inside the file, not as separate files.

text
src/systems/<sut>/<scenario-name>.ts

The file exports one composing function returning Promise<Result>. The composition typically uses sequentialize over four phases that thread state through a closure-scoped accumulator.

This shape comes from the literature:

  • **Sam Newman, Building Microservices (2nd ed., 2021), ch. 10

"Semantic Monitoring."** "*With synthetic transactions, we inject fake user behavior into our production system. This fake user behavior has known inputs and expected outputs.*" One transaction = one logical unit.

  • **Google, SRE book (2016), ch. 17 "Black-Box Monitoring" and

"Production Probes."** A probe runs "*a protocol check against a target and reports success or failure*" — and probes can "*validate the response payload of the protocol… and even extract and export values as time-series.*" Drive plus payload validation, in one unit.

Splitting drive / observe / verify into separate files (or folders) fragments the transaction and makes shared state harder to thread. Resist the urge.

Recipe #

  1. Pick the scenario name. Kebab-case, descriptive of the

workflow (domain-activation, bulk-import, webhook-replay).

  1. Create one file src/systems/<sut>/<scenario-name>.ts.
  1. Implement each phase as a private async helper inside the

file. One function per phase. Each helper:

  • Returns a Result (per ADR-0012). Never throws.
  • Takes primitives only (URLs, IDs, cookies); no env /

targets reads inside helpers — let the segment composition read those once and pass them down.

  • Skip-on-empty for any required arg.
  • Tags log lines with [<sut>.<scenario>.<phase>].
  1. Compose the phases. Export one

runScenarioName(): Promise<Result> that uses sequentialize(NAME, [...]) to chain the phases. State threading: capture each phase's Result into a closure-scoped priorResults array and read Result.context.<field> for downstream phases.

  1. Cleanup must be idempotent and tolerant of partial state.

The cleanup phase runs even when earlier phases failed; it should skip cleanly when the prior phase that produced the cleanup handle didn't run. Read the relevant Result.context field; skip if missing.

  1. Wire as a segment. Add a `step("N", "<scenario-name>",

() => runScenarioName()) entry to src/systems/<sut>/index.ts. By current convention the scenario file is the segment file — no separate segment wrapper. See add-a-segment.md` ↗.

  1. Run make check && make test.

Reference implementation #

src/systems/snappy/domain-activation.ts is the worked example — one file, four private phases, one composed runDomainActivation export. PRD-03 describes the design choices end-to-end: docs/prds/03-snappy-activation-segment.md.

Cover-test before merging #

Pretend the next reader knows the codebase but not your scenario. The two questions that catch most issues:

  • Does cleanup run cleanly when the create step failed? If not,

every failed run leaves orphaned state in the SUT.

  • Are the failure modes obvious from the log message alone? If

not, add the missing field to the structured log payload.

Was this page helpful?