tuiwright¶
Playwright-style end-to-end testing for terminal UI applications.
tuiwright spawns any TUI binary under a real pseudo-terminal, parses
its output through a faithful VT102 emulator, and lets you assert on
the rendered screen with an async pytest API. Keys, text, mouse,
bracketed paste, resize, focus events — all first-class. Cell-grid and
PNG snapshot regression out of the box.
import pytest
from tuiwright import TuiSession
from tuiwright._snapshot import ScreenSnapshotExtension
pytestmark = pytest.mark.asyncio
async def test_save_flow(tui: TuiSession, snapshot):
await tui.start("myapp", cols=120, rows=40)
await tui.wait_for_text("Ready")
await tui.type("hello world")
await tui.press("ctrl+s")
await tui.wait_for_text("Saved")
await tui.click(row=5, col=12)
await tui.assert_region(title="Logs", contains="saved hello world")
assert tui.screen == snapshot(extension_class=ScreenSnapshotExtension)
Why it exists¶
The TUI testing ecosystem is fragmented:
-
pexpect/expect
Line/regex-oriented. Break the moment your app uses cursor addressing.
-
vhs,asciinema
Demo recording, not designed for assertions.
-
Textual
Pilot,teatest
In-process — never exercise the real binary or the PTY.
-
insta,syrupy
Assertion layer only, no driver.
tuiwright is the missing piece: black-box, async, snapshot-aware,
ergonomic.
Highlights¶
- Pure Python. Pure-Python deps too — no native build, no docker, no chromium.
- Real input. Keys, text, mouse (click/scroll/drag/hover), bracketed paste, resize, focus events.
- No
sleep()ever. Wait primitives are predicate-driven and settle-based; tests are fast on healthy machines and reliable on slow ones. - Two regression modes. Cell-grid
snapshots (text + per-cell attrs, reviewable as a diff) and PNG
snapshots (via
agg+pixelmatch). - First-class pytest.
tuifixture, marker, CLI flags, asciinema cast retention on failure. - Record & generate. Drive your
TUI interactively under
tuiwright record; codegen produces a ready-to-commit pytest test from the recording.
Get started in 30 seconds¶
Then write a test:
async def test_my_tui(tui):
await tui.start("path/to/binary")
await tui.wait_for_text("Ready")
await tui.type("hello")
await tui.press("enter")
assert "hello" in tui.screen.text
Continue with the Install guide →