Running Actions¶
This page describes how hsemulator executes actions, how CLI flags interact with config.yaml, and how runs behave internally.
All behaviour described here matches the actual execution model.
Basic Execution¶
From the project root:
hsemulate run
This will:
Load
config.yamlApply any CLI overrides
Execute the action once per fixture
Apply assertions, snapshots, and budgets
Emit output according to
output.modeExit non-zero if any failure occurs
If any fixture or run fails, the command exits with an error.
Command Variants¶
run (default)¶
hsemulate run
Uses a single
config.yamlHuman-oriented output (unless overridden)
Fails immediately on errors
test (CI mode)¶
hsemulate test
CI-first mode with different semantics:
Recursively discovers all
config.yamlfilesForces:
mode = cisnapshots.enabled = true
Always emits one stable JSON blob
Never prints human-readable logs
Fails fast on the first failing run per config
This is the recommended entry point for CI pipelines.
Fixtures and Repeats¶
Multiple Fixtures¶
fixtures:
- fixtures/event_1.json
- fixtures/event_2.json
Execution behaviour:
Each fixture is executed independently
Failures are reported with fixture context
Any failure causes the overall run to fail
Repeated Runs (Flaky Detection)¶
repeat: 5
Or via CLI:
hsemulate run --repeat 5
Behaviour:
Each fixture is executed
repeattimesAssertions and snapshots are applied per run
Failures are aggregated
Used to detect non-deterministic behaviour
There is no warning-only mode. Flaky behaviour is treated as a failure.
CLI Overrides¶
CLI flags override config.yaml values at runtime.
Supported overrides:
--action <path>→ overridesaction.entry--fixture <path>(repeatable) → overridesfixtures--snapshot→ forcessnapshots.enabled = true--watch→ enables watch mode--repeat <n>→ overridesrepeat--budget-time <ms>→ overridesbudgets.duration_ms--budget-mem <mb>→ overridesbudgets.memory_mb--assert <file>→ overrides the assertions source
Overrides are applied before execution and do not mutate config.yaml.
Watch Mode¶
hsemulate run --watch
Watch mode:
Re-runs on changes to:
config.yamlAction entry file
Fixture files
Clears the screen between runs
Prints a minimal pass/fail summary
Never exits
This is intended for tight local iteration.
Execution Model (Per Run)¶
For each fixture and repeat iteration, hsemulator:
Writes the fixture JSON to a temporary directory
Selects runtime based on file extension:
.js,.mjs,.cjs→ Node.py→ Python
Injects environment variables
Executes the runtime shim
Captures:
Structured JSON output
Execution time
Peak memory usage
Applies checks in this order:
ok == trueAssertions
Budgets
Snapshots
Emits output
Aggregates failures
Output Emission Rules¶
Output behaviour depends on execution context and output.mode:
Local runs emit human-readable output unless
output.mode = fileoutput.mode = filewrites JSON to disk and emits nothing to stdoutCI mode emits a single JSON blob only
Watch mode emits minimal pass/fail status
Assertions, snapshots, and budgets always run regardless of output mode.
Exit Codes¶
Exit codes are stable and CI-friendly:
0– All fixtures and runs passed1– Any failure (assertion, snapshot, budget, or runtime error)
CI mode always exits non-zero on failure.
Determinism Expectations¶
hsemulator is intentionally strict.
To avoid failures:
Avoid random or time-based logic
Ignore non-deterministic snapshot fields
Treat flaky behaviour as a defect, not a warning
Summary¶
runis developer-focused and interactivetestis CI-first and JSON-onlyCLI overrides are explicit and deterministic
Failures are contextual, aggregated, and fatal