Skip to content

Commit 70a9c2d

Browse files
committed
Document animated recording workflows
Signed-off-by: cpendery <cpendery@vt.edu>
1 parent 19d5948 commit 70a9c2d

4 files changed

Lines changed: 81 additions & 16 deletions

File tree

README.md

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
```sh
1515
cargo add tui-test-rs@0.1.0-beta.1
16+
# Add APNG/GIF export support when the Rust application needs raster recording:
17+
cargo add tui-test-rs@0.1.0-beta.1 --features recording-raster
1618
```
1719

1820
Raster recording uses installed system fonts unless a JetBrains Mono bundle
@@ -279,19 +281,46 @@ Screenshots render a snapshot of the session in the current terminal by default,
279281

280282
### Recording
281283

282-
Every session records automatically from the moment it opens, in the standard
283-
[asciinema v2](https://docs.asciinema.org/manual/asciicast/v2/) cast format.
284+
Record a selected part of a session directly to animated APNG (primary), GIF
285+
(fallback), or standard
286+
[asciinema v2](https://docs.asciinema.org/manual/asciicast/v2/) cast:
284287

285-
| Command | Description |
286-
| ------------------------- | ----------------------------------------------- |
287-
| `get-recording [session]` | Print the session's recording (cast) to stdout. |
288+
| Command | Description |
289+
| --- | --- |
290+
| `record start OUT [--format apng\|gif\|cast] [--fps N] [--speed N] [--idle-time-limit SEC]` | Start recording. Format is inferred from `.png`/`.apng`, `.gif`, or `.cast`. |
291+
| `record stop` | Stop recording and finish the output file. |
292+
| `get-recording [session]` | Print the separate, always-on session cast to stdout. |
288293

289294
```sh
290-
tui-test get-recording > demo.cast # capture the current session's recording
291-
asciinema play demo.cast # replay it
292-
agg demo.cast demo.gif # render a GIF
295+
tui-test open
296+
tui-test record start demo.png # lossless animated PNG
297+
tui-test submit "echo hello"
298+
tui-test wait command
299+
tui-test record stop
293300
```
294301

302+
APNG keeps full 24/32-bit color. APNG and GIF both render at 2x pixel density
303+
for sharper text; GIF additionally uses palette quantization for viewers that
304+
cannot display APNG. Defaults are 30 fps, 1x speed, a 5-second idle-gap limit,
305+
and a 3-second final hold. If a process exits before `record stop`, APNG/GIF
306+
capture remains beside the target as `OUT.tui-test.cast`.
307+
308+
Raster export uses the selected JetBrains Mono bundle tier, when enabled, plus
309+
installed system fonts for Unicode fallbacks. The CLI and language bindings
310+
enable the styled tier; `recording-raster` alone stays system-font-only. Set
311+
`TUI_TEST_RECORDING_FONT_FAMILIES=Family One,Family Two` to prioritize specific
312+
installed families. Export fails with the missing code points instead of
313+
silently substituting unsupported glyphs.
314+
315+
<p align="center">
316+
<img alt="animated APNG terminal recording produced by tui-test" src="static/recording.png" width="400">
317+
</p>
318+
319+
Every session also records automatically from open in `.cast` format. Export it
320+
with `tui-test get-recording > demo.cast` for the wider asciicast ecosystem.
321+
This interoperability is implemented directly from the public asciicast v2
322+
format and does not add or depend on GPL tooling.
323+
295324
### Live monitor
296325

297326
Watch a live session in a second terminal while an agent drives it. Both share
@@ -406,7 +435,7 @@ promises.
406435
| Testing / snapshots |`expect` text / output / exit-code / snapshot |||
407436
| Color & per-cell attributes | ✅ fg/bg, ANSI-256/hex/rgb, `cells` | ❌ plain text (+ highlights) | via PNG |
408437
| Image screenshots | ✅ SVG || ✅ PNG |
409-
| Built-in recording | ✅ always-on asciinema cast |||
438+
| Built-in recording |APNG/GIF export + always-on asciinema cast |||
410439
| Live monitor view ||||
411440
| Stable exit-code taxonomy for agents ||||
412441
| Python & JavaScript bindings ||||

SKILL.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,9 @@ Colors accept ansi-256 (`9`), hex (`#ff0000`), or rgb (`255,0,0`).
125125

126126
| Command | Description |
127127
| ----------------------------------- | ---------------------------------------------------------------------------- |
128-
| `get-recording [session]` | Print a session's asciinema v2 cast to stdout (works even after it stopped). |
128+
| `record start OUT [options]` | Start APNG, GIF, or asciicast recording (format inferred from extension). |
129+
| `record stop` | Finish the active recording. |
130+
| `get-recording [session]` | Print the always-on asciinema v2 cast (works even after the session stopped).|
129131
| `monitor` | Watch the session live, full-color, in another terminal. |
130132
| `usage` / `agent-context` / `skill` | Self-documentation (see top of guide). |
131133

@@ -211,8 +213,20 @@ tui-test get-recording > demo.cast # current session's recording to stdout
211213
tui-test get-recording work > w.cast # a specific session by name (even if stopped)
212214
```
213215

214-
Play it with `asciinema play demo.cast`, or render a GIF with
215-
`agg demo.cast demo.gif`.
216+
Record a selected span directly to APNG (primary), GIF (fallback), or cast:
217+
218+
```sh
219+
tui-test record start demo.png
220+
tui-test submit "echo hello"
221+
tui-test wait command
222+
tui-test record stop
223+
```
224+
225+
APNG and GIF render at 2x pixel density. Use `--fps`, `--speed`, and
226+
`--idle-time-limit` to tune playback. `.cast` output interoperates with the
227+
asciicast ecosystem without adding any GPL dependency to tui-test. If a
228+
process exits before `record stop`, an APNG/GIF capture remains beside the
229+
target as `OUT.tui-test.cast`.
216230

217231
## Live monitor
218232

@@ -325,12 +339,14 @@ Python and JavaScript methods mirror the cli commands: `open` / `run`, `submit`
325339
`resize`, `signal` / `kill`, `state`, `text`, `cells`, the dedicated
326340
`get_command` / `get_output` / `get_exit_code` / `get_cwd` / `get_cursor` /
327341
`get_size` / `get_title` methods,
328-
`screenshot`, `wait_text` / `wait_idle` / `wait_command` / `wait_exit` /
342+
`screenshot`, `start_recording` / `stop_recording`, `wait_text` / `wait_idle` /
343+
`wait_command` / `wait_exit` /
329344
`wait_ready`, `expect_text` / `expect_exit_code` / `expect_output` /
330345
`expect_snapshot`, and `close`. Python module-level helpers are `sessions`,
331346
`close_all`, and `get_recording`; JavaScript exports `sessions`, `closeAll`,
332347
and `getRecording`. The JavaScript client otherwise uses the same names in
333-
camelCase (`waitCommand`, `expectText`, `getExitCode`, etc.).
348+
camelCase (`startRecording`, `stopRecording`, `waitCommand`, `expectText`,
349+
`getExitCode`, etc.).
334350

335351
The constructors accept a session name plus profile, timeout, and artifact
336352
options: `TuiTest(session="default", *, timeouts=None, profile=None,

bindings/js/README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ All derive from `TuiTestError` and carry `kind` and `exitCode`. `waitX` and `exp
4949

5050
## API
5151

52-
`new TuiTest(session?, { profile?, timeouts?, artifacts? })` mirrors the cli: `open` / `run`, `type` / `write`, `submit`, `press` / `keys`, `mouse.click|move|down|up|drag|scroll`, `resize`, `signal` / `kill`, `state`, `text`, `cells`, `getCommand` / `getOutput` / `getExitCode` / `getCwd` / `getCursor` / `getSize` / `getTitle`, `screenshot`, `waitText` / `waitTitle` / `waitIdle` / `waitCommand` / `waitExit` / `waitReady`, `expectText` / `expectTitle` / `expectExitCode` / `expectOutput` / `expectSnapshot`, `close`, and `closeQuiet`.
52+
`new TuiTest(session?, { profile?, timeouts?, artifacts? })` mirrors the cli: `open` / `run`, `type` / `write`, `submit`, `press` / `keys`, `mouse.click|move|down|up|drag|scroll`, `resize`, `signal` / `kill`, `state`, `text`, `cells`, `getCommand` / `getOutput` / `getExitCode` / `getCwd` / `getCursor` / `getSize` / `getTitle`, `screenshot`, `startRecording` / `stopRecording`, `waitText` / `waitTitle` / `waitIdle` / `waitCommand` / `waitExit` / `waitReady`, `expectText` / `expectTitle` / `expectExitCode` / `expectOutput` / `expectSnapshot`, `close`, and `closeQuiet`.
5353

5454
Module-level helpers: `sessions()`, `closeAll()`, `getRecording()`, `uniqueSession()`.
5555

@@ -98,6 +98,16 @@ Cancelling a promise does not cancel the underlying Rust operation. Operations f
9898

9999
Closing a session removes it from `sessions()`, but keeps its recording. `getRecording()` can read that recording for the rest of the process. The 1024 most recently closed sessions have their recordings retained.
100100

101+
```js
102+
await su.startRecording("demo.png", { fps: 30, speed: 1 });
103+
await su.submit("echo hello");
104+
await su.waitCommand();
105+
const path = await su.stopRecording();
106+
```
107+
108+
`.png`/`.apng` selects lossless APNG, `.gif` selects GIF, and `.cast` selects
109+
asciicast v2. The `format` option can override extension inference.
110+
101111
## Configuration
102112

103113
| Variable | Purpose |

bindings/python/README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ All derive from `TuiTestError`. `wait_*` and `expect_*` raise `ExpectationError`
5454

5555
## API
5656

57-
`TuiTest(session="default", *, timeouts=None, profile=None, artifacts=None)` mirrors the cli: `open` / `run`, `type` / `write`, `submit`, `press` / `keys`, `mouse.click|move|down|up|drag|scroll`, `resize`, `signal` / `kill`, `state`, `text`, `cells`, `get_command` / `get_output` / `get_exit_code` / `get_cwd` / `get_cursor` / `get_size` / `get_title`, `screenshot`, `wait_text` / `wait_title` / `wait_idle` / `wait_command` / `wait_exit` / `wait_ready`, `expect_text` / `expect_title` / `expect_exit_code` / `expect_output` / `expect_snapshot`, `close`, and `close_quiet`.
57+
`TuiTest(session="default", *, timeouts=None, profile=None, artifacts=None)` mirrors the cli: `open` / `run`, `type` / `write`, `submit`, `press` / `keys`, `mouse.click|move|down|up|drag|scroll`, `resize`, `signal` / `kill`, `state`, `text`, `cells`, `get_command` / `get_output` / `get_exit_code` / `get_cwd` / `get_cursor` / `get_size` / `get_title`, `screenshot`, `start_recording` / `stop_recording`, `wait_text` / `wait_title` / `wait_idle` / `wait_command` / `wait_exit` / `wait_ready`, `expect_text` / `expect_title` / `expect_exit_code` / `expect_output` / `expect_snapshot`, `close`, and `close_quiet`.
5858

5959
Module-level helpers: `sessions()`, `close_all()`, `get_recording()`, `unique_session()`.
6060

@@ -102,6 +102,16 @@ Cancelling a task does not cancel the underlying Rust operation. Operations for
102102

103103
Closing a session removes it from `sessions()`, but keeps its recording. `get_recording()` can read that recording for the rest of the process. The 1024 most recently closed sessions have their recordings retained.
104104

105+
```python
106+
await su.start_recording("demo.png", fps=30, speed=1.0)
107+
await su.submit("echo hello")
108+
await su.wait_command()
109+
path = await su.stop_recording()
110+
```
111+
112+
`.png`/`.apng` selects lossless APNG, `.gif` selects GIF, and `.cast` selects
113+
asciicast v2. Pass `format=` to override extension inference.
114+
105115
## Configuration
106116

107117
| Variable | Purpose |

0 commit comments

Comments
 (0)