Reference

On this page

Subcommands #

CommandDoes
atago runrun specs and report results
atago recordrun a command once and write a spec from what it observed (--pty for interactive sessions)
atago initscaffold a spec (--template for browser, cli, db, grpc, http, mock, services, ssh; cli is the default)
atago snapshot updaterecord or refresh golden files
atago explaindescribe what a spec does without running it, or what an ATG code means (atago explain ATG2201)
atago docgenerate Markdown from specs, with fixtures and golden files inlined
atago manifestemit a stable JSON summary of specs for tooling
atago listshow scenarios, tags, and artifacts
atago completionprint a shell completion script

explain, doc, manifest, and list all load and validate the spec first — exit code 2 on a schema error — so any of them doubles as a lint step in CI.

Selecting scenarios #

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 testing #

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):

scrub:
  - {pattern: 'id=\d+', placeholder: 'id=<ID>'}

See the scrub example.

Spec file keys #

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).

Top level #

The keys of a spec file document.

KeyTypeDescriptionSince
defaultsdefaultsSpec-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
permissionsobjectThe spec’s security policy.v0.1.0
networkobjectNetwork egress policy for the spec.v0.1.0
allowarray 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).v0.1.0
runnersmap of runnerNamed 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.v0.1.0
scenariosarray of scenarioRequired. The named behaviors under test. Each scenario runs in its own isolated temporary workdir.v0.1.0
scrubarray of objectsDeclarative 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
patternstring | number | booleanRequired. A Go (RE2) regular expression; every match is replaced. Required.v0.5.0
placeholderstring | number | booleanLiteral replacement text (no $1 expansion), e.g. “<ID>”. Defaults to empty, which deletes matches.v0.5.0
secretsarray of [string number boolean]Environment variable names whose VALUES are masked as *** in output, reports, and snapshots.v0.1.0
suiteobjectRequired. 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
descriptionstring | number | booleanOptional 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
envmap of [string number boolean]Environment exported to every scenario, setup, and teardown step (a scenario’s own env wins per key).v0.2.0
namestring | number | booleanRequired. The suite name shown in reports and generated docs.v0.1.0
setuparray of stepSteps 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.v0.2.0
teardownarray of stepSteps 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
timeoutstringSuite-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
version1 | 1Required. Spec format version. Write “1” (quoted); a bare 1 is accepted for convenience — the loader coerces it to “1”.v0.1.0

scenarios[*] #

KeyTypeDescriptionSince
descriptionstring | number | booleanOptional 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
envmap of [string number boolean]Environment variables for every step in the scenario (a step’s own env wins per key).v0.1.0
expect_failobjectDeclares 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
issuestring | number | booleanWhere the bug is tracked. Optional, but it is what makes an XPASS actionable — the message can name the issue to close.v0.19.0
reasonstring | number | booleanRequired. 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
matrixarray of objectMakes the scenario a template: one concrete instance per row, with each row’s key/value pairs seeded as ${name} variables.v0.1.0
mock_serversarray of mockServerDeclarative 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
namestring | number | booleanRequired. The scenario name shown in reports; with matrix:, ${var} placeholders make each instance’s name unique.v0.1.0
onlyconditionRuns the scenario only when the condition holds.v0.1.0
servicesarray of serviceBackground processes started before the steps and terminated (with their process group) when the scenario ends — peers the CLI under test talks to.v0.1.0
skipconditionSkips the scenario when the condition holds (os match, env var set, or probe command succeeding).v0.1.0
stepsarray of stepRequired. The ordered actions: fixtures, commands, requests, assertions, captures.v0.1.0
tagsarray of [string number boolean]Labels for run-time selection with --tag / --skip-tag.v0.1.0
teardownarray of stepSteps 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.v0.1.0

steps[*] #

KeyTypeDescriptionSince
assertassertChecks externally observable behavior of the preceding step. Exactly one target family per assert block.v0.1.0
cdpcdpDrives a headless Chrome through a browser runner with a declarative action list.v0.1.0
fixturefixtureMaterializes an input file in the scenario workdir before the command runs.v0.1.0
grpcgrpcCalls a unary gRPC method through a grpc runner via server reflection.v0.1.0
httphttpIssues an HTTP request through an http runner — as a peer of the CLI under test.v0.1.0
mock_servermockServerStarts a suite-wide stub HTTP server at this point in the sequence (#24). Valid only inside suite.setup.v0.3.0
ptyptyRuns 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).v0.2.0
queryqueryRuns a SQL statement through a db runner; SELECT rows feed the rows: assert.v0.1.0
runrunExecutes a command and captures its exit code, stdout, and stderr.v0.1.0
serviceserviceStarts a suite-wide background process at this point in the sequence. Valid only inside suite.setup; the loader rejects it anywhere else.v0.2.0
signalsignalSends a named POSIX signal to a managed service — the race-free alternative to kill hacks for graceful-shutdown tests. POSIX-only.v0.3.0
storestoreCaptures a value into the variable store; later steps reference it as ${name}.v0.1.0

fixture #

