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

product: maestro audience: test-developer authority: normative

Anti-Patterns

❌ Don't ✅ Do
Omit unit on numeric measurements Always include unit — it appears in reports
Use {{test.variable}} (legacy Scriban syntax) Use {{variable}} (current engine syntax)
Leave low_limit > high_limit Verify limits are in correct order
Forget max_iterations on repeat: Always set a safety cap
Use precondition syntax inside {{}} templates Templates are for substitution; preconditions use bare variable names
Create steps without measurements when the step measures something Always attach a measurement: block for traceable results
Skip post_execution_action: terminate-on-fail on critical setup steps Mark power-on, connection, and init steps as terminate-on-fail
Use type: mock in production tests Mock is for development only; use runner: dotnet or runner: python
Hard-code measurement values in gRPC runner steps Return values from your test function and use {{var}} templates
Put pass/fail limit logic in Python (if v < lo or v > hi: raise ...) Python returns the raw value; declare low_limit/high_limit in the YAML measurement: block — limits in Python are invisible to the report engine, yield stats, and Cp/Cpk. validate.py will reject this pattern
Forget run_on_abort: true on cleanup steps Always mark power-down and instrument-release steps
Write one large method that connects, measures, and disconnects Split into one method per YAML step — see sdk/design-principle.md
Hard-code instrument addresses in YAML Store in Station Config; reference via {{cfg.AGENT_HOST}}
Scatter magic strings through MSTest methods Declare constants at the top with cfg.*/exec.* comments — see sdk/mstest-debugging.md
Declare pipPackages but forget to vendor wheels for production Vendor wheels in wheels/ — see sdk/python-dependencies.md
Use pip install inside test code at runtime Pre-install all dependencies; runtime pip calls are fragile
Import a third-party module without testing locally first Run python -c "import mypackage" in the runner container first
Read a channel value without resolving the net name Always resolve the fully-qualified net name via GetNamesAsync()
Drive a channel without configuring its direction Always call Channels.ConfigureAsync() with the correct Direction first
Treat Resources.GetValueAsync return as a number GetValueAsync always returns string — parse explicitly
Forget .Wait() / .Result on async AccordionQ2Client calls All AccordionQ2Client methods are async; call .Wait() or .Result in synchronous code
Use Thread.Sleep inside a test method to wait for hardware to settle Use a type: delay YAML step — keeps delay visible in results and tuneable without a rebuild
Omit timeout_ms from hardware runner steps Set timeout_ms on every runner: dotnet / runner: python step that communicates with an instrument
Omit requires: on a step that uses a shared instrument Declare requires: [dmm, ...] for every shared instrument the step touches — without it, two tests on a parallel station can use the instrument at once and corrupt each other's measurements. Harmless on single-execution stations, so always declare it
Write one large monolithic YAML test file Split into focused sub-sequence files, each with its own setup:/teardown:, then compose them with a top-level file — see yaml/standalone-composite-pattern.md
Omit run_on_abort: true on cleanup in the composite teardown When sub-sequences are called from a composite file their teardowns are skipped; the composite's teardown: must mark all cleanup steps run_on_abort: true
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.