tuiwright 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pandelis Zembashis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,246 @@
1
+ Metadata-Version: 2.4
2
+ Name: tuiwright
3
+ Version: 0.1.0
4
+ Summary: Playwright-style end-to-end testing for TUI applications. Drives any TUI binary via a real PTY + terminal emulator, with cell-grid and PNG snapshot regression.
5
+ Keywords: tui,testing,terminal,pty,snapshot,ratatui,crossterm,pytest,e2e,playwright
6
+ Author: Pandelis Zembashis
7
+ Author-email: Pandelis Zembashis <p@ndel.is>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Framework :: Pytest
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Testing
19
+ Classifier: Topic :: Terminals
20
+ Classifier: Typing :: Typed
21
+ Requires-Dist: ptyprocess>=0.7.0
22
+ Requires-Dist: pyte>=0.8.2
23
+ Requires-Dist: pytest>=8.0
24
+ Requires-Dist: pytest-asyncio>=0.23
25
+ Requires-Dist: syrupy>=4.6
26
+ Requires-Dist: pillow>=10.0
27
+ Requires-Dist: pixelmatch>=0.3.0
28
+ Requires-Dist: urwid>=2.6 ; extra == 'examples'
29
+ Requires-Python: >=3.11
30
+ Project-URL: Homepage, https://pandelisz.github.io/tuiwright/
31
+ Project-URL: Documentation, https://pandelisz.github.io/tuiwright/
32
+ Project-URL: Repository, https://github.com/PandelisZ/tuiwright
33
+ Project-URL: Issues, https://github.com/PandelisZ/tuiwright/issues
34
+ Project-URL: Changelog, https://github.com/PandelisZ/tuiwright/blob/master/CHANGELOG.md
35
+ Provides-Extra: examples
36
+ Description-Content-Type: text/markdown
37
+
38
+ # tuiwright
39
+
40
+ **Playwright-style end-to-end testing for terminal user interfaces.**
41
+
42
+ `tuiwright` drives any TUI binary under a real PTY plus a faithful
43
+ terminal emulator, then lets you assert on the rendered screen with an
44
+ async pytest API. It covers keys, text, mouse, resize, bracketed paste,
45
+ and focus events out of the box, with cell-grid and PNG snapshot
46
+ regression.
47
+
48
+ ```python
49
+ async def test_save_flow(tui, snapshot):
50
+ await tui.start("myapp", cols=120, rows=40)
51
+ await tui.wait_for_text("Ready")
52
+
53
+ await tui.type("hello world")
54
+ await tui.press("ctrl+s")
55
+ await tui.wait_for_text("Saved")
56
+
57
+ await tui.click(row=5, col=12)
58
+ await tui.assert_region(title="Logs", contains="saved hello world")
59
+
60
+ assert tui.screen == snapshot(extension_class=ScreenSnapshotExtension)
61
+ ```
62
+
63
+ ## Why
64
+
65
+ | Existing tool | Limitation |
66
+ |---|---|
67
+ | `pexpect` / `expect` | Line/regex oriented — broken on cursor-addressed full-screen apps |
68
+ | `vhs`, `asciinema` | Demo recording, not designed for assertions |
69
+ | Textual `Pilot`, `teatest` | In-process — never exercise the real binary or PTY |
70
+ | `ratatui::TestBackend` | Same — model-level only |
71
+ | `insta`, `syrupy` | Assertion layer only, no driver |
72
+
73
+ `tuiwright` is the missing piece: **black-box, async, snapshot-aware,
74
+ ergonomic**.
75
+
76
+ ## Install
77
+
78
+ ```bash
79
+ uv add --dev tuiwright
80
+ # or
81
+ pip install tuiwright
82
+ ```
83
+
84
+ Optional, for PNG regression:
85
+
86
+ ```bash
87
+ # macOS
88
+ brew install agg
89
+ # from source (recommended for latest)
90
+ cargo install --git https://github.com/asciinema/agg
91
+ ```
92
+
93
+ Without `agg`, cell-grid snapshots still work; PNG assertions raise a
94
+ clear `FileNotFoundError`.
95
+
96
+ ## Quick start
97
+
98
+ `tuiwright` registers itself as a `pytest` plugin — no
99
+ `conftest.py` boilerplate. Just write `async def test_*`:
100
+
101
+ ```python
102
+ # tests/test_my_tui.py
103
+ from tuiwright._snapshot import ScreenSnapshotExtension
104
+
105
+ async def test_help_panel_opens(tui, snapshot):
106
+ await tui.start(["myapp", "--no-color"], cols=100, rows=30)
107
+ await tui.wait_for_text("Ready")
108
+ await tui.press("?")
109
+ await tui.wait_for_text("Help", region=tui.region(title="Help"))
110
+ assert tui.screen == snapshot(extension_class=ScreenSnapshotExtension)
111
+ ```
112
+
113
+ Run it:
114
+
115
+ ```bash
116
+ pytest # red on first run — no snapshot yet
117
+ pytest --snapshot-update # green; commit the .screen file
118
+ pytest # green forever, until the rendering changes
119
+ ```
120
+
121
+ Snapshot files are plain text (an ASCII frame plus a small JSON sidecar
122
+ of cell attributes) and live in `tests/__snapshots__/<test_module>/`.
123
+ They diff cleanly in PR review.
124
+
125
+ ## API
126
+
127
+ ### `TuiSession` (the `tui` fixture)
128
+
129
+ | Method | Purpose |
130
+ |---|---|
131
+ | `await start(cmd, *, env=, cwd=, cols=, rows=, cast_path=)` | Spawn a binary under a PTY |
132
+ | `await stop(timeout=2.0)` | Graceful SIGTERM → SIGKILL escalation |
133
+ | `await press(key)` | `"enter"`, `"ctrl+s"`, `"shift+tab"`, `"alt+left"`, `"f5"`, `"ctrl+shift+f5"` |
134
+ | `await type(text, delay=0)` | Per-char input with optional delay |
135
+ | `await paste(text)` | Wrapped in `\x1b[200~ … \x1b[201~`; falls back to `type` if app didn't enable bracketed paste |
136
+ | `await click(row, col, button="left", modifiers=())` | SGR 1006 mouse encoding, 0-based coords |
137
+ | `await double_click(row, col)` | Two clicks within `interval=` seconds |
138
+ | `await drag(from_row, from_col, to_row, to_col, steps=4)` | Press → motion events → release |
139
+ | `await scroll(row, col, direction="down", lines=1)` | Mouse wheel |
140
+ | `await hover(row, col)` | Motion-no-button (requires mode 1003) |
141
+ | `await resize(cols, rows)` | `TIOCSWINSZ` + SIGWINCH |
142
+ | `await focus(in_=True)` | Focus in/out (`\x1b[I` / `\x1b[O`) |
143
+ | `await wait_for_text(needle, timeout=, region=, regex=False)` | Returns the `re.Match` |
144
+ | `await wait_for_predicate(fn, timeout=)` | `fn(screen) -> bool`, sync or async |
145
+ | `await wait_for_stable(quiet_ms=50, timeout=)` | Settle on no-change |
146
+ | `screen` | Current `Screen` (sync property) |
147
+ | `region(title=, rows=, cols=)` | Subview into the current screen |
148
+ | `png()` | Render current cast to PNG via `agg` |
149
+ | `cast_path` | Path to the live asciinema cast file |
150
+ | `alive` | `True` until the child exits |
151
+
152
+ ### `Screen`, `Region`, `Cell`
153
+
154
+ ```python
155
+ screen.text # all rows joined with '\n', trailing spaces stripped
156
+ screen.row(0) # one row as a string
157
+ screen.row_containing("Error") # row index or None
158
+ screen.find(r"\d+", regex=True) # list[Position]
159
+ screen.contains("Ready")
160
+ screen.region(title="Logs") # heuristic detection of ┌─ Logs ─┐ ratatui frames
161
+ screen.region(rows=(3, 8), cols=(10, 40))
162
+
163
+ cell = screen.cells[row][col]
164
+ cell.char, cell.fg, cell.bg, cell.bold, cell.italic, cell.reverse, ...
165
+ ```
166
+
167
+ ### CLI flags
168
+
169
+ ```
170
+ --tui-trace=on|retain-on-failure|off # default: retain-on-failure
171
+ --tui-trace-dir=DIR # where to keep cast files (default: tmp_path)
172
+ --tui-cols=N, --tui-rows=N # default terminal size
173
+ --tui-timeout=SECONDS # default wait_for_* timeout
174
+ --snapshot-update # from syrupy: refresh all snapshots
175
+ ```
176
+
177
+ ### Marker
178
+
179
+ ```python
180
+ @pytest.mark.tui(cols=120, rows=40, timeout=10, strict_mouse=True)
181
+ async def test_large_screen(tui):
182
+ ...
183
+ ```
184
+
185
+ `strict_mouse=True` raises if mouse input is sent before the app has
186
+ enabled mouse tracking (DEC modes 1000/1002/1003). Off by default — a
187
+ single warning is emitted.
188
+
189
+ ## How it works
190
+
191
+ ```
192
+ ┌─ pytest fixture (tui) ──────────────────────────────────────┐
193
+ │ TuiSession │
194
+ │ ├─ Input encoders ── press / type / paste / mouse / resize │
195
+ │ ├─ Emulator (pyte) ── parses PTY output → 2D cell grid │
196
+ │ ├─ Cast recorder ─── asciinema v2 file for replay + PNG │
197
+ │ └─ PTY transport ── ptyprocess, async via add_reader │
198
+ └─────────────────────────────────────────────────────────────┘
199
+ │ stdin (bytes) ▲ stdout
200
+ ▼ │
201
+ ┌──────────────── child process ─────────────────┐
202
+ │ the TUI binary under test │
203
+ └─────────────────────────────────────────────────┘
204
+ ```
205
+
206
+ - **PTY** (`ptyprocess`): real pseudo-terminal — the app cannot tell it
207
+ isn't running under iTerm. SIGWINCH on resize, real flow control, the
208
+ whole shape.
209
+ - **Emulator** (`pyte`): VT102 parser. Exposes the cell grid plus DEC
210
+ private modes (mouse, paste, focus) so input encoders know what the
211
+ app will accept.
212
+ - **Cast recorder**: tees PTY output into an asciinema v2 file. Renders
213
+ to PNG on demand via `agg`, and can be replayed in
214
+ asciinema-player for trace viewing.
215
+ - **Snapshot extensions**: syrupy plugins for `Screen` (text + JSON
216
+ sidecar) and PNG (with `pixelmatch` for pixel-tolerant diff).
217
+
218
+ ## Project layout
219
+
220
+ ```
221
+ src/tuiwright/
222
+ ├── session.py # TuiSession — public API
223
+ ├── screen.py # Screen, Region, Cell, Color, Cursor
224
+ ├── _pty.py # ptyprocess wrapper
225
+ ├── _emulator.py # pyte + DEC mode tracking
226
+ ├── _input.py # key/mouse/paste encoders
227
+ ├── _trace/recorder.py # asciinema cast writer
228
+ ├── _snapshot/cells.py # syrupy ext for Screen
229
+ ├── _snapshot/png.py # syrupy ext for PNG (pixelmatch)
230
+ └── pytest_plugin.py # tui fixture, marker, CLI flags
231
+ ```
232
+
233
+ ## Limitations (v0.1)
234
+
235
+ - POSIX only (macOS + Linux). Windows ConPTY is on the roadmap.
236
+ - Mouse encoding is SGR 1006 (the modern default). Legacy X10 / urxvt
237
+ encodings are not implemented.
238
+ - Sixel, Kitty graphics, OSC 52 clipboard are passed through but not
239
+ parsed.
240
+ - The `region(title=...)` heuristic looks for ratatui-style
241
+ single-line box drawing borders (`┌─ Title ─┐`). For other border
242
+ styles fall back to explicit `rows=`, `cols=`.
243
+
244
+ ## License
245
+
246
+ MIT.
@@ -0,0 +1,209 @@
1
+ # tuiwright
2
+
3
+ **Playwright-style end-to-end testing for terminal user interfaces.**
4
+
5
+ `tuiwright` drives any TUI binary under a real PTY plus a faithful
6
+ terminal emulator, then lets you assert on the rendered screen with an
7
+ async pytest API. It covers keys, text, mouse, resize, bracketed paste,
8
+ and focus events out of the box, with cell-grid and PNG snapshot
9
+ regression.
10
+
11
+ ```python
12
+ async def test_save_flow(tui, snapshot):
13
+ await tui.start("myapp", cols=120, rows=40)
14
+ await tui.wait_for_text("Ready")
15
+
16
+ await tui.type("hello world")
17
+ await tui.press("ctrl+s")
18
+ await tui.wait_for_text("Saved")
19
+
20
+ await tui.click(row=5, col=12)
21
+ await tui.assert_region(title="Logs", contains="saved hello world")
22
+
23
+ assert tui.screen == snapshot(extension_class=ScreenSnapshotExtension)
24
+ ```
25
+
26
+ ## Why
27
+
28
+ | Existing tool | Limitation |
29
+ |---|---|
30
+ | `pexpect` / `expect` | Line/regex oriented — broken on cursor-addressed full-screen apps |
31
+ | `vhs`, `asciinema` | Demo recording, not designed for assertions |
32
+ | Textual `Pilot`, `teatest` | In-process — never exercise the real binary or PTY |
33
+ | `ratatui::TestBackend` | Same — model-level only |
34
+ | `insta`, `syrupy` | Assertion layer only, no driver |
35
+
36
+ `tuiwright` is the missing piece: **black-box, async, snapshot-aware,
37
+ ergonomic**.
38
+
39
+ ## Install
40
+
41
+ ```bash
42
+ uv add --dev tuiwright
43
+ # or
44
+ pip install tuiwright
45
+ ```
46
+
47
+ Optional, for PNG regression:
48
+
49
+ ```bash
50
+ # macOS
51
+ brew install agg
52
+ # from source (recommended for latest)
53
+ cargo install --git https://github.com/asciinema/agg
54
+ ```
55
+
56
+ Without `agg`, cell-grid snapshots still work; PNG assertions raise a
57
+ clear `FileNotFoundError`.
58
+
59
+ ## Quick start
60
+
61
+ `tuiwright` registers itself as a `pytest` plugin — no
62
+ `conftest.py` boilerplate. Just write `async def test_*`:
63
+
64
+ ```python
65
+ # tests/test_my_tui.py
66
+ from tuiwright._snapshot import ScreenSnapshotExtension
67
+
68
+ async def test_help_panel_opens(tui, snapshot):
69
+ await tui.start(["myapp", "--no-color"], cols=100, rows=30)
70
+ await tui.wait_for_text("Ready")
71
+ await tui.press("?")
72
+ await tui.wait_for_text("Help", region=tui.region(title="Help"))
73
+ assert tui.screen == snapshot(extension_class=ScreenSnapshotExtension)
74
+ ```
75
+
76
+ Run it:
77
+
78
+ ```bash
79
+ pytest # red on first run — no snapshot yet
80
+ pytest --snapshot-update # green; commit the .screen file
81
+ pytest # green forever, until the rendering changes
82
+ ```
83
+
84
+ Snapshot files are plain text (an ASCII frame plus a small JSON sidecar
85
+ of cell attributes) and live in `tests/__snapshots__/<test_module>/`.
86
+ They diff cleanly in PR review.
87
+
88
+ ## API
89
+
90
+ ### `TuiSession` (the `tui` fixture)
91
+
92
+ | Method | Purpose |
93
+ |---|---|
94
+ | `await start(cmd, *, env=, cwd=, cols=, rows=, cast_path=)` | Spawn a binary under a PTY |
95
+ | `await stop(timeout=2.0)` | Graceful SIGTERM → SIGKILL escalation |
96
+ | `await press(key)` | `"enter"`, `"ctrl+s"`, `"shift+tab"`, `"alt+left"`, `"f5"`, `"ctrl+shift+f5"` |
97
+ | `await type(text, delay=0)` | Per-char input with optional delay |
98
+ | `await paste(text)` | Wrapped in `\x1b[200~ … \x1b[201~`; falls back to `type` if app didn't enable bracketed paste |
99
+ | `await click(row, col, button="left", modifiers=())` | SGR 1006 mouse encoding, 0-based coords |
100
+ | `await double_click(row, col)` | Two clicks within `interval=` seconds |
101
+ | `await drag(from_row, from_col, to_row, to_col, steps=4)` | Press → motion events → release |
102
+ | `await scroll(row, col, direction="down", lines=1)` | Mouse wheel |
103
+ | `await hover(row, col)` | Motion-no-button (requires mode 1003) |
104
+ | `await resize(cols, rows)` | `TIOCSWINSZ` + SIGWINCH |
105
+ | `await focus(in_=True)` | Focus in/out (`\x1b[I` / `\x1b[O`) |
106
+ | `await wait_for_text(needle, timeout=, region=, regex=False)` | Returns the `re.Match` |
107
+ | `await wait_for_predicate(fn, timeout=)` | `fn(screen) -> bool`, sync or async |
108
+ | `await wait_for_stable(quiet_ms=50, timeout=)` | Settle on no-change |
109
+ | `screen` | Current `Screen` (sync property) |
110
+ | `region(title=, rows=, cols=)` | Subview into the current screen |
111
+ | `png()` | Render current cast to PNG via `agg` |
112
+ | `cast_path` | Path to the live asciinema cast file |
113
+ | `alive` | `True` until the child exits |
114
+
115
+ ### `Screen`, `Region`, `Cell`
116
+
117
+ ```python
118
+ screen.text # all rows joined with '\n', trailing spaces stripped
119
+ screen.row(0) # one row as a string
120
+ screen.row_containing("Error") # row index or None
121
+ screen.find(r"\d+", regex=True) # list[Position]
122
+ screen.contains("Ready")
123
+ screen.region(title="Logs") # heuristic detection of ┌─ Logs ─┐ ratatui frames
124
+ screen.region(rows=(3, 8), cols=(10, 40))
125
+
126
+ cell = screen.cells[row][col]
127
+ cell.char, cell.fg, cell.bg, cell.bold, cell.italic, cell.reverse, ...
128
+ ```
129
+
130
+ ### CLI flags
131
+
132
+ ```
133
+ --tui-trace=on|retain-on-failure|off # default: retain-on-failure
134
+ --tui-trace-dir=DIR # where to keep cast files (default: tmp_path)
135
+ --tui-cols=N, --tui-rows=N # default terminal size
136
+ --tui-timeout=SECONDS # default wait_for_* timeout
137
+ --snapshot-update # from syrupy: refresh all snapshots
138
+ ```
139
+
140
+ ### Marker
141
+
142
+ ```python
143
+ @pytest.mark.tui(cols=120, rows=40, timeout=10, strict_mouse=True)
144
+ async def test_large_screen(tui):
145
+ ...
146
+ ```
147
+
148
+ `strict_mouse=True` raises if mouse input is sent before the app has
149
+ enabled mouse tracking (DEC modes 1000/1002/1003). Off by default — a
150
+ single warning is emitted.
151
+
152
+ ## How it works
153
+
154
+ ```
155
+ ┌─ pytest fixture (tui) ──────────────────────────────────────┐
156
+ │ TuiSession │
157
+ │ ├─ Input encoders ── press / type / paste / mouse / resize │
158
+ │ ├─ Emulator (pyte) ── parses PTY output → 2D cell grid │
159
+ │ ├─ Cast recorder ─── asciinema v2 file for replay + PNG │
160
+ │ └─ PTY transport ── ptyprocess, async via add_reader │
161
+ └─────────────────────────────────────────────────────────────┘
162
+ │ stdin (bytes) ▲ stdout
163
+ ▼ │
164
+ ┌──────────────── child process ─────────────────┐
165
+ │ the TUI binary under test │
166
+ └─────────────────────────────────────────────────┘
167
+ ```
168
+
169
+ - **PTY** (`ptyprocess`): real pseudo-terminal — the app cannot tell it
170
+ isn't running under iTerm. SIGWINCH on resize, real flow control, the
171
+ whole shape.
172
+ - **Emulator** (`pyte`): VT102 parser. Exposes the cell grid plus DEC
173
+ private modes (mouse, paste, focus) so input encoders know what the
174
+ app will accept.
175
+ - **Cast recorder**: tees PTY output into an asciinema v2 file. Renders
176
+ to PNG on demand via `agg`, and can be replayed in
177
+ asciinema-player for trace viewing.
178
+ - **Snapshot extensions**: syrupy plugins for `Screen` (text + JSON
179
+ sidecar) and PNG (with `pixelmatch` for pixel-tolerant diff).
180
+
181
+ ## Project layout
182
+
183
+ ```
184
+ src/tuiwright/
185
+ ├── session.py # TuiSession — public API
186
+ ├── screen.py # Screen, Region, Cell, Color, Cursor
187
+ ├── _pty.py # ptyprocess wrapper
188
+ ├── _emulator.py # pyte + DEC mode tracking
189
+ ├── _input.py # key/mouse/paste encoders
190
+ ├── _trace/recorder.py # asciinema cast writer
191
+ ├── _snapshot/cells.py # syrupy ext for Screen
192
+ ├── _snapshot/png.py # syrupy ext for PNG (pixelmatch)
193
+ └── pytest_plugin.py # tui fixture, marker, CLI flags
194
+ ```
195
+
196
+ ## Limitations (v0.1)
197
+
198
+ - POSIX only (macOS + Linux). Windows ConPTY is on the roadmap.
199
+ - Mouse encoding is SGR 1006 (the modern default). Legacy X10 / urxvt
200
+ encodings are not implemented.
201
+ - Sixel, Kitty graphics, OSC 52 clipboard are passed through but not
202
+ parsed.
203
+ - The `region(title=...)` heuristic looks for ratatui-style
204
+ single-line box drawing borders (`┌─ Title ─┐`). For other border
205
+ styles fall back to explicit `rows=`, `cols=`.
206
+
207
+ ## License
208
+
209
+ MIT.
@@ -0,0 +1,82 @@
1
+ [project]
2
+ name = "tuiwright"
3
+ version = "0.1.0"
4
+ description = "Playwright-style end-to-end testing for TUI applications. Drives any TUI binary via a real PTY + terminal emulator, with cell-grid and PNG snapshot regression."
5
+ authors = [
6
+ { name = "Pandelis Zembashis", email = "p@ndel.is" }
7
+ ]
8
+ requires-python = ">=3.11"
9
+ license = "MIT"
10
+ license-files = ["LICENSE"]
11
+ readme = "README.md"
12
+ keywords = ["tui", "testing", "terminal", "pty", "snapshot", "ratatui", "crossterm", "pytest", "e2e", "playwright"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Framework :: Pytest",
16
+ "Intended Audience :: Developers",
17
+ "Operating System :: MacOS",
18
+ "Operating System :: POSIX :: Linux",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Topic :: Software Development :: Testing",
23
+ "Topic :: Terminals",
24
+ "Typing :: Typed",
25
+ ]
26
+ dependencies = [
27
+ "ptyprocess>=0.7.0",
28
+ "pyte>=0.8.2",
29
+ "pytest>=8.0",
30
+ "pytest-asyncio>=0.23",
31
+ "syrupy>=4.6",
32
+ "pillow>=10.0",
33
+ "pixelmatch>=0.3.0",
34
+ ]
35
+
36
+ [project.optional-dependencies]
37
+ examples = [
38
+ "urwid>=2.6",
39
+ ]
40
+
41
+ [project.entry-points.pytest11]
42
+ tuiwright = "tuiwright.pytest_plugin"
43
+
44
+ [project.urls]
45
+ Homepage = "https://pandelisz.github.io/tuiwright/"
46
+ Documentation = "https://pandelisz.github.io/tuiwright/"
47
+ Repository = "https://github.com/PandelisZ/tuiwright"
48
+ Issues = "https://github.com/PandelisZ/tuiwright/issues"
49
+ Changelog = "https://github.com/PandelisZ/tuiwright/blob/master/CHANGELOG.md"
50
+
51
+ [build-system]
52
+ requires = ["uv_build>=0.11.7,<0.12.0"]
53
+ build-backend = "uv_build"
54
+
55
+ [dependency-groups]
56
+ dev = [
57
+ "pytest>=8.0",
58
+ "pytest-asyncio>=0.23",
59
+ "pytest-timeout>=2.3",
60
+ "ruff>=0.6",
61
+ "mypy>=1.10",
62
+ "urwid>=2.6",
63
+ ]
64
+ docs = [
65
+ "mkdocs>=1.6",
66
+ "mkdocs-material>=9.5",
67
+ "mkdocstrings[python]>=0.27",
68
+ "pymdown-extensions>=10.0",
69
+ ]
70
+
71
+ [tool.pytest.ini_options]
72
+ asyncio_mode = "auto"
73
+ asyncio_default_fixture_loop_scope = "function"
74
+ testpaths = ["tests"]
75
+
76
+ [tool.ruff]
77
+ line-length = 100
78
+ target-version = "py311"
79
+
80
+ [tool.ruff.lint]
81
+ select = ["E", "F", "I", "W", "B", "UP", "RUF"]
82
+ ignore = ["E501"]
@@ -0,0 +1,31 @@
1
+ """tuiwright — Playwright-style end-to-end testing for TUI applications.
2
+
3
+ Drives any TUI binary under a real PTY + terminal emulator, with cell-grid
4
+ and PNG snapshot regression.
5
+
6
+ Quick start:
7
+
8
+ async def test_app(tui, snapshot):
9
+ await tui.start("myapp")
10
+ await tui.wait_for_text("Ready")
11
+ await tui.type("hello")
12
+ await tui.press("enter")
13
+ assert tui.screen == snapshot
14
+ """
15
+
16
+ from tuiwright.screen import Cell, Color, Cursor, Position, Region, Screen
17
+ from tuiwright.session import TuiSession, TuiTimeoutError
18
+
19
+ __all__ = [
20
+ "Cell",
21
+ "Color",
22
+ "Cursor",
23
+ "Position",
24
+ "Region",
25
+ "Screen",
26
+ "TuiSession",
27
+ "TuiTimeoutError",
28
+ "__version__",
29
+ ]
30
+
31
+ __version__ = "0.1.0"