tuispec: Playwright-like black-box testing for terminal UIs over PTY

[ library, mit, program, testing ] [ Propose Tags ] [ Report a vulnerability ]

tuispec is a Haskell framework for black-box testing of terminal user interfaces (TUIs) over PTY. . It provides a Playwright-inspired DSL for launching apps, sending keystrokes, waiting for text, and capturing snapshots (ANSI text + PNG). . Tests are regular compiled Haskell programs using tasty with per-test isolation via fresh PTY processes. The framework is generic to any TUI binary runnable from a shell, with no instrumentation required inside the target app. . Features include: . * PTY transport with per-test isolation * Text selectors (Exact, Regex, At, Within, Nth) * Keypress and text input actions * Snapshot assertions with ANSI text + PNG artifacts * Configurable retry, timeout, and ambiguity modes * JSON-RPC server for interactive orchestration * REPL-style session mode for ad-hoc exploration


[Skip to Readme]

Modules

  • TuiSpec
    • TuiSpec.Render
    • TuiSpec.Runner
    • TuiSpec.Server
    • TuiSpec.Types

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0
Change log CHANGELOG.md
Dependencies aeson (>=2.0 && <2.3), base (>=4.16 && <5), bytestring (>=0.11 && <0.13), containers (>=0.6 && <0.8), directory (>=1.3 && <1.4), filepath (>=1.4 && <1.6), jsonrpc (>=0.2 && <0.3), optparse-applicative (>=0.18 && <0.20), posix-pty (>=0.2 && <0.3), process (>=1.6 && <1.7), tasty (>=1.4 && <1.6), tasty-hunit (>=0.10 && <0.11), text (>=1.2 && <2.2), time (>=1.9 && <1.15), tuispec, unix (>=2.8 && <2.9) [details]
Tested with ghc ==9.12.2
License MIT
Copyright 2026 Matthias Pall Gissurarson
Author Matthias Pall Gissurarson
Maintainer mpg@mpg.is
Uploaded by tritlo at 2026-02-23T02:48:51Z
Category Testing
Home page https://github.com/Tritlo/tuispec
Bug tracker https://github.com/Tritlo/tuispec/issues
Source repo head: git clone https://github.com/Tritlo/tuispec.git
Distributions
Executables tuispec
Downloads 0 total (0 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs not available [build log]
All reported builds failed as of 2026-02-23 [all 2 reports]

Readme for tuispec-0.1.0.0

[back to package description]

tuispec

Hackage MIT license

Playwright-like black-box testing for terminal UIs over PTY.

tuispec is a Haskell library that lets you write reliable TUI tests as normal Haskell programs. Interact with any terminal application via PTY — send keystrokes, wait for text, and capture snapshots — without instrumenting the target app.

Example output

Snapshots captured from the included Brick demo app using tuispec:

Board snapshot (IoskeleyMono, light theme) Dashboard snapshot (IoskeleyMono, dark theme)

Features

  • PTY transport — tests interact with real terminal apps over PTY
  • Per-test isolation — fresh PTY process per test, no shared state
  • Snapshot assertions — baseline comparison with ANSI text + PNG artifacts
  • Text selectorsExact, Regex, At, Within, Nth
  • tasty integration — tests are regular tasty test trees
  • JSON-RPC server — agentic orchestration of TUIs via tuispec server
  • REPL sessions — ad-hoc exploration with withTuiSession

Quick start

Add tuispec to your build-depends and write a test:

{-# LANGUAGE OverloadedStrings #-}

import Test.Tasty (defaultMain, testGroup)
import TuiSpec

main :: IO ()
main =
  defaultMain $ testGroup "demo"
    [ tuiTest defaultRunOptions "counter" $ \tui -> do
        launch tui (App "my-tui" [])
        waitForText tui (Exact "Ready")
        press tui (CharKey '+')
        expectSnapshot tui "counter-updated"
        press tui (CharKey 'q')
    ]

Run:

cabal test

Building from source

cabal build

Run root smoke tests:

cabal test

Run the Brick demo integration suite:

cd example
cabal test

DSL overview

Actions

launch    :: Tui -> App -> IO ()
press     :: Tui -> Key -> IO ()
pressCombo :: Tui -> [Modifier] -> Key -> IO ()
typeText  :: Tui -> Text -> IO ()
sendLine  :: Tui -> Text -> IO ()

Waits and assertions

waitForText    :: Tui -> Selector -> IO ()
expectVisible  :: Tui -> Selector -> IO ()
expectNotVisible :: Tui -> Selector -> IO ()
expectSnapshot :: Tui -> SnapshotName -> IO ()
dumpView       :: Tui -> SnapshotName -> IO FilePath

Selectors

Exact  Text           -- exact substring match
Regex  Text           -- lightweight regex (|, .*, literal parens stripped)
At     Int Int        -- position-based (col, row)
Within Rect Selector  -- restrict to a rectangle
Nth    Int Selector   -- pick the Nth match

Keys

Named keys: Enter, Esc, Tab, Backspace, arrows, FunctionKey 1..12

Character keys: CharKey c, Ctrl c, AltKey c

Combos: pressCombo [Control] (CharKey 'c')

Snapshots

For a test named my test (slug: my-test) with artifactsDir = "artifacts":

  • Baselines: artifacts/snapshots/my-test/<snapshot>.ansi.txt
  • Per-run: artifacts/tests/my-test/snapshots/<snapshot>.ansi.txt

Render any ANSI snapshot to PNG:

cabal run tuispec -- render artifacts/tests/my-test/snapshots/<snapshot>.ansi.txt

Specify an explicit font file:

cabal run tuispec -- render --font /path/to/YourMono.ttf artifacts/tests/my-test/snapshots/<snapshot>.ansi.txt

Render visible plain text:

cabal run tuispec -- render-text artifacts/tests/my-test/snapshots/<snapshot>.ansi.txt

JSON-RPC server

cabal run tuispec -- server --artifact-dir artifacts/server

Newline-delimited JSON-RPC 2.0 on stdin/stdout for agentic orchestration of TUIs. See SERVER.md for the full protocol reference.

Ping example:

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"server.ping","params":{}}' \
  | cabal run tuispec -- server --artifact-dir artifacts/server

End-to-end session example:

cat <<'JSON' | cabal run tuispec -- server --artifact-dir artifacts/server
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"name":"rpc-demo","terminalCols":134,"terminalRows":40}}
{"jsonrpc":"2.0","id":2,"method":"launch","params":{"command":"sh","args":[]}}
{"jsonrpc":"2.0","id":3,"method":"sendLine","params":{"text":"printf 'hello from rpc\\n'"}}
{"jsonrpc":"2.0","id":4,"method":"waitForText","params":{"selector":{"type":"exact","text":"hello from rpc"}}}
{"jsonrpc":"2.0","id":5,"method":"dumpView","params":{"name":"after-hello"}}
{"jsonrpc":"2.0","id":6,"method":"server.shutdown","params":{}}
JSON

