@webreel/core 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.
Files changed (94) hide show
  1. package/README.md +188 -0
  2. package/assets/click-1.mp3 +0 -0
  3. package/assets/click-2.mp3 +0 -0
  4. package/assets/click-3.mp3 +0 -0
  5. package/assets/click-4.mp3 +0 -0
  6. package/assets/key-1.mp3 +0 -0
  7. package/assets/key-2.mp3 +0 -0
  8. package/assets/key-3.mp3 +0 -0
  9. package/assets/key-4.mp3 +0 -0
  10. package/dist/__tests__/actions.test.d.ts +2 -0
  11. package/dist/__tests__/actions.test.d.ts.map +1 -0
  12. package/dist/__tests__/actions.test.js +252 -0
  13. package/dist/__tests__/actions.test.js.map +1 -0
  14. package/dist/__tests__/chrome.test.d.ts +2 -0
  15. package/dist/__tests__/chrome.test.d.ts.map +1 -0
  16. package/dist/__tests__/chrome.test.js +29 -0
  17. package/dist/__tests__/chrome.test.js.map +1 -0
  18. package/dist/__tests__/cursor-motion.test.d.ts +2 -0
  19. package/dist/__tests__/cursor-motion.test.d.ts.map +1 -0
  20. package/dist/__tests__/cursor-motion.test.js +39 -0
  21. package/dist/__tests__/cursor-motion.test.js.map +1 -0
  22. package/dist/__tests__/ffmpeg.test.d.ts +2 -0
  23. package/dist/__tests__/ffmpeg.test.d.ts.map +1 -0
  24. package/dist/__tests__/ffmpeg.test.js +90 -0
  25. package/dist/__tests__/ffmpeg.test.js.map +1 -0
  26. package/dist/__tests__/media.test.d.ts +2 -0
  27. package/dist/__tests__/media.test.d.ts.map +1 -0
  28. package/dist/__tests__/media.test.js +98 -0
  29. package/dist/__tests__/media.test.js.map +1 -0
  30. package/dist/__tests__/overlays.test.d.ts +2 -0
  31. package/dist/__tests__/overlays.test.d.ts.map +1 -0
  32. package/dist/__tests__/overlays.test.js +109 -0
  33. package/dist/__tests__/overlays.test.js.map +1 -0
  34. package/dist/__tests__/recording-context.test.d.ts +2 -0
  35. package/dist/__tests__/recording-context.test.d.ts.map +1 -0
  36. package/dist/__tests__/recording-context.test.js +46 -0
  37. package/dist/__tests__/recording-context.test.js.map +1 -0
  38. package/dist/__tests__/timeline.test.d.ts +2 -0
  39. package/dist/__tests__/timeline.test.d.ts.map +1 -0
  40. package/dist/__tests__/timeline.test.js +88 -0
  41. package/dist/__tests__/timeline.test.js.map +1 -0
  42. package/dist/actions.d.ts +65 -0
  43. package/dist/actions.d.ts.map +1 -0
  44. package/dist/actions.js +729 -0
  45. package/dist/actions.js.map +1 -0
  46. package/dist/cdp.d.ts +3 -0
  47. package/dist/cdp.d.ts.map +1 -0
  48. package/dist/cdp.js +5 -0
  49. package/dist/cdp.js.map +1 -0
  50. package/dist/chrome.d.ts +12 -0
  51. package/dist/chrome.d.ts.map +1 -0
  52. package/dist/chrome.js +241 -0
  53. package/dist/chrome.js.map +1 -0
  54. package/dist/compositor.d.ts +8 -0
  55. package/dist/compositor.d.ts.map +1 -0
  56. package/dist/compositor.js +224 -0
  57. package/dist/compositor.js.map +1 -0
  58. package/dist/cursor-motion.d.ts +17 -0
  59. package/dist/cursor-motion.d.ts.map +1 -0
  60. package/dist/cursor-motion.js +138 -0
  61. package/dist/cursor-motion.js.map +1 -0
  62. package/dist/download.d.ts +6 -0
  63. package/dist/download.d.ts.map +1 -0
  64. package/dist/download.js +78 -0
  65. package/dist/download.js.map +1 -0
  66. package/dist/ffmpeg.d.ts +5 -0
  67. package/dist/ffmpeg.d.ts.map +1 -0
  68. package/dist/ffmpeg.js +106 -0
  69. package/dist/ffmpeg.js.map +1 -0
  70. package/dist/index.d.ts +12 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +11 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/media.d.ts +23 -0
  75. package/dist/media.d.ts.map +1 -0
  76. package/dist/media.js +155 -0
  77. package/dist/media.js.map +1 -0
  78. package/dist/overlays.d.ts +21 -0
  79. package/dist/overlays.d.ts.map +1 -0
  80. package/dist/overlays.js +97 -0
  81. package/dist/overlays.js.map +1 -0
  82. package/dist/recorder.d.ts +41 -0
  83. package/dist/recorder.d.ts.map +1 -0
  84. package/dist/recorder.js +223 -0
  85. package/dist/recorder.js.map +1 -0
  86. package/dist/timeline.d.ts +82 -0
  87. package/dist/timeline.d.ts.map +1 -0
  88. package/dist/timeline.js +140 -0
  89. package/dist/timeline.js.map +1 -0
  90. package/dist/types.d.ts +90 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +16 -0
  93. package/dist/types.js.map +1 -0
  94. package/package.json +51 -0
