Selection flags compose with any spec: --filter NAME (repeatable, and comma-separated for OR — --filter a,b or --filter a --filter b runs scenarios whose name contains a or b), --tag T and --skip-tag T (tags match exactly, not by substring — atago list shows the available tags), --parallel N, --fail-fast, and --rerun-failed. atago run --rerun-failed re-runs only the scenarios the previous run recorded as failed in .atago/last-failed.json, so the fix-and-recheck loop replays just the failures instead of the whole suite. While authoring, --verbose traces every command, capture, and assertion verdict — for passing scenarios too. Under --ci, a selection that matches no scenario fails the run (exit 3) rather than passing an empty suite. Flags may be written before, after, or between the paths — atago run ./specs --report json and atago run --report json ./specs are the same command — and -- ends flag parsing, so a path spelled like a flag can still be named. atago record is the exception: everything after its -- is the recorded program’s own command line.
snapshot matchers compare output against committed golden files; ANSI colors, temp paths, UUIDs, timestamps, ports, and CRLF are normalized so snapshots stay stable across machines. Record or refresh them with:
atago snapshot update spec.atago.yaml
For volatile patterns the built-ins do not cover — auto-increment IDs, request identifiers, epoch times — declare spec-wide scrub: rules that rewrite each regex match to a placeholder before the compare (applied after secrets: masking):
Every key a spec file accepts, generated from the committed
JSON Schema
— the same document that powers editor completion, so this reference cannot
drift from what the loader accepts. Indentation shows nesting; a type that
links (like step) is documented in its own section rather than
repeated inline.
All keys belong to spec format version 1 — the only format version so far;
version: "1" is the first line of every spec. The Since column is the atago release
that introduced the key (unreleased = merged to main, not yet in a tagged
release).
Spec-wide default fragments merged into every matching element at load time — authoring sugar to cut repetition. An explicitly authored value always wins; maps merge per key; a defaulted boolean cannot be turned off per element.
v0.1.0
permissions
object
The spec’s security policy.
v0.1.0
network
object
Network egress policy for the spec.
v0.1.0
allow
array of [string number boolean]
Hosts that scenarios may contact; egress to any other host is a policy violation (exit 6). Enforced on http, grpc, ssh, and db egress, and on a cdp step’s navigate: URLs — a page loaded from an allowed host may still fetch subresources from anywhere, which no navigate: action passes through (#486).
Named runner configurations. cmd is the implicit default for run steps; http supplies a base_url for http steps; db a dsn for query steps; ssh, grpc, and browser configure their step types.
Required. The named behaviors under test. Each scenario runs in its own isolated temporary workdir.
v0.1.0
scrub
array of objects
Declarative output-normalization rules (#137): each replaces every regex match in captured output with a literal placeholder before a snapshot is compared or written. Where secrets masks known values, scrub normalizes volatile patterns the built-in normalizers do not cover (auto-increment IDs, request identifiers, custom timestamps). Rules apply in order, after secret masking and before the built-in ANSI/UUID/timestamp/port/path normalization.
v0.5.0
pattern
string | number | boolean
Required. A Go (RE2) regular expression; every match is replaced. Required.
v0.5.0
placeholder
string | number | boolean
Literal replacement text (no $1 expansion), e.g. “<ID>”. Defaults to empty, which deletes matches.
v0.5.0
secrets
array of [string number boolean]
Environment variable names whose VALUES are masked as *** in output, reports, and snapshots.
v0.1.0
suite
object
Required. Groups the file’s scenarios under a name, with optional suite-wide env, default step timeout, and one-time setup/teardown steps.
v0.1.0
description
string | number | boolean
Optional prose describing what the suite guarantees, rendered as Markdown under the suite heading by atago doc. Documentation only: it never affects execution, and ${name} references in it are not expanded.
v0.14.0
env
map of [string number boolean]
Environment exported to every scenario, setup, and teardown step (a scenario’s own env wins per key).
v0.2.0
name
string | number | boolean
Required. The suite name shown in reports and generated docs.
Steps run ONCE before any scenario, in order, inside the ${suitedir} scratch dir. Allowed kinds: fixture, run, store, assert, and service (a suite-wide background process started at that point in the sequence). A failing setup step errors every scenario.
Steps that always run after the last scenario, while suite services are still up (services stop last, LIFO). Failures are reported but never change the suite verdict.
v0.2.0
timeout
string
Suite-level default step timeout (#17): every run/http/query/grpc step without a more specific timeout (step > runner > defaults.run > suite) is bounded by this Go duration. “0” disables. When no level configures one, a built-in 60s default applies.
v0.3.0
version
1 | 1
Required. Spec format version. Write “1” (quoted); a bare 1 is accepted for convenience — the loader coerces it to “1”.
Optional prose describing the behavior this scenario pins down and why it matters, rendered as Markdown under the scenario heading by atago doc. Documentation only: it never affects execution, and ${name} references in it are not expanded.
v0.14.0
env
map of [string number boolean]
Environment variables for every step in the scenario (a step’s own env wins per key).
v0.1.0
expect_fail
object
Declares that this scenario documents a KNOWN bug (#395). Failing is reported as XFAIL and does NOT fail the run, so a reproduction can live in CI and execute on every commit. Passing is XPASS, which DOES fail the run (unless --allow-xpass): the fix landed, and the scenario has to be promoted into the suite that guards against a regression. An execution ERROR is still an error — expect_fail says the program gives the wrong answer, not that the spec cannot run.
v0.19.0
issue
string | number | boolean
Where the bug is tracked. Optional, but it is what makes an XPASS actionable — the message can name the issue to close.
v0.19.0
reason
string | number | boolean
Required. What is broken, in the author’s words. Required: an expected failure with no stated reason is indistinguishable from a test somebody gave up on.
v0.19.0
matrix
array of object
Makes the scenario a template: one concrete instance per row, with each row’s key/value pairs seeded as ${name} variables.
Declarative stub HTTP servers (#24): canned routes on an ephemeral loopback port, every request recorded for the mock: assertion target; ${<name>.url} / ${<name>.port} are seeded before steps run.
v0.3.0
name
string | number | boolean
Required. The scenario name shown in reports; with matrix:, ${var} placeholders make each instance’s name unique.
Steps that always run after steps — pass, fail, error, or interrupt — sharing the scenario’s variable store. For external side effects the isolated workdir cannot undo. A teardown failure is reported but does not change the scenario’s verdict.
Runs a command inside a real pseudo-terminal and drives it with a declarative expect/send session — for prompts, REPLs, and TUIs. Works on Linux, macOS, and Windows (ConPTY).
Start the child from an empty environment instead of inheriting the host environment (#16). On Windows a small system-critical set (SystemRoot, SystemDrive, TEMP, TMP, PATHEXT) is always retained.
v0.3.0
command
string | number | boolean
Required. The program to run. Tokenized and exec’d directly unless shell: true.
v0.1.0
cwd
string | number | boolean
Working directory relative to the scenario workdir (default: the workdir itself).
Re-runs the command and requires the declared observables to come back byte-identical (#398) — the same-input-same-output property that catches iteration order leaking into output. Assertions, store, snapshots, and changes: all still describe the FIRST run. Only meaningful for an effectively read-only command; not combinable with retry.
v0.19.0
env
map of [string number boolean]
Environment variables for the child process, layered over the scenario env; values get ${name} expansion.
v0.1.0
pass_env
array of [string number boolean]
Host variables copied into the cleared environment (#16). Requires clear_env: true; unset host variables are skipped.
Re-runs the command until the until assertion passes — declarative polling for async behavior. The last attempt’s result is what later steps observe.
v0.1.0
runner
string | number | boolean
Names a declared runner to execute through (e.g. an ssh runner runs the command remotely). Default: local execution.
v0.1.0
sandbox_home
boolean
Point the child’s home and per-OS config/cache/data/state dirs at ${workdir}/.atago-home so a CLI touching ~/.config, ~/.cache, or %APPDATA% runs hermetically (#71). Precedence: step env > sandbox > pass_env > host; composes with clear_env.
v0.3.0
shell
boolean
Runs the command through the shell (/bin/sh on POSIX, cmd.exe on Windows) — for pipes, redirects, and builtins.
v0.1.0
stderr_to
string | number | boolean
Write the command’s captured stderr to this workdir-relative file (create/truncate), without needing shell redirection.
v0.1.0
stdin
string | number | boolean | object
Standard input for the command (#18): a plain string (inline text), {file: path} (workdir-relative, path-confined), or {base64: data} for binary bytes.
v0.1.0
file
string | number | boolean
Required. Feeds the command’s stdin from a workdir-relative file.
v0.3.0
base64
string | number | boolean
Required. Feeds the command’s stdin with exact binary bytes, base64-encoded.
v0.3.0
stdout_to
string | number | boolean
Write the command’s captured stdout to this workdir-relative file (create/truncate), without needing shell redirection.
v0.1.0
timeout
string
Bounds this command as a Go duration. Precedence: step > runner > defaults.run > suite > built-in 60s.
Runs one command inside a real pseudo-terminal and drives it with a declarative expect/send session (#8) — for CLIs that branch on TTY-ness or present interactive prompts. POSIX-only at runtime; Windows reports a clear execution error.
Key
Type
Description
Since
clear_env
boolean
Start the child from an empty environment instead of inheriting the host environment (#16). On Windows a small system-critical set (SystemRoot, SystemDrive, TEMP, TMP, PATHEXT) is always retained.
v0.3.0
cols
integer
Terminal width in columns (default 80).
v0.2.0
command
string | number | boolean
Required. The program to run inside a real pseudo-terminal (a ConPTY on Windows). The captured transcript becomes the step’s stdout.
v0.2.0
cwd
string | number | boolean
Working directory relative to the scenario workdir.
v0.2.0
env
map of [string number boolean]
Environment variables for the pty child. The child gets TERM=xterm-256color by default; override here if needed.
v0.2.0
pass_env
array of [string number boolean]
Host variables copied into the cleared environment (#16). Requires clear_env: true; unset host variables are skipped.
v0.3.0
rows
integer
Terminal height in rows (default 24). Also sizes the screen: assertion’s emulator.
v0.2.0
sandbox_home
boolean
Point the pty child’s home and per-OS config/cache/data/state dirs at ${workdir}/.atago-home so a CLI touching ~/.config, ~/.cache, or %APPDATA% runs hermetically (#71).
v0.3.0
session
array of objects
Ordered expect/send script. Each entry waits for the transcript to match (expect) or types into the terminal (send). Deliberately no branching.
v0.2.0
exec
string | number | boolean | object
Runs one command on the HOST while the program under test keeps running, so a session can test what a TUI does when the world changes underneath it — a commit made outside lazygit, a file another process creates, a line appended to a log a viewer is following. It blocks until the command exits, which is the point: after it, the change exists, so the expect_screen that follows is waiting on the program noticing rather than on a race. Runs in the scenario workdir with the same environment the pty child got, so sandbox_home / clear_env isolation still holds. A non-zero exit, a timeout, or a failure to start is an execution error — the command is scaffolding, not the subject under test. Its output never joins the transcript. Note that files it writes appear in a following changes: assert, so list or ignore: them.
v0.19.0
command
string | number | boolean
Required. The command to run on the host.
v0.19.0
shell
boolean
Runs the command through the shell, like run.shell.
v0.19.0
timeout
string
Go duration bounding this command (default 10s). The remaining session budget bounds it too, whichever is shorter.
v0.19.0
expect
string | number | boolean
Waits until the transcript (scanned after the previous match) matches this Go regular expression; a never-matching expect fails when the session timeout elapses.
v0.2.0
expect_screen
object
Waits until the CURRENT rendered terminal screen matches, using the same text/JSON/YAML matcher surface as screen: except snapshot:. stable_for requires the screen to keep matching continuously for that duration, which is safer than a blind sleep across Linux/macOS/Windows redraw timing.
v0.12.0
attrs
array of objects
Checks how text is DRAWN, not only what it says (#382): the error line is red, the selected row is reverse-video, --no-color really did leave the frame uncolored. Every entry must hold. An entry is position-free by default — it passes when at least one occurrence of its text has every one of its cells carrying the demanded styling — so a styling claim does not break each time the layout shifts; pin row when the position is the point.
v0.19.0
bg
string | number | boolean
Background color, same vocabulary as fg.
v0.19.0
blink
boolean
Whether the text blinks.
v0.19.0
bold
boolean
Whether the text is bold. false is a real claim (“must NOT be bold”), not the absence of one.
v0.19.0
fg
string | number | boolean
Foreground color: an ANSI name (red, bright-red), a 256-palette index (203), or default — the terminal’s own color, which is how a --no-color contract becomes assertable.
v0.19.0
italic
boolean
Whether the text is italic.
v0.19.0
reverse
boolean
Whether the text is reverse-video, which is how most TUIs draw the selected row.
v0.19.0
row
integer
Restricts the search to this 1-based screen row, addressed the same way line addresses the text matchers.
v0.19.0
text
string | number | boolean
Required. The literal substring whose cells are checked.
Requires every listed substring (a scalar counts as a one-element list) to be present on the rendered screen.
v0.12.0
count
integer
Exact number of times the contains substring or matches regexp next to it occurs on the rendered screen (#396). Occurrences are non-overlapping.
v0.19.0
empty
boolean
Asserts the rendered screen is empty (true) or non-empty (false). A screen carrying only whitespace counts as empty, which is what makes the check usable at all: a terminal pads every unwritten cell with spaces, so a blank screen is never zero bytes.
v0.12.0
equals
string | number | boolean
Requires the rendered screen to equal this string exactly (CRLF and one trailing newline are normalized).
Parses the rendered screen as YAML and applies one JSONPath check, or a list of checks that must all hold.
v0.12.0
resize
object
Changes the terminal size mid-session, delivered the way a real terminal delivers it (SIGWINCH on POSIX, a ConPTY notification on Windows), so a TUI’s relayout is testable instead of being fixed at the size the step started with. The rendered screen follows: every later expect_screen, the final screen: assert, and its snapshot see each part of the transcript drawn at the size it was produced under. Settle the screen with an expect or expect_screen before and after a resize — output already in flight when it lands is attributed to the old size, exactly as on a real terminal.
v0.19.0
cols
integer
Required. New terminal width in columns.
v0.19.0
rows
integer
Required. New terminal height in rows.
v0.19.0
send
string | number | boolean | object
Writes to the terminal: a string verbatim ("" sends EOF/^D; ${name} expansion applies), {key: <name>} for a named key — enter, tab, shift-tab, esc, arrows, insert, f1-f12, ctrl-a..ctrl-z, alt-a..alt-z, modified arrows such as ctrl-left and shift-up, ctrl-space/ctrl-@, ctrl-[, ctrl-\, ctrl-], ctrl-^, ctrl-_, ctrl-hyphen/ctrl-minus — or {paste: <text>} to deliver the text as a bracketed paste.
Sends a mouse event as an xterm SGR (1006) report, for TUIs that accept clicks and scrolling. The step fails if the program has not enabled mouse tracking (ESC [?1000h, ESC [?1002h, or ESC [?1003h) together with SGR encoding (ESC [?1006h), because the bytes would otherwise mean nothing to it. Mutually exclusive with key, paste, and times.
v0.19.0
action
click | press | release
click (default) sends the press and its release in one write, the way a real click arrives; press and release send one half. A wheel button has no release, so click on one sends a single scroll notch and release is rejected.
v0.19.0
button
left | middle | right | wheel-up | wheel-down
Which button (default left). A wheel button sends one scroll notch.
v0.19.0
col
integer
Required. 1-based screen column of the cell to act on.
v0.19.0
mods
array of [string number boolean]
Modifier keys held during the event.
v0.19.0
row
integer
Required. 1-based screen row of the cell to act on.
v0.19.0
paste
string | number | boolean
Delivers the text as a BRACKETED PASTE, wrapped in the markers a terminal puts around pasted input, so a REPL or editor takes its paste path instead of its typing path. The step fails if the program has not enabled bracketed paste (ESC [?2004h), because the markers would otherwise arrive as ordinary characters. Mutually exclusive with key.
v0.19.0
times
integer
Presses the key this many times, as one terminal write — {key: left, times: 16} instead of sixteen identical session entries. Requires key; omit it (or 1) for a single press.
v0.19.0
shell
boolean
Runs the command through the shell, like run.shell.
v0.2.0
timeout
string
Go duration bounding the WHOLE session (default 30s).
Required. Browser actions run in order against one session; the value captured by the last text/eval/attribute/title action feeds the value: assert and store from.value.
v0.1.0
attribute
object
Captures an element attribute value.
v0.1.0
name
string | number | boolean
Required. The attribute name to capture.
v0.1.0
selector
string | number | boolean
Required. The element to read.
v0.1.0
check
string | number | boolean
Ticks the checkbox matched by the selector.
v0.1.0
click
string | number | boolean
Clicks the element matched by the selector.
v0.1.0
download
object
Clicks to trigger a download and captures the file using the server-suggested filename; the captured value is the final filename.
v0.1.0
click
string | number | boolean
Required. The element to click to start the download.
v0.1.0
dir
string | number | boolean
Workdir-relative directory to save into (default: the workdir root).
v0.1.0
eval
string | number | boolean
Evaluates a JavaScript expression and captures the result as JSON.
v0.1.0
navigate
string | number | boolean
Loads a URL.
v0.1.0
press
object
Presses a single key on an element.
v0.1.0
key
string | number | boolean
Required. The key to press (e.g. “Enter”, “Tab”, or a printable character).
v0.1.0
selector
string | number | boolean
Required. The element to receive the key press.
v0.1.0
screenshot
object
Writes a PNG of the page (or one element) into the workdir for file/image assertions.
v0.1.0
path
string | number | boolean
Required. The workdir-relative PNG path to write.
v0.1.0
selector
string | number | boolean
Limits the screenshot to this element (default: the whole page).
v0.1.0
select
object
Chooses an <option> in a <select>.
v0.1.0
selector
string | number | boolean
Required. The <select> element.
v0.1.0
value
string | number | boolean
Required. The option value to choose.
v0.1.0
send_keys
object
Types text into an element.
v0.1.0
selector
string | number | boolean
Required. The element to type into.
v0.1.0
value
string | number | boolean
Required. The text to type.
v0.1.0
text
string | number | boolean
Captures the text of the element matched by the selector.
v0.1.0
title
boolean
Captures the page title.
v0.1.0
uncheck
string | number | boolean
Unticks the checkbox matched by the selector.
v0.1.0
upload
object
Sets a file on an <input type=file> — no scripted file dialogs.
v0.1.0
file
string | number | boolean
Required. The workdir-relative file to attach; must exist.
Sends a named POSIX signal to a managed service’s process group (#23) - the race-free alternative to kill/killall for graceful-shutdown tests. POSIX-only at runtime; Windows reports a clear execution error.
Key
Type
Description
Since
service
string | number | boolean
Required. A service declared in the scenario’s services: list or started by a suite.setup service: step.
v0.3.0
signal
string | number | boolean
Required. TERM, INT, HUP, USR1, USR2, or KILL (optional SIG prefix accepted).
v0.3.0
wait
object
Block until the signaled process exits; a still-running process fails the step.
Start the child from an empty environment instead of inheriting the host environment (#16). On Windows a small system-critical set (SystemRoot, SystemDrive, TEMP, TMP, PATHEXT) is always retained.
v0.3.0
command
string | number | boolean
Required. The program to run. Tokenized and exec’d directly unless shell: true.
v0.1.0
cwd
string | number | boolean
Working directory relative to the scenario workdir.
v0.1.0
env
map of [string number boolean]
Environment variables, layered over the scenario env, with ${name} expansion.
v0.1.0
max_log_bytes
integer
Maximum bytes of combined stdout/stderr atago retains for this service. The oldest bytes are dropped first and the retained log starts with a truncation notice, so readiness excerpts and preserved log artifacts (which only ever need the tail) stay bounded. Omit for the 8 MiB default.
v0.10.0
name
string | number | boolean
Required. Identifies the service in diagnostics and signal: steps; unique per scenario.
v0.1.0
pass_env
array of [string number boolean]
Host variables copied into the cleared environment (#16). Requires clear_env: true; unset host variables are skipped.
v0.3.0
ready
object
How to wait until the service accepts work before steps run. When omitted, steps start as soon as the process is spawned.
v0.1.0
delay
string
Simply waits this Go duration — a last resort when no observable readiness signal exists.
v0.1.0
file
string | number | boolean
Ready when this workdir-relative file exists and is non-empty — the canonical pattern for a server publishing its listen address.
v0.1.0
log
string | number | boolean
Ready when the service’s combined stdout/stderr matches this regular expression.
v0.1.0
port
string | number | boolean
Ready when this TCP address (host:port) accepts a connection.
v0.1.0
store
string | number | boolean
Used with file: captures the ready file’s trimmed content into ${<name>} so steps can reference a dynamic address or port.
v0.1.0
timeout
string
Bounds the readiness wait as a Go duration (default “5s”).
v0.1.0
shell
boolean
Runs the command through the POSIX shell (pipes, redirects, ${}).
A declarative stub HTTP server (#24): routes match on exact method+path (query string excluded); an unmatched request answers 404 and is still recorded.
Key
Type
Description
Since
name
string | number | boolean
Required. Identifies the server: seeds ${<name>.url} / ${<name>.port} and is referenced by mock: asserts. Unique per scenario.
Canned responses, matched top-down on exact method+path (query string excluded; deliberately no patterns). An unmatched request answers 404 and is still recorded.
Valid after a run/pty step: pins exactly which files that step created, modified, and deleted in the scenario workdir. Each set list is exhaustive in both directions.
Inspects a generated image’s decoded properties (format, dimensions, alpha) and optionally compares its pixels against a baseline. Every set field must hold.
Asserts what the CLI under test actually sent to a declared mock server: request count, and header/body matchers on the last matching recorded request.
Requires every listed substring (a scalar counts as a one-element list) to be present.
v0.1.0
count
integer
Exact number of times the contains substring or matches regexp next to it occurs (#396). Occurrences are non-overlapping. Needs exactly one countable matcher; not combinable with equals/not_equals/empty/json/yaml/snapshot.
v0.19.0
empty
boolean
Asserts the stream is empty (true) or non-empty (false). A stream carrying only whitespace counts as empty, so a stray newline does not fail empty: true; empty: false therefore asserts the stream carried something legible, not that it carried bytes.
v0.1.0
equals
string | number | boolean
Requires the stream to equal this string exactly (CRLF and one trailing newline are normalized).
Requires the stream NOT to match this Go (RE2) regular expression.
v0.1.0
snapshot
string | number | boolean
Compares the stream against a committed golden file (spec-relative path). Volatile details (ANSI, temp paths, UUIDs, timestamps, ports, CRLF) are normalized; refresh with atago snapshot update.
A file assertion: one content matcher and any of the size bounds, which compose ({exists: true, size: 0}, {min_size: 1, max_size: 4096}). At least one matcher is required here; the loader additionally rejects two content matchers on one assert (ATG2104).
Requires every listed substring to be present in the file content.
v0.1.0
count
integer
Exact number of times the contains substring next to it occurs in the file (#396). Occurrences are non-overlapping.
v0.19.0
equals
string | number | boolean
Byte-exact content match against an inline literal (no CRLF/newline normalization).
v0.6.0
equals_file
string | number | boolean
Byte-exact content match against another workdir-confined file (round-trip/idempotence; no CRLF/newline normalization).
v0.6.0
executable
boolean
Asserts whether the file has an executable bit set (POSIX). A directory fails: every directory carries the execute bit, which means “can be entered”, not “is a program”.
v0.1.0
exists
boolean
Asserts the file exists (true) or is absent (false). A directory at that path is not a file: it fails either way and points at the dir: assertion.
Requires every listed substring to be absent from the file content.
v0.1.0
path
string | number | boolean
Required. The file under test, resolved against the scenario workdir when relative (confined to it).
v0.1.0
size
integer
Exact file length in bytes (#397). Composes with the content matchers and may also stand alone: size: 0 asserts a failed run left an empty file rather than a half-written one. Bytes are counted as written (no CRLF or trailing-newline normalization).
v0.19.0
snapshot
string | number | boolean
Compares the file content against a committed golden file, with the standard snapshot normalization.
v0.1.0
text
boolean
Store-only (#158): capture the whole file content verbatim instead of extracting a value via a json path.
Child paths (relative to path) that must exist; nested paths are allowed and confined to the directory.
v0.1.0
count
integer
Asserts the exact number of direct entries (files only under recursive: true).
v0.1.0
exists
boolean
Asserts the path exists and is a directory (false asserts it is absent).
v0.1.0
glob
string | number | boolean
Requires at least one entry to match this shell glob (basename match for patterns without /).
v0.1.0
ignore
array of [string number boolean]
Glob patterns excluded from the recursive walk and the snapshot manifest (*.log, .git/**).
v0.3.0
max_count
integer
Upper bound on the number of entries.
v0.1.0
min_count
integer
Lower bound on the number of entries.
v0.1.0
not_contains
array of [string number boolean]
Child paths (relative to path) that must NOT exist.
v0.1.0
path
string | number | boolean
Required. The directory under test, resolved against the scenario workdir when relative.
v0.1.0
recursive
boolean
Apply contains/not_contains/count/glob to the whole tree (#25): counts see files only; glob matches relative paths, or basenames for patterns without /.
v0.3.0
snapshot
string | number | boolean
Golden tree manifest (#25): sorted relative paths, one line per entry (dir/file sha256/link). Composes only with ignore; refresh with –update-snapshots.
Pins the exact workdir delta of the immediately preceding run/pty step (#70): which files it created, modified, and deleted. Each set field is EXHAUSTIVE in both directions (every observed path must match an entry, every entry must match a path), so modified: [] asserts “modified nothing”. An omitted field is unconstrained. Regular files and symlinks are tracked; directories are not (an empty directory is not a file the delta reasons about). A symlink is compared by the target it names, so planting one is a creation, retargeting it is a modification, and a dangling link is still visible. Entries are workdir-relative doublestar globs, always /-separated: a single * stays within one path segment while ** crosses / at any depth (site/** covers the whole tree, dist/**/*.css composes with a suffix). A backslash escapes a literal metacharacter (\[, \?, \*) — a\[1\].txt matches the file a[1].txt; the escape is portable because entries are always /-separated.
Exhaustive list of paths the step deleted. [] asserts “deleted nothing”.
v0.3.0
ignore
array of [string number boolean]
Workdir-relative doublestar globs whose matches are dropped from the observed delta before the categories are compared, for a path the program writes only sometimes (a state file, a cache in the sandboxed HOME). An ignored path neither counts as an unexpected change nor satisfies an entry, and an ignore glob that matches nothing is fine.
Bounds the wall-clock time of the immediately preceding run/http/query/grpc/pty step (#31). At least one bound; lt/lte and gt/gte are mutually exclusive. Values are Go duration strings (2s, 100ms).
Key
Type
Description
Since
gt
string
Exclusive lower bound as a Go duration.
v0.3.0
gte
string
Inclusive lower bound as a Go duration.
v0.3.0
lt
string
Exclusive upper bound as a Go duration (e.g. “2s”).
Asserts the encoded format, detected from content: png, jpeg, gif, webp, bmp, tiff, avif, or svg.
v0.1.0
height
integer
Asserts the exact pixel height.
v0.1.0
max_diff
number
Maximum allowed normalized mean per-pixel difference (0..1) for similar_to. Defaults to 0 (exact); lossy formats need a small tolerance like 0.02.
v0.1.0
max_height
integer
Upper bound on the pixel height.
v0.1.0
max_width
integer
Upper bound on the pixel width.
v0.1.0
min_height
integer
Lower bound on the pixel height.
v0.1.0
min_width
integer
Lower bound on the pixel width.
v0.1.0
path
string | number | boolean
Required. The image file under test, resolved against the scenario workdir when relative.
v0.1.0
similar_to
string | number | boolean
Baseline image to compare decoded pixels against. A relative path resolves against the spec file’s directory (a committed golden), falling back to the scenario workdir when no such file is there — which is how two images the run itself produced, such as the two ends of an encoder round trip, are compared. Both images must share dimensions.
Maps an Info-dictionary field (title, author, subject, keywords, creator, producer; case-insensitive) to a substring its value must contain. The field is read whether the producer leaves the Info dictionary in the clear or packs it into a compressed object stream (PDF 1.5+).
v0.1.0
min_pages
integer
Lower bound on the page count.
v0.1.0
pages
integer
Asserts the exact page count.
v0.1.0
path
string | number | boolean
Required. The PDF under test, resolved against the scenario workdir when relative.
Checks what the CLI under test sent to a mock server (#24): filter by path/method, pin the exact count (or require at least one match), and match header/body of the LAST matching request.
A json/yaml node: path selects the value, and a matcher decides what is asserted about it. An assertion needs exactly one matcher and the loader rejects it otherwise (ATG2104); a store capture takes the path alone, because it extracts the value instead of judging it.
Key
Type
Description
Since
equals
any
Requires the selected value to equal this value, compared by JSON type (the string “true” does not equal the boolean true). Write null to assert the value is JSON null; omitting the key means no matcher was set.
v0.1.0
gt
number
Requires the selected numeric value to be greater than this bound.
v0.1.0
gte
number
Requires the selected numeric value to be at least this bound.
v0.1.0
length
integer
Asserts the selected array or string has exactly this length.
v0.1.0
lt
number
Requires the selected numeric value to be less than this bound.
v0.1.0
lte
number
Requires the selected numeric value to be at most this bound.
v0.1.0
matches
string | number | boolean
Requires the selected value (as a string) to match this Go regular expression.
v0.1.0
path
string | number | boolean
Required. JSONPath selecting the value under test (e.g. “$.items[0].name”).
A runner is discriminated by type: only the fields for that type are allowed, so cross-type fields are rejected and editors can narrow completion (#44).
Key
Type
Description
Since
type: cmd
cwd
string | number | boolean
Working directory for commands run through this runner, relative to the scenario workdir. Beaten by a step’s own cwd, and beats defaults.run.cwd.
v0.1.0
timeout
string
Default timeout for steps using this runner, as a Go duration.
v0.1.0
type
any
Selects the cmd runner: local command execution (the implicit default for run steps).
v0.1.0
type: http
base_url
string | number | boolean
Base URL every http step’s path is appended to.
v0.1.0
cwd
string | number | boolean
Unused for http runners; accepted for uniformity.
v0.1.0
timeout
string
Request timeout as a Go duration.
v0.1.0
type
any
Selects the http runner: HTTP requests from http steps.
v0.1.0
type: db
cwd
string | number | boolean
Unused for db runners; accepted for uniformity.
v0.1.0
driver
sqlite | sqlite3 | postgres | postgresql | mysql
Names the database/sql driver explicitly (sqlite, postgres, or mysql), overriding scheme inference from the dsn.
v0.1.0
dsn
string | number | boolean
Required. Data source name, e.g. “sqlite:${workdir}/app.db”, “postgres://user:pass@host/db”, or “mysql://user:pass@host:3306/db”. Pure-Go drivers are bundled.
v0.1.0
timeout
string
Statement timeout as a Go duration.
v0.1.0
type
any
Selects the db runner: SQL from query steps.
v0.1.0
type: ssh
cwd
string | number | boolean
Remote working directory for commands.
v0.1.0
host
string | number | boolean
Required. Remote host, as host or host:port (default port 22).
v0.1.0
insecure_host_key
boolean
Must be set to true to connect without a known_hosts file, explicitly disabling host-key verification.
v0.1.0
key_file
string | number | boolean
Path to a private key for key authentication.
v0.1.0
known_hosts
string | number | boolean
known_hosts file verifying the host key (recommended).
v0.1.0
password
string | number | boolean
Password authentication (prefer key_file, and keep the value in an env var).
v0.1.0
timeout
string
Command timeout as a Go duration.
v0.1.0
type
any
Selects the ssh runner: running commands on a remote host.
v0.1.0
user
string | number | boolean
Required. Login user.
v0.1.0
type: grpc
cwd
string | number | boolean
Unused for grpc runners; accepted for uniformity.
v0.1.0
target
string | number | boolean
Required. host:port of the gRPC server; the schema is resolved via server reflection.
v0.1.0
timeout
string
Call timeout as a Go duration.
v0.1.0
tls
boolean
Connect with TLS (default plaintext).
v0.1.0
type
any
Selects the grpc runner: unary gRPC calls from grpc steps.
v0.1.0
type: browser
browser_args
array of [string number boolean]
Extra Chrome launch flags (bare names, no leading ‘–’), e.g. ‘disable-gpu’ or ‘window-size=1280,720’.
v0.1.0
cwd
string | number | boolean
Unused for browser runners; accepted for uniformity.
v0.1.0
exec_path
string | number | boolean
Path to a specific Chrome/Chromium binary instead of the one discovered on PATH.
v0.1.0
headless
boolean
Run Chrome without a visible window (default true); set false to debug headed.
v0.1.0
timeout
string
Session timeout as a Go duration.
v0.1.0
type
any
Selects the browser runner: headless Chrome driven by cdp steps.
Spec-wide default fragments merged into every matching element at load time (#39). Authoring sugar only: an explicit value always wins, maps shallow-merge, and a boolean default is OR-ed in. Not a macro/include system.
Key
Type
Description
Since
run
object
Layered beneath every run step. command and retry are per-step and rejected here. The environment-shaping subset (env, clear_env, pass_env, sandbox_home) also layers onto pty steps, which share the same environment surface (#77); the run-only fields (runner, shell, cwd, timeout, stdin, redirects) stay per-step and never reach pty steps.
v0.1.0
clear_env
boolean
Start the child from an empty environment instead of inheriting the host environment (#16). On Windows a small system-critical set (SystemRoot, SystemDrive, TEMP, TMP, PATHEXT) is always retained.
v0.3.0
cwd
string | number | boolean
Default working directory for every run step. Precedence: step > runner > defaults.run.
v0.1.0
env
map of [string number boolean]
Default env map shallow-merged beneath every run step’s own env.
v0.1.0
pass_env
array of [string number boolean]
Host variables copied into the cleared environment (#16). Requires clear_env: true; unset host variables are skipped.
v0.3.0
runner
string | number | boolean
Default runner name for every run step.
v0.1.0
sandbox_home
boolean
Point the child’s home and per-OS config/cache/data/state dirs at ${workdir}/.atago-home so a CLI touching ~/.config, ~/.cache, or %APPDATA% runs hermetically (#71). Precedence: step env > sandbox > pass_env > host; composes with clear_env.
v0.3.0
shell
boolean
Default shell for every run step (an authored shell: false on a step cannot override a defaulted true).
v0.1.0
timeout
string
Default timeout for every run step (a step’s own timeout wins).
v0.1.0
scenario
object
Scenario-level defaults: the env every scenario shares, and the gate every scenario is selected by.
v0.1.0
env
map of [string number boolean]
Env map shallow-merged beneath every scenario’s own env.
Default selection gate: a scenario without its own only: runs only when this holds. Declaring a probe-first suite’s gate once — only: {command: mytool --version} — is what keeps some scenarios in the file from being left ungated, which is how a suite ends up erroring on a machine without the tool instead of skipping. A scenario that states its own only: uses that one instead; the two are not combined.
Default exclusion gate: a scenario without its own skip: is skipped when this holds. A scenario that states its own skip: uses that one instead; the two are not combined.
v0.21.0
service
object
Layered beneath every service. name and command identify a service and are rejected here; a whole ready probe is copied in when a service declares none.
v0.1.0
clear_env
boolean
Start the child from an empty environment instead of inheriting the host environment (#16). On Windows a small system-critical set (SystemRoot, SystemDrive, TEMP, TMP, PATHEXT) is always retained.
v0.3.0
cwd
string | number | boolean
Default working directory for every service.
v0.1.0
env
map of [string number boolean]
Default env map for every service.
v0.1.0
pass_env
array of [string number boolean]
Host variables copied into the cleared environment (#16). Requires clear_env: true; unset host variables are skipped.
v0.3.0
ready
object
Default readiness probe for every service (a service’s own ready wins).
atago run builds it once per invocation, before any scenario, and prepends the artifact’s directory to PATH. --profile NAME swaps in that profile’s build command (whole-command replacement) and layers its env. A failing build — or one that exits 0 without writing ${artifact} — is a run-level error and no scenario executes.
It is discovered by walking up from a spec to the nearest one, so atago run ./e2e and atago run ./e2e/one.atago.yaml resolve the same configuration. Precedence is host < project < suite < scenario < step for env, and a spec file’s own defaults: beat the manifest’s. fixtures_dir resolves against the manifest’s directory and must exist at load time. atago explain prints the manifest that applied and the resolved fixtures directory. Its own schema is atago.project.schema.json.
Variable
Is
${workdir}
this scenario’s isolated temp directory — the only one it owns
${suitedir}
the suite’s scratch directory, shared by suite.setup and every scenario
${specdir}
the directory holding the spec file (read-only input)
${fixtures}
the manifest’s fixtures_dir (read-only input); unset when no manifest declares one
All four are absolute, because a scenario runs somewhere other than where its spec lives.
A JSON Schema lives at schema/atago.schema.json. With the YAML language server you get completion and validation as you type — step types, every matcher, and the ${workdir} / ${env:NAME} / ${name} / $${...} expansion rules. atago init and atago record already emit this header as the first line of every generated spec, so scaffolded specs get completion out of the box. To add it to an existing spec, use the absolute URL (it resolves in any project, unlike a repo-relative path):
atago runs on Linux, macOS, and Windows, and CI tests all three: the unit suite on every OS, the self-hosted E2E suite on Linux and macOS, and on Windows both under the native cmd.exe and under a POSIX shell. Almost everything behaves identically. This section is the short list of what does not, and why.
Behavior
Linux / macOS
Windows
shell: true
/bin/sh -c, resolved absolutely so the program under test cannot supply it
%SystemRoot%\System32\cmd.exe /S /C, resolved the same way. ATAGO_SHELL overrides on both
signal: steps
delivers TERM, INT, HUP, USR1, USR2, KILL to the service’s process group
not supported — Windows has no POSIX signals. Gate with skip: {os: windows}
cancel / timeout teardown
kills the whole process group
kills the whole process tree (taskkill /T)
pty: steps and atago record --pty
a real pty
a ConPTY, which needs Windows 10 version 1809 or later. record --pty cannot auto-detect a password prompt there, because a ConPTY exposes no echo state — convert a secret send to ${env:...} by hand
file: {executable: ...}
the mode bits
the file extension against PATHEXT, which is what Windows uses to decide what it runs by name. There is no execute bit to read
fixture: {mode: ...}
sets the permission bits
no effect — Windows has no POSIX permissions
fixture: {symlink: ...}
always available
needs Developer Mode or an elevated process
changes:
compares content, symlink target, kind, and permission bits
compares content, symlink target, and kind. Permissions are left out: Go synthesizes a mode from the read-only attribute, so including it would make one spec report a different delta per OS
shell: true runs the platform’s own interpreter, so a command written for /bin/sh does not run under cmd.exe. Two ways out. Keep the command portable — echo and exit are builtins of both, and run.env:, run.stdin:, run.stdout_to: cover the variable prefixes and redirects a spec usually reaches for a shell to get. Or point atago at the shell you want:
ATAGO_SHELL='C:\Program Files\Git\bin\bash.exe' atago run ./e2e
ATAGO_SHELL takes an absolute path on either platform. atago picks the calling convention from the name: /S /C for cmd.exe, -c for anything else, which covers the bash that ships with Git for Windows and MSYS2 as well as PowerShell. One caveat when pairing a POSIX shell with Windows paths: ${workdir}, ${specdir}, and ${atago} expand to backslash paths, and a POSIX shell reads a backslash as an escape — so interpolate them into argv-form commands (shell: false) rather than into shell commands.
no unexpected failures — every scenario passed, was skipped, or was an XFAIL (an expect_fail: scenario that failed as declared)
1
one or more failed (including an XPASS: an expect_fail: scenario that passed, unless --allow-xpass)
2
spec error (YAML syntax or schema/semantic validation)
3
CLI-invocation error (unknown subcommand, bad flag, or no matching spec files)
4
execution error
5
internal error
6
security policy violation
Ctrl-C/SIGTERM stops the run cleanly: in-flight processes, services, and sessions are torn down, partial results are reported, and the run exits 4.
Errors also carry a diagnostic code such as ATG2201, whose first digit is the exit code above — so ATG2xxx always exits 2. The codes are searchable and stable across rewordings of the message; Error codes lists what each one means, what to change, and which families carry codes today. Assertion failures carry none: exit 1 is a result, not an error. atago explain ATG2201 prints the same entry without a browser, and the JSON report carries the code as a code field on each failure so a dashboard can group by cause.