Input examples:

{"jsonrpc":"2.0","id":7,"method":"sendKey","params":{"key":"+"}}
{"jsonrpc":"2.0","id":8,"method":"sendKey","params":{"key":"Ctrl+C"}}
{"jsonrpc":"2.0","id":9,"method":"sendText","params":{"text":"hello"}}

REPL-style sessions

For ad-hoc exploration outside tasty:

withTuiSession defaultRunOptions "demo" $ \tui -> do
  launch tui (App "sh" [])
  sendLine tui "echo hello"
  _ <- dumpView tui "step-1"
  pure ()

Configuration

RunOptions fields (all overridable via environment variables):

Field Default Env var
timeoutSeconds 5 TUISPEC_TIMEOUT_SECONDS
retries 0 TUISPEC_RETRIES
stepRetries 0 TUISPEC_STEP_RETRIES
terminalCols 134 TUISPEC_TERMINAL_COLS
terminalRows 40 TUISPEC_TERMINAL_ROWS
artifactsDir "artifacts" TUISPEC_ARTIFACTS_DIR
updateSnapshots False TUISPEC_UPDATE_SNAPSHOTS
ambiguityMode FailOnAmbiguous TUISPEC_AMBIGUITY_MODE
snapshotTheme "auto" TUISPEC_SNAPSHOT_THEME

Requirements

  • GHC 9.12+
  • Linux terminal environment (PTY-based)
  • python3 with Pillow for PNG rendering
  • a host monospace TTF/TTC font (or pass --font, or TUISPEC_FONT_PATH)

License

MIT — Matthias Pall Gissurarson