talktrack 0.0.0 → 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joseph Mearman
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.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # talktrack
2
+
3
+ Narrated, cursor-driven demo recordings from Playwright. Playwright drives the page, talktrack makes the recording read like a person at the keyboard with someone talking over their shoulder: a visible cursor that glides, clicks and types at human speed, caption text timed to what was actually happening on screen, and a framed result with a header band above the app and the narration below it.
4
+
5
+ It works inside `@playwright/test` specs and from plain Node scripts, with the same helpers in both.
6
+
7
+ ## Requirements
8
+
9
+ - Node 20 or newer
10
+ - `@playwright/test` 1.55 or newer (a peer dependency)
11
+ - `ffmpeg` and `ffprobe` on `PATH`. The framing pass shells out to them and needs the `libvpx-vp9` encoder, which a standard `brew install ffmpeg` or `apt-get install ffmpeg` includes. If either binary is missing, the error says so.
12
+
13
+ ```sh
14
+ pnpm add -D talktrack
15
+ ```
16
+
17
+ ## In a Playwright test
18
+
19
+ ```ts
20
+ import { test } from "@playwright/test";
21
+ import {
22
+ finalizeFramedRecording,
23
+ gotoWithCursor,
24
+ installCursor,
25
+ moveAndClick,
26
+ narrateScene,
27
+ } from "talktrack";
28
+
29
+ const size = { width: 1440, height: 810 };
30
+ test.use({ viewport: size, video: { mode: "on", size } });
31
+
32
+ test("checkout demo", async ({ page }) => {
33
+ await installCursor(page);
34
+ await gotoWithCursor(page, "https://example.com");
35
+ await narrateScene(page, "The page loads", () => page.getByRole("heading").waitFor());
36
+ await moveAndClick(page, page.getByRole("link", { name: "More information" }));
37
+ });
38
+
39
+ test.afterEach(async ({ page }, testInfo) => {
40
+ await finalizeFramedRecording(page, testInfo);
41
+ });
42
+ ```
43
+
44
+ Set the video `size` to the viewport size. Playwright otherwise scales the recording down to fit inside 800x800, the bands would no longer line up with the picture, and `finalizeFramedRecording` throws rather than produce a mis-laid-out file.
45
+
46
+ `finalizeFramedRecording` closes the page (Playwright only finalises a video once its page is closed), writes `<video>-framed.webm` next to the raw recording and attaches it to the test. It returns the framed path, or `undefined` when the run has no video.
47
+
48
+ ## From a script
49
+
50
+ ```ts
51
+ import { recordDemo } from "talktrack";
52
+
53
+ const demo = await recordDemo("checkout", {
54
+ viewport: { width: 1440, height: 810 },
55
+ url: "https://example.com",
56
+ });
57
+
58
+ await demo.narrateScene("The page loads", () => demo.page.getByRole("heading").waitFor());
59
+ await demo.moveAndClick(demo.page.getByRole("link", { name: "More information" }));
60
+ demo.caption("That is the whole flow");
61
+ await demo.beat(1500);
62
+ demo.hideCaption();
63
+
64
+ const framedPath = await demo.finish();
65
+ ```
66
+
67
+ `recordDemo` launches Chromium (or uses the `browser` / `browserType` you pass), records at exactly the viewport size and writes to `talktrack-output/` unless you set `outputDir`. Call `finish()` once at the end: it closes the recording context so Playwright flushes the video, frames it and returns the path.
68
+
69
+ ## Helpers
70
+
71
+ All take the `page` first in test mode; on a `recordDemo` session they are methods without it.
72
+
73
+ | Helper | What it does |
74
+ |---|---|
75
+ | `installCursor(page)` | Injects the cursor overlay and starts the recording clock. Call it first. |
76
+ | `gotoWithCursor(page, url)` / `reloadWithCursor(page)` | Navigate without the cursor snapping back to the corner. |
77
+ | `moveAndClick(page, locator, pauseMs?)` | Scrolls the target into view, glides the cursor to it along an eased, slightly curved path, clicks. |
78
+ | `fillVisibly(page, locator, text, pauseMs?)` | Clicks into the field and types character by character. |
79
+ | `naturalScrollIntoView(page, locator)` | Scrolls with real wheel events instead of an instant jump. |
80
+ | `showCaption(page, text)` / `hideCaption(page)` | Start and end a narration line. |
81
+ | `narrateScene(page, text, settle, opts?)` | Shows a caption, waits for `settle` (the real page condition the caption claims), holds a short dwell, hides it. |
82
+ | `beat(page, ms?)` | A jittered pause for reading time. |
83
+
84
+ Captions are timestamps, not an in-page overlay. Playwright records exactly the browser viewport, so anything the page draws is inside the recorded rectangle; the framing pass is what puts narration below the app.
85
+
86
+ ## Framing and styling
87
+
88
+ The framed recording is the raw video padded onto a taller canvas: a static header band (the short commit hash and recording time by default, just the time outside a git repository) above the app, and a footer band that shows each caption for exactly the span it was on screen. The bands are rendered as PNGs by a throwaway browser context and composited with ffmpeg's `pad` and `overlay` filters, so no ffmpeg build with `drawtext` is needed.
89
+
90
+ Override any part of the look, and the header text:
91
+
92
+ ```ts
93
+ await finalizeFramedRecording(page, testInfo, {
94
+ headerText: "Checkout flow, v2",
95
+ bands: { background: "#0b1020", footerHeightPx: 120, footerFontSizePx: 24 },
96
+ });
97
+ ```
98
+
99
+ `bands` accepts any subset of `BandStyle`: `headerHeightPx`, `footerHeightPx`, `background`, `textColor`, `fontStack`, `headerFontSizePx`, `footerFontSizePx`. The same options exist on `recordDemo`.
100
+
101
+ ## Licence
102
+
103
+ MIT