Screens & regions¶
A Screen is an immutable snapshot of the terminal grid at one
moment. Cells are addressed (row, col), 0-based, top-left origin.
Accessing the screen¶
The screen is not a live view. If you want the latest state,
re-read tui.screen. This is intentional: snapshots are reproducible
and equality is well-defined.
Cell-level access¶
cell = screen.cells[row][col]
cell.char # str (single grapheme)
cell.fg, cell.bg # Color (named, default, or rgb hex)
cell.bold # bool
cell.italic
cell.underline
cell.reverse
cell.strike
cell.blink
Cell is a frozen dataclass — equality and hashing work as expected,
so you can drop cells into sets and use them as dict keys.
Text views¶
screen.text # all rows joined with '\n', trailing spaces stripped
screen.row(2) # one row, trimmed
screen.row_padded(2) # one row, exactly cols wide
screen.text is the workhorse for assertions:
Searching¶
from re import Pattern
import re
screen.contains("Ready")
screen.contains(re.compile(r"v\d+\.\d+")) # regex pattern
screen.contains(r"v\d+\.\d+", regex=True) # str + flag
screen.row_containing("Error") # int or None
screen.find("foo") # list[Position]
screen.find(r"\d+", regex=True) # all matches
Position is a NamedTuple of (row, col).
Regions¶
A Region is a rectangular sub-view of a screen. Use them to scope
assertions to one panel and avoid false matches.
By title (heuristic)¶
tuiwright looks for ratatui-style framed blocks
(┌─ Title ─┐ … └─────┘):
The heuristic walks the box-drawing characters around the title to
find the inside rectangle. It works for the default ratatui
Block::default().borders(Borders::ALL).title("Logs") plus the
double-line variant.
If the heuristic doesn't match (custom borders, different chars), fall back to explicit coordinates.
By coordinates¶
header = tui.region(rows=(0, 1), cols=(0, tui.screen.cols))
status = tui.region(rows=(tui.screen.rows - 1, tui.screen.rows), cols=(0, tui.screen.cols))
Bounds are half-open: rows=(0, 1) is one row.
Region methods¶
region.text # joined rows, trimmed
region.row(0) # one row of the region
region.rows # height
region.cols # width
region.contains("foo")
region.contains(r"v\d+\.\d+", regex=True)
Equality¶
Screen equality is cell-by-cell, attributes included:
Two screens differ if:
- Any character differs
- Any cell's fg/bg differs
- Any cell's bold/italic/underline/reverse/strike/blink differs
- Cursor position or visibility differs
- Active DEC mode set differs
This makes cell-grid snapshots sensitive to colour and attribute changes, not just text.
If you only care about visible text, compare screen.text strings
instead of full screens.
Common patterns¶
Assert on a status line¶
Assert on a region's first matching row¶
Find every error marker¶
errors = tui.screen.find("✗")
assert len(errors) == 3
for pos in errors:
print(f"error at row {pos.row}, col {pos.col}")
Wait until a region has a specific value¶
wait_for_text accepts a region — it re-derives the region from the
current screen on every poll, so it stays correct even as the screen
updates:
Next: Waiting (no sleep!) →