Termwright
A Playwright-like automation framework for terminal TUI applications.
Termwright enables AI agents and integration tests to interact with and observe terminal user interfaces by wrapping applications in a pseudo-terminal (PTY).
Features
- PTY Wrapping: Spawn and control any terminal application
- Screen Reading: Access text, colors, cursor position, and cell attributes
- Wait Conditions: Wait for text, regex patterns, screen stability, or process exit
- Input Simulation: Send keystrokes, special keys, and control sequences
- Multiple Output Formats: Plain text, JSON (for AI agents), and PNG screenshots
- Box Detection: Detect UI boundaries using box-drawing characters
- Framework Agnostic: Works with any TUI framework (ratatui, crossterm, ncurses, etc.)
Installation
Add to your Cargo.toml:
[]
= "0.1"
= { = "1", = ["rt-multi-thread", "macros"] }
Or install the CLI:
Quick Start
Library Usage
use *;
async
CLI Usage
Capture terminal output as text:
Take a screenshot of a TUI application:
Get JSON output for AI processing:
Daemon Usage
The daemon subcommand runs a long-lived terminal session and exposes a local Unix socket for automation. This is useful when you want to keep an app running and interact with it incrementally (similar to how Playwright keeps a browser process alive).
Start a daemon (foreground; blocks until you close it):
# prints a socket path like:
# /tmp/termwright-12345.sock
Start a daemon in the background (returns immediately):
SOCK=
Stop a daemon (sends a close request):
|
Shell Scripting Quick Start
The daemon mode makes termwright ideal for shell-based E2E testing of TUI applications. Here's how to get started:
Prerequisites
# Install termwright
# Install helper tools
# or: sudo apt-get install socat jq # Ubuntu/Debian
Basic Pattern
#!/bin/bash
# 1. Start the daemon with your TUI app
SOCK="/tmp/my-test-.sock"
&
# Wait for socket
while [; do ; done
# 2. Helper function to send commands
# 3. Wait for app to be ready
# 4. Interact with the app
# 5. Read screen content
SCREEN=
# 6. Take a screenshot
RESULT=
| |
# 7. Clean up
Available Daemon Commands
| Method | Params | Description |
|---|---|---|
handshake |
null |
Get daemon info (pid, version) |
screen |
{"format":"text"|"json"} |
Get current screen content |
screenshot |
{} |
Get PNG screenshot as base64 |
press |
{"key":"Enter"} |
Press a key (Enter, Escape, Tab, Up, Down, etc.) |
type |
{"text":"..."} |
Type text |
hotkey |
{"ctrl":true,"ch":"c"} |
Send Ctrl/Alt combinations |
wait_for_text |
{"text":"...","timeout_ms":5000} |
Wait for text to appear |
wait_for_idle |
{"idle_ms":500,"timeout_ms":5000} |
Wait for screen to stabilize |
status |
null |
Check if process is still running |
close |
null |
Terminate the daemon and child process |
Reusable Test Library
For multiple tests, create a shared library (e.g., lib.sh):
#!/bin/bash
# lib.sh - Shared test helpers
REQUEST_ID=1
# Send command with auto-incrementing ID
# Convenience wrappers
# Assert screen contains text
Example Test Script
#!/bin/bash
# test_my_app.sh
SOCK="/tmp/test-.sock"
&
while [; do ; done
# Wait for app to initialize
# Run tests
Key Names Reference
Common key names for the press command:
- Navigation:
Up,Down,Left,Right,Home,End,PageUp,PageDown - Actions:
Enter,Escape,Tab,Backspace,Delete,Insert - Function keys:
F1throughF12 - Characters: Any single character like
a,1,?
CLI Reference
termwright fonts
List available font families on the system (helpful for selecting a monospace font for screenshots).
termwright fonts
termwright run
Run a command and capture its output.
termwright run [OPTIONS] -- <COMMAND> [ARGS]...
Options:
--cols <COLS> Terminal width [default: 80]
--rows <ROWS> Terminal height [default: 24]
--wait-for <TEXT> Wait for this text to appear before capturing
--delay <MS> Delay in milliseconds before capturing [default: 500]
--format <FORMAT> Output format: text, json, json-compact [default: text]
--timeout <SECS> Timeout for wait conditions [default: 30]
termwright screenshot
Take a PNG screenshot of a terminal application.
termwright screenshot [OPTIONS] -- <COMMAND> [ARGS]...
Options:
--cols <COLS> Terminal width [default: 80]
--rows <ROWS> Terminal height [default: 24]
--wait-for <TEXT> Wait for this text to appear before capturing
--delay <MS> Delay in milliseconds before capturing [default: 500]
-o, --output <PATH> Output file path (defaults to stdout)
--font <NAME> Font name for rendering
--font-size <SIZE> Font size in pixels [default: 14]
--timeout <SECS> Timeout for wait conditions [default: 30]
termwright run-steps
Run a YAML or JSON steps file for end-to-end testing.
termwright run-steps [OPTIONS] <FILE>
Options:
--connect <PATH> Connect to an existing daemon socket instead of spawning
--trace Write a trace.json file in the artifacts directory
termwright exec
Execute a single daemon request and print the response.
termwright exec --socket <PATH> --method <NAME> [--params <JSON>]
termwright hub
Start or stop multiple daemon sessions for parallel agents.
termwright hub start --count <N> [--cols <COLS>] [--rows <ROWS>] [--output <FILE>] -- <COMMAND> [ARGS]...
termwright hub stop --socket <PATH>... [--input <FILE>]
termwright daemon
Run a single TUI session and expose it over a Unix socket.
termwright daemon [OPTIONS] -- <COMMAND> [ARGS]...
Options:
--cols <COLS> Terminal width [default: 80]
--rows <ROWS> Terminal height [default: 24]
--socket <PATH> Unix socket path (defaults to a temp path)
--background Start daemon in the background
The command prints the socket path to stdout.
Daemon User Guide
Connecting from Rust
use Duration;
use *;
async
Notes / Caveats
- The daemon is local-only: it listens on a Unix socket you control.
- Mouse events are best-effort: many TUIs ignore mouse input unless they explicitly enable mouse reporting.
- Coordinate system for
mouse_move/mouse_clickisrow/colin terminal cells (0-based).
API Overview
Terminal
The main entry point for controlling terminal applications:
let term = builder
.size
.spawn
.await?;
// Input
term.type_str.await?;
term.send_key.await?;
term.enter.await?; // Shorthand for Enter key
// Screen access
let screen = term.screen.await;
// Wait conditions
term.expect.timeout.await?;
term.wait_exit.await?;
// Screenshots
term.screenshot.await.save?;
Screen
Query the terminal screen state:
let screen = term.screen.await;
// Text access
let text = screen.text;
let line = screen.line;
assert!;
// Cell-level access
let cell = screen.cell;
println!;
// Cursor position
let cursor = screen.cursor;
println!;
// Region extraction
let region = screen.region;
// Pattern matching
if let Some = screen.find_text
// Box detection (UI boundaries)
let boxes = screen.detect_boxes;
// Output formats
println!; // Pretty JSON
println!; // Compact JSON
Keys
Available key types for input:
Char // Regular characters
Enter // Enter/Return
Tab // Tab
Escape // Escape
Backspace // Backspace
Up, Down, Left, Right // Arrow keys
Home, End
PageUp, PageDown
Insert, Delete
F..F // Function keys
Ctrl // Ctrl combinations
Alt // Alt combinations
Requirements
- Rust 1.85.0 or later (Edition 2024)
- macOS or Linux (Windows not supported)
- For screenshots: A monospace font (uses system fonts via font-kit)
Use Cases
- AI Agents: Enable LLMs to observe and interact with terminal UIs via JSON output
- Integration Testing: Automated testing of TUI applications
- Documentation: Generate screenshots for documentation
- Accessibility: Extract text content from visual terminal applications
License
MIT License - see LICENSE for details.