KeyTypeDescriptionSince
base64string | number | booleanInline binary content, base64-encoded — carries exact bytes YAML text cannot.v0.1.0
contentstring | number | booleanInline text content for the file. Exactly one source (content/base64/from/symlink) is set.v0.1.0
filestring | number | booleanRequired. The workdir-relative path to create.v0.1.0
fromstring | number | booleanCopies an existing file (e.g. committed testdata), resolved relative to the spec file’s directory.v0.1.0
modestring | number | booleanOctal file mode (e.g. “0755”) applied after writing.v0.1.0
mtimestring | number | booleanRFC3339 modification time applied after writing — pins timestamps for freshness/change-detection tests.v0.1.0
symlinkstring | number | booleanMakes file a symbolic link to this target instead of a regular file. The target is written verbatim.v0.1.0

run #

KeyTypeDescriptionSince
clear_envbooleanStart 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
commandstring | number | booleanRequired. The program to run. Tokenized and exec’d directly unless shell: true.v0.1.0
cwdstring | number | booleanWorking directory relative to the scenario workdir (default: the workdir itself).v0.1.0
deterministicdeterministicRe-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
envmap of [string number boolean]Environment variables for the child process, layered over the scenario env; values get ${name} expansion.v0.1.0
pass_envarray of [string number boolean]Host variables copied into the cleared environment (#16). Requires clear_env: true; unset host variables are skipped.v0.3.0
retryretryRe-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
runnerstring | number | booleanNames a declared runner to execute through (e.g. an ssh runner runs the command remotely). Default: local execution.v0.1.0
sandbox_homebooleanPoint 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
shellbooleanRuns the command through the shell (/bin/sh on POSIX, cmd.exe on Windows) — for pipes, redirects, and builtins.v0.1.0
stderr_tostring | number | booleanWrite the command’s captured stderr to this workdir-relative file (create/truncate), without needing shell redirection.v0.1.0
stdinstring | number | boolean | objectStandard 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
filestring | number | booleanRequired. Feeds the command’s stdin from a workdir-relative file.v0.3.0
base64string | number | booleanRequired. Feeds the command’s stdin with exact binary bytes, base64-encoded.v0.3.0
stdout_tostring | number | booleanWrite the command’s captured stdout to this workdir-relative file (create/truncate), without needing shell redirection.v0.1.0
timeoutstringBounds this command as a Go duration. Precedence: step > runner > defaults.run > suite > built-in 60s.v0.1.0

pty #

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.

KeyTypeDescriptionSince
clear_envbooleanStart 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
colsintegerTerminal width in columns (default 80).v0.2.0
commandstring | number | booleanRequired. The program to run inside a real pseudo-terminal (a ConPTY on Windows). The captured transcript becomes the step’s stdout.v0.2.0
cwdstring | number | booleanWorking directory relative to the scenario workdir.v0.2.0
envmap 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_envarray of [string number boolean]Host variables copied into the cleared environment (#16). Requires clear_env: true; unset host variables are skipped.v0.3.0
rowsintegerTerminal height in rows (default 24). Also sizes the screen: assertion’s emulator.v0.2.0
sandbox_homebooleanPoint 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
sessionarray of objectsOrdered expect/send script. Each entry waits for the transcript to match (expect) or types into the terminal (send). Deliberately no branching.v0.2.0
execstring | number | boolean | objectRuns 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
commandstring | number | booleanRequired. The command to run on the host.v0.19.0
shellbooleanRuns the command through the shell, like run.shell.v0.19.0
timeoutstringGo duration bounding this command (default 10s). The remaining session budget bounds it too, whichever is shorter.v0.19.0
expectstring | number | booleanWaits 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_screenobjectWaits 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
attrsarray of objectsChecks 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
bgstring | number | booleanBackground color, same vocabulary as fg.v0.19.0
blinkbooleanWhether the text blinks.v0.19.0
boldbooleanWhether the text is bold. false is a real claim (“must NOT be bold”), not the absence of one.v0.19.0
fgstring | number | booleanForeground 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
italicbooleanWhether the text is italic.v0.19.0
reversebooleanWhether the text is reverse-video, which is how most TUIs draw the selected row.v0.19.0
rowintegerRestricts the search to this 1-based screen row, addressed the same way line addresses the text matchers.v0.19.0
textstring | number | booleanRequired. The literal substring whose cells are checked.v0.19.0
underlinebooleanWhether the text is underlined.v0.19.0
containsstringOrListRequires every listed substring (a scalar counts as a one-element list) to be present on the rendered screen.v0.12.0
countintegerExact 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
emptybooleanAsserts 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
equalsstring | number | booleanRequires the rendered screen to equal this string exactly (CRLF and one trailing newline are normalized).v0.12.0
jsonjsonChecksParses the rendered screen as JSON and applies one JSONPath check, or a list of checks that must all hold.v0.12.0
lineinteger1-based line selector: applies the matcher to that single rendered row instead of the whole screen.v0.12.0
matchesstring | number | booleanRequires the rendered screen to match this Go (RE2) regular expression.v0.12.0
max_countintegerUpper bound on how many times the contains substring or matches regexp occurs on the rendered screen (#396).v0.19.0
min_countintegerLower bound on how many times the contains substring or matches regexp occurs on the rendered screen (#396).v0.19.0
not_containsstringOrListRequires every listed substring to be absent from the rendered screen.v0.12.0
not_equalsstring | number | booleanRequires the rendered screen NOT to equal this string.v0.12.0
not_matchesstring | number | booleanRequires the rendered screen NOT to match this Go (RE2) regular expression.v0.12.0
stable_forstringOptional Go duration the rendered screen must keep matching continuously before the wait passes.v0.12.0
timeoutstringOptional Go duration bounding only this wait; the enclosing pty timeout still caps the whole session.v0.12.0
yamljsonChecksParses the rendered screen as YAML and applies one JSONPath check, or a list of checks that must all hold.v0.12.0
resizeobjectChanges 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
colsintegerRequired. New terminal width in columns.v0.19.0
rowsintegerRequired. New terminal height in rows.v0.19.0
sendstring | number | boolean | objectWrites 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.v0.2.0
keystring | number | booleanNamed key: enter, tab, shift-tab (alias backtab), esc, space, backspace, delete, insert, arrows, home/end, pageup/pagedown, f1-f12, ctrl-a..ctrl-z, alt-a..alt-z, alt-enter, alt-backspace, modified arrows (ctrl-left, shift-up, …), ctrl-space/ctrl-@, ctrl-[, ctrl-\, ctrl-], ctrl-^, ctrl-_, ctrl-hyphen/ctrl-minus.v0.3.0
mouseobjectSends 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
actionclick | press | releaseclick (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
buttonleft | middle | right | wheel-up | wheel-downWhich button (default left). A wheel button sends one scroll notch.v0.19.0
colintegerRequired. 1-based screen column of the cell to act on.v0.19.0
modsarray of [string number boolean]Modifier keys held during the event.v0.19.0
rowintegerRequired. 1-based screen row of the cell to act on.v0.19.0
pastestring | number | booleanDelivers 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
timesintegerPresses 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
shellbooleanRuns the command through the shell, like run.shell.v0.2.0
timeoutstringGo duration bounding the WHOLE session (default 30s).v0.2.0

http #

KeyTypeDescriptionSince
bodystring | number | booleanRaw string payload sent verbatim (default Content-Type text/plain). The payload fields (json/body/body_file/form+files) are mutually exclusive.v0.1.0
body_filestring | number | booleanWorkdir-relative file streamed as the raw request body (binary-safe) — for upload endpoints that take file content directly.v0.1.0
body_tostring | number | booleanWrite the response body to this workdir-relative file, so file/image/pdf assertions can inspect a download (the http analog of run’s stdout_to).v0.1.0
filesarray of objectsFiles attached as multipart/form-data parts (the browser-style upload most web apps expect).v0.1.0
content_typestring | number | booleanOverrides the part’s Content-Type (default: detected from content, falling back to application/octet-stream).v0.1.0
fieldstring | number | booleanRequired. The multipart form field name the server reads the file from.v0.1.0
pathstring | number | booleanRequired. The workdir-relative file whose content becomes the part body.v0.1.0
follow_redirectsbooleanFollow 3xx responses (default true). Set false to assert on the redirect itself: its status code and Location header.v0.1.0
formmap of [string number boolean]Form fields: application/x-www-form-urlencoded alone, multipart/form-data when files is also set.v0.1.0
headermap of [string number boolean]Request headers.v0.1.0
jsonanyv0.1.0
methodstring | number | booleanRequired. The HTTP method (GET, POST, …).v0.1.0
pathstring | number | booleanRequest path appended to the runner’s base_url; ${name} expansion applies.v0.1.0
retryretryRe-issues the request until the until assertion passes — declarative polling for eventually-consistent endpoints.v0.1.0
runnerstring | number | booleanNames the http runner declaring the base_url. With exactly one http runner declared it may be omitted.v0.1.0

query #

KeyTypeDescriptionSince
runnerstring | number | booleanRequired. Names the db runner declaring the dsn.v0.1.0
sqlstring | number | booleanRequired. The SQL statement. SELECT rows are captured as JSON for rows: asserts and store from.rows; other statements record their affected-row count.v0.1.0

grpc #

KeyTypeDescriptionSince
headermap of [string number boolean]Request metadata headers.v0.1.0
jsonanyv0.1.0
methodstring | number | booleanRequired. The unary method to call as “package.Service/Method”; the schema is resolved via server reflection (no compiled stubs).v0.1.0
runnerstring | number | booleanRequired. Names the grpc runner declaring the target server.v0.1.0

cdp #

KeyTypeDescriptionSince
actionsarray of objectsRequired. 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
attributeobjectCaptures an element attribute value.v0.1.0
namestring | number | booleanRequired. The attribute name to capture.v0.1.0
selectorstring | number | booleanRequired. The element to read.v0.1.0
checkstring | number | booleanTicks the checkbox matched by the selector.v0.1.0
clickstring | number | booleanClicks the element matched by the selector.v0.1.0
downloadobjectClicks to trigger a download and captures the file using the server-suggested filename; the captured value is the final filename.v0.1.0
clickstring | number | booleanRequired. The element to click to start the download.v0.1.0
dirstring | number | booleanWorkdir-relative directory to save into (default: the workdir root).v0.1.0
evalstring | number | booleanEvaluates a JavaScript expression and captures the result as JSON.v0.1.0
navigatestring | number | booleanLoads a URL.v0.1.0
pressobjectPresses a single key on an element.v0.1.0
keystring | number | booleanRequired. The key to press (e.g. “Enter”, “Tab”, or a printable character).v0.1.0
selectorstring | number | booleanRequired. The element to receive the key press.v0.1.0
screenshotobjectWrites a PNG of the page (or one element) into the workdir for file/image assertions.v0.1.0
pathstring | number | booleanRequired. The workdir-relative PNG path to write.v0.1.0
selectorstring | number | booleanLimits the screenshot to this element (default: the whole page).v0.1.0
selectobjectChooses an <option> in a <select>.v0.1.0
selectorstring | number | booleanRequired. The <select> element.v0.1.0
valuestring | number | booleanRequired. The option value to choose.v0.1.0
send_keysobjectTypes text into an element.v0.1.0
selectorstring | number | booleanRequired. The element to type into.v0.1.0
valuestring | number | booleanRequired. The text to type.v0.1.0
textstring | number | booleanCaptures the text of the element matched by the selector.v0.1.0
titlebooleanCaptures the page title.v0.1.0
uncheckstring | number | booleanUnticks the checkbox matched by the selector.v0.1.0
uploadobjectSets a file on an <input type=file> — no scripted file dialogs.v0.1.0
filestring | number | booleanRequired. The workdir-relative file to attach; must exist.v0.1.0
selectorstring | number | booleanRequired. The <input type=file> element.v0.1.0
wait_hiddenstring | number | booleanWaits until the CSS selector is hidden or absent.v0.1.0
wait_visiblestring | number | booleanWaits until the CSS selector is visible.v0.1.0
runnerstring | number | booleanRequired. Names the browser runner to drive.v0.1.0

store #

KeyTypeDescriptionSince
fromobjectRequired. Where the value comes from — exactly one source.v0.1.0
bodystreamExtracts from the preceding http step’s response body via a json path or regex.v0.1.0
filefileReads a generated file: a json path selects a value, or text: true captures the whole content.v0.1.0
headerstring | number | booleanCaptures an HTTP response header value by name.v0.1.0
messagestreamExtracts from the preceding grpc step’s response message via a json path.v0.1.0
rowsstreamExtracts from the preceding query step’s result rows (a JSON array) via a json path.v0.1.0
stdoutstreamExtracts from the preceding step’s stdout via a json path or matches regex, or captures the whole stream with trim.v0.1.0
valuestreamExtracts from the last browser-captured value via a json path or regex.v0.1.0
namestring | number | booleanRequired. The variable name; later steps reference the captured value as ${name}.v0.1.0

signal #

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.

KeyTypeDescriptionSince
servicestring | number | booleanRequired. A service declared in the scenario’s services: list or started by a suite.setup service: step.v0.3.0
signalstring | number | booleanRequired. TERM, INT, HUP, USR1, USR2, or KILL (optional SIG prefix accepted).v0.3.0
waitobjectBlock until the signaled process exits; a still-running process fails the step.v0.3.0
timeoutstringGo duration bounding the wait (default 5s).v0.3.0

service #

KeyTypeDescriptionSince
clear_envbooleanStart 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
commandstring | number | booleanRequired. The program to run. Tokenized and exec’d directly unless shell: true.v0.1.0
cwdstring | number | booleanWorking directory relative to the scenario workdir.v0.1.0
envmap of [string number boolean]Environment variables, layered over the scenario env, with ${name} expansion.v0.1.0
max_log_bytesintegerMaximum 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
namestring | number | booleanRequired. Identifies the service in diagnostics and signal: steps; unique per scenario.v0.1.0
pass_envarray of [string number boolean]Host variables copied into the cleared environment (#16). Requires clear_env: true; unset host variables are skipped.v0.3.0
readyobjectHow to wait until the service accepts work before steps run. When omitted, steps start as soon as the process is spawned.v0.1.0
delaystringSimply waits this Go duration — a last resort when no observable readiness signal exists.v0.1.0
filestring | number | booleanReady when this workdir-relative file exists and is non-empty — the canonical pattern for a server publishing its listen address.v0.1.0
logstring | number | booleanReady when the service’s combined stdout/stderr matches this regular expression.v0.1.0
portstring | number | booleanReady when this TCP address (host:port) accepts a connection.v0.1.0
storestring | number | booleanUsed with file: captures the ready file’s trimmed content into ${<name>} so steps can reference a dynamic address or port.v0.1.0
timeoutstringBounds the readiness wait as a Go duration (default “5s”).v0.1.0
shellbooleanRuns the command through the POSIX shell (pipes, redirects, ${}).v0.1.0

mock_server #

A declarative stub HTTP server (#24): routes match on exact method+path (query string excluded); an unmatched request answers 404 and is still recorded.

KeyTypeDescriptionSince
namestring | number | booleanRequired. Identifies the server: seeds ${<name>.url} / ${<name>.port} and is referenced by mock: asserts. Unique per scenario.v0.3.0
routesarray of mockRouteCanned responses, matched top-down on exact method+path (query string excluded; deliberately no patterns). An unmatched request answers 404 and is still recorded.v0.3.0

mock_server routes[*] #

One canned response. At most one of json/body/body_file supplies the payload; status defaults to 200.

KeyTypeDescriptionSince
bodystring | number | booleanInline text response body.v0.3.0
body_filestring | number | booleanSpec-relative response body file, confined to the spec directory.v0.3.0
delaystringGo duration to sleep before responding - for retry testing.v0.3.0
headermap of [string number boolean]Extra response headers to set.v0.3.0
jsonanyInline response document, marshaled with Content-Type: application/json.v0.3.0
methodstring | number | booleanRequired. HTTP method to match (case-insensitive).v0.3.0
pathstring | number | booleanRequired. Exact request path to match (query string excluded).v0.3.0
statusintegerResponse status code (default 200).v0.3.0

assert #

An assert sets one or more target families; each is an independent check and all must hold (e.g. exit_code + stdout + file in one block).

KeyTypeDescriptionSince
bodystreamMatches the HTTP response body of the preceding http step with the stream matchers.v0.1.0
changeschangesAssertValid 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.v0.3.0
dirdirBlack-box checks over a generated directory: existence, expected/forbidden children, entry counts, glob coverage, or a whole-tree snapshot manifest.v0.1.0
durationdurationAssertBounds how long the preceding measurable step (run/http/query/grpc/pty) took, with Go-duration lt/lte/gt/gte bounds.v0.3.0
exit_codeexitCodeAsserts the preceding command’s exit status: a bare integer, {not: N}, or a documented set {in: [0, 2]}.v0.1.0
filefileChecks a generated file: existence, content substrings, byte-exact equality, JSONPath checks, executability, or a snapshot.v0.1.0
grpc_statusintegerAsserts the numeric gRPC status code of the preceding grpc step (0 = OK).v0.1.0
headerheaderMatchMatches one HTTP response header of the preceding http step by name.v0.1.0
imageimageInspects a generated image’s decoded properties (format, dimensions, alpha) and optionally compares its pixels against a baseline. Every set field must hold.v0.1.0
messagestreamMatches the preceding grpc step’s response message (as JSON) with the stream matchers.v0.1.0
mockmockAssertAsserts what the CLI under test actually sent to a declared mock server: request count, and header/body matchers on the last matching recorded request.v0.3.0
pdfpdfChecks a generated PDF: page count, Info-dictionary metadata, and extracted text. Every set field must hold.v0.1.0
rowsstreamMatches the preceding query step’s result rows, captured as a JSON array, with the stream matchers (json path/length, contains, …).v0.1.0
screenscreenAssertThe rendered terminal screen of the preceding pty step (#27), asserted as plain text and — with attrs — as colors and styling (#382).v0.3.0
statusintegerAsserts the HTTP response status code of the preceding http step.v0.1.0
stderrstreamMatches the preceding step’s captured standard error with exactly one stream matcher.v0.1.0
stdoutstreamMatches the preceding step’s captured standard output with exactly one stream matcher.v0.1.0
valuestreamMatches the value captured by the last browser text/eval/attribute/title action with the stream matchers.v0.1.0

stream matchers (stdout / stderr / body / rows / message / value / screen) #

KeyTypeDescriptionSince
containsstringOrListRequires every listed substring (a scalar counts as a one-element list) to be present.v0.1.0
countintegerExact 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
emptybooleanAsserts 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
equalsstring | number | booleanRequires the stream to equal this string exactly (CRLF and one trailing newline are normalized).v0.1.0
jsonjsonChecksParses the stream as JSON and applies one JSONPath check, or a list of checks that must all hold.v0.1.0
lineinteger1-based line selector: applies the matcher to that single line instead of the whole stream. Not itself a matcher; does not compose with json/snapshot.v0.1.0
matchesstring | number | booleanRequires the stream to match this Go (RE2) regular expression.v0.1.0
max_countintegerUpper bound on how many times the contains substring or matches regexp occurs (#396). max_count: 0 reads as “never”.v0.19.0
min_countintegerLower bound on how many times the contains substring or matches regexp occurs (#396).v0.19.0
not_containsstringOrListRequires every listed substring to be absent.v0.1.0
not_equalsstring | number | booleanRequires the stream NOT to equal this string.v0.1.0
not_matchesstring | number | booleanRequires the stream NOT to match this Go (RE2) regular expression.v0.1.0
snapshotstring | number | booleanCompares 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.v0.1.0
trimbooleanStore-only (#158): capture the whole stream. trim: true strips surrounding whitespace; trim: false keeps bytes verbatim.v0.6.0
yamljsonChecksParses the stream as YAML and applies one JSONPath check, or a list of checks that must all hold.v0.1.0

exit_code #

KeyTypeDescriptionSince
notintegerRequired. Asserts the exit code is anything but this value.v0.1.0
inarray of integerRequired. The exit code must be one of these values (#19) — the contract shape of grep (0/1) or terraform plan -detailed-exitcode (0/2).v0.3.0

file #

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).

KeyTypeDescriptionSince
containsstringOrListRequires every listed substring to be present in the file content.v0.1.0
countintegerExact number of times the contains substring next to it occurs in the file (#396). Occurrences are non-overlapping.v0.19.0
equalsstring | number | booleanByte-exact content match against an inline literal (no CRLF/newline normalization).v0.6.0
equals_filestring | number | booleanByte-exact content match against another workdir-confined file (round-trip/idempotence; no CRLF/newline normalization).v0.6.0
executablebooleanAsserts 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
existsbooleanAsserts 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.v0.1.0
jsonjsonChecksParses the file as JSON and applies one JSONPath check, or a list of checks that must all hold.v0.1.0
max_countintegerUpper bound on how many times the contains substring occurs in the file (#396).v0.19.0
max_sizeintegerUpper bound on the file length in bytes (#397) — the regression shape of a compression or bundling bug, where output size is the product.v0.19.0
min_countintegerLower bound on how many times the contains substring occurs in the file (#396).v0.19.0
min_sizeintegerLower bound on the file length in bytes (#397). min_size: 1 is the portable spelling of “non-empty”.v0.19.0
not_containsstringOrListRequires every listed substring to be absent from the file content.v0.1.0
pathstring | number | booleanRequired. The file under test, resolved against the scenario workdir when relative (confined to it).v0.1.0
sizeintegerExact 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
snapshotstring | number | booleanCompares the file content against a committed golden file, with the standard snapshot normalization.v0.1.0
textbooleanStore-only (#158): capture the whole file content verbatim instead of extracting a value via a json path.v0.6.0

dir #

KeyTypeDescriptionSince
containsarray of [string number boolean]Child paths (relative to path) that must exist; nested paths are allowed and confined to the directory.v0.1.0
countintegerAsserts the exact number of direct entries (files only under recursive: true).v0.1.0
existsbooleanAsserts the path exists and is a directory (false asserts it is absent).v0.1.0
globstring | number | booleanRequires at least one entry to match this shell glob (basename match for patterns without /).v0.1.0
ignorearray of [string number boolean]Glob patterns excluded from the recursive walk and the snapshot manifest (*.log, .git/**).v0.3.0
max_countintegerUpper bound on the number of entries.v0.1.0
min_countintegerLower bound on the number of entries.v0.1.0
not_containsarray of [string number boolean]Child paths (relative to path) that must NOT exist.v0.1.0
pathstring | number | booleanRequired. The directory under test, resolved against the scenario workdir when relative.v0.1.0
recursivebooleanApply 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
snapshotstring | number | booleanGolden tree manifest (#25): sorted relative paths, one line per entry (dir/file sha256/link). Composes only with ignore; refresh with –update-snapshots.v0.3.0

changes #

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.

KeyTypeDescriptionSince
createdchangesEntriesExhaustive list of paths the step created: every observed creation must match an entry and vice versa. [] asserts “created nothing”.v0.3.0
deletedchangesEntriesExhaustive list of paths the step deleted. [] asserts “deleted nothing”.v0.3.0
ignorearray 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.v0.16.0
modifiedchangesEntriesExhaustive list of paths the step modified (content-based, not mtime; for a symlink, a change of target). [] asserts “modified nothing”.v0.3.0

duration #

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).

KeyTypeDescriptionSince
gtstringExclusive lower bound as a Go duration.v0.3.0
gtestringInclusive lower bound as a Go duration.v0.3.0
ltstringExclusive upper bound as a Go duration (e.g. “2s”).v0.3.0
ltestringInclusive upper bound as a Go duration.v0.3.0

image #

KeyTypeDescriptionSince
alphabooleanAsserts whether the image actually carries transparency (any non-opaque pixel), scanning decoded pixels.v0.1.0
formatpng | jpeg | gif | webp | bmp | tiff | avif | svgAsserts the encoded format, detected from content: png, jpeg, gif, webp, bmp, tiff, avif, or svg.v0.1.0
heightintegerAsserts the exact pixel height.v0.1.0
max_diffnumberMaximum 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_heightintegerUpper bound on the pixel height.v0.1.0
max_widthintegerUpper bound on the pixel width.v0.1.0
min_heightintegerLower bound on the pixel height.v0.1.0
min_widthintegerLower bound on the pixel width.v0.1.0
pathstring | number | booleanRequired. The image file under test, resolved against the scenario workdir when relative.v0.1.0
similar_tostring | number | booleanBaseline 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.v0.1.0
widthintegerAsserts the exact pixel width.v0.1.0

pdf #

KeyTypeDescriptionSince
max_pagesintegerUpper bound on the page count.v0.1.0
metadatamap of [string number boolean]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_pagesintegerLower bound on the page count.v0.1.0
pagesintegerAsserts the exact page count.v0.1.0
pathstring | number | booleanRequired. The PDF under test, resolved against the scenario workdir when relative.v0.1.0
textstreamApplies the stream matchers to the text extracted from the PDF’s content streams.v0.1.0

mock #

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.

KeyTypeDescriptionSince
bodystreamMatches the body of the LAST matching recorded request with the stream matchers.v0.3.0
countintegerAsserts the exact number of matching requests (without it, at least one must exist).v0.3.0
headerheaderMatchMatches a header of the LAST matching recorded request.v0.3.0
methodstring | number | booleanFilters recorded requests by method (case-insensitive).v0.3.0
namestring | number | booleanRequired. References a declared mock server by name.v0.3.0
pathstring | number | booleanFilters recorded requests by exact path.v0.3.0

header matcher #

KeyTypeDescriptionSince
containsstring | number | booleanRequires the header value to contain this substring.v0.1.0
equalsstring | number | booleanRequires the header value to equal this string exactly.v0.1.0
matchesstring | number | booleanRegexp the header value must match - the natural shape for auth headers (^Bearer ).v0.3.0
namestring | number | booleanRequired. The header name to check (case-insensitive).v0.1.0

json / yaml checks #

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.

KeyTypeDescriptionSince
equalsanyRequires 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
gtnumberRequires the selected numeric value to be greater than this bound.v0.1.0
gtenumberRequires the selected numeric value to be at least this bound.v0.1.0
lengthintegerAsserts the selected array or string has exactly this length.v0.1.0
ltnumberRequires the selected numeric value to be less than this bound.v0.1.0
ltenumberRequires the selected numeric value to be at most this bound.v0.1.0
matchesstring | number | booleanRequires the selected value (as a string) to match this Go regular expression.v0.1.0
pathstring | number | booleanRequired. JSONPath selecting the value under test (e.g. “$.items[0].name”).v0.1.0

retry #

KeyTypeDescriptionSince
intervalstringWait between attempts as a Go duration (e.g. “200ms”). Empty means no wait.v0.1.0
timesintegerRequired. Maximum number of attempts (>= 1).v0.1.0
untilassertRequired. A single assertion polled after each attempt; the loop stops as soon as it passes. If it never passes, the step fails.v0.1.0

skip / only conditions #

KeyTypeDescriptionSince
commandstring | number | booleanTrue when this probe command (run through the shell) exits 0.v0.1.0
envstring | number | booleanTrue when the named environment variable is non-empty.v0.1.0
oslinux | darwin | windowsMatches the host operating system (linux, darwin, windows).v0.1.0

runners.<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).

KeyTypeDescriptionSince
type: cmd
cwdstring | number | booleanWorking 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
timeoutstringDefault timeout for steps using this runner, as a Go duration.v0.1.0
typeanySelects the cmd runner: local command execution (the implicit default for run steps).v0.1.0
type: http
base_urlstring | number | booleanBase URL every http step’s path is appended to.v0.1.0
cwdstring | number | booleanUnused for http runners; accepted for uniformity.v0.1.0
timeoutstringRequest timeout as a Go duration.v0.1.0
typeanySelects the http runner: HTTP requests from http steps.v0.1.0
type: db
cwdstring | number | booleanUnused for db runners; accepted for uniformity.v0.1.0
driversqlite | sqlite3 | postgres | postgresql | mysqlNames the database/sql driver explicitly (sqlite, postgres, or mysql), overriding scheme inference from the dsn.v0.1.0
dsnstring | number | booleanRequired. 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
timeoutstringStatement timeout as a Go duration.v0.1.0
typeanySelects the db runner: SQL from query steps.v0.1.0
type: ssh
cwdstring | number | booleanRemote working directory for commands.v0.1.0
hoststring | number | booleanRequired. Remote host, as host or host:port (default port 22).v0.1.0
insecure_host_keybooleanMust be set to true to connect without a known_hosts file, explicitly disabling host-key verification.v0.1.0
key_filestring | number | booleanPath to a private key for key authentication.v0.1.0
known_hostsstring | number | booleanknown_hosts file verifying the host key (recommended).v0.1.0
passwordstring | number | booleanPassword authentication (prefer key_file, and keep the value in an env var).v0.1.0
timeoutstringCommand timeout as a Go duration.v0.1.0
typeanySelects the ssh runner: running commands on a remote host.v0.1.0
userstring | number | booleanRequired. Login user.v0.1.0
type: grpc
cwdstring | number | booleanUnused for grpc runners; accepted for uniformity.v0.1.0
targetstring | number | booleanRequired. host:port of the gRPC server; the schema is resolved via server reflection.v0.1.0
timeoutstringCall timeout as a Go duration.v0.1.0
tlsbooleanConnect with TLS (default plaintext).v0.1.0
typeanySelects the grpc runner: unary gRPC calls from grpc steps.v0.1.0
type: browser
browser_argsarray of [string number boolean]Extra Chrome launch flags (bare names, no leading ‘–’), e.g. ‘disable-gpu’ or ‘window-size=1280,720’.v0.1.0
cwdstring | number | booleanUnused for browser runners; accepted for uniformity.v0.1.0
exec_pathstring | number | booleanPath to a specific Chrome/Chromium binary instead of the one discovered on PATH.v0.1.0
headlessbooleanRun Chrome without a visible window (default true); set false to debug headed.v0.1.0
timeoutstringSession timeout as a Go duration.v0.1.0
typeanySelects the browser runner: headless Chrome driven by cdp steps.v0.1.0

defaults #

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.

KeyTypeDescriptionSince
runobjectLayered 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_envbooleanStart 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
cwdstring | number | booleanDefault working directory for every run step. Precedence: step > runner > defaults.run.v0.1.0
envmap of [string number boolean]Default env map shallow-merged beneath every run step’s own env.v0.1.0
pass_envarray of [string number boolean]Host variables copied into the cleared environment (#16). Requires clear_env: true; unset host variables are skipped.v0.3.0
runnerstring | number | booleanDefault runner name for every run step.v0.1.0
sandbox_homebooleanPoint 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
shellbooleanDefault shell for every run step (an authored shell: false on a step cannot override a defaulted true).v0.1.0
timeoutstringDefault timeout for every run step (a step’s own timeout wins).v0.1.0
scenarioobjectScenario-level defaults: the env every scenario shares, and the gate every scenario is selected by.v0.1.0
envmap of [string number boolean]Env map shallow-merged beneath every scenario’s own env.v0.1.0
onlyconditionDefault 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.v0.21.0
skipconditionDefault 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
serviceobjectLayered 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_envbooleanStart 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
cwdstring | number | booleanDefault working directory for every service.v0.1.0
envmap of [string number boolean]Default env map for every service.v0.1.0
pass_envarray of [string number boolean]Host variables copied into the cleared environment (#16). Requires clear_env: true; unset host variables are skipped.v0.3.0
readyobjectDefault readiness probe for every service (a service’s own ready wins).v0.1.0
delaystringSee service.ready.delay.v0.1.0
filestring | number | booleanSee service.ready.file.v0.1.0
logstring | number | booleanSee service.ready.log.v0.1.0
portstring | number | booleanSee service.ready.port.v0.1.0
storestring | number | booleanSee service.ready.store.v0.1.0
timeoutstringSee service.ready.timeout.v0.1.0
shellbooleanDefault shell for every service.v0.1.0

Directory manifest #

A directory of specs may carry an atago.project.yaml holding what belongs to the tree rather than to one file:

env:
  MYTOOL_REGISTRY: "http://127.0.0.1:8080"
defaults:
  run:
    sandbox_home: true
fixtures_dir: testdata

It can also declare the binary under test:

subject:
  name: mytool
  artifact: bin/mytool
  build:
    command: "go build -o ${artifact} ."
    cwd: ".."
profiles:
  cover:
    build:
      command: "go build -cover -covermode=atomic -coverpkg=./... -o ${artifact} ."
    env:
      GOCOVERDIR: "${env:GOCOVERDIR}"

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.

VariableIs
${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.

Editor support (JSON Schema) #

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):

# yaml-language-server: $schema=https://raw.githubusercontent.com/nao1215/atago/main/schema/atago.schema.json
version: "1"

The report and manifest outputs have schemas too: report.schema.json and manifest.schema.json.

Platform support #

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.

BehaviorLinux / macOSWindows
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: stepsdelivers TERM, INT, HUP, USR1, USR2, KILL to the service’s process groupnot supported — Windows has no POSIX signals. Gate with skip: {os: windows}
cancel / timeout teardownkills the whole process groupkills the whole process tree (taskkill /T)
pty: steps and atago record --ptya real ptya 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 bitsthe 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 bitsno effect — Windows has no POSIX permissions
fixture: {symlink: ...}always availableneeds Developer Mode or an elevated process
changes:compares content, symlink target, kind, and permission bitscompares 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
sandbox_home: trueredirects HOME and the XDG base directoriesredirects USERPROFILE, APPDATA, LOCALAPPDATA, HOMEDRIVE, HOMEPATH
clear_env: truestarts from an empty environment plus pass_envthe same, plus SystemRoot, SystemDrive, TEMP, TMP, and PATHEXT, without which a process cannot start at all

Choosing the shell #

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.

Shell completion #

atago completion <bash|zsh|fish|powershell> prints a completion script for your shell.

Exit codes #

CodeMeaning
0no unexpected failures — every scenario passed, was skipped, or was an XFAIL (an expect_fail: scenario that failed as declared)
1one or more failed (including an XPASS: an expect_fail: scenario that passed, unless --allow-xpass)
2spec error (YAML syntax or schema/semantic validation)
3CLI-invocation error (unknown subcommand, bad flag, or no matching spec files)
4execution error
5internal error
6security 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.