| name | termwright-tui-testing |
| description | Automates end-to-end testing and debugging of terminal UIs using Termwright. Use when testing TUI apps (ratatui, crossterm, ncurses), automating terminal interactions, capturing screenshots, or debugging terminal-based interfaces. Handles step files, daemon control, and artifact analysis. |
| allowed-tools | ["Bash","Read","Write","Edit","Glob","Grep"] |
Termwright TUI Testing
Automate end-to-end testing of terminal user interfaces with Termwright's daemon-based architecture.
Progressive Discovery
For detailed syntax, use termwright info:
termwright info steps - All step types
termwright info steps waitForText - Specific step syntax
termwright info keys - Valid key names
termwright info protocols - Daemon methods
termwright info capabilities - Runtime info
- Add
--json for machine-readable output
Quick Start
Run a Step File Test
termwright run-steps --trace test.yaml
termwright run-steps --connect /tmp/termwright-123.sock steps.yaml
Interactive Daemon Session
SOCK=$(termwright daemon --background -- vim test.txt)
termwright exec --socket "$SOCK" --method handshake
termwright exec --socket "$SOCK" --method wait_for_text --params '{"text":"VIM","timeout_ms":5000}'
termwright exec --socket "$SOCK" --method press --params '{"key":"i"}'
termwright exec --socket "$SOCK" --method type --params '{"text":"Hello"}'
termwright exec --socket "$SOCK" --method screen --params '{"format":"text"}'
termwright exec --socket "$SOCK" --method close
Quick Screenshot
termwright screenshot --wait-for "Ready" -o app.png -- ./my-tui-app
Core Workflows
1. Writing Step Files
Create YAML files for reproducible E2E tests:
session:
command: vim
args: [test.txt]
cols: 120
rows: 40
steps:
- waitForText: {text: "VIM", timeoutMs: 5000}
- press: {key: i}
- type: {text: "Hello, world!"}
- press: {key: Escape}
- expectText: {text: "Hello, world!"}
- screenshot: {name: vim-content}
- type: {text: ":q!"}
- press: {key: Enter}
artifacts:
mode: always
dir: ./test-artifacts
Available Steps:
| Step | Purpose | Example |
|---|
waitForText | Wait for text to appear | {text: "Ready", timeoutMs: 5000} |
waitForPattern | Wait for regex match | {pattern: "error|warning"} |
waitForIdle | Wait for screen stability | {idleMs: 500, timeoutMs: 5000} |
waitForTextGone | Wait for text to disappear | {text: "Loading...", timeoutMs: 5000} |
waitForPatternGone | Wait for pattern to stop matching | {pattern: "\\d+%", timeoutMs: 10000} |
press | Press a key | {key: "Enter"} |
type | Type text | {text: "hello"} |
hotkey | Modifier combo | {ctrl: true, ch: "c"} |
expectText | Assert text exists | {text: "Success"} |
expectPattern | Assert pattern matches | {pattern: "completed"} |
notExpectText | Assert text NOT present | {text: "ERROR"} |
notExpectPattern | Assert pattern does NOT match | {pattern: "fail|crash"} |
screenshot | Capture PNG | {name: "result"} |
Note: screenshot steps require artifacts mode onFailure or always (not off).
Note: notExpectText and notExpectPattern are immediate checks (no timeout).
Run termwright info steps to see all available steps with detailed syntax.
For full schema details, see step-schema.md.
2. Daemon Protocol
The daemon uses JSON-over-Unix-socket communication:
Request format:
{"id": 1, "method": "screen", "params": {"format": "text"}}
Response format:
{"id": 1, "result": "screen content...", "error": null}
Key Methods:
handshake - Initialize connection, get version info
screen - Get screen content (text/json/json_compact)
screenshot - Capture PNG (returns base64)
type / press / hotkey - Input simulation
wait_for_text / wait_for_pattern / wait_for_idle - Wait conditions
mouse_click / mouse_move - Mouse events
status - Check if process exited
close - Terminate daemon
For complete protocol reference, see protocol-reference.md.
3. Analyzing Failures
When tests fail and artifacts are enabled (onFailure or always), examine the output directory:
./termwright-artifacts/20250119-143022/
├── failure-001-screen.txt
├── failure-001-screen.json
├── vim-content.png
└── trace.json
Interpreting trace.json:
[
{"step": 1, "action": "waitForText", "duration_ms": 245, "before_hash": 123, "after_hash": 456, "error": null},
{"step": 2, "action": "press", "duration_ms": 15, "before_hash": 456, "after_hash": 789, "error": null},
{"step": 3, "action": "expectText", "duration_ms": 3001, "before_hash": 789, "after_hash": 789, "error": "timeout waiting for text"}
]
before_hash / after_hash - Detect if step changed screen
duration_ms - Identify slow operations
error - Null on success, message on failure
4. Parallel Testing with Hub
Run multiple TUI sessions simultaneously:
termwright hub start --count 5 --output sessions.json -- ./my-app
termwright hub stop --input sessions.json
CLI Reference
| Command | Purpose |
|---|
termwright run | One-shot capture (text or JSON) |
termwright screenshot | One-shot PNG screenshot |
termwright run-steps | Execute step file |
termwright exec | Single daemon command |
termwright daemon | Start long-lived daemon |
termwright hub start/stop | Manage session pool |
termwright fonts | List available fonts |
For all options, see cli-reference.md.
Shell Scripting Pattern
For custom test scripts without YAML:
#!/bin/bash
set -euo pipefail
SOCK=$(termwright daemon --background -- htop)
trap "termwright exec --socket '$SOCK' --method close 2>/dev/null || true" EXIT
while ! [ -S "$SOCK" ]; do sleep 0.1; done
tw() {
termwright exec --socket "$SOCK" --method "$1" --params "${2:-null}"
}
tw handshake
tw wait_for_idle '{"idle_ms":1000,"timeout_ms":10000}'
SCREEN=$(tw screen '{"format":"text"}' | jq -r '.result')
if echo "$SCREEN" | grep -q "CPU"; then
echo "PASS: htop loaded"
else
echo "FAIL: CPU info not found"
exit 1
fi
tw screenshot | jq -r '.result.png_base64' | base64 -d > htop.png
echo "Screenshot saved to htop.png"
Valid Key Names
For press and step files:
- Navigation:
Up, Down, Left, Right, Home, End, PageUp, PageDown
- Special:
Enter, Tab, Escape, Backspace, Delete, Insert
- Function:
F1 through F12
- Characters: Any single character (
a, A, 1, @, etc.)
Coordinate System
All coordinates are 0-indexed:
- Row 0 = top line
- Column 0 = leftmost character
- Format:
(row, col) for mouse operations
Debugging Tips
- Start with
--trace - Always use trace output when developing tests
- Use
wait_for_idle - Ensure screen has stabilized before assertions
- Check
screen.txt - Compare expected vs actual content
- Inspect
screen.json - Verify cursor position and colors
- Lower timeouts during dev - Faster feedback on failures
- Use
artifacts: always - Capture state at every step while debugging
Common Patterns
Wait for App Startup
steps:
- waitForIdle: {idleMs: 1000, timeoutMs: 10000}
- waitForText: {text: "Ready", timeoutMs: 5000}
Modal Dialog Handling
steps:
- press: {key: "Escape"}
- waitForIdle: {idleMs: 200}
- hotkey: {ctrl: true, ch: "s"}
- waitForText: {text: "Saved"}
Navigation and Selection
steps:
- press: {key: "Down"}
- press: {key: "Down"}
- press: {key: "Enter"}
- expectText: {text: "Selected item"}
Form Input
steps:
- waitForText: {text: "Username:"}
- type: {text: "admin"}
- press: {key: "Tab"}
- type: {text: "password123"}
- press: {key: "Enter"}
Reference Files
Troubleshooting
| Issue | Solution |
|---|
| Socket not found | Wait for socket file: while ! [ -S "$SOCK" ]; do sleep 0.1; done |
| Timeout on wait | Increase timeoutMs, check if text actually appears |
| Wrong screen content | Use wait_for_idle before assertions |
| Mouse not working | Not all TUIs support mouse; use keyboard navigation |
| Colors not captured | Use screen with format: json for full cell data |