tmux CLI Testing
Test a CLI or TUI by running it in a tmux session: wait for a condition, send input, assert on the captured frame, kill the session.
Never sleep — always wait on a condition. A sleep is a guess about timing;
it makes tests both slower and flakier than polling for the thing you actually
need.
Helpers
source .claude/skills/tmux-cli-test/scripts/tmux_helpers.sh
| Function | Purpose |
|----------|---------|
| tmux_start <session> <cmd> | Launch command in a detached tmux session |
| tmux_kill <session> | Kill session |
| tmux_is_alive <session> | Check if session is running |
| tmux_capture <session> | Get pane text |
| tmux_capture_ansi <session> | Get pane text with ANSI codes |
| tmux_capture_to_file <session> <path> | Save pane text to a file |
| tmux_wait_for <session> <text> [timeout] | Poll until text appears |
| tmux_wait_for_regex <session> <pattern> [timeout] | Poll until regex matches |
| tmux_wait_gone <session> <text> [timeout] | Poll until text disappears |
| tmux_wait_exit <session> [timeout] | Poll until the process exits |
| tmux_send <session> <keys...> | Send keys (tmux key names) |
| tmux_type <session> <text> | Type literal text |
| tmux_assert_contains <session> <text> [label] | Assert text present |
| tmux_assert_not_contains <session> <text> [label] | Assert text absent |
| tmux_assert_matches <session> <pattern> [label] | Assert regex matches |
| tmux_send_and_wait <session> <keys> <text> [timeout] | Send then wait |
| tmux_test <session> <cmd> <ready_text> <fn> | Full lifecycle test |
Screenshot helpers (tmux_screenshot, tmux_screenshot_sizes) and color
assertions (tmux_assert_not_monochrome, tmux_assert_min_colors,
tmux_assert_has_color, tmux_assert_text_color) are documented in
references/color-capture.md, together with the
tmux true-color override that RGB-painting TUIs require.
Override the defaults before calling anything:
TMUX_TEST_POLL_INTERVAL=0.3 # seconds between polls
TMUX_TEST_TIMEOUT=30 # max wait seconds
TMUX_TEST_WIDTH=120 # terminal columns
TMUX_TEST_HEIGHT=30 # terminal rows
Workflow
Every test is the same five steps: start the session, wait for a ready signal, interact, assert on the captured output, kill the session.
source .claude/skills/tmux-cli-test/scripts/tmux_helpers.sh
tmux_start "test-help" "./crates/target/debug/gpu --help"
tmux_wait_for "test-help" "Usage:" 10
tmux_assert_contains "test-help" "run"
tmux_assert_contains "test-help" "dashboard"
tmux_kill "test-help"
Prefer tmux_test when a test has more than a couple of assertions — it kills
the session even when one fails.
More Patterns
| Need | Read |
|---|---|
| Dashboards, interactive prompts, error paths, C-c interrupts, driving tmux without the helpers, debugging a failed test, session naming | references/examples.md |
| Running the CLI inside a container | references/docker.md |
| True color, screenshots, color assertions | references/color-capture.md |
Anti-Patterns
| Bad | Good | Why |
|-----|------|-----|
| sleep 3 | tmux_wait_for s "Ready" | Sleeps are flaky and slow |
| sleep 5 && tmux capture-pane | tmux_wait_for s "expected" && tmux_capture s | Wait on a condition, not a duration |
| Hardcoded binary path | GPU_BIN=./crates/target/debug/gpu | Easy to switch debug/release |
| No cleanup on failure | tmux_test or an explicit tmux_kill | Leftover sessions break the next run |
| grep -q with no timeout loop | tmux_wait_for | The text may not be rendered yet |
| Checking .len() of TUI text | Check displayed content only | Unicode width is not byte length |
Scan to join WeChat group