Terminal testing
Test an interactive terminal program by running it in a tape, waiting for expected output, and saving state JSON for assertions.
Output target/snapshots/app.gif
Type "my-app --demo"EnterWait+Screen "Ready"State target/snapshots/ready.jsonScreenshot target/snapshots/ready.pngThe tape fails if expected text does not appear before the configured timeout. The PNG helps with visual review, while State JSON can be compared in snapshot tests.
What to assert
Section titled “What to assert”Prefer asserting text and important style spans instead of every pixel. Pixel assertions are useful for renderer work, but they are often brittle for application behavior tests.
Good assertions include:
- expected viewport text
- expected scrollback text
- selected style spans for important colors or attributes
- cursor visibility and location when it affects behavior
- whether a prompt, status line, or selected row appears
- whether hidden setup avoided extra visible frames
Stable tapes
Section titled “Stable tapes”Use waits as synchronization points:
Type "my-tui --scripted"EnterWait+Screen "Loaded 12 rows"Prefer Wait+Screen for TUIs because cursor location may move as the program draws. Use regex waits
for dynamic output:
Wait+Screen "/Loaded [0-9]+ rows/"Use Env to make application behavior deterministic:
Env NO_COLOR 1Env TZ UTCEnv BETAMAX_TEST 1Use Set CursorBlink false when animation frame differences do not matter to the test.
Wait before sleep
Section titled “Wait before sleep”After an action, use Wait, Wait+Line, or Wait+Screen to check the expected text before
continuing. Add Sleep if viewers need time to read the recording.
Type "my-tui --scripted"EnterWait+Screen "Loaded 12 rows"State target/snapshots/loaded.jsonSleep 500msKeep validation sleeps short and rare. If a sleep is compensating for behavior that cannot be matched with visible text, a prompt, or a status line, keep it as small as the program allows and leave a tape comment explaining what external effect it covers.
Snapshot workflow
Section titled “Snapshot workflow”To check a snapshot from a Rust test:
- Run
betamax run tests/fixtures/profile.tape. - Read
target/snapshots/profile.json. - Deserialize it with
serde_json. - Assert targeted fields directly, or snapshot the whole file with
insta.
For tests that call Betamax directly from Rust, betamax-core exposes tape parsing, the runner,
and terminal state types.
Choosing artifacts for tests
Section titled “Choosing artifacts for tests”State JSON should be the primary assertion artifact. Screenshots are best as debugging attachments when a snapshot changes. GIFs are useful when reviewing interaction timing, but they should usually not be the only automated assertion.
See Examples for scrollback.tape, text-styles.tape, and outputs.tape,
which exercise the most useful testing paths.