recordable 0.1.0 → 0.3.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/README.md +67 -24
- package/dist/actions.d.ts +9 -1
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +10 -3
- package/dist/actions.js.map +1 -1
- package/dist/browser/cursor.d.ts +17 -5
- package/dist/browser/cursor.d.ts.map +1 -1
- package/dist/browser/cursor.js +81 -47
- package/dist/browser/cursor.js.map +1 -1
- package/dist/browser/runtime.d.ts +34 -6
- package/dist/browser/runtime.d.ts.map +1 -1
- package/dist/browser/runtime.js +119 -37
- package/dist/browser/runtime.js.map +1 -1
- package/dist/compose/recordable.d.ts +28 -12
- package/dist/compose/recordable.d.ts.map +1 -1
- package/dist/compose/recordable.js +33 -16
- package/dist/compose/recordable.js.map +1 -1
- package/dist/compose/session.d.ts +15 -1
- package/dist/compose/session.d.ts.map +1 -1
- package/dist/compose/session.js +65 -18
- package/dist/compose/session.js.map +1 -1
- package/dist/config.d.ts +22 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -1
- package/dist/formats/markdown/parse.d.ts.map +1 -1
- package/dist/formats/markdown/parse.js +11 -1
- package/dist/formats/markdown/parse.js.map +1 -1
- package/dist/targets.d.ts +13 -2
- package/dist/targets.d.ts.map +1 -1
- package/dist/targets.js +31 -3
- package/dist/targets.js.map +1 -1
- package/dist/timing.d.ts +0 -2
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +6 -11
- package/dist/timing.js.map +1 -1
- package/dist/video/recorder.d.ts +4 -1
- package/dist/video/recorder.d.ts.map +1 -1
- package/dist/video/recorder.js +12 -2
- package/dist/video/recorder.js.map +1 -1
- package/package.json +2 -1
- package/recordable.schema.json +45 -3
package/README.md
CHANGED
|
@@ -36,10 +36,13 @@ want on camera; every captured segment is stitched into one seamless MP4.
|
|
|
36
36
|
- **Animated cursor overlay** with realistic movement and click feedback.
|
|
37
37
|
- **Smooth zoom & scroll** that animate origin and scale together.
|
|
38
38
|
- **Human-like typing** with jitter and natural pauses.
|
|
39
|
-
- **Element targeting** by CSS selector or visible text (`
|
|
39
|
+
- **Element targeting** by full CSS selector or visible text — `:text(…)`
|
|
40
|
+
composes with CSS, e.g. `button:text(Save)`.
|
|
40
41
|
- **Off-camera segments** — `pause()`/`resume()` skip setup, navigations, or whole
|
|
41
42
|
screens; segments are auto-stitched into one seamless video.
|
|
42
|
-
- **
|
|
43
|
+
- **New-tab recording** — `click(target, { followNewTab: true })` follows a link
|
|
44
|
+
that opens in a new tab and keeps recording there, stitched into the same MP4.
|
|
45
|
+
- **Manual steps / logins** — `resumeOnPlay()` waits for an in-page ▶ Play button
|
|
43
46
|
(see below), so you can sign in by hand before recording.
|
|
44
47
|
- **Auto-scroll** to bring elements into view before interacting.
|
|
45
48
|
- **Declarative JSON scripts + CLI** — author a recording as JSON (with a published
|
|
@@ -147,22 +150,26 @@ await new Recordable()
|
|
|
147
150
|
## Recording behind a login (manual steps)
|
|
148
151
|
|
|
149
152
|
Run **headful** (`headless: false`) so the Chrome window is interactive. Keep the
|
|
150
|
-
camera off while you sign in by hand, then `
|
|
153
|
+
camera off while you sign in by hand, then `resumeOnPlay()` waits for you to
|
|
151
154
|
click an **in-page ▶ Play button** (or press Enter) before recording resumes:
|
|
152
155
|
|
|
153
156
|
```ts
|
|
154
157
|
await new Recordable({ headless: false })
|
|
155
158
|
.pause() // camera off — the login isn't recorded
|
|
156
159
|
.visit("https://app.example.com/login")
|
|
157
|
-
.
|
|
160
|
+
.resumeOnPlay("Log in, then click ▶ Play to start recording")
|
|
158
161
|
.visit("https://app.example.com/dashboard")
|
|
159
162
|
.click("text:New project")
|
|
160
163
|
.run();
|
|
161
164
|
```
|
|
162
165
|
|
|
163
|
-
- **`
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
- **`resumeOnPlay(message?)`** waits for ▶ Play, then resumes recording. It's a thin
|
|
167
|
+
wrapper for **`waitForPlay().resume()`** — the ▶ Play button is injected into the
|
|
168
|
+
page itself and blocks until you click it (Enter in the terminal also works), and
|
|
169
|
+
it's re-injected across navigations so it survives login redirects.
|
|
170
|
+
- **`waitForPlay(message?)`** is the gate on its own — it blocks on ▶ Play but leaves
|
|
171
|
+
the camera untouched. Use it when you want to hold the script for a manual step
|
|
172
|
+
that should stay off-camera, or pair it with `resume()` yourself.
|
|
166
173
|
- Prefer an automatic trigger? Use **`waitFor("#dashboard")`** after `resume()` to
|
|
167
174
|
carry on once a post-login element appears — no clicking required.
|
|
168
175
|
|
|
@@ -209,12 +216,13 @@ Create an instance with optional [config](#configuration), chain actions, then
|
|
|
209
216
|
Recording is on by default and finalises automatically on `.run()`. These control
|
|
210
217
|
what lands on camera:
|
|
211
218
|
|
|
212
|
-
| Method
|
|
213
|
-
|
|
|
214
|
-
| `pause()`
|
|
215
|
-
| `resume()`
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
219
|
+
| Method | Description |
|
|
220
|
+
| ------------------------ | ----------------------------------------------------------------------------------------------------------------- |
|
|
221
|
+
| `pause()` | Stop capturing; the chain keeps running off-camera. |
|
|
222
|
+
| `resume()` | Resume capturing in a fresh segment, immediately. |
|
|
223
|
+
| `waitForPlay(message?)` | Block until the user clicks the in-page ▶ Play button (or presses Enter); leaves recording state untouched. |
|
|
224
|
+
| `resumeOnPlay(message?)` | Wait for ▶ Play, then resume capturing — `waitForPlay().resume()`. |
|
|
225
|
+
| `insert(path, opts?)` | Splice an external clip (intro / outro / mid-roll) into the timeline; `opts.fadeIn`/`fadeOut` (ms) cross-fade it. |
|
|
218
226
|
|
|
219
227
|
### Navigation & waiting
|
|
220
228
|
|
|
@@ -226,20 +234,36 @@ what lands on camera:
|
|
|
226
234
|
|
|
227
235
|
### Interactions
|
|
228
236
|
|
|
229
|
-
| Method | Description
|
|
230
|
-
| ----------------------------------- |
|
|
231
|
-
| `click(target)`
|
|
232
|
-
| `hover(target)` | Move onto an element to reveal `:hover` state (no click).
|
|
233
|
-
| `type(target, text, { duration? })` | Type into a field with human-like timing; `duration` (ms) spreads keystrokes evenly with no jitter.
|
|
234
|
-
| `clear(target)` | Select-all + delete the contents of a field.
|
|
235
|
-
| `select(target, value)` | Choose an option in a native `<select>` (
|
|
236
|
-
| `key(key)` | Press a key, e.g. `"Escape"`, `"Enter"`, `"Tab"`.
|
|
237
|
-
| `mouse(target \| {x, y})` | Move the cursor to an element or coordinates.
|
|
237
|
+
| Method | Description |
|
|
238
|
+
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
239
|
+
| `click(target, { waitForNav?, followNewTab? })` | Click an element. Returns immediately by default; pass `{ waitForNav: true }` when the click triggers a full-page navigation, or `{ followNewTab: true }` to keep recording in a tab the click opens (see notes below). |
|
|
240
|
+
| `hover(target)` | Move onto an element to reveal `:hover` state (no click). |
|
|
241
|
+
| `type(target, text, { duration? })` | Type into a field with human-like timing; `duration` (ms) spreads keystrokes evenly with no jitter. |
|
|
242
|
+
| `clear(target)` | Select-all + delete the contents of a field. |
|
|
243
|
+
| `select(target, value)` | Choose an option in a native `<select>` by `value`, or by `:option-index(N)` / `:option-label(Text)` (see note below; OS-drawn list isn't captured). |
|
|
244
|
+
| `key(key)` | Press a key, e.g. `"Escape"`, `"Enter"`, `"Tab"`. |
|
|
245
|
+
| `mouse(target \| {x, y})` | Move the cursor to an element or coordinates. |
|
|
238
246
|
|
|
239
247
|
> The browser draws an open `<select>`'s option list with the OS, outside the page,
|
|
240
248
|
> so the screencast can't capture it — `select()` shows the cursor and the value
|
|
241
249
|
> changing, but not the dropdown. For an on-camera dropdown, build a custom one from
|
|
242
250
|
> `click()`s.
|
|
251
|
+
>
|
|
252
|
+
> `value` matches the `<option>`'s `value` attribute by default. To pick without
|
|
253
|
+
> knowing it, use `":option-index(1)"` (1-based, like `:nth-child`) or
|
|
254
|
+
> `":option-label(Pro tier)"` to match the option's visible text.
|
|
255
|
+
|
|
256
|
+
> A plain `click()` does not wait for navigation. If the click loads a new page
|
|
257
|
+
> (a link, a form submit), add `{ waitForNav: true }` so the next action lands on
|
|
258
|
+
> the loaded page instead of racing it — the wait is armed before the click and
|
|
259
|
+
> the navigation must land, like `visit()`. For SPA route changes or async content
|
|
260
|
+
> (no full-page load) there's nothing to wait on — follow the click with
|
|
261
|
+
> `waitFor("<selector>")` for an element on the new view instead.
|
|
262
|
+
|
|
263
|
+
> When a click opens a link in a **new tab**, pass `{ followNewTab: true }`:
|
|
264
|
+
> `recordable` switches capture to the new tab and stitches it into the same
|
|
265
|
+
> recording (the new tab's load happens off-camera, the old tab stays open).
|
|
266
|
+
> Without it, recording stays on the original tab.
|
|
243
267
|
|
|
244
268
|
### Camera
|
|
245
269
|
|
|
@@ -254,8 +278,23 @@ what lands on camera:
|
|
|
254
278
|
|
|
255
279
|
Anywhere a `target` is accepted you can pass:
|
|
256
280
|
|
|
257
|
-
- a **CSS selector** —
|
|
258
|
-
-
|
|
281
|
+
- a **full CSS selector** — IDs, classes, attributes, combinators, and
|
|
282
|
+
positional pseudo-classes all work: `"#id"`, `".card"`, `'[name="email"]'`,
|
|
283
|
+
`"nav > ul li[data-active]"`, `"table tr:nth-child(3) td:first-child"`,
|
|
284
|
+
`"section:has(> h2)"`.
|
|
285
|
+
- **visible text** with the `:text(…)` pseudo — `":text(Sign up)"` matches the
|
|
286
|
+
smallest element containing that text, and it **composes with CSS** so you can
|
|
287
|
+
scope it: `"button:text(Save)"`, `"nav a:text(Pricing)"`,
|
|
288
|
+
`"table tr:nth-child(3) td:text(Done)"`. The text is bare (unquoted); it can
|
|
289
|
+
hold spaces and commas but not a literal `)`.
|
|
290
|
+
- **Puppeteer selectors** also pass through untouched — `::-p-aria(Submit)` for
|
|
291
|
+
accessible name, `>>>` to pierce shadow DOM.
|
|
292
|
+
|
|
293
|
+
> The legacy whole-string `text:` prefix (`"text:Sign up"`) still works as an
|
|
294
|
+
> alias for `:text(…)`.
|
|
295
|
+
|
|
296
|
+
If a target matches more than one element, `recordable` logs a warning and acts
|
|
297
|
+
on the first — tighten the selector to silence it.
|
|
259
298
|
|
|
260
299
|
## Configuration
|
|
261
300
|
|
|
@@ -264,11 +303,13 @@ All options are optional; defaults shown.
|
|
|
264
303
|
```ts
|
|
265
304
|
new Recordable({
|
|
266
305
|
viewport: { width: 1920, height: 1080 },
|
|
306
|
+
pageZoom: 1, // browser page zoom (Ctrl +/−); <1 reflows to fit more on screen
|
|
267
307
|
fps: 30,
|
|
268
308
|
outputDir: "./output",
|
|
269
309
|
outputName: "recordable",
|
|
270
310
|
outputTimestamp: true, // prepend an ISO timestamp to the filename
|
|
271
311
|
headless: false,
|
|
312
|
+
language: "", // BCP-47 locale, e.g. "fr-FR" (--lang + Accept-Language); "" = system
|
|
272
313
|
typingSpeed: 7, // characters per second
|
|
273
314
|
videoCrf: 18, // lower = better quality, larger file
|
|
274
315
|
videoCodec: "libx264",
|
|
@@ -291,6 +332,8 @@ new Recordable({
|
|
|
291
332
|
npm install
|
|
292
333
|
npm run build # type-check + emit dist/ with .d.ts
|
|
293
334
|
npm run gen:schema # regenerate recordable.schema.json from the action manifest
|
|
335
|
+
npm test # unit + ffmpeg I/O tests
|
|
336
|
+
npm run test:e2e # opt-in end-to-end pipeline run (launches a browser)
|
|
294
337
|
npx tsx my-script.ts # run a recording script directly
|
|
295
338
|
node dist/cli.js demo.json # run a JSON script through the CLI locally
|
|
296
339
|
```
|
package/dist/actions.d.ts
CHANGED
|
@@ -6,7 +6,10 @@ import * as z from "zod";
|
|
|
6
6
|
declare const ACTIONS: {
|
|
7
7
|
pause: z.ZodObject<{}, z.core.$strict>;
|
|
8
8
|
resume: z.ZodObject<{}, z.core.$strict>;
|
|
9
|
-
|
|
9
|
+
waitForPlay: z.ZodObject<{
|
|
10
|
+
message: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
resumeOnPlay: z.ZodObject<{
|
|
10
13
|
message: z.ZodOptional<z.ZodString>;
|
|
11
14
|
}, z.core.$strict>;
|
|
12
15
|
insert: z.ZodObject<{
|
|
@@ -25,6 +28,7 @@ declare const ACTIONS: {
|
|
|
25
28
|
width: z.ZodNumber;
|
|
26
29
|
height: z.ZodNumber;
|
|
27
30
|
}, z.core.$strict>>;
|
|
31
|
+
pageZoom: z.ZodDefault<z.ZodNumber>;
|
|
28
32
|
fps: z.ZodDefault<z.ZodNumber>;
|
|
29
33
|
outputDir: z.ZodDefault<z.ZodString>;
|
|
30
34
|
outputName: z.ZodDefault<z.ZodString>;
|
|
@@ -32,6 +36,7 @@ declare const ACTIONS: {
|
|
|
32
36
|
assetsDir: z.ZodDefault<z.ZodString>;
|
|
33
37
|
headless: z.ZodDefault<z.ZodBoolean>;
|
|
34
38
|
launchArgs: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
39
|
+
language: z.ZodDefault<z.ZodString>;
|
|
35
40
|
typingSpeed: z.ZodDefault<z.ZodNumber>;
|
|
36
41
|
videoCrf: z.ZodDefault<z.ZodNumber>;
|
|
37
42
|
videoCodec: z.ZodDefault<z.ZodString>;
|
|
@@ -65,6 +70,9 @@ declare const ACTIONS: {
|
|
|
65
70
|
}, z.core.$strict>;
|
|
66
71
|
click: z.ZodObject<{
|
|
67
72
|
target: z.ZodString;
|
|
73
|
+
waitForNav: z.ZodOptional<z.ZodBoolean>;
|
|
74
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
75
|
+
followNewTab: z.ZodOptional<z.ZodBoolean>;
|
|
68
76
|
}, z.core.$strict>;
|
|
69
77
|
hover: z.ZodObject<{
|
|
70
78
|
target: z.ZodString;
|
package/dist/actions.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAezB;;;GAGG;AACH,QAAA,MAAM,OAAO
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAezB;;;GAGG;AACH,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+D0B,CAAC;AAYxC,sEAAsE;AACtE,MAAM,MAAM,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAuChE;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAgBjD;AAED;;;;;;GAMG;AACH,iBAAS,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAcxD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CA2C3E;AAED,wEAAwE;AACxE,OAAO,EAAE,OAAO,EAAE,CAAC;AAEnB,iFAAiF;AACjF,OAAO,EAAE,SAAS,EAAE,CAAC"}
|
package/dist/actions.js
CHANGED
|
@@ -18,7 +18,8 @@ const ACTIONS = {
|
|
|
18
18
|
// Recording control
|
|
19
19
|
pause: z.strictObject({}),
|
|
20
20
|
resume: z.strictObject({}),
|
|
21
|
-
|
|
21
|
+
waitForPlay: z.strictObject({ message: z.string().optional() }),
|
|
22
|
+
resumeOnPlay: z.strictObject({ message: z.string().optional() }),
|
|
22
23
|
insert: z.strictObject({
|
|
23
24
|
path: z.string(),
|
|
24
25
|
fadeIn: z.number().optional(),
|
|
@@ -43,7 +44,12 @@ const ACTIONS = {
|
|
|
43
44
|
timeout: z.number().optional(),
|
|
44
45
|
}),
|
|
45
46
|
// Interactions
|
|
46
|
-
click: z.strictObject({
|
|
47
|
+
click: z.strictObject({
|
|
48
|
+
target: z.string(),
|
|
49
|
+
waitForNav: z.boolean().optional(),
|
|
50
|
+
timeout: z.number().optional(),
|
|
51
|
+
followNewTab: z.boolean().optional(),
|
|
52
|
+
}),
|
|
47
53
|
hover: z.strictObject({ target: z.string() }),
|
|
48
54
|
type: z.strictObject({
|
|
49
55
|
target: z.string(),
|
|
@@ -74,7 +80,8 @@ const ACTIONS = {
|
|
|
74
80
|
* fact not derivable from the schema.
|
|
75
81
|
*/
|
|
76
82
|
const POSITIONAL_OPTIONAL = {
|
|
77
|
-
|
|
83
|
+
waitForPlay: ["message"],
|
|
84
|
+
resumeOnPlay: ["message"],
|
|
78
85
|
};
|
|
79
86
|
// ─── Manifest derivation ─────────────────────────────────────────────────────
|
|
80
87
|
//
|
package/dist/actions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,gFAAgF;AAChF,EAAE;AACF,iFAAiF;AACjF,+EAA+E;AAC/E,iFAAiF;AACjF,8EAA8E;AAC9E,6EAA6E;AAE7E,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;AACvD,MAAM,EAAE,GAAG,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAE5D;;;GAGG;AACH,MAAM,OAAO,GAAG;IACd,oBAAoB;IACpB,KAAK,EAAE,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC;IACzB,MAAM,EAAE,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC;IAC1B,
|
|
1
|
+
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
|
package/dist/browser/cursor.d.ts
CHANGED
|
@@ -12,19 +12,31 @@ export interface ZoomState {
|
|
|
12
12
|
*/
|
|
13
13
|
export declare class Cursor {
|
|
14
14
|
private pos;
|
|
15
|
+
private parked;
|
|
16
|
+
/** Snapshot the current position (called on pause) for a later unpark(). */
|
|
17
|
+
park(): void;
|
|
18
|
+
/** Restore the parked position (if any) and re-inject — called on resume so the
|
|
19
|
+
* new segment opens with the cursor where the previous one ended. */
|
|
20
|
+
unpark(page: Page, zoom?: ZoomState, pageZoom?: number): Promise<void>;
|
|
15
21
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
22
|
+
* Ensure the cursor overlay exists and sits at the carried position, then sync
|
|
23
|
+
* the real mouse there. Safe to call repeatedly: it creates the overlay if the
|
|
24
|
+
* document doesn't have one (e.g. after a navigation) and otherwise just
|
|
25
|
+
* repositions it — so a resume() can restore the cursor even when the overlay
|
|
26
|
+
* survived the off-camera gap.
|
|
19
27
|
*/
|
|
20
|
-
inject(page: Page, zoom?: ZoomState): Promise<void>;
|
|
28
|
+
inject(page: Page, zoom?: ZoomState, pageZoom?: number): Promise<void>;
|
|
21
29
|
/** Ease the overlay (and the real mouse) to viewport coords `toX,toY`. */
|
|
22
|
-
moveTo(page: Page, toX: number, toY: number, zoom: ZoomState): Promise<void>;
|
|
30
|
+
moveTo(page: Page, toX: number, toY: number, zoom: ZoomState, pageZoom?: number): Promise<void>;
|
|
23
31
|
/**
|
|
24
32
|
* Convert viewport coords → document coords for the overlay's transform.
|
|
25
33
|
* When documentElement has a CSS transform (zoom), position:fixed children
|
|
26
34
|
* are positioned relative to that ancestor (not the viewport) and scroll with
|
|
27
35
|
* the page, so we add scroll and apply the inverse zoom transform.
|
|
36
|
+
*
|
|
37
|
+
* `pageZoom` is the static CSS `zoom` on documentElement (the pageZoom config).
|
|
38
|
+
* The overlay inherits that zoom, so its translate renders `pageZoom`× larger —
|
|
39
|
+
* divide it out so the cursor still lands on the target's visual position.
|
|
28
40
|
*/
|
|
29
41
|
private _toDocCoords;
|
|
30
42
|
/** Briefly scale the cursor down to signal a press. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/browser/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/browser/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAOtC,mFAAmF;AACnF,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;;;GAIG;AACH,qBAAa,MAAM;IAIjB,OAAO,CAAC,GAAG,CAAkB;IAK7B,OAAO,CAAC,MAAM,CAAyC;IAEvD,4EAA4E;IAC5E,IAAI,IAAI,IAAI;IAIZ;0EACsE;IAChE,MAAM,CACV,IAAI,EAAE,IAAI,EACV,IAAI,GAAE,SAAkC,EACxC,QAAQ,SAAI,GACX,OAAO,CAAC,IAAI,CAAC;IAQhB;;;;;;OAMG;IACG,MAAM,CACV,IAAI,EAAE,IAAI,EACV,IAAI,GAAE,SAAkC,EACxC,QAAQ,SAAI,GACX,OAAO,CAAC,IAAI,CAAC;IAiEhB,0EAA0E;IACpE,MAAM,CACV,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,SAAS,EACf,QAAQ,SAAI,GACX,OAAO,CAAC,IAAI,CAAC;IAiChB;;;;;;;;;OASG;YACW,YAAY;IAiB1B,uDAAuD;IACjD,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAa7C"}
|
package/dist/browser/cursor.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sleep } from "../utils.js";
|
|
2
2
|
import { cursorMoveMs, PRESS_DOWN_MS, PRESS_SETTLE_MS } from "../timing.js";
|
|
3
3
|
const CURSOR_ID = "__recordable_cursor__";
|
|
4
|
+
const CURSOR_STYLE_ID = "__recordable_cursor_style__";
|
|
4
5
|
/**
|
|
5
6
|
* An animated cursor overlay drawn into the page. Tracks its own position so
|
|
6
7
|
* each move can ease from where it last was, and dips on click for a tactile
|
|
@@ -11,67 +12,93 @@ export class Cursor {
|
|
|
11
12
|
// re-injects, and we want the cursor to reappear where it last was (like a
|
|
12
13
|
// real pointer) rather than snapping to the top-left corner.
|
|
13
14
|
pos = { x: 0, y: 0 };
|
|
15
|
+
// Position snapshotted at pause() so resume() can restore the cursor to exactly
|
|
16
|
+
// where the camera left it — off-camera steps between pause and resume may move
|
|
17
|
+
// `pos`, but the resumed segment should open where the last one ended.
|
|
18
|
+
parked = null;
|
|
19
|
+
/** Snapshot the current position (called on pause) for a later unpark(). */
|
|
20
|
+
park() {
|
|
21
|
+
this.parked = { ...this.pos };
|
|
22
|
+
}
|
|
23
|
+
/** Restore the parked position (if any) and re-inject — called on resume so the
|
|
24
|
+
* new segment opens with the cursor where the previous one ended. */
|
|
25
|
+
async unpark(page, zoom = { tx: 0, ty: 0, s: 1 }, pageZoom = 1) {
|
|
26
|
+
if (this.parked) {
|
|
27
|
+
this.pos = this.parked;
|
|
28
|
+
this.parked = null;
|
|
29
|
+
}
|
|
30
|
+
await this.inject(page, zoom, pageZoom);
|
|
31
|
+
}
|
|
14
32
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
33
|
+
* Ensure the cursor overlay exists and sits at the carried position, then sync
|
|
34
|
+
* the real mouse there. Safe to call repeatedly: it creates the overlay if the
|
|
35
|
+
* document doesn't have one (e.g. after a navigation) and otherwise just
|
|
36
|
+
* repositions it — so a resume() can restore the cursor even when the overlay
|
|
37
|
+
* survived the off-camera gap.
|
|
18
38
|
*/
|
|
19
|
-
async inject(page, zoom = { tx: 0, ty: 0, s: 1 }) {
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
!!document.getElementById(id), CURSOR_ID);
|
|
26
|
-
if (skip)
|
|
39
|
+
async inject(page, zoom = { tx: 0, ty: 0, s: 1 }, pageZoom = 1) {
|
|
40
|
+
// Can't draw into an iframe or before the document's <body> has parsed. A
|
|
41
|
+
// too-early call (e.g. from a navigation event) is a no-op; the next moveTo
|
|
42
|
+
// re-injects when ready.
|
|
43
|
+
const ready = await page.evaluate(() => window === window.parent && !!document.body);
|
|
44
|
+
if (!ready)
|
|
27
45
|
return;
|
|
28
|
-
const { cx, cy } = await this._toDocCoords(page, this.pos.x, this.pos.y, zoom);
|
|
29
|
-
await page.evaluate(({ id, cx, cy }) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
const { cx, cy } = await this._toDocCoords(page, this.pos.x, this.pos.y, zoom, pageZoom);
|
|
47
|
+
await page.evaluate(({ id, styleId, cx, cy }) => {
|
|
48
|
+
// Note: we intentionally do NOT hide the native pointer. The screencast
|
|
49
|
+
// doesn't capture the OS cursor, so it never reaches the video; hiding it
|
|
50
|
+
// only blanked the real pointer in the live headful window (incl. during
|
|
51
|
+
// manual wait-for-input steps). Both cursors showing live is harmless.
|
|
52
|
+
if (!document.getElementById(styleId)) {
|
|
53
|
+
const style = document.createElement("style");
|
|
54
|
+
style.id = styleId;
|
|
55
|
+
style.textContent = `
|
|
56
|
+
#${id} {
|
|
57
|
+
position: fixed;
|
|
58
|
+
top: 0; left: 0;
|
|
59
|
+
margin: -2px 0 0 -4px;
|
|
60
|
+
z-index: 2147483647;
|
|
61
|
+
pointer-events: none;
|
|
62
|
+
will-change: transform;
|
|
63
|
+
transition: transform 0.15s;
|
|
64
|
+
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.4));
|
|
65
|
+
}
|
|
66
|
+
#${id}.pressing {
|
|
67
|
+
transform: var(--recordable-pos) scale(0.88) !important;
|
|
68
|
+
transition: transform 0.08s !important;
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
document.head.appendChild(style);
|
|
72
|
+
}
|
|
73
|
+
let cursor = document.getElementById(id);
|
|
74
|
+
if (!cursor) {
|
|
75
|
+
cursor = document.createElement("div");
|
|
76
|
+
cursor.id = id;
|
|
77
|
+
cursor.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
|
78
|
+
<path d="M4 2 L4 19 L8.5 14.5 L12 22 L14 21 L10.5 13.5 L17 13.5 Z"
|
|
79
|
+
fill="white" stroke="#1e1b4b" stroke-width="1.2" stroke-linejoin="round"/>
|
|
80
|
+
</svg>`;
|
|
81
|
+
document.body.appendChild(cursor);
|
|
82
|
+
}
|
|
83
|
+
// Place at the carried position with no transition, so it appears there
|
|
84
|
+
// immediately instead of animating in from wherever it was.
|
|
53
85
|
cursor.style.transition = "none";
|
|
54
86
|
cursor.style.setProperty("--recordable-pos", `translate(${cx}px, ${cy}px)`);
|
|
55
87
|
cursor.style.transform = `translate(${cx}px, ${cy}px)`;
|
|
56
|
-
|
|
57
|
-
<path d="M4 2 L4 19 L8.5 14.5 L12 22 L14 21 L10.5 13.5 L17 13.5 Z"
|
|
58
|
-
fill="white" stroke="#1e1b4b" stroke-width="1.2" stroke-linejoin="round"/>
|
|
59
|
-
</svg>`;
|
|
60
|
-
document.body.appendChild(cursor);
|
|
61
|
-
}, { id: CURSOR_ID, cx, cy });
|
|
88
|
+
}, { id: CURSOR_ID, styleId: CURSOR_STYLE_ID, cx, cy });
|
|
62
89
|
await page.mouse.move(this.pos.x, this.pos.y);
|
|
63
90
|
}
|
|
64
91
|
/** Ease the overlay (and the real mouse) to viewport coords `toX,toY`. */
|
|
65
|
-
async moveTo(page, toX, toY, zoom) {
|
|
92
|
+
async moveTo(page, toX, toY, zoom, pageZoom = 1) {
|
|
66
93
|
// Self-heal: a navigation (incl. click-triggered ones with no following
|
|
67
94
|
// visit/waitFor) wipes the overlay. Re-inject at the carried position before
|
|
68
95
|
// animating, so the move is always visible — never an instant, cursor-less jump.
|
|
69
|
-
await this.inject(page, zoom);
|
|
96
|
+
await this.inject(page, zoom, pageZoom);
|
|
70
97
|
const dx = toX - this.pos.x;
|
|
71
98
|
const dy = toY - this.pos.y;
|
|
72
99
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
73
100
|
const dur = cursorMoveMs(dist);
|
|
74
|
-
const { cx, cy } = await this._toDocCoords(page, toX, toY, zoom);
|
|
101
|
+
const { cx, cy } = await this._toDocCoords(page, toX, toY, zoom, pageZoom);
|
|
75
102
|
await page.evaluate(({ id, cx, cy, dur }) => new Promise((resolve) => {
|
|
76
103
|
const cursor = document.getElementById(id);
|
|
77
104
|
if (!cursor) {
|
|
@@ -91,13 +118,20 @@ export class Cursor {
|
|
|
91
118
|
* When documentElement has a CSS transform (zoom), position:fixed children
|
|
92
119
|
* are positioned relative to that ancestor (not the viewport) and scroll with
|
|
93
120
|
* the page, so we add scroll and apply the inverse zoom transform.
|
|
121
|
+
*
|
|
122
|
+
* `pageZoom` is the static CSS `zoom` on documentElement (the pageZoom config).
|
|
123
|
+
* The overlay inherits that zoom, so its translate renders `pageZoom`× larger —
|
|
124
|
+
* divide it out so the cursor still lands on the target's visual position.
|
|
94
125
|
*/
|
|
95
|
-
async _toDocCoords(page, x, y, { tx, ty, s }) {
|
|
126
|
+
async _toDocCoords(page, x, y, { tx, ty, s }, pageZoom = 1) {
|
|
96
127
|
const hasTransform = s !== 1 || tx !== 0 || ty !== 0;
|
|
97
128
|
const scroll = hasTransform
|
|
98
129
|
? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY }))
|
|
99
130
|
: { x: 0, y: 0 };
|
|
100
|
-
return {
|
|
131
|
+
return {
|
|
132
|
+
cx: (x + scroll.x - tx) / s / pageZoom,
|
|
133
|
+
cy: (y + scroll.y - ty) / s / pageZoom,
|
|
134
|
+
};
|
|
101
135
|
}
|
|
102
136
|
/** Briefly scale the cursor down to signal a press. */
|
|
103
137
|
async clickEffect(page) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/browser/cursor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE5E,MAAM,SAAS,GAAG,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/browser/cursor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Page, type GoToOptions } from "puppeteer";
|
|
2
|
-
import type { ResolvedConfig, WaitForOptions } from "../config.js";
|
|
2
|
+
import type { ClickOptions, ResolvedConfig, WaitForOptions } from "../config.js";
|
|
3
3
|
import { type Logger } from "../logger.js";
|
|
4
4
|
import { type ZoomState } from "./cursor.js";
|
|
5
5
|
export declare class Runtime {
|
|
@@ -17,15 +17,35 @@ export declare class Runtime {
|
|
|
17
17
|
resetZoomState(): void;
|
|
18
18
|
/** (Re-)inject the cursor overlay at the carried position, if enabled. */
|
|
19
19
|
injectCursor(page: Page): Promise<void>;
|
|
20
|
+
/** Snapshot the cursor position (on pause) so a later resume() can restore it,
|
|
21
|
+
* ignoring any off-camera moves between pause and resume. */
|
|
22
|
+
parkCursor(): void;
|
|
23
|
+
/** Re-inject the cursor at the parked position (on resume), so the resumed
|
|
24
|
+
* segment opens with the cursor exactly where the paused one left it. */
|
|
25
|
+
restoreCursor(page: Page): Promise<void>;
|
|
20
26
|
visit(page: Page, url: string, options?: GoToOptions): Promise<void>;
|
|
21
27
|
waitFor(page: Page, target: string, options?: WaitForOptions): Promise<void>;
|
|
22
|
-
|
|
28
|
+
/** Click a target. Returns the new tab's `Page` when `followNewTab` is set and the
|
|
29
|
+
* click opened one — the session then switches recording to it. Otherwise void. */
|
|
30
|
+
click(page: Page, target: string, options?: ClickOptions): Promise<Page | void>;
|
|
31
|
+
/**
|
|
32
|
+
* Click a link that opens a new tab and resolve to the new `Page` (or warn and
|
|
33
|
+
* return void if none appears). The popup listener is armed *before* the click so a
|
|
34
|
+
* fast open can't be missed. The new tab is *not* waited on here — the session seals
|
|
35
|
+
* the current segment first, then waits for the load off-camera so it's trimmed.
|
|
36
|
+
*/
|
|
37
|
+
private _clickNewTab;
|
|
38
|
+
/** Resolve to the next popup `page.once("popup")` opens, or null after `timeout`.
|
|
39
|
+
* A late timer after the popup resolves is a harmless no-op; unref it so it can't
|
|
40
|
+
* hold the process open. */
|
|
41
|
+
private _waitForPopup;
|
|
23
42
|
hover(page: Page, target: string): Promise<void>;
|
|
24
43
|
type(page: Page, target: string, text: string, options?: {
|
|
25
44
|
duration?: number;
|
|
26
45
|
}): Promise<void>;
|
|
27
46
|
clear(page: Page, target: string): Promise<void>;
|
|
28
47
|
select(page: Page, target: string, value: string): Promise<void>;
|
|
48
|
+
private _optionValue;
|
|
29
49
|
key(page: Page, key: string): Promise<void>;
|
|
30
50
|
mouse(page: Page, target: string | {
|
|
31
51
|
x: number;
|
|
@@ -45,16 +65,24 @@ export declare class Runtime {
|
|
|
45
65
|
* to avoid Puppeteer's built-in scrollIntoView overriding our centred scroll. */
|
|
46
66
|
private _click;
|
|
47
67
|
/**
|
|
48
|
-
* Click at viewport coords
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
68
|
+
* Click at viewport coords. By default the click returns immediately — clicks
|
|
69
|
+
* don't wait for navigation. Pass `waitForNav: true` when the click triggers a
|
|
70
|
+
* full-page navigation: the wait is armed *before* the click so a fast commit
|
|
71
|
+
* can't be missed, and the navigation must land, so the click then behaves like
|
|
72
|
+
* `visit()`. The network settle afterwards is best-effort, so a perpetually-busy
|
|
73
|
+
* page can't fail an otherwise-successful navigation. SPA route changes are not
|
|
74
|
+
* full-page navigations — gate those with a following `waitFor("<selector>")`.
|
|
52
75
|
*/
|
|
53
76
|
private _clickPoint;
|
|
54
77
|
/** Move to viewport coords, animating the overlay when the cursor is enabled. */
|
|
55
78
|
private _moveTo;
|
|
56
79
|
/** Scroll a target into view using the configured margin/speed. */
|
|
57
80
|
private _scrollIntoView;
|
|
81
|
+
/** Fragile-selector lint: warn (once) when a target resolves to more than one
|
|
82
|
+
* element, then act on the first. Best-effort — it does not wait for the
|
|
83
|
+
* element, so it stays silent if the matches haven't rendered yet, and never
|
|
84
|
+
* throws (a genuine miss surfaces later at getHandle). */
|
|
85
|
+
private checkAmbiguous;
|
|
58
86
|
/**
|
|
59
87
|
* Block until the user resumes — by clicking the in-page ▶ Play button, or by
|
|
60
88
|
* pressing Enter in the *terminal*. The in-page button is click-only so the live
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/browser/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/browser/runtime.ts"],"names":[],"mappings":"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"}
|