This release is a pre-release and may not be stable for production use.
tui-test for Python
Control, inspect, and test terminal apps from Python.
Install
pip install --pre tui-test
Python 3.8+ is supported.
Quick start
from tui_test import TuiTest
async with TuiTest.ephemeral() as terminal:
await terminal.run("my-app")
await terminal.get_by_text("Ready").expect()
await terminal.get_by_text("Continue").click()
await terminal.get_by_text("Done").expect()
API
TuiTest
TuiTest(session=None, *, backend=None, timeouts=None, profile=None, artifacts=None, recording=None)
| Option | Type | Default |
|---|---|---|
session |
str |
TUI_TEST_SESSION or "default" |
backend |
"alacritty" | "ghostty" | "rio" | "xtermjs" |
"alacritty" |
timeouts |
Timeouts | dict |
built-in defaults |
profile |
Profile | dict |
built-in profile |
artifacts |
dict |
off |
recording |
AutomaticRecording | dict |
{"mode": "always"} |
artifacts["on_failure"] is "svg", "text", or "none". Recording mode is "disabled", "on-failure", or "always".
Properties
| Property | Type |
|---|---|
session |
str |
keyboard |
keyboard helper |
mouse |
mouse helper |
Lifecycle
| Method | Description |
|---|---|
TuiTest.ephemeral(prefix=None, **options) |
Create a unique session. |
await open(**options) |
Open a shell. |
await run(program, *args, **options) |
Run a program. |
await restart(graceful_timeout=5000) |
Restart the session. |
await close() |
Close the session. |
await close_quiet() |
Close without raising. |
async with TuiTest() |
Close on exit. |
open() options are shell, backend, cols, rows, cwd, env, wait_ready, restart, retries, profile, and timeouts. run() accepts the same options except shell.
The default size is 80 by 30. Timeout defaults are 5 seconds for text and idle, and 30 seconds for command, exit, and ready.
Input
| Method | Description |
|---|---|
await submit(text=None) |
Type text and press Enter. |
await type(text) |
Type text. |
await write(data) |
Write raw bytes. |
await press(*keys) |
Alias for keyboard.press(). |
await resize(cols, rows) |
Resize the terminal. |
await signal(name) |
Send INT, TERM, KILL, or QUIT. |
await kill() |
Kill the child process. |
State
| Method | Returns |
|---|---|
await state() |
State |
await text(full=False) |
str |
await cells(x, y, w=1, h=1) |
list[Cell] |
await get_command() |
str | None |
await get_output() |
str | None |
await get_exit_code() |
int | None |
await get_cwd() |
str | None |
await get_cursor() |
dict |
await get_size() |
dict |
await get_title() |
str | None |
await get_clipboard() |
str |
await get_bell_count() |
int |
await get_bell_events() |
list[BellEvent] |
Waits and assertions
| Method | Description |
|---|---|
await wait_title(text, regex=False, not_=False, timeout=None) |
Wait for a title. |
await wait_clipboard(text=None, timeout=None) |
Wait for a clipboard change or match. |
await wait_idle(timeout=None) |
Wait for the screen to stop changing. |
await wait_command(timeout=None) |
Wait for a submitted command. |
await wait_exit(timeout=None) |
Wait for the program to exit. |
await wait_ready(timeout=None) |
Wait for a shell prompt. |
await wait_bell(timeout=None) |
Wait for a bell. |
await expect_title(text, regex=False, not_=False, timeout=None) |
Assert the title. |
await expect_exit_code(code, timeout=None) |
Assert the last exit code. |
await expect_output(text, regex=False) |
Assert command output. |
await expect_bell_count(count, timeout=None) |
Wait until the cumulative bell count reaches count. |
await expect_snapshot(name, **options) |
Assert or update a snapshot. |
wait_clipboard() waits for the next change. A string matches text. A compiled re.Pattern matches a regular expression.
Snapshot options are update, include_style, and include_title.
Capture
| Method | Description |
|---|---|
await screenshot(path=None, full=False, zoom=None, background=None, transparent=False) |
Return text or save SVG or PNG. |
await start_recording(path, **options) |
Start APNG, GIF, MP4, or asciinema recording. |
await stop_recording() |
Finish the recording and return its path. |
Recording options: format, fps, speed, idle_time_limit, zoom, background, and transparent. MP4 requires ffmpeg and does not support transparency.
The extension selects the format: .png or .apng, .gif, .mp4, or .cast. format overrides it.
Locator
Locators resolve against the latest terminal screen before every read or action.
from tui_test import TextStyle
save = (
terminal
.get_by_text("Settings")
.get_by_text("Save", direction="after")
.get_by_style(TextStyle(foreground="green"))
.unique()
)
await save.click()
Create a locator
| Method | Options |
|---|---|
terminal.get_by_text(text, **options) |
regex, full, whitespace |
terminal.get_by_style(style, **options) |
full |
terminal.get_by_link(uri, **options) |
full |
locator.get_by_text(text, **options) |
regex, full, whitespace, direction |
locator.get_by_style(style, **options) |
full, direction |
locator.get_by_link(uri, **options) |
full, direction |
whitespace is "exact" or "normalize". direction is "within", "after", or "before".
TextStyle fields are foreground, background, bold, dim, italic, underline_style, underline_color, inverse, hidden, strikethrough, and blink.
get_by_link(uri) matches an exact OSC 8 target, not visible URL text;
get_by_link("") requires no link. Root style/link selectors find runs within
each row. Chained calls with the default within direction check whole
matches. Styles skip blanks if visible text exists; links check every cell.
Compose locators
link = terminal.get_by_link("https://example.com")
bold = terminal.get_by_style(TextStyle(bold=True))
bold_link_cells = bold.and_(link)
either = link.or_(terminal.get_by_text("Help"))
sections = terminal.get_by_text("Docs and Help")
contains_link = sections.filter(has=link)
without_old_text = sections.filter(has_not=terminal.get_by_text("old"))
entirely_linked = sections.get_by_link("https://example.com")
and_() keeps shared cells; or_() combines cells without duplicates. Adjacent
cells merge within each physical row, even across original matches. Gaps and
row breaks split runs. Counts and clicks use these runs; text keeps exact
whitespace.
filter accepts only locators. has requires a match inside each candidate;
has_not requires none. Both conditions apply when supplied, and the inner
match may cover the whole candidate. For partially linked "Docs",
filter(has=link) keeps the whole word, get_by_link(uri) rejects it, and
and_(link) returns its linked cells.
Use locators from one TuiTest. Composition leaves them unchanged and reads
one fresh snapshot when used. Selection order matters: a.first().and_(b)
differs from a.and_(b).first(). Any full branch includes scrollback for the
whole query. Errors propagate.
Select matches
| Method | Description |
|---|---|
any() |
Keep all matches. |
unique() |
Require one match. |
first() |
Select the first match. |
last() |
Select the last match. |
nth(index) |
Select a zero-based match. |
Read and act
| Method | Description |
|---|---|
await locations() |
Return all selected locations. |
await location() |
Return one location. |
await count() |
Return the current count. |
await all() |
Return one locator per current match. |
await wait(state="visible", timeout=None) |
Wait for "visible" or "hidden". |
await expect(not_=False, timeout=None) |
Assert the locator. |
await click(**options) |
Click the middle cell. |
await highlight(timeout=None) |
Highlight matches. |
click() accepts button, alt, ctrl, shift, clicks, and timeout. button is "left", "middle", or "right".
location() and click() require one match. all() does not wait.
Keyboard
| Method | Description |
|---|---|
await keyboard.press(*keys) |
Press keys. |
await keyboard.down(*keys) |
Send keydown events. |
await keyboard.repeat(*keys) |
Send repeat events. |
await keyboard.up(*keys) |
Send keyup events. |
await terminal.keyboard.press("Ctrl+C")
await terminal.keyboard.press("Escape", ":", "w", "q", "Enter")
Named keys include Up, Down, Left, Right, Home, End, PageUp, PageDown, Insert, Delete, Backspace, Tab, Enter, Space, Escape, and F1 through F12. Join modifiers such as Ctrl, Alt, Shift, Super, Meta, or Hyper with +.
Mouse
Coordinates are zero-based terminal cells.
| Method | Description |
|---|---|
await mouse.click(x=None, y=None, **options) |
Click a cell or on_text. |
await mouse.move(x, y) |
Move the pointer. |
await mouse.down(x, y, **options) |
Press a button. |
await mouse.up(x, y, **options) |
Release a button. |
await mouse.drag(x1, y1, x2, y2, **options) |
Drag between cells. |
await mouse.scroll("up" | "down", amount=3) |
Scroll. |
Button options are button, alt, ctrl, and shift. Click also accepts on_text and clicks.
await terminal.mouse.click(10, 5, button="right", ctrl=True)
Module functions
| Function | Description |
|---|---|
await sessions() |
List sessions in this process. |
await close_all() |
Close all sessions in this process. |
await get_recording(session=None) |
Return an automatic asciinema recording. |
unique_session(prefix=None) |
Create a unique session name. |
Test helpers
Import from tui_test.testing.
| Function | Description |
|---|---|
await create_terminal(**options) |
Create, open, and track a terminal. |
async with terminal(**options) |
Open and close a terminal. |
await close_all_tracked() |
Close tracked terminals. |
set_terminal_defaults(**options) |
Set suite defaults. |
reset_terminal_defaults() |
Reset suite defaults. |
track_terminal(terminal) |
Track a terminal. |
untrack_terminal(terminal) |
Stop tracking a terminal. |
tracked_count() |
Count tracked terminals. |
terminal_snapshot(text) |
Normalize text for snapshots. |
TerminalOptions adds shell, program, session, and prefix to the client and spawn options.
DEFAULT_SHELL is the platform default.
from tui_test.testing import terminal
async with terminal(program=("my-app",)) as app:
await app.get_by_text("Ready").expect()
Configuration
from tui_test import AutomaticRecording, Colors, Profile, Timeouts
terminal = TuiTest(
profile=Profile(
scrollback=500,
colors=Colors(foreground="#ffffff", background="#000000"),
),
timeouts=Timeouts(text=10_000, command=60_000),
artifacts={"dir": "artifacts", "on_failure": "svg"},
recording=AutomaticRecording(mode="on-failure", directory="artifacts"),
)
Types
| Type | Description |
|---|---|
State |
Session state and visible text. |
Cell |
One terminal cell and its style. |
TextMatch |
Matched text, positions, and spans. |
TextStyle |
Locator style fields. |
Profile |
Scrollback and colors. |
Timeouts |
Text, idle, command, exit, and ready timeouts. |
AutomaticRecording |
Automatic recording mode and directory. |
Colors |
Terminal palette. |
MouseButton |
"left", "middle", or "right". |
TextPosition, TextSpan |
Match coordinates. |
__version__ contains the package version.
Errors
| Error | Exit code |
|---|---|
ExpectationError |
1 |
UsageError |
2 |
NoSessionError |
3 |
InternalError |
5 |
All errors extend TuiTestError. Expectation errors can include terminal.text and terminal.screenshot.
Sessions are local to the current process and cannot be controlled by the CLI. Cancelling a task does not stop an active terminal operation.
Release files for tui-test 0.1.0b4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tui_test-0.1.0b4.tar.gz | 4.2 MB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| tui_test-0.1.0b4-cp38-abi3-win_amd64.whl | CPython 3.8 | abi3 | Windows x86-64 | Details |
| tui_test-0.1.0b4-cp38-abi3-musllinux_1_2_x86_64.whl | CPython 3.8 | abi3 | Linux musl 1.2+ x86-64 | Details |
| tui_test-0.1.0b4-cp38-abi3-musllinux_1_2_aarch64.whl | CPython 3.8 | abi3 | Linux musl 1.2+ ARM64 | Details |
| tui_test-0.1.0b4-cp38-abi3-manylinux_2_28_x86_64.whl | CPython 3.8 | abi3 | Linux glibc 2.28+ x86-64 | Details |
| tui_test-0.1.0b4-cp38-abi3-manylinux_2_28_aarch64.whl | CPython 3.8 | abi3 | Linux glibc 2.28+ ARM64 | Details |
| tui_test-0.1.0b4-cp38-abi3-macosx_11_0_arm64.whl | CPython 3.8 | abi3 | macOS 11.0+ ARM64 | Details |
| tui_test-0.1.0b4-cp38-abi3-macosx_10_12_x86_64.whl | CPython 3.8 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 61.5 MB
Release files / tui_test-0.1.0b4.tar.gz
| Download URL | tui_test-0.1.0b4.tar.gz |
|---|---|
| Size | 4.2 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
88fcb21a171b99b716a0a79140e31c6dad1db2327554f95d4a843df734fd60e8
|
|
BLAKE2b-256 checksum How to use checksums |
906249f236c2e7cd80e8e7e715d3f8ee230fda7e7dcfd2189e1c84bc9ad6541b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency logRelease files / tui_test-0.1.0b4-cp38-abi3-win_amd64.whl
| Download URL | tui_test-0.1.0b4-cp38-abi3-win_amd64.whl |
|---|---|
| Size | 6.7 MB |
| Tags | CPython 3.8 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
1560ba7067e7e7ce33b56069a08229e8cbaecfd12dd2463ca3550c49754976bb
|
|
BLAKE2b-256 checksum How to use checksums |
070b696709b512172224b3f1a43e1552dee75b67696cc25522f2376f808c745f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency logRelease files / tui_test-0.1.0b4-cp38-abi3-musllinux_1_2_x86_64.whl
| Download URL | tui_test-0.1.0b4-cp38-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 13.2 MB |
| Tags | CPython 3.8 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
11f850f6385573f733bf94c94a4d512ab60529d20018d431a5ad8b46d7947ebd
|
|
BLAKE2b-256 checksum How to use checksums |
b34a3a8fce8add3509f06937576d4e0e56f259f0ab6a869f8b8ce629c5f97151
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency logRelease files / tui_test-0.1.0b4-cp38-abi3-musllinux_1_2_aarch64.whl
| Download URL | tui_test-0.1.0b4-cp38-abi3-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 12.7 MB |
| Tags | CPython 3.8 Linux musl 1.2+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
b4cbdb00c4661f446608e267f36a557b1aef4a324bfdaa0de2bd8cc195b5e93c
|
|
BLAKE2b-256 checksum How to use checksums |
f7b3762cc6c9fb440989430f91d66ef30daae257a66f4d70eff57ac4c20a452a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency logRelease files / tui_test-0.1.0b4-cp38-abi3-manylinux_2_28_x86_64.whl
| Download URL | tui_test-0.1.0b4-cp38-abi3-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 6.6 MB |
| Tags | CPython 3.8 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
e009b4e153abe876af5ae7c7c24754ee424a49d94a4c9a44257ae8f6564c47a2
|
|
BLAKE2b-256 checksum How to use checksums |
2b386f8e9d672e1cc84720f7fac5b7362f48282c566fc70f15b425e6247771c8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency logRelease files / tui_test-0.1.0b4-cp38-abi3-manylinux_2_28_aarch64.whl
| Download URL | tui_test-0.1.0b4-cp38-abi3-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 6.2 MB |
| Tags | CPython 3.8 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
0ecf1f415c840001a9905138356f6aba4d286a114d4d037b69df187ab076d5be
|
|
BLAKE2b-256 checksum How to use checksums |
99f3e28396e3150c7a3e2427ba9b40bc6567c1d7f3d0740ee32bf3872725fc12
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency logRelease files / tui_test-0.1.0b4-cp38-abi3-macosx_11_0_arm64.whl
| Download URL | tui_test-0.1.0b4-cp38-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 5.8 MB |
| Tags | CPython 3.8 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
079c10b2d91723a516daaafb2a57768d44330633c14291a1f3bde0e8faa2b428
|
|
BLAKE2b-256 checksum How to use checksums |
d281f65ac5fc200b97da53e631ac237b4e76e6f8938163a32ad9b72f2dac5435
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency logRelease files / tui_test-0.1.0b4-cp38-abi3-macosx_10_12_x86_64.whl
| Download URL | tui_test-0.1.0b4-cp38-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 6.1 MB |
| Tags | CPython 3.8 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
d0cc341dac936c04eb68175e8f6398ef5388f440e8af011e832a7d76ca548ee3
|
|
BLAKE2b-256 checksum How to use checksums |
a79b9340462a5dedcd9f3b4e093e6f030e18e644c1d358ce71084a125c8e2fd6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency log