The recent release of tui-test on PyPI marks a significant step forward for developers who rely on terminal-based user interfaces (TUIs) and need reliable automation, inspection, and assertion capabilities. By providing Python bindings to a core engine written in Rust, the project combines the safety and performance of Rust with the accessibility and rich ecosystem of Python. This hybrid approach addresses a growing market need as more developer tools, configuration utilities, and even games adopt terminal interfaces that demand rigorous testing. The bindings are available as pre‑compiled wheels for common platforms, meaning users can install tui-test with a simple pip command and avoid the complexity of compiling Rust code themselves, lowering the barrier to adoption for teams that prioritize rapid iteration.
Under the hood, tui-test leverages maturin to build and distribute wheels that support Python 3.8 and later, ensuring broad compatibility across Linux, macOS, and Windows environments. The decision to expose the Rust engine through Pythonic idioms allows testers to write scripts that feel native to the language while still benefiting from the deterministic behavior of a compiled backend. This architecture also facilitates seamless integration with existing Python test frameworks such as pytest or unittest, enabling teams to incorporate TUI verification into their continuous integration pipelines without rewriting large portions of their test suite. The result is a tool that feels at home in a Python developer’s workflow while delivering the robustness expected from a systems‑level automation engine.
At the heart of the API is the TuiTest class, which mirrors the command‑line interface of the underlying tui-test utility but offers fine‑grained programmatic control. Users can instantiate a session with optional timeouts and artifact directories, then drive a full‑screen TUI through a rich set of actions: typing text, writing buffers, submitting input, pressing individual keys or key combinations, and manipulating the mouse with click, move, drag, and scroll operations. Beyond interaction, the library provides queries for internal state such as the current command, output, exit code, working directory, cursor position, terminal size, and title, allowing tests to assert on both visible content and hidden process details. This breadth of functionality makes it possible to simulate complex user workflows that would be tedious or error‑prone to perform manually.
Error handling in tui-test is designed to give developers immediate, actionable feedback when expectations are not met. All exceptions derive from a base TuiTestError, with wait_* and expect_* methods raising a more specific ExpectationError when a condition fails to occur within the allotted time. Crucially, assertion errors automatically capture and include the current visible terminal content in their message, turning a vague failure into a clear snapshot of what the test saw at the moment of breakdown. This diagnostic richness reduces the time spent reproducing issues and helps teams maintain confidence in their test suites, especially when dealing with flaky or timing‑dependent terminal applications.
Session configuration is flexible yet straightforward. The TuiTest constructor accepts parameters such as session name, timeouts (as a dictionary or a dedicated Timeouts object), and artifacts directory for storing screenshots or recordings. These settings mirror the flags available in the command‑line tool, ensuring consistency between interactive use and automated scripts. The open() and run() methods additionally accept wait_ready, retries, and per‑call timeout overrides, allowing tests to adapt to slow‑starting applications or flaky environments without altering global defaults. Unknown timeout class names trigger an immediate error, helping catch configuration mistakes early in the test development cycle.
The library defines five distinct timeout categories—text, idle, command, exit, and ready—each governing a different aspect of terminal interaction. Text timeouts apply to waiting for specific strings to appear, idle timeouts guard against prolonged inactivity, command timeouts limit how long a spawned process may run before being considered stuck, exit timeouts focus on the duration until a process terminates, and ready timeouts wait for the terminal to reach a usable state after launch. By setting these values either at the session constructor or globally via the Timeouts object, teams can fine‑tune sensitivity to match the performance characteristics of their target applications, reducing false positives while still catching genuine regressions.
For testing convenience, tui_test.testing provides a collection of helpers that streamline common workflows. The create_terminal function spawns a new terminal session with a guaranteed unique name, preventing collisions when tests run in parallel. The terminal async context manager simplifies setup and teardown, automatically closing the session when the block exits, while close_all_tracked offers a way to clean up any lingering sessions created via the helper. Constants such as DEFAULT_SHELL point to the system’s default command interpreter, and terminal_snapshot captures the entire visible buffer for later comparison, supporting snapshot‑based testing strategies that detect unintended UI changes with minimal boilerplate.
Parallel execution safety is a key consideration for modern test suites, and tui-test addresses it by ensuring each terminal session receives a unique identifier derived from the provided name or an auto‑generated suffix. This uniqueness prevents interference between workers when tests are distributed across multiple processes or threads. Furthermore, the set_terminal_defaults function allows teams to establish suite‑wide defaults for timeouts, artifact directories, and other options, ensuring consistency across all tests without requiring repetitive configuration in every test file. This centralization simplifies maintenance and helps enforce organizational standards for test reliability and resource management.
An important nuance of the library’s design concerns task cancellation: cancelling an asyncio task that is awaiting a tui-test operation does not interrupt the underlying Rust process. Operations such as close() or close_all() will continue to run to completion, guaranteeing that resources are properly released and that no orphaned processes are left behind. This behavior protects against half‑cleaned‑up states that could corrupt subsequent tests or consume system resources unnecessarily. Developers should therefore rely on the library’s own cleanup mechanisms rather than assuming that language‑level cancellation will propagate to the backend, and structure their test teardown accordingly.
When a session is closed, it is removed from the active sessions list returned by sessions(), but its recording—a chronological log of all interactions and terminal states—remains accessible via get_recording() for the lifetime of the process. The library retains recordings for the 1024 most recently closed sessions, providing a valuable buffer for post‑mortem analysis without unbounded memory growth. This design enables teams to review exactly what happened during a failing test, step through inputs and outputs, and even replay scenarios for debugging, all while keeping memory usage predictable and bounded.
From a licensing and adoption perspective, tui-test is released under the permissive MIT license, making it suitable for both open‑source projects and commercial products. The reliance on maturin for wheel distribution means that users on supported platforms avoid compiling Rust code locally, which can save significant time in CI environments and reduce variability caused by differing toolchains. The project’s Python‑>=3.8 requirement aligns with the language’s current adoption curve, ensuring that most modern deployments can benefit without needing to maintain legacy interpreter support. Early adopters have noted the library’s clear documentation and active issue responsiveness as factors that lower the friction of integration.
To get started with tui-test, teams should first install the package via pip, then write a small exploratory script that opens a familiar terminal application such as bash or vim and asserts on a known prompt or screen layout. Incorporating the terminal async context manager into pytest fixtures can provide clean, isolated sessions for each test. Leveraging snapshot testing with terminal_snapshot helps detect unintended UI shifts early, while configuring appropriate timeouts based on application start‑up characteristics reduces flakiness. As test suites grow, consider using set_terminal_defaults to enforce consistent behavior across suites and monitor the retention of recordings to ensure they serve as a useful debugging aid without causing storage concerns. Finally, staying engaged with the project’s release notes and contributing feedback or improvements to the Rust core can help shape the tool’s evolution to meet emerging needs in terminal automation.