tuibot is an automated testing tool for terminal user interface applications. It launches a target inside a Docker container, gives it a real headless PTY, explores the interface like a user, and brings back screens, actions, crashes, generated tests, and optional line + widget coverage.
The short version: point tuibot at source code, choose an explorer, set a budget, and get a browser-replayable testing expedition instead of a pile of terminal logs.
| Strategy | Personality | Best for | |
|---|---|---|---|
| 🎲 | random |
Fast, deterministic chaos | Fuzzing key paths, mouse handling, resize behavior, and crash recovery |
| 🧭 | llm_guided |
Looks, thinks, acts, repeats | Goal-oriented exploration when the next useful action depends on the screen |
| 🧪 | llm_script |
Reads the source and writes tests | Reusable, assertion-driven shell-use scenarios generated from the real app |
Every explorer runs under a time or action budget. If the app exits early, tuibot can relaunch it without resetting the session log or accumulated coverage.
- Python 3.11 or newer, managed with
uv - Docker with a reachable daemon
- An Anthropic-compatible API key only for LLM-backed strategies or generation features
Microsoft shell-use and
tuicov live inside the test container;
neither needs to be installed on the host.
uv sync
uv run tuibot --helpuv run tuibot init # create config.toml
$EDITOR config.toml # point it at the app and choose a strategy
uv run tuibot doctor # check Docker, screenshots, coverage, and credentials
uv run tuibot run # start exploring
uv run tuibot replay runs/<session-id>config.toml.example is the complete configuration reference. A small random smoke
run looks like this:
[run]
name = "first expedition"
strategy = "random"
seed = 4242
[target]
source_path = "../my-tui"
[budget]
type = "actions"
actions = 50
[entrypoint]
mode = "config"
command = ["python", "-m", "my_tui"]
[docker]
mode = "image"
image = "python:3.13-slim"
[logging]
formats = ["raw", "svg"]See config.toml.example for input files, generated entry
points, Dockerfile generation, strategy tuning, relaunches, screenshots, LLM gateways,
and coverage.
Each run gets one timestamped directory that can be copied, archived, or replayed on another machine:
runs/20260731-164500-a1b2c3/
├── config.resolved.toml frozen configuration for this run
├── session.jsonl actions, launches, captures, exits, and errors
├── session.json final outcome, budget, launches, and coverage totals
├── screens/ timestamped raw, SVG, and/or PNG captures
├── entrypoint.json configured or generated launch recipe
├── inputs/ configured or generated input files
├── coverage/ cumulative tuicov work directory
└── scripts/ generated tests for the llm_script strategy
The replay server turns that directory into a browsable timeline:
uv run tuibot replay runs/<session-id> --port 8080flowchart LR
S["Target source"] --> D["Docker image"]
D --> P["shell-use PTY"]
R["🎲 Random"] --> P
G["🧭 LLM guided"] --> P
T["🧪 LLM scripts"] --> P
P --> A["Screens + actions"]
P --> C["tuicov coverage"]
A --> O["One run directory"]
C --> O
O --> W["Browser replay"]
tuibot talks to the shell-use CLI through docker exec. The PTY, target process,
and coverage runtime stay together inside the container, while the bind-mounted run
directory makes captures and reports appear immediately on the host.
The session budget is the single clock. A crash, clean exit, generated-script failure, or relaunch is recorded as part of the same expedition rather than becoming a separate run.
Coverage is powered by public companion project
tuicov, which uses the target language's
native coverage mechanism and normalizes the results.
tuibot instruments once, runs with --no-build on every launch, and reuses one work
directory. Snapshots are cumulative, so relaunches add evidence instead of erasing it.
Install tuicov from a pinned GitHub archive inside the runtime image:
[coverage]
enabled = true
types = ["line", "widget"]
[docker]
tuicov_source = "tuicov @ https://github.com/tui-testing/tuicov/archive/<commit>.zip"During tuicov development, point at a checkout instead:
[docker]
tuicov_source = "/path/to/tuicov"A local checkout is mounted read-only at /opt/tuicov and run from source, so edits
apply to the next session without rebuilding the image. A package requirement or URL
is installed and cached in the generated runtime layer.
Pin a release tag or commit for reproducible runs. Use the
mainarchive only when intentionally following development.
| Format | Host dependency | Notes |
|---|---|---|
raw |
None | Compact terminal text, ideal for LLM-guided exploration |
svg |
None | Native shell-use output with sharp, selectable text |
png |
Rasterizer + font | Convenient for image models and portable previews |
For PNG support:
uv sync --extra png
uv run tuibot doctorThe optional extra installs resvg as a
self-contained wheel. resvg, rsvg-convert, inkscape, or a custom
logging.png_command also work.
A rasterizer without fonts can produce a perfectly valid PNG with no visible text.
tuibot probes for that failure before the run begins. On fontless systems, set
logging.png_font to a monospace .ttf, .otf, or .ttc file.
| Command | What it does |
|---|---|
tuibot init [path] |
Write a documented starter configuration |
tuibot doctor [-c config.toml] |
Validate config, Docker, screenshots, credentials, and coverage wiring |
tuibot run [-c config.toml] |
Run one testing expedition |
tuibot replay <run-dir> [--port N] |
Open the standalone browser replay tool |
uv sync
uv run pytest
uv run tuibot --helpStart with AGENTS.md for repository architecture and contributor rules.
The companion coverage engine lives at
tui-testing/tuicov.
tuibot is available under the MIT License.
If you use tuibot in your research, please cite:
@misc{peng2026llmstestterminaluser,
title = {Can LLMs Test Terminal User Interfaces?},
author = {Chao Peng and Ruida Hu and Ajitha Rajan and Tegawendé F Bissyandé and Jacques Klein and Cuiyun Gao},
year = {2026},
eprint = {2608.03743},
archivePrefix = {arXiv},
primaryClass = {cs.SE},
url = {https://arxiv.org/abs/2608.03743},
}