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

product: assert audience: test-developer authority: normative

Measurement Patterns

Numeric measurement (default type)

Pass condition: value >= low_limit && value <= high_limit.

measurement:
  name: "VOUT_5V"              # REQUIRED — stored in every record
  target: 5.05                 # mock: used as measured value
  value: "{{measured_v}}"      # gRPC runners: template resolves to actual
  low_limit: 4.75              # lower bound (inclusive)
  high_limit: 5.25             # upper bound (inclusive)
  unit: "V"

Numeric comparison operators

operator Aliases Evaluation
(omitted) value >= low_limit && value <= high_limit
equal eq value == target
notequal ne value != target
greaterthan gt value > low_limit
greaterthanorequal gte, ge value >= low_limit
lessthan lt value < high_limit
lessthanorequal lte, le value <= high_limit
log Always PASS; informational only

These operators apply to type: numeric only. For type: boolean and type: string the only evaluation mechanism IS the expected: field — however, operator: log IS supported on any type (numeric, boolean, string) to make the measurement informational-only.

Boolean measurement

Pass condition: value == expected.

Operators are NOT supported for type: boolean. Omitting expected: causes the measurement to always PASS (informational-only).

measurement:
  name: "RELAY_SELFTEST"
  type: boolean
  value: "true"
  expected: "true"

String measurement

Pass condition: value == expected (exact, case-sensitive).

Operators are NOT supported for type: string. Omitting expected: causes the measurement to always PASS.

measurement:
  name: "FW_VERSION"
  type: string
  value: "v2.1.0"
  expected: "v2.1.0"

Informational-only measurement (log operator)

operator: log applies to all measurement types. The measurement always evaluates to PASS — the value IS recorded without any limit or expected-value check.

# Numeric — record a current draw without limits
measurement:
  name: "SUPPLY_CURRENT_LOG"
  target: 0.245
  unit: "A"
  operator: log

# String — record a MAC address
measurement:
  name: "MAC_ADDRESS"
  type: string
  operator: log
  value: "{{mac_address}}"

# Boolean — record a pin state
measurement:
  name: "BOOT_FLAG"
  type: boolean
  operator: log
  value: "{{boot_flag}}"

Multiple measurements from one step

Use measurements: (plural) when a single function returns several values:

- name: "Read all power rails"
  runner: python
  runner_type: python3.11
  module: "instruments"
  function: "read_power_rails"
  outputs:
    rail_3v3: "{{v3v3}}"
    rail_5v0: "{{v5v0}}"
  measurements:
    - name: "RAIL_3V3"
      value: "{{v3v3}}"
      low_limit: 3.135
      high_limit: 3.465
      unit: "V"
    - name: "RAIL_5V0"
      value: "{{v5v0}}"
      low_limit: 4.75
      high_limit: 5.25
      unit: "V"

measurements: takes precedence over measurement: when both are present.

Grouping measurements (group)

Every measurement accepts an optional group label. It is purely organisational — it never affects the verdict — and lets you tag measurements so reports can group and filter them instead of showing one long flat list. The classic case is repeated, identical hardware: a DUT with five identical connectors can tag each connector's measurements with CON1..CON5.

measurements:
  - name: "CONT_PIN1"
    value: "{{con1_pin1}}"
    low_limit: 0
    high_limit: 1
    unit: "Ohm"
    group: "CON1"          # ← organisational tag; shown as a badge in the report
  - name: "CONT_PIN2"
    value: "{{con1_pin2}}"
    low_limit: 0
    high_limit: 1
    unit: "Ohm"
    group: "CON1"

group supports {{variable}} templates, so a sub-sequence reused per connector can set group: "CON{{connector_index}}" and each invocation lands in its own group.

Notes:

  • group is part of the measurement's structural identity: two measurements that are otherwise identical but carry different groups are distinct definitions (so their statistics/yield are tracked separately).
  • Max length 100 characters. Omit it entirely for ungrouped measurements.
  • Grouping is complementary to structuring the test into one step (or sub-sequence) per connector, which already gives each connector its own collapsible section and verdict. Use per-step structure for independent verdicts/yield; use group when you want to tag measurements that live within a shared step.

Declaring a "no result" value (invalid_value)

Many packages seed their numeric variables to a sentinel such as -999, so that a step which errors or never runs FAILS honestly instead of recording a plausible midpoint. That is the right thing to do for the verdict, and it quietly ruins the statistics: nothing else in the system distinguishes this is not a reading from this is a reading, so the sentinel is averaged in and Cp/Cpk end up describing a process that nobody runs.

invalid_value declares it. Numeric measurements only.

variables:
  vout: "-999"             # seeded so an unrun step cannot pass

measurements:
  - name: "VOUT_5V"
    value: "{{vout}}"
    low_limit: 4.75
    high_limit: 5.25
    unit: "V"
    invalid_value: -999    # ← "no result", not a measurement

What it changes, and what it deliberately does not:

Verdict Unchanged. -999 is still outside the limits, so the step still FAILS.
Report Unchanged. The operator still sees the value that was recorded.
Yield Unchanged. The failure still counts against yield.
Statistics The sample is excluded from mean, sigma, Cp and Cpk, and counted in excludedInvalidSamples.

The exclusion is applied at query time as well as at record time, so declaring invalid_value on an existing measurement also cleans up the history that was recorded before you declared it.

It is part of the structural specification, like low_limit and group, so adding or changing it creates a new definition version.

When the step fails: measurements that were not acquired

A step whose method throws (or whose runner call fails) is FAIL, and every measurement it declares is still recorded, so the report shows what was expected. What each measurement records depends on where its value: comes from:

value: reads Recorded as
Only variables the failing step itself wrote (e.g. an output the runner returned before failing) That value, verdict FAIL - visible for triage, never PASS
Any variable the failing step did NOT write Not acquired: verdict FAIL, no value at all
A variable that does not exist (the template stays unresolved), on any step Not acquired: verdict FAIL, no value at all, and the step FAILS
A literal (no {{...}}) That literal, verdict FAIL

"Not acquired" means the measurement has no reading: every value field is empty in the record, the report and the live monitor show not acquired instead of a number, and the statistics never see it (it has no value to average, so no invalid_value is needed to keep it out). The step's error message lists the measurements that were not acquired.

This matters most in a sub-sequence invoked once per connector. Before 2.6.0, when CON4's read threw, value: "{{loopback_serial}}" still resolved to the value CON3 had left in the variable and was published under CON4's group. Now CON4's measurement is not acquired, and - see Variable System - CON4 starts from the sub-sequence's declared defaults rather than from CON3's values.

For the same reason, when a step fails its outputs: mapping skips any entry whose template reads a variable the step did not write, rather than copying an earlier step's value into it.

A step that PASSES may still measure a variable set by an earlier step - the usual "prompt, then evaluate" pattern - and that is recorded as a reading, as before.

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.