package/README.md ADDED
@@ -0,0 +1,188 @@
1
+ # @webreel/core
2
+
3
+ Chrome automation, recording, and overlay engine for webreel.
4
+
5
+ Launches a headless Chrome instance via the Chrome DevTools Protocol, captures screenshots at ~60fps, and encodes the result to MP4 with ffmpeg. Provides actions for clicking, typing, dragging, and cursor animation, plus on-screen overlays for keystroke labels and a custom cursor.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @webreel/core
11
+ ```
12
+
13
+ ## Examples
14
+
15
+ <!-- EXAMPLES:START -->
16
+
17
+ **[custom-theme](../../../examples/custom-theme)** - Demonstrates fully customizing the cursor overlay and keystroke HUD appearance using a code editor page.
18
+
19
+ <video src="../../../examples/custom-theme/videos/custom-theme.mp4" controls muted width="100%"></video>
20
+
21
+ **[drag-and-drop](../../../examples/drag-and-drop)** - Demonstrates dragging elements between positions on a kanban board.
22
+
23
+ <video src="../../../examples/drag-and-drop/videos/drag-and-drop.mp4" controls muted width="100%"></video>
24
+
25
+ **[form-filling](../../../examples/form-filling)** - Demonstrates typing into form fields and clicking a submit button, simulating a login flow.
26
+
27
+ <video src="../../../examples/form-filling/videos/form-filling.mp4" controls muted width="100%"></video>
28
+
29
+ **[gif-output](../../../examples/gif-output)** - Demonstrates outputting the recording as an animated GIF instead of the default MP4.
30
+
31
+ <video src="../../../examples/gif-output/videos/gif-output.gif" controls muted width="100%"></video>
32
+
33
+ **[hello-world](../../../examples/hello-world)** - The simplest possible webreel example. Opens a landing page and clicks the call-to-action button.
34
+
35
+ <video src="../../../examples/hello-world/videos/hello-world.mp4" controls muted width="100%"></video>
36
+
37
+ **[keyboard-shortcuts](../../../examples/keyboard-shortcuts)** - Demonstrates pressing key combos and displaying them in the keystroke HUD overlay. Uses a code editor page as the target.
38
+
39
+ <video src="../../../examples/keyboard-shortcuts/videos/keyboard-shortcuts.mp4" controls muted width="100%"></video>
40
+
41
+ **[mobile-viewport](../../../examples/mobile-viewport)** - Demonstrates recording at mobile device dimensions using a finance app interface.
42
+
43
+ <video src="../../../examples/mobile-viewport/videos/mobile-viewport.mp4" controls muted width="100%"></video>
44
+
45
+ **[modifier-clicks](../../../examples/modifier-clicks)** - Demonstrates clicking elements with modifier keys held down, simulating multi-select in a file manager.
46
+
47
+ <video src="../../../examples/modifier-clicks/videos/modifier-clicks.mp4" controls muted width="100%"></video>
48
+
49
+ **[multi-demo](../../../examples/multi-demo)** - Demonstrates defining multiple videos in a single config file, each producing its own output from the same page.
50
+
51
+ <video src="../../../examples/multi-demo/videos/homepage.mp4" controls muted width="100%"></video>
52
+
53
+ **[page-scrolling](../../../examples/page-scrolling)** - Demonstrates scrolling the page and scrolling within a specific container element on a blog post layout.
54
+
55
+ <video src="../../../examples/page-scrolling/videos/page-scrolling.mp4" controls muted width="100%"></video>
56
+
57
+ **[screenshots](../../../examples/screenshots)** - Demonstrates capturing PNG screenshots at specific points during a recording. Useful for generating static marketing assets or documentation images alongside videos.
58
+
59
+ <video src="../../../examples/screenshots/videos/screenshots.mp4" controls muted width="100%"></video>
60
+
61
+ **[shared-steps](../../../examples/shared-steps)** - Demonstrates using `include` to share common setup steps across videos. The shared steps dismiss a cookie consent banner before the main video steps run.
62
+
63
+ <video src="../../../examples/shared-steps/shared-steps.mp4" controls muted width="100%"></video>
64
+
65
+ **[webm-output](../../../examples/webm-output)** - Demonstrates outputting the recording as a WebM video using VP9 encoding.
66
+
67
+ <video src="../../../examples/webm-output/webm-output.webm" controls muted width="100%"></video>
68
+
69
+ <!-- EXAMPLES:END -->
70
+
71
+ ## Usage
72
+
73
+ ```ts
74
+ import {
75
+ RecordingContext,
76
+ launchChrome,
77
+ connectCDP,
78
+ navigate,
79
+ clickAt,
80
+ pressKey,
81
+ pause,
82
+ Recorder,
83
+ InteractionTimeline,
84
+ compose,
85
+ } from "@webreel/core";
86
+
87
+ const ctx = new RecordingContext();
88
+ ctx.setMode("record");
89
+
90
+ const chrome = await launchChrome({ headless: true });
91
+ const client = await connectCDP(chrome.port);
92
+
93
+ await client.Page.enable();
94
+ await client.Runtime.enable();
95
+ await client.Emulation.setDeviceMetricsOverride({
96
+ width: 1080,
97
+ height: 1080,
98
+ deviceScaleFactor: 2,
99
+ mobile: false,
100
+ });
101
+
102
+ const timeline = new InteractionTimeline(1080, 1080, { zoom: 2 });
103
+ ctx.setTimeline(timeline);
104
+
105
+ await navigate(client, "https://example.com");
106
+
107
+ const recorder = new Recorder(1080, 1080);
108
+ recorder.setTimeline(timeline);
109
+ await recorder.start(client, "demo.mp4", ctx);
110
+
111
+ await pause(500);
112
+ await clickAt(ctx, client, 540, 400);
113
+ await pressKey(ctx, client, "cmd+a");
114
+ await pause(1000);
115
+
116
+ await recorder.stop();
117
+
118
+ await compose(recorder.getTempVideoPath(), timeline.toJSON(), "demo.mp4");
119
+
120
+ await client.close();
121
+ chrome.kill();
122
+ ```
123
+
124
+ ## API
125
+
126
+ ### Chrome
127
+
128
+ #### `launchChrome(options?): Promise<ChromeInstance>`
129
+
130
+ Launches a Chrome process with remote debugging enabled.
131
+
132
+ | Option | Type | Default | Description |
133
+ | ---------- | --------- | ------- | -------------------- |
134
+ | `headless` | `boolean` | `true` | Run in headless mode |
135
+
136
+ Returns a `ChromeInstance` with `process`, `port`, and `kill()`.
137
+
138
+ ### Recorder
139
+
140
+ #### `new Recorder(width?, height?, assetsDir?)`
141
+
142
+ Creates a recorder that captures screenshots and encodes them to MP4.
143
+
144
+ #### `recorder.start(client, outputPath, ctx?): Promise<void>`
145
+
146
+ Begin capturing frames. Pass an optional `RecordingContext` to track cursor position and timeline events.
147
+
148
+ #### `recorder.stop(): Promise<void>`
149
+
150
+ Stop capturing, encode to MP4 with sound effects, and clean up temp files.
151
+
152
+ ### Actions
153
+
154
+ All action functions that animate the cursor take a `RecordingContext` as their first argument.
155
+
156
+ | Function | Description |
157
+ | -------------------------------------------------- | ------------------------------------------------- |
158
+ | `navigate(client, url)` | Navigate to a URL and wait for load |
159
+ | `waitForSelector(client, selector, timeout?)` | Poll until a CSS selector matches |
160
+ | `findElementByText(client, text, within?)` | Find an element's bounding box by text content |
161
+ | `findElementBySelector(client, selector, within?)` | Find an element's bounding box by CSS selector |
162
+ | `moveCursorTo(ctx, client, x, y)` | Animate the overlay cursor to a position |
163
+ | `clickAt(ctx, client, x, y, modifiers?)` | Move cursor and click with optional modifier keys |
164
+ | `pressKey(ctx, client, key, label?)` | Press a key combo (e.g. `"cmd+z"`) with overlay |
165
+ | `typeText(ctx, client, text, delayMs?)` | Type text character by character |
166
+ | `dragFromTo(ctx, client, fromBox, toBox)` | Drag between two elements |
167
+ | `captureScreenshot(client, outputPath)` | Save a PNG screenshot |
168
+ | `pause(ms?)` | Wait for a duration (default 1200ms) |
169
+ | `modKey()` | Returns `"cmd"` on macOS, `"ctrl"` elsewhere |
170
+
171
+ ### Overlays
172
+
173
+ | Function | Description |
174
+ | -------------------------------------------------- | ----------------------------------------------------- |
175
+ | `injectOverlays(client, theme?, initialPosition?)` | Add cursor and keystroke overlay elements to the page |
176
+ | `showKeys(client, labels)` | Display keystroke labels on screen |
177
+ | `hideKeys(client)` | Hide the keystroke overlay |
178
+
179
+ ## Prerequisites
180
+
181
+ - [Google Chrome](https://www.google.com/chrome/) (or Chromium)
182
+ - [ffmpeg](https://ffmpeg.org/)
183
+
184
+ Set `CHROME_PATH` to override the default Chrome location.
185
+
186
+ ## License
187
+
188
+ Apache-2.0
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=actions.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/actions.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,252 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { modKey, RecordingContext, resolveMod, modifierFlag, modifiersToFlag, modLabel, modKeyInfo, resolveCommands, KEY_CODES, CHAR_CODES, SHORTCUT_COMMANDS, } from "../actions.js";
3
+ describe("modKey", () => {
4
+ it("returns cmd or ctrl based on platform", () => {
5
+ const result = modKey();
6
+ if (process.platform === "darwin") {
7
+ expect(result).toBe("cmd");
8
+ }
9
+ else {
10
+ expect(result).toBe("ctrl");
11
+ }
12
+ });
13
+ });
14
+ describe("resolveMod", () => {
15
+ it('resolves "mod" to platform-specific key', () => {
16
+ const result = resolveMod("mod");
17
+ if (process.platform === "darwin") {
18
+ expect(result).toBe("cmd");
19
+ }
20
+ else {
21
+ expect(result).toBe("ctrl");
22
+ }
23
+ });
24
+ it("passes through non-mod values unchanged", () => {
25
+ expect(resolveMod("shift")).toBe("shift");
26
+ expect(resolveMod("alt")).toBe("alt");
27
+ expect(resolveMod("ctrl")).toBe("ctrl");
28
+ expect(resolveMod("cmd")).toBe("cmd");
29
+ });
30
+ it("is case-insensitive for mod", () => {
31
+ const lower = resolveMod("mod");
32
+ const upper = resolveMod("MOD");
33
+ const mixed = resolveMod("Mod");
34
+ expect(lower).toBe(upper);
35
+ expect(lower).toBe(mixed);
36
+ });
37
+ });
38
+ describe("modifierFlag", () => {
39
+ it("maps alt to 1", () => {
40
+ expect(modifierFlag("alt")).toBe(1);
41
+ });
42
+ it("maps ctrl to 2", () => {
43
+ expect(modifierFlag("ctrl")).toBe(2);
44
+ expect(modifierFlag("control")).toBe(2);
45
+ });
46
+ it("maps cmd/meta to 4", () => {
47
+ expect(modifierFlag("cmd")).toBe(4);
48
+ expect(modifierFlag("meta")).toBe(4);
49
+ });
50
+ it("maps shift to 8", () => {
51
+ expect(modifierFlag("shift")).toBe(8);
52
+ });
53
+ it("returns 0 for unknown modifiers", () => {
54
+ expect(modifierFlag("unknown")).toBe(0);
55
+ });
56
+ });
57
+ describe("modifiersToFlag", () => {
58
+ it("returns 0 for undefined or empty array", () => {
59
+ expect(modifiersToFlag(undefined)).toBe(0);
60
+ expect(modifiersToFlag([])).toBe(0);
61
+ });
62
+ it("combines multiple modifiers with bitwise OR", () => {
63
+ expect(modifiersToFlag(["alt", "shift"])).toBe(1 | 8);
64
+ expect(modifiersToFlag(["ctrl", "meta"])).toBe(2 | 4);
65
+ expect(modifiersToFlag(["alt", "ctrl", "shift", "meta"])).toBe(1 | 2 | 4 | 8);
66
+ });
67
+ it("is case-insensitive", () => {
68
+ expect(modifiersToFlag(["ALT", "SHIFT"])).toBe(1 | 8);
69
+ });
70
+ });
71
+ describe("modLabel", () => {
72
+ it("returns command symbol for cmd/meta", () => {
73
+ expect(modLabel("cmd")).toBe("\u2318");
74
+ expect(modLabel("meta")).toBe("\u2318");
75
+ });
76
+ it("returns Ctrl for ctrl/control", () => {
77
+ expect(modLabel("ctrl")).toBe("Ctrl");
78
+ expect(modLabel("control")).toBe("Ctrl");
79
+ });
80
+ it("returns shift symbol for shift", () => {
81
+ expect(modLabel("shift")).toBe("\u21E7");
82
+ });
83
+ it("returns option symbol for alt", () => {
84
+ expect(modLabel("alt")).toBe("\u2325");
85
+ });
86
+ it("returns the original string for unknown modifiers", () => {
87
+ expect(modLabel("foo")).toBe("foo");
88
+ });
89
+ });
90
+ describe("modKeyInfo", () => {
91
+ it("returns key info for cmd/meta", () => {
92
+ const info = modKeyInfo("cmd");
93
+ expect(info).toEqual({
94
+ key: "Meta",
95
+ code: "MetaLeft",
96
+ keyCode: 91,
97
+ location: 1,
98
+ });
99
+ expect(modKeyInfo("meta")).toEqual(info);
100
+ });
101
+ it("returns key info for ctrl/control", () => {
102
+ const info = modKeyInfo("ctrl");
103
+ expect(info).toEqual({
104
+ key: "Control",
105
+ code: "ControlLeft",
106
+ keyCode: 17,
107
+ location: 1,
108
+ });
109
+ expect(modKeyInfo("control")).toEqual(info);
110
+ });
111
+ it("returns key info for shift", () => {
112
+ expect(modKeyInfo("shift")).toEqual({
113
+ key: "Shift",
114
+ code: "ShiftLeft",
115
+ keyCode: 16,
116
+ location: 1,
117
+ });
118
+ });
119
+ it("returns key info for alt", () => {
120
+ expect(modKeyInfo("alt")).toEqual({
121
+ key: "Alt",
122
+ code: "AltLeft",
123
+ keyCode: 18,
124
+ location: 1,
125
+ });
126
+ });
127
+ it("returns null for unknown modifiers", () => {
128
+ expect(modKeyInfo("unknown")).toBeNull();
129
+ });
130
+ });
131
+ describe("resolveCommands", () => {
132
+ it("resolves meta+z to undo", () => {
133
+ expect(resolveCommands(["meta"], "z")).toEqual(["undo"]);
134
+ });
135
+ it("resolves meta+shift+z to redo", () => {
136
+ expect(resolveCommands(["meta", "shift"], "z")).toEqual(["redo"]);
137
+ });
138
+ it("resolves ctrl+a to selectAll", () => {
139
+ expect(resolveCommands(["ctrl"], "a")).toEqual(["selectAll"]);
140
+ });
141
+ it("resolves ctrl+c to copy", () => {
142
+ expect(resolveCommands(["ctrl"], "c")).toEqual(["copy"]);
143
+ });
144
+ it("resolves ctrl+x to cut", () => {
145
+ expect(resolveCommands(["ctrl"], "x")).toEqual(["cut"]);
146
+ });
147
+ it("resolves ctrl+v to paste", () => {
148
+ expect(resolveCommands(["ctrl"], "v")).toEqual(["paste"]);
149
+ });
150
+ it("resolves mod to platform-specific modifier", () => {
151
+ const result = resolveCommands(["mod"], "z");
152
+ expect(result).toEqual(["undo"]);
153
+ });
154
+ it("returns undefined for unknown shortcuts", () => {
155
+ expect(resolveCommands(["meta"], "q")).toBeUndefined();
156
+ });
157
+ });
158
+ describe("KEY_CODES", () => {
159
+ it("contains expected keys", () => {
160
+ expect(KEY_CODES.Enter).toEqual({ code: "Enter", keyCode: 13 });
161
+ expect(KEY_CODES.Escape).toEqual({ code: "Escape", keyCode: 27 });
162
+ expect(KEY_CODES.Tab).toEqual({ code: "Tab", keyCode: 9 });
163
+ expect(KEY_CODES.Backspace).toEqual({ code: "Backspace", keyCode: 8 });
164
+ expect(KEY_CODES.Delete).toEqual({ code: "Delete", keyCode: 46 });
165
+ expect(KEY_CODES.ArrowUp).toEqual({ code: "ArrowUp", keyCode: 38 });
166
+ expect(KEY_CODES.ArrowDown).toEqual({ code: "ArrowDown", keyCode: 40 });
167
+ expect(KEY_CODES.ArrowLeft).toEqual({ code: "ArrowLeft", keyCode: 37 });
168
+ expect(KEY_CODES.ArrowRight).toEqual({ code: "ArrowRight", keyCode: 39 });
169
+ });
170
+ it("contains letter keys a-d, v, x, z", () => {
171
+ expect(KEY_CODES.a).toEqual({ code: "KeyA", keyCode: 65 });
172
+ expect(KEY_CODES.z).toEqual({ code: "KeyZ", keyCode: 90 });
173
+ expect(KEY_CODES.v).toEqual({ code: "KeyV", keyCode: 86 });
174
+ });
175
+ });
176
+ describe("CHAR_CODES", () => {
177
+ it("maps space correctly", () => {
178
+ expect(CHAR_CODES[" "]).toEqual({ code: "Space", keyCode: 32 });
179
+ });
180
+ it("maps digits correctly", () => {
181
+ expect(CHAR_CODES["0"]).toEqual({ code: "Digit0", keyCode: 48 });
182
+ expect(CHAR_CODES["9"]).toEqual({ code: "Digit9", keyCode: 57 });
183
+ });
184
+ it("maps punctuation correctly", () => {
185
+ expect(CHAR_CODES["."]).toEqual({ code: "Period", keyCode: 190 });
186
+ expect(CHAR_CODES[","]).toEqual({ code: "Comma", keyCode: 188 });
187
+ expect(CHAR_CODES["/"]).toEqual({ code: "Slash", keyCode: 191 });
188
+ });
189
+ });
190
+ describe("SHORTCUT_COMMANDS", () => {
191
+ it("has meta and ctrl variants for standard shortcuts", () => {
192
+ expect(SHORTCUT_COMMANDS["meta+a"]).toEqual(["selectAll"]);
193
+ expect(SHORTCUT_COMMANDS["ctrl+a"]).toEqual(["selectAll"]);
194
+ expect(SHORTCUT_COMMANDS["meta+c"]).toEqual(["copy"]);
195
+ expect(SHORTCUT_COMMANDS["ctrl+c"]).toEqual(["copy"]);
196
+ expect(SHORTCUT_COMMANDS["meta+z"]).toEqual(["undo"]);
197
+ expect(SHORTCUT_COMMANDS["ctrl+z"]).toEqual(["undo"]);
198
+ expect(SHORTCUT_COMMANDS["meta+shift+z"]).toEqual(["redo"]);
199
+ expect(SHORTCUT_COMMANDS["ctrl+shift+z"]).toEqual(["redo"]);
200
+ });
201
+ });
202
+ describe("RecordingContext", () => {
203
+ it("defaults to preview mode", () => {
204
+ const ctx = new RecordingContext();
205
+ expect(ctx.mode).toBe("preview");
206
+ expect(ctx.isRecording).toBe(false);
207
+ });
208
+ it("reports isRecording only when mode is record and timeline is set", () => {
209
+ const ctx = new RecordingContext();
210
+ ctx.setMode("record");
211
+ expect(ctx.isRecording).toBe(false);
212
+ ctx.setTimeline({ addEvent: () => { } });
213
+ expect(ctx.isRecording).toBe(true);
214
+ });
215
+ it("tracks cursor position", () => {
216
+ const ctx = new RecordingContext();
217
+ ctx.setCursorPosition(100, 200);
218
+ expect(ctx.cursorX).toBe(100);
219
+ expect(ctx.cursorY).toBe(200);
220
+ expect(ctx.getCursorPosition()).toEqual({ x: 100, y: 200 });
221
+ });
222
+ it("getClickDwellMs uses configured value when set", () => {
223
+ const ctx = new RecordingContext();
224
+ ctx.setClickDwell(50);
225
+ expect(ctx.getClickDwellMs()).toBe(50);
226
+ });
227
+ it("getClickDwellMs returns random value in range when not configured", () => {
228
+ const ctx = new RecordingContext();
229
+ for (let i = 0; i < 20; i++) {
230
+ const dwell = ctx.getClickDwellMs();
231
+ expect(dwell).toBeGreaterThanOrEqual(80);
232
+ expect(dwell).toBeLessThanOrEqual(180);
233
+ }
234
+ });
235
+ it("markEvent delegates to timeline and recorder", () => {
236
+ const ctx = new RecordingContext();
237
+ const events = [];
238
+ ctx.setMode("record");
239
+ ctx.setTimeline({ addEvent: (t) => events.push(`tl:${t}`) });
240
+ ctx.setRecorder({ addEvent: (t) => events.push(`rec:${t}`) });
241
+ ctx.markEvent("click");
242
+ expect(events).toEqual(["tl:click", "rec:click"]);
243
+ });
244
+ it("markEvent skips timeline when not recording", () => {
245
+ const ctx = new RecordingContext();
246
+ const events = [];
247
+ ctx.setRecorder({ addEvent: (t) => events.push(`rec:${t}`) });
248
+ ctx.markEvent("key");
249
+ expect(events).toEqual(["rec:key"]);
250
+ });
251
+ });
252
+ //# sourceMappingURL=actions.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.test.js","sourceRoot":"","sources":["../../src/__tests__/actions.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=chrome.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chrome.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/chrome.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,29 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { cftPlatform } from "../chrome.js";
3
+ describe("cftPlatform", () => {
4
+ it("returns a valid Chrome for Testing platform string", () => {
5
+ const result = cftPlatform();
6
+ const valid = ["mac-arm64", "mac-x64", "linux64", "linux-arm64", "win64"];
7
+ expect(valid).toContain(result);
8
+ });
9
+ it("returns correct value for current platform", () => {
10
+ const { platform, arch } = process;
11
+ const result = cftPlatform();
12
+ if (platform === "darwin" && arch === "arm64") {
13
+ expect(result).toBe("mac-arm64");
14
+ }
15
+ else if (platform === "darwin") {
16
+ expect(result).toBe("mac-x64");
17
+ }
18
+ else if (platform === "linux" && arch === "arm64") {
19
+ expect(result).toBe("linux-arm64");
20
+ }
21
+ else if (platform === "linux") {
22
+ expect(result).toBe("linux64");
23
+ }
24
+ else if (platform === "win32") {
25
+ expect(result).toBe("win64");
26
+ }
27
+ });
28
+ });
29
+ //# sourceMappingURL=chrome.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chrome.test.js","sourceRoot":"","sources":["../../src/__tests__/chrome.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,CAAC,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;QAC1E,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;QACnC,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC;QAE7B,IAAI,QAAQ,KAAK,QAAQ,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YAC9C,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACnC,CAAC;aAAM,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACjC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjC,CAAC;aAAM,IAAI,QAAQ,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACpD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACrC,CAAC;aAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;YAChC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjC,CAAC;aAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;YAChC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cursor-motion.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-motion.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/cursor-motion.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { computeEasedPath, computeDragTiming } from "../cursor-motion.js";
3
+ describe("computeEasedPath", () => {
4
+ it("returns a single destination point when distance is < 1", () => {
5
+ const pts = computeEasedPath(10, 10, 10.5, 10.5, 20);
6
+ expect(pts).toHaveLength(1);
7
+ expect(pts[0]).toEqual({ x: 10.5, y: 10.5 });
8
+ });
9
+ it("returns the requested number of steps", () => {
10
+ const pts = computeEasedPath(0, 0, 100, 100, 10);
11
+ expect(pts).toHaveLength(10);
12
+ });
13
+ it("ends at the exact destination", () => {
14
+ const pts = computeEasedPath(0, 0, 200, 300, 15);
15
+ expect(pts[pts.length - 1]).toEqual({ x: 200, y: 300 });
16
+ });
17
+ it("intermediate points move generally toward the target", () => {
18
+ const pts = computeEasedPath(0, 0, 500, 0, 20);
19
+ for (let i = 1; i < pts.length; i++) {
20
+ expect(pts[i].x).toBeGreaterThanOrEqual(pts[i - 1].x - 5);
21
+ }
22
+ });
23
+ });
24
+ describe("computeDragTiming", () => {
25
+ it("returns at least 12 steps", () => {
26
+ const { steps } = computeDragTiming(10);
27
+ expect(steps).toBeGreaterThanOrEqual(12);
28
+ });
29
+ it("increases steps with distance", () => {
30
+ const short = computeDragTiming(50);
31
+ const long = computeDragTiming(1000);
32
+ expect(long.steps).toBeGreaterThan(short.steps);
33
+ });
34
+ it("delayMs is positive", () => {
35
+ const { delayMs } = computeDragTiming(200);
36
+ expect(delayMs).toBeGreaterThan(0);
37
+ });
38
+ });
39
+ //# sourceMappingURL=cursor-motion.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-motion.test.js","sourceRoot":"","sources":["../../src/__tests__/cursor-motion.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE1E,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,GAAG,GAAG,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;QACrD,MAAM,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,GAAG,GAAG,gBAAgB,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QACjD,MAAM,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,GAAG,GAAG,gBAAgB,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QACjD,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,GAAG,GAAG,gBAAgB,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACpC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,EAAE,KAAK,EAAE,GAAG,iBAAiB,CAAC,EAAE,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,KAAK,GAAG,iBAAiB,CAAC,EAAE,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,EAAE,OAAO,EAAE,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAC3C,MAAM,CAAC,OAAO,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ffmpeg.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ffmpeg.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ffmpeg.test.ts"],"names":[],"mappings":""}