Write a test sequence in YAML. Run it against real hardware. Get a report you can hand to someone.
A small, offline-first alternative to the big test-executive suites. You describe a sequence of steps in a text file — talk to a serial instrument, read a Modbus register, send a UDP frame, ask the operator — and each step is judged against a rule you state. The result is a self-contained HTML report and a CSV, produced deterministically: the same measurements always produce the same verdicts.
Built for closed networks. No cloud, no telemetry, no account, no network access of any kind beyond the instruments you point it at.
- Why
- Install
- Five-minute tour
- Writing a sequence
- Actions
- Verdicts
- Variables
- Failure handling
- Reports
- Building from source
- Design notes
- Licence
Bench testing usually falls into one of two holes. Either it lives in a spreadsheet and a pair of hands — which does not survive the person leaving — or it needs a commercial test executive that costs more than the project. This sits between them: a sequence is a text file you keep in version control, and running it produces a document you can put in a report pack.
Three things it takes seriously:
- Judgement is deterministic. Pass and fail come from a pure function of (rule, measurement). No clock, no I/O, no state. Two runs over the same readings cannot disagree.
- A skipped step is not a pass. If a step never ran, the run failed. Reporting an unknown as a pass is the exact failure this kind of tool exists to prevent.
- Protocol details are cited, not guessed. Modbus constants and frame layouts come from
spec/modbus.md, which names the published documents they were taken from. Anything not on that page is unimplemented and says so, rather than being improvised.
Windows — download NoktraSequencer.exe from the
latest release. It is self-contained: no .NET runtime to install, nothing
to unpack. Double-click it.
macOS and Linux — build from source. One command.
-
Start the application and press Open sequence…, or pass a file on the command line:
NoktraSequencer.exe examples\psu-acceptance.yamlAdd
--runto start it immediately — useful for a desktop shortcut to a routine check. -
The left panel shows what was loaded; the middle lists every step before anything has run, so you can see what is coming as well as what has happened.
-
Press Run. Steps light up as they execute, with the measurement, the rule it was judged against, and the verdict.
-
Press Save report… to write an HTML and a CSV report side by side.
Two sequences in examples/ run anywhere with no hardware attached:
| File | What it shows |
|---|---|
psu-acceptance.yaml |
A passing run: limits, tolerance, a regex that pulls a serial number out of an instrument reply |
failure-handling.yaml |
A failing run: abort, skip, and teardown still running |
hardware-instrument.yaml |
A real bench sequence — SCPI over serial plus Modbus RTU. Needs hardware |
A sequence is a YAML file. There is no editor built in, on purpose — you already have one you like, and the file is small enough to read in full.
name: Power supply acceptance
description: 5 V rail, ripple and load regulation on a bench supply
variables:
serial: PSU-0042
nominal: "5.0"
settings:
defaultTimeoutMs: 2000
setup:
- name: warmUp
action: wait
params: { ms: 300 }
steps:
- name: railVoltage
action: const
description: No-load output on the 5 V rail
params:
value: "5.02"
verdict:
type: limits
min: 4.75
max: 5.25
- name: loadRegulation
action: const
params:
value: "4.98"
verdict:
type: tolerance
expected: ${nominal}
tolerance: 0.1
teardown:
- name: outputOff
action: const
params:
value: "off"
verdict:
type: equals
expected: "off"setup runs first, then steps, then teardown. Teardown runs whatever happened before it —
leaving a supply at 30 V because the measurement before it failed is worse than the failed
measurement.
The full schema, including every default, is in spec/sequence-schema.md.
Every mistake that can be caught before the run is caught at load time — an unknown action, a rule
missing its bounds, a ${...} pointing at a step that runs later — and all of them are reported at
once, with line numbers, instead of one per attempt.
| Action | What it does | Key parameters |
|---|---|---|
const |
Yields a fixed value — how you exercise a sequence without hardware | value |
wait |
Waits | ms |
prompt |
Asks the operator to confirm, or to type in a reading | message, expectText |
serial-write |
Writes text or hex to a serial port | port, baud, data, terminator, encoding |
serial-read |
Reads until a terminator or a byte count | port, until, maxBytes, encoding |
udp-send |
Sends one datagram | host, port, data, localPort |
udp-recv |
Waits for one datagram | port, from |
modbus-read |
Reads coils, discrete inputs, holding or input registers | transport, port/host, unit, type, address, count |
modbus-write |
Writes coils or holding registers | as above, plus value or values |
shell |
Runs a command and captures its output | command, args, expectExitCode |
shell refuses to run unless the file sets settings.allowShell: true. A sequence is a document
that gets e-mailed around, and one arriving from outside should not be able to run commands because
somebody pressed Run.
Modbus speaks both RTU (transport: rtu, over a serial port) and TCP (transport: tcp). Ports and
sockets are opened once and shared for the whole run, so a write and the read that answers it are
the same connection.
| Type | Passes when | Keys |
|---|---|---|
limits |
the measurement is inside the window, both bounds inclusive | min, max (at least one) |
tolerance |
|measured − expected| ≤ tolerance | expected, tolerance |
equals |
the text matches | expected, trim (default true) |
regex |
the pattern matches — and capture group 1 becomes the step's value | pattern |
The regex capture is what makes chatty instruments usable. Ask a meter for a reading, get back
NOKTRA,PSU,PSU-0042,1.4\r\n, pull the field you want out of it, and judge that in the next step:
- name: identity
action: serial-read
params: { port: COM3, until: "\n" }
verdict:
type: regex
pattern: "NOKTRA,PSU,([A-Z0-9-]+)"
- name: checkSerial
action: const
params:
value: ${identity.value} # -> "PSU-0042"
verdict:
type: equals
expected: ${serial}Numbers are parsed the same way on every machine, so a sequence reaches the same verdict in Seoul
and in Seattle. 4,9 is text, not a number.
${name}— a value declared invariables:. Usable in step parameters and in verdicts.${step.field}— a result of a step that ran earlier, wherefieldis one ofvalue,raw,elapsedMs,status.
value is what the verdict produced (the regex capture, if there was one); raw is exactly what the
equipment sent, before anything touched it.
A ${step.field} cannot be used inside a verdict — a rule that changes shape mid-run is not a rule.
Compare the result in a following step instead, as above.
Each step chooses what its failure means:
onFailure: continue(default) — record it and carry on.onFailure: abort— stop; every remaining step in the body is reported as skipped, and teardown still runs.
retry: n gives a step n further attempts before it is called failed.
A run passes only when nothing failed and nothing was skipped.
HTML — one self-contained file. Inline CSS, a logo embedded as a data URI if you set
settings.reportLogo, and no external requests of any kind. A report opened in five years on a
machine with no network renders exactly as it did on the bench.
CSV — RFC 4180, written with a UTF-8 byte order mark so Excel reads it correctly.
Both are deterministic: same run, byte-identical output. That is what makes two archived reports comparable.
Requires the .NET 8 SDK.
git clone https://github.com/Kim-Hakseong/NOKTRA-test-sequencer.git
cd NOKTRA-test-sequencer
dotnet test # 226 tests
dotnet run --project src/Sq.App -- examples/psu-acceptance.yamlTo produce the Windows executable:
dotnet publish src/Sq.App -c Release -r win-x64 --self-contained true \
-p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true \
-p:EnableCompressionInSingleFile=trueSwap win-x64 for osx-arm64 or linux-x64 to build for those instead.
src/Sq.Core/ schema and loader, verdicts, runner, actions, transports, Modbus, reports (no UI)
src/Sq.App/ Avalonia desktop application
tests/ 226 tests, none of which sleep
spec/ the sequence schema, and the Modbus constants with their sources
examples/ sequences that run
A few decisions worth knowing about if you read the code:
- The verdict engine is a pure function. It has no clock and no I/O, so replaying measurements cannot produce a different answer.
- All time flows through one interface. Tests drive a virtual clock, so a step that overruns a 200 ms timeout is proved to fail at exactly 200 ms without a real millisecond passing.
- YAML is parsed by a small purpose-built parser. It accepts the subset a sequence file needs and makes everything else a hard error with a line number. A file that parses differently than its author expected is a wrong test result.
- Transports are polled, not blocking. A blocking socket read would take the timeout decision away from the runner and put it somewhere a test cannot reach.
- The Modbus test slave answers real frames, so protocol tests check the bytes on the wire rather than an agreement between two halves of one assumption.
MIT — see LICENSE.
Noktra — verification tools that work in the dark (offline-first).


