Documentation
¶
Overview ¶
Package crawler provides black-box testing for terminal user interfaces.
crawler runs a real binary inside an isolated tmux server, sends keystrokes, captures screen output, and performs assertions through the standard testing.TB interface. It is framework-agnostic and works with any program that renders in a terminal.
Quick Start ¶
func TestMyApp(t *testing.T) {
term := crawler.Open(t, "./my-app")
term.WaitFor(crawler.Text("Welcome"))
term.Type("hello")
term.Press(crawler.Enter)
term.WaitFor(crawler.Text("hello"))
}
Cleanup is automatic through t.Cleanup; there is no Close method.
Session Lifecycle ¶
Open creates a dedicated tmux server for each test, using a unique socket path under os.TempDir. This gives subtests and parallel tests full isolation.
Internally, crawler starts tmux with a temporary config file that enables:
- remain-on-exit on
- status off
- deterministic history-limit
The tmux server is torn down with kill-server during cleanup.
Waiting and Matchers ¶
Terminal.WaitFor and Terminal.WaitForScreen poll until a Matcher succeeds or a timeout expires. This is the core reliability mechanism and avoids ad hoc sleeps in tests.
Wait behavior:
- Defaults: 5s timeout, 50ms poll interval
- Per-terminal overrides: WithTimeout, WithPollInterval (trusted, not clamped)
- Per-call overrides: WithinTimeout, WithWaitPollInterval
- Per-call poll intervals under 10ms are clamped to 10ms
- Per-call negative timeout or poll values fail the test immediately
- If the process exits early, waits fail immediately with diagnostics
Built-in matchers include Text, Regexp, Line, LineContains, Not, All, Any, Empty, and Cursor.
Screen Capture ¶
Terminal.Screen captures the visible pane. Terminal.Scrollback captures full scrollback history. A Screen is immutable and provides helpers such as Screen.String, Screen.Lines, Screen.Line, Screen.Contains, and Screen.Size.
Snapshots ¶
Terminal.MatchSnapshot and Screen.MatchSnapshot compare screen content to golden files under testdata. Set CRAWLER_UPDATE=1 to create or update golden files.
Snapshot content is normalized for stable diffs by trimming trailing spaces, trimming trailing blank lines, and writing a single trailing newline.
Diagnostics ¶
On wait failures, crawler reports:
- expected matcher description
- timeout or exit details
- multiple recent screen captures (oldest to newest)
This keeps failures actionable without extra debug tooling.
Requirements ¶
- Go 1.24+
- tmux 3.0+
- Linux or macOS
tmux is resolved in this order:
- WithTmuxPath
- CRAWLER_TMUX
- PATH lookup for tmux
Index ¶
- type Key
- type Matcher
- func All(matchers ...Matcher) Matcher
- func Any(matchers ...Matcher) Matcher
- func Cursor(row, col int) Matcher
- func Empty() Matcher
- func Line(n int, s string) Matcher
- func LineContains(n int, substr string) Matcher
- func Not(m Matcher) Matcher
- func Regexp(pattern string) Matcher
- func Text(s string) Matcher
- type Option
- func WithArgs(args ...string) Option
- func WithDir(dir string) Option
- func WithEnv(env ...string) Option
- func WithHistoryLimit(limit int) Option
- func WithPollInterval(d time.Duration) Option
- func WithSize(width, height int) Option
- func WithTimeout(d time.Duration) Option
- func WithTmuxPath(path string) Option
- type Screen
- type Terminal
- func (term *Terminal) MatchSnapshot(name string)
- func (term *Terminal) Press(keys ...Key)
- func (term *Terminal) Resize(width, height int)
- func (term *Terminal) Screen() *Screen
- func (term *Terminal) Scrollback() *Screen
- func (term *Terminal) SendKeys(keys ...string)
- func (term *Terminal) Type(s string)
- func (term *Terminal) WaitExit(wopts ...WaitOption) int
- func (term *Terminal) WaitFor(m Matcher, wopts ...WaitOption)
- func (term *Terminal) WaitForScreen(m Matcher, wopts ...WaitOption) *Screen
- type WaitOption
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Key ¶
type Key string
Key represents a tmux key sequence.
const ( Enter Key = "Enter" Escape Key = "Escape" Tab Key = "Tab" Backspace Key = "BSpace" Up Key = "Up" Down Key = "Down" Left Key = "Left" Right Key = "Right" Home Key = "Home" End Key = "End" PageUp Key = "PageUp" PageDown Key = "PageDown" Space Key = "Space" Delete Key = "DC" F1 Key = "F1" F2 Key = "F2" F3 Key = "F3" F4 Key = "F4" F5 Key = "F5" F6 Key = "F6" F7 Key = "F7" F8 Key = "F8" F9 Key = "F9" F10 Key = "F10" F11 Key = "F11" F12 Key = "F12" )
Special key constants for use with Press.
type Matcher ¶
A Matcher reports whether a Screen satisfies a condition. The string return is a human-readable description for error messages.
func Cursor ¶
Cursor matches if the cursor is at the given position. Uses tmux display-message to query cursor position. Note: row and col are 0-indexed. This matcher takes (row, col) to follow the usual row-then-column convention.
func Line ¶
Line matches if the given line (0-indexed) equals s after trimming trailing spaces from the screen line.
func LineContains ¶
LineContains matches if the given line (0-indexed) contains the substring.
type Option ¶
type Option func(*options)
Option configures a Terminal created by Open.
func WithEnv ¶
WithEnv appends environment variables to the process environment. Each entry should be in "KEY=VALUE" format.
func WithHistoryLimit ¶
WithHistoryLimit sets the tmux scrollback history limit for the test session. A value of 0 uses the default set by Open (10000).
func WithPollInterval ¶
WithPollInterval sets the default polling interval for WaitFor and WaitForScreen.
func WithTimeout ¶
WithTimeout sets the default timeout for WaitFor and WaitForScreen.
func WithTmuxPath ¶
WithTmuxPath sets the path to the tmux binary. Defaults to "tmux" (resolved via $PATH). The CRAWLER_TMUX environment variable can also be used as a fallback before the default.
type Screen ¶
type Screen struct {
// contains filtered or unexported fields
}
Screen is an immutable capture of terminal content.
func (*Screen) Line ¶
Line returns the content of a single row (0-indexed). Panics if n is out of range.
func (*Screen) Lines ¶
Lines returns a copy of the screen content as a slice of strings, one per row. The returned slice is a shallow copy; callers may modify it without affecting the Screen.
func (*Screen) MatchSnapshot ¶
MatchSnapshot on Screen allows snapshotting a previously captured screen.
type Terminal ¶
type Terminal struct {
// contains filtered or unexported fields
}
Terminal is a handle to a TUI program running inside a tmux session. It is created with Open and cleaned up automatically via t.Cleanup.
func Open ¶
Open starts the binary in a new tmux session. Cleanup is automatic via t.Cleanup — no defer needed.
Example ¶
package main
import (
"testing"
"time"
"github.com/cboone/crawler"
)
func main() {
_ = func(t *testing.T) {
term := crawler.Open(t, "./my-app",
crawler.WithArgs("--verbose"),
crawler.WithSize(120, 40),
crawler.WithTimeout(10*time.Second),
)
term.WaitFor(crawler.Text("Welcome"))
}
}
func (*Terminal) MatchSnapshot ¶
MatchSnapshot compares the current screen against a golden file stored in testdata/<sanitized-test-name>/<sanitized-name>.txt.
Set CRAWLER_UPDATE=1 to create or update golden files.
Example ¶
package main
import (
"testing"
"github.com/cboone/crawler"
)
func main() {
_ = func(t *testing.T) {
term := crawler.Open(t, "./my-app")
term.WaitFor(crawler.Text("Dashboard"))
term.MatchSnapshot("dashboard")
}
}
func (*Terminal) Resize ¶
Resize changes the terminal dimensions. This sends a SIGWINCH to the running program.
func (*Terminal) Scrollback ¶
Scrollback captures the full scrollback buffer, not just the visible screen.
The returned Screen has one line per scrollback row (oldest to newest). Its height (and len(Lines())) reflects the total number of captured lines, which is typically larger than the pane's visible height. Width is the maximum line width across all captured lines. Callers should use len(s.Lines()) to reason about scrollback length, rather than relying on the visible height returned by s.Size().
func (*Terminal) WaitExit ¶
func (term *Terminal) WaitExit(wopts ...WaitOption) int
WaitExit waits for the TUI process to exit and returns its exit code. Useful for testing that a program terminates cleanly.
func (*Terminal) WaitFor ¶
func (term *Terminal) WaitFor(m Matcher, wopts ...WaitOption)
WaitFor polls the screen until the matcher succeeds or the timeout expires. On timeout it calls t.Fatal with a description of what was expected and the last screen content.
Example ¶
package main
import (
"testing"
"github.com/cboone/crawler"
)
func main() {
_ = func(t *testing.T) {
term := crawler.Open(t, "./my-app")
term.WaitFor(crawler.Text("Name:"))
term.Type("Alice")
term.Press(crawler.Enter)
term.WaitFor(crawler.All(
crawler.Text("Saved"),
crawler.Not(crawler.Text("Error")),
))
}
}
func (*Terminal) WaitForScreen ¶
func (term *Terminal) WaitForScreen(m Matcher, wopts ...WaitOption) *Screen
WaitForScreen has the same timeout behavior as WaitFor: it polls until the matcher succeeds or the timeout expires, calling t.Fatal on timeout. On success it returns the matching Screen.
type WaitOption ¶
type WaitOption func(*waitOptions)
WaitOption configures a single WaitFor, WaitForScreen, or WaitExit call.
func WithWaitPollInterval ¶
func WithWaitPollInterval(d time.Duration) WaitOption
WithWaitPollInterval overrides the polling interval for a single wait call. A value of 0 means "use defaults". Negative values cause t.Fatal. Positive values under 10ms are clamped to 10ms.
func WithinTimeout ¶
func WithinTimeout(d time.Duration) WaitOption
WithinTimeout overrides the call timeout for a single wait call. A value of 0 means "use defaults". Negative values cause t.Fatal.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
testbin
command
Command testbin is a minimal TUI fixture program for testing the crawler library.
|
Command testbin is a minimal TUI fixture program for testing the crawler library. |
|
tmuxcli
Package tmuxcli provides low-level tmux command execution and socket-path management.
|
Package tmuxcli provides low-level tmux command execution and socket-path management. |