Live demo — data resets daily at 03:00 UTC. Nothing you enter is saved. Server UI →

product: maestro audience: test-developer authority: normative

Standalone / Composite Test Pattern

The problem

A manufacturing test suite often grows to dozens of steps. Running the full suite every time is slow, but running only part of it risks skipping necessary hardware initialisation (setup) and cleanup (teardown). The typical result is either one huge monolithic YAML file, or fragile partial tests that leave hardware in an unknown state.

The solution

Split the suite into focused sub-sequence files. Each file:

  1. Has its own setup: and teardown: so it can be run standalone.
  2. Contains only the steps: that belong to its responsibility.

Then create a top-level composite file that has its own setup: and teardown: and calls each sub-sequence in order.

When a sub-sequence is called from the composite file, the engine executes only its steps: — the sub-sequence's own setup: and teardown: are ignored. The composite file's setup/teardown handle the full lifecycle.

This gives you both:

Run mode What executes
power-tests.yaml standalone its own setup: → its steps: → its teardown:
full-suite.yaml composite composite setup: → all sub-sequence steps: → composite teardown:

Engine behaviour (normative)

When a YAML file is invoked via type: sequence, the engine executes only steps:. The setup: and teardown: blocks in the child file are silently ignored.

This is verified by Sequence_SetupAndTeardownInChildYaml_AreIgnored_OnlyStepsExecute in WorkflowEngine.Api.Tests/Integration/NestedSequenceTests.cs.

File layout

tests/
  setup-common.yaml        # optional: shared hardware init as a sub-sequence
  power-tests.yaml         # runnable standalone OR called from full-suite
  comms-tests.yaml         # runnable standalone OR called from full-suite
  functional-tests.yaml    # runnable standalone OR called from full-suite
  full-suite.yaml          # composite — calls all three in sequence

Sub-sequence file structure

Each sub-sequence file is a complete, valid test definition. It contains setup: and teardown: so it can be run independently from the Maestro UI:

# power-tests.yaml
test:
  name: "Power Rail Tests"
  version: "1.0.0"

  setup:
    - name: "Power on DUT"
      runner: dotnet
      runner_type: net10.0
      assembly: "BoardTests.dll"
      class: "BoardTests.Power"
      method: "PowerOn"
      post_execution_action: terminate-on-fail

  steps:
    - name: "Measure 3V3 rail"
      runner: dotnet
      runner_type: net10.0
      assembly: "BoardTests.dll"
      class: "BoardTests.Power"
      method: "Measure3V3"
      measurement:
        name: "V3V3"
        value: "{{voltage}}"
        low_limit: 3.135
        high_limit: 3.465
        unit: "V"
      post_execution_action: terminate-on-fail

    - name: "Measure 5V rail"
      runner: dotnet
      runner_type: net10.0
      assembly: "BoardTests.dll"
      class: "BoardTests.Power"
      method: "Measure5V"
      measurement:
        name: "V5V"
        value: "{{voltage}}"
        low_limit: 4.75
        high_limit: 5.25
        unit: "V"

  teardown:
    - name: "Power off DUT"
      runner: dotnet
      runner_type: net10.0
      assembly: "BoardTests.dll"
      class: "BoardTests.Power"
      method: "PowerOff"
      run_on_abort: true

Composite file structure

The composite file provides the overarching setup/teardown and calls each sub-sequence as a step. The sub-sequences' own setup/teardown are not executed:

# full-suite.yaml
test:
  name: "Full Board Test Suite"
  version: "1.0.0"

  setup:
    - name: "Power on DUT"
      runner: dotnet
      runner_type: net10.0
      assembly: "BoardTests.dll"
      class: "BoardTests.Power"
      method: "PowerOn"
      post_execution_action: terminate-on-fail

    - name: "Connect instruments"
      runner: dotnet
      runner_type: net10.0
      assembly: "BoardTests.dll"
      class: "BoardTests.Instruments"
      method: "ConnectAll"
      post_execution_action: terminate-on-fail

  steps:
    - name: "Power Rail Tests"
      type: sequence
      sequence: "tests/power-tests.yaml"
      post_execution_action: terminate-on-fail

    - name: "Comms Tests"
      type: sequence
      sequence: "tests/comms-tests.yaml"
      post_execution_action: terminate-on-fail

    - name: "Functional Tests"
      type: sequence
      sequence: "tests/functional-tests.yaml"

  teardown:
    - name: "Disconnect instruments"
      runner: dotnet
      runner_type: net10.0
      assembly: "BoardTests.dll"
      class: "BoardTests.Instruments"
      method: "DisconnectAll"
      run_on_abort: true

    - name: "Power off DUT"
      runner: dotnet
      runner_type: net10.0
      assembly: "BoardTests.dll"
      class: "BoardTests.Power"
      method: "PowerOff"
      run_on_abort: true

Test Monitor — identifying composite files

In the Maestro Test Monitor (/test-monitor), the Test File dropdown automatically marks any YAML file that contains at least one type: sequence step with a prefix:

★ full-suite         ← composite file (calls sub-sequences)
  comms-tests        ← sub-sequence (no sequence steps)
  functional-tests   ← sub-sequence (no sequence steps)
  power-tests        ← sub-sequence (no sequence steps)

This is a visual hint only — the engine treats all YAML files identically. If multiple files in a package each call sub-sequences, all of them will be starred. There is no concept of a single "root" enforced by Maestro; the star simply reflects that the file orchestrates other files.


What the operator sees

When running power-tests.yaml standalone:

[PASS] Power on DUT          ← setup
[PASS] Measure 3V3 rail      ← steps
[PASS] Measure 5V rail       ← steps
[PASS] Power off DUT         ← teardown

When running full-suite.yaml:

[PASS] Power on DUT          ← composite setup
[PASS] Connect instruments   ← composite setup
  [PASS] Measure 3V3 rail    ← power-tests steps (its setup/teardown skipped)
  [PASS] Measure 5V rail     ← power-tests steps
  [PASS] ...comms steps...
  [PASS] ...functional steps...
[PASS] Disconnect instruments ← composite teardown
[PASS] Power off DUT         ← composite teardown

Teardown and abort safety

Because sub-sequence teardowns are ignored in the composite run, the composite file must mark all cleanup steps with run_on_abort: true. This guarantees power-off and instrument release happen even if a step aborts mid-suite.

When running a sub-sequence standalone, its own teardown runs unconditionally (Maestro's teardown: always executes regardless of the main steps verdict — see yaml/schema.md).

Variable scope

All sub-sequences share the same variable scope as the composite test. A variable set in the setup: phase or in an earlier sub-sequence is visible to all later sub-sequences. Use parameters: and outputs: on the type: sequence step to make data flow explicit when needed.

- name: "Power Rail Tests"
  type: sequence
  sequence: "tests/power-tests.yaml"
  outputs:
    supply_voltage: "{{measured_supply}}"   # expose a child variable to the parent scope

- name: "Comms Tests"
  type: sequence
  sequence: "tests/comms-tests.yaml"
  parameters:
    expected_supply: "{{supply_voltage}}"   # pass parent variable into next sub-sequence

Checklist

  • Each sub-sequence file has setup: and teardown: so it runs cleanly standalone.
  • Each sub-sequence teardown: marks all cleanup steps run_on_abort: true.
  • The composite file's teardown: marks all cleanup steps run_on_abort: true.
  • The composite setup: has post_execution_action: terminate-on-fail on all init steps so partial initialisation does not reach the test steps.
  • test.version is bumped in every file before deploying.
An unhandled error has occurred. Reload 🗙

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.