recordable 0.2.0 → 0.4.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 +49 -18
- package/dist/actions.d.ts +4 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +7 -5
- package/dist/actions.js.map +1 -1
- package/dist/audio/track.d.ts.map +1 -1
- package/dist/audio/track.js.map +1 -1
- package/dist/browser/cursor.d.ts +6 -0
- package/dist/browser/cursor.d.ts.map +1 -1
- package/dist/browser/cursor.js +24 -6
- package/dist/browser/cursor.js.map +1 -1
- package/dist/browser/dom.d.ts +33 -13
- package/dist/browser/dom.d.ts.map +1 -1
- package/dist/browser/dom.js +158 -56
- package/dist/browser/dom.js.map +1 -1
- package/dist/browser/page-zoom.d.ts +11 -0
- package/dist/browser/page-zoom.d.ts.map +1 -0
- package/dist/browser/page-zoom.js +37 -0
- package/dist/browser/page-zoom.js.map +1 -0
- package/dist/browser/play-button.d.ts.map +1 -1
- package/dist/browser/play-button.js.map +1 -1
- package/dist/browser/runtime.d.ts +21 -1
- package/dist/browser/runtime.d.ts.map +1 -1
- package/dist/browser/runtime.js +91 -4
- package/dist/browser/runtime.js.map +1 -1
- package/dist/browser/targets.d.ts +17 -0
- package/dist/browser/targets.d.ts.map +1 -0
- package/dist/browser/targets.js +42 -0
- package/dist/browser/targets.js.map +1 -0
- package/dist/compose/mix.d.ts.map +1 -1
- package/dist/compose/mix.js +1 -1
- package/dist/compose/mix.js.map +1 -1
- package/dist/compose/recordable.d.ts +13 -7
- package/dist/compose/recordable.d.ts.map +1 -1
- package/dist/compose/recordable.js +28 -13
- package/dist/compose/recordable.js.map +1 -1
- package/dist/compose/session.d.ts +16 -1
- package/dist/compose/session.d.ts.map +1 -1
- package/dist/compose/session.js +76 -18
- package/dist/compose/session.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/ffmpeg.d.ts +3 -0
- package/dist/ffmpeg.d.ts.map +1 -1
- package/dist/ffmpeg.js +14 -1
- package/dist/ffmpeg.js.map +1 -1
- package/dist/formats/markdown/method.d.ts.map +1 -1
- package/dist/formats/markdown/method.js +4 -3
- package/dist/formats/markdown/method.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/logger.d.ts.map +1 -1
- package/dist/logger.js +3 -1
- package/dist/logger.js.map +1 -1
- package/dist/timing.d.ts +2 -0
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +10 -4
- package/dist/timing.js.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +11 -10
- package/dist/validate.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 +20 -16
- package/dist/video/recorder.js.map +1 -1
- package/dist/video/stitch.js +3 -17
- package/dist/video/stitch.js.map +1 -1
- package/dist/voiceover/compile.d.ts.map +1 -1
- package/dist/voiceover/compile.js +42 -24
- package/dist/voiceover/compile.js.map +1 -1
- package/dist/voiceover/elevenlabs.d.ts.map +1 -1
- package/dist/voiceover/elevenlabs.js +41 -11
- package/dist/voiceover/elevenlabs.js.map +1 -1
- package/dist/voiceover/mock.d.ts.map +1 -1
- package/dist/voiceover/mock.js +38 -15
- package/dist/voiceover/mock.js.map +1 -1
- package/package.json +3 -1
- package/recordable.schema.json +12 -0
- package/dist/targets.d.ts +0 -6
- package/dist/targets.d.ts.map +0 -1
- package/dist/targets.js +0 -13
- package/dist/targets.js.map +0 -1
package/dist/browser/dom.js
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
|
-
import { isPositionValue, resolveTarget } from "
|
|
2
|
-
|
|
1
|
+
import { isPositionValue, resolveTarget } from "./targets.js";
|
|
2
|
+
import { RecordableError } from "../errors.js";
|
|
3
|
+
/** Jitter spread as a fraction of an element's dimension (±20% of each side). */
|
|
4
|
+
const JITTER_FRACTION = 0.4;
|
|
5
|
+
/** Frame interval in ms (≈ one 60fps frame). */
|
|
6
|
+
const FRAME_MS = 16;
|
|
7
|
+
/** Default viewport height (px) when the page has no viewport set. */
|
|
8
|
+
const DEFAULT_VIEWPORT_HEIGHT = 900;
|
|
9
|
+
/** Resolve a target and wait for the element to be *visible* in any frame, then
|
|
10
|
+
* return its handle.
|
|
11
|
+
*
|
|
12
|
+
* Two reasons this is more than a bare `page.locator(...).waitHandle()`:
|
|
13
|
+
* - Visibility (not mere existence): callers immediately read `boundingBox()`
|
|
14
|
+
* to click/scroll, but Puppeteer's default locator resolves the instant the
|
|
15
|
+
* node enters the DOM (`waitForSelector(..., {visible:false})`) — so an
|
|
16
|
+
* element inserted-but-not-yet-laid-out yields a null box. `setVisibility`
|
|
17
|
+
* makes it wait for a real layout box.
|
|
18
|
+
* - Frames: modal dialogs (e.g. APEX `apex.navigation.dialog`) render their
|
|
19
|
+
* content in an `<iframe>`. The top frame can hold a hidden placeholder with
|
|
20
|
+
* the same id, so we race every frame and take the first *visible* match.
|
|
21
|
+
* An iframe element's `boundingBox()` is reported in main-frame coordinates,
|
|
22
|
+
* so the coordinate-based click still lands correctly. */
|
|
3
23
|
export async function getHandle(page, target) {
|
|
24
|
+
const selector = resolveTarget(target);
|
|
4
25
|
try {
|
|
5
|
-
return await
|
|
26
|
+
return await Promise.any(page
|
|
27
|
+
.frames()
|
|
28
|
+
.map((f) => f.locator(selector).setVisibility("visible").waitHandle()));
|
|
6
29
|
}
|
|
7
30
|
catch {
|
|
8
|
-
throw new
|
|
31
|
+
throw new RecordableError("TARGET_NOT_FOUND", `Could not find target: "${target}"`);
|
|
9
32
|
}
|
|
10
33
|
}
|
|
11
34
|
/** Centre coords of a target element, jittered up to 20% of each dimension. */
|
|
@@ -13,8 +36,8 @@ export async function getElementCenter(page, target) {
|
|
|
13
36
|
const handle = await getHandle(page, target);
|
|
14
37
|
const box = await handle.boundingBox();
|
|
15
38
|
if (!box)
|
|
16
|
-
throw new
|
|
17
|
-
const offset = (range) => (Math.random() - 0.5) * range *
|
|
39
|
+
throw new RecordableError("TARGET_NOT_FOUND", `No bounding box for "${target}"`);
|
|
40
|
+
const offset = (range) => (Math.random() - 0.5) * range * JITTER_FRACTION;
|
|
18
41
|
return {
|
|
19
42
|
x: box.x + box.width / 2 + offset(box.width),
|
|
20
43
|
y: box.y + box.height / 2 + offset(box.height),
|
|
@@ -54,81 +77,160 @@ export async function originToCoords(page, origin) {
|
|
|
54
77
|
return { x: (vw * px) / 100, y: (vh * py) / 100 };
|
|
55
78
|
}, origin);
|
|
56
79
|
}
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Animate a scroller's vertical position to `targetTop` over `duration` ms with an
|
|
82
|
+
* ease curve. `container` null scrolls the window; a handle scrolls that element's
|
|
83
|
+
* `scrollTop`. The target is clamped to the scroller's range.
|
|
84
|
+
*/
|
|
85
|
+
export async function smoothScroll(page, targetTop, duration, container = null) {
|
|
86
|
+
await page.evaluate(
|
|
87
|
+
// frameMs is passed in: a module-level const isn't visible inside this
|
|
88
|
+
// browser-context closure.
|
|
89
|
+
(el, { targetTop, duration, frameMs }) => {
|
|
60
90
|
return new Promise((resolve) => {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
91
|
+
const max = el
|
|
92
|
+
? el.scrollHeight - el.clientHeight
|
|
93
|
+
: document.documentElement.scrollHeight - window.innerHeight;
|
|
94
|
+
const end = Math.max(0, Math.min(targetTop, max));
|
|
95
|
+
const startY = el ? el.scrollTop : window.scrollY;
|
|
96
|
+
const dist = end - startY;
|
|
97
|
+
const frames = Math.ceil(duration / frameMs);
|
|
64
98
|
let i = 0;
|
|
65
99
|
const id = setInterval(() => {
|
|
66
100
|
i++;
|
|
67
101
|
const p = Math.min(i / frames, 1);
|
|
68
102
|
const e = p < 0.5 ? 4 * p * p * p : (p - 1) * (2 * p - 2) * (2 * p - 2) + 1;
|
|
69
|
-
|
|
103
|
+
const y = startY + dist * e;
|
|
104
|
+
if (el)
|
|
105
|
+
el.scrollTop = y;
|
|
106
|
+
else
|
|
107
|
+
window.scrollTo(0, y);
|
|
70
108
|
if (p >= 1) {
|
|
71
109
|
clearInterval(id);
|
|
72
110
|
resolve();
|
|
73
111
|
}
|
|
74
|
-
},
|
|
112
|
+
}, frameMs);
|
|
75
113
|
});
|
|
76
|
-
}, {
|
|
114
|
+
}, container, { targetTop, duration, frameMs: FRAME_MS });
|
|
77
115
|
}
|
|
78
116
|
/**
|
|
79
|
-
* Smooth-scroll to an element or position
|
|
80
|
-
*
|
|
81
|
-
* -
|
|
82
|
-
* -
|
|
117
|
+
* Smooth-scroll to an element or position. Without `container` the window scrolls;
|
|
118
|
+
* with one, `target` is resolved against that scroll container instead:
|
|
119
|
+
* - `"top"` / `"bottom"` → scroller extremes
|
|
120
|
+
* - number → absolute scrollTop (px)
|
|
121
|
+
* - CSS selector or `text:` prefix → element centred within the scroller
|
|
83
122
|
*/
|
|
84
|
-
export async function smoothScrollToTarget(page, target, duration) {
|
|
123
|
+
export async function smoothScrollToTarget(page, target, duration, container) {
|
|
124
|
+
const scroller = container ? await getHandle(page, container) : null;
|
|
85
125
|
if (typeof target === "number")
|
|
86
|
-
return smoothScroll(page, target, duration);
|
|
126
|
+
return smoothScroll(page, target, duration, scroller);
|
|
87
127
|
if (target === "top")
|
|
88
|
-
return smoothScroll(page, 0, duration);
|
|
128
|
+
return smoothScroll(page, 0, duration, scroller);
|
|
89
129
|
if (target === "bottom") {
|
|
90
|
-
const bottom = await page.evaluate(() => document.body.scrollHeight);
|
|
91
|
-
return smoothScroll(page, bottom, duration);
|
|
130
|
+
const bottom = await page.evaluate((el) => (el ? el.scrollHeight : document.body.scrollHeight), scroller);
|
|
131
|
+
return smoothScroll(page, bottom, duration, scroller);
|
|
92
132
|
}
|
|
93
133
|
const handle = await getHandle(page, target);
|
|
94
|
-
const
|
|
134
|
+
const top = await page.evaluate((el, scrollEl, vh) => {
|
|
95
135
|
const rect = el.getBoundingClientRect();
|
|
136
|
+
if (scrollEl) {
|
|
137
|
+
// Centre the child within the container's visible area.
|
|
138
|
+
const box = scrollEl.getBoundingClientRect();
|
|
139
|
+
return (scrollEl.scrollTop +
|
|
140
|
+
(rect.top - box.top) +
|
|
141
|
+
rect.height / 2 -
|
|
142
|
+
scrollEl.clientHeight / 2);
|
|
143
|
+
}
|
|
96
144
|
return window.scrollY + rect.top + rect.height / 2 - vh / 2;
|
|
97
|
-
}, handle, page.viewport()?.height ??
|
|
98
|
-
return smoothScroll(page,
|
|
145
|
+
}, handle, scroller, page.viewport()?.height ?? DEFAULT_VIEWPORT_HEIGHT);
|
|
146
|
+
return smoothScroll(page, top, duration, scroller);
|
|
147
|
+
}
|
|
148
|
+
/** The scrollTop that brings the element comfortably into the scroller's view, or
|
|
149
|
+
* null when it's already comfortably visible. Pure geometry, shared by the window
|
|
150
|
+
* and container passes so both reveal an element the same way. */
|
|
151
|
+
function comfortTarget(m, margin) {
|
|
152
|
+
const { relTop, height, vh, scrollTop } = m;
|
|
153
|
+
const relBottom = relTop + height;
|
|
154
|
+
const comfort = margin * 2;
|
|
155
|
+
if (relTop >= comfort && relBottom <= vh - comfort)
|
|
156
|
+
return null;
|
|
157
|
+
// Tall element: top-align with margin.
|
|
158
|
+
if (height > vh - margin * 2)
|
|
159
|
+
return scrollTop + relTop - margin;
|
|
160
|
+
// Below the bottom comfort zone: scroll just enough to show it fully (centring
|
|
161
|
+
// would often overshoot the scroller's max and leave it pinned to the edge).
|
|
162
|
+
if (relBottom > vh - margin)
|
|
163
|
+
return scrollTop + relBottom - (vh - margin);
|
|
164
|
+
// Above the top comfort zone: scroll to show the top.
|
|
165
|
+
if (relTop < margin)
|
|
166
|
+
return scrollTop + relTop - margin;
|
|
167
|
+
// Within the comfort band but not comfortable: centre it.
|
|
168
|
+
return scrollTop + relTop + height / 2 - vh / 2;
|
|
169
|
+
}
|
|
170
|
+
/** Read the target's position within `container` (an element scroller) or, when
|
|
171
|
+
* null, within the window. */
|
|
172
|
+
function readMetrics(page, handle, container) {
|
|
173
|
+
return page.evaluate((el, c) => {
|
|
174
|
+
const r = el.getBoundingClientRect();
|
|
175
|
+
if (c) {
|
|
176
|
+
const b = c.getBoundingClientRect();
|
|
177
|
+
return {
|
|
178
|
+
relTop: r.top - b.top,
|
|
179
|
+
height: r.height,
|
|
180
|
+
vh: c.clientHeight,
|
|
181
|
+
scrollTop: c.scrollTop,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
relTop: r.top,
|
|
186
|
+
height: r.height,
|
|
187
|
+
vh: window.innerHeight,
|
|
188
|
+
scrollTop: window.scrollY,
|
|
189
|
+
};
|
|
190
|
+
}, handle, container);
|
|
191
|
+
}
|
|
192
|
+
/** The nearest ancestor that actually scrolls vertically (overflow auto/scroll +
|
|
193
|
+
* overflowing content), or null if none short of the document — in which case the
|
|
194
|
+
* window is the scroller. */
|
|
195
|
+
async function nearestScrollableAncestor(handle) {
|
|
196
|
+
const h = await handle.evaluateHandle((el) => {
|
|
197
|
+
let p = el.parentElement;
|
|
198
|
+
while (p && p !== document.body && p !== document.documentElement) {
|
|
199
|
+
const oy = getComputedStyle(p).overflowY;
|
|
200
|
+
if ((oy === "auto" || oy === "scroll" || oy === "overlay") &&
|
|
201
|
+
p.scrollHeight > p.clientHeight)
|
|
202
|
+
return p;
|
|
203
|
+
p = p.parentElement;
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
});
|
|
207
|
+
const el = h.asElement();
|
|
208
|
+
if (!el)
|
|
209
|
+
await h.dispose();
|
|
210
|
+
return el;
|
|
99
211
|
}
|
|
100
212
|
/**
|
|
101
|
-
* Scroll `target` into view if it lies outside the visible
|
|
102
|
-
*
|
|
103
|
-
*
|
|
213
|
+
* Scroll `target` into view if it lies outside the visible area (keeping `margin`
|
|
214
|
+
* px clear on each side). No-op when already comfortably visible. `speed` (px/s)
|
|
215
|
+
* sets the scroll duration. Scrolls the target's nearest scrollable container
|
|
216
|
+
* first (so it's revealed inside a modal / sidebar / pane), then the window so the
|
|
217
|
+
* container itself is on screen.
|
|
104
218
|
*/
|
|
105
219
|
export async function scrollIntoView(page, target, margin, speed) {
|
|
106
220
|
const handle = await getHandle(page, target);
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// Element extends above the top comfort zone: scroll to show top
|
|
122
|
-
if (rect.top < margin)
|
|
123
|
-
return window.scrollY + rect.top - margin;
|
|
124
|
-
// In view but within the comfort band: centre it
|
|
125
|
-
return window.scrollY + rect.top + rect.height / 2 - vh / 2;
|
|
126
|
-
}, handle, margin);
|
|
127
|
-
if (scrollY === null)
|
|
128
|
-
return;
|
|
129
|
-
const currentY = await page.evaluate(() => window.scrollY);
|
|
130
|
-
const dist = Math.abs(scrollY - currentY);
|
|
131
|
-
const duration = Math.max(200, (dist / speed) * 1000);
|
|
132
|
-
await smoothScroll(page, scrollY, duration);
|
|
221
|
+
const container = await nearestScrollableAncestor(handle);
|
|
222
|
+
const reveal = async (scroller) => {
|
|
223
|
+
const metrics = await readMetrics(page, handle, scroller);
|
|
224
|
+
const top = comfortTarget(metrics, margin);
|
|
225
|
+
if (top === null)
|
|
226
|
+
return;
|
|
227
|
+
const dist = Math.abs(top - metrics.scrollTop);
|
|
228
|
+
const duration = Math.max(200, (dist / speed) * 1000);
|
|
229
|
+
await smoothScroll(page, top, duration, scroller);
|
|
230
|
+
};
|
|
231
|
+
if (container)
|
|
232
|
+
await reveal(container);
|
|
233
|
+
await reveal(null);
|
|
234
|
+
await container?.dispose();
|
|
133
235
|
}
|
|
134
236
|
//# sourceMappingURL=dom.js.map
|
package/dist/browser/dom.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom.js","sourceRoot":"","sources":["../../src/browser/dom.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"dom.js","sourceRoot":"","sources":["../../src/browser/dom.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ZoomExtension {
|
|
2
|
+
/** Launch args that load the extension. */
|
|
3
|
+
args: string[];
|
|
4
|
+
/** Remove the generated temp directory. */
|
|
5
|
+
cleanup: () => void;
|
|
6
|
+
}
|
|
7
|
+
/** Generate the bundled page-zoom extension for `zoom` and return its launch args
|
|
8
|
+
* plus a cleanup. Caller adds `args` to `puppeteer.launch` and calls `cleanup`
|
|
9
|
+
* once the browser has closed. */
|
|
10
|
+
export declare function createZoomExtension(zoom: number): ZoomExtension;
|
|
11
|
+
//# sourceMappingURL=page-zoom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-zoom.d.ts","sourceRoot":"","sources":["../../src/browser/page-zoom.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,2CAA2C;IAC3C,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAiBD;;mCAEmC;AACnC,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAkB/D"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
// Registers its listeners synchronously at top level (MV3 requirement) and
|
|
5
|
+
// re-applies on create + every load — page zoom is per-origin persistent, so this
|
|
6
|
+
// covers same-origin navigations and any `followNewTab` tabs.
|
|
7
|
+
const background = (zoom) => `
|
|
8
|
+
const ZOOM = ${zoom};
|
|
9
|
+
const apply = (id) => chrome.tabs.setZoom(id, ZOOM).catch(() => {});
|
|
10
|
+
chrome.tabs.onCreated.addListener((t) => apply(t.id));
|
|
11
|
+
chrome.tabs.onUpdated.addListener((id, info) => {
|
|
12
|
+
if (info.status === "loading" || info.status === "complete") apply(id);
|
|
13
|
+
});
|
|
14
|
+
chrome.runtime.onInstalled.addListener(() =>
|
|
15
|
+
chrome.tabs.query({}, (tabs) => tabs.forEach((t) => apply(t.id))),
|
|
16
|
+
);
|
|
17
|
+
`;
|
|
18
|
+
/** Generate the bundled page-zoom extension for `zoom` and return its launch args
|
|
19
|
+
* plus a cleanup. Caller adds `args` to `puppeteer.launch` and calls `cleanup`
|
|
20
|
+
* once the browser has closed. */
|
|
21
|
+
export function createZoomExtension(zoom) {
|
|
22
|
+
const dir = mkdtempSync(join(tmpdir(), "recordable-zoom-"));
|
|
23
|
+
writeFileSync(join(dir, "manifest.json"), JSON.stringify({
|
|
24
|
+
manifest_version: 3,
|
|
25
|
+
name: "recordable-page-zoom",
|
|
26
|
+
version: "1.0",
|
|
27
|
+
permissions: ["tabs"],
|
|
28
|
+
host_permissions: ["<all_urls>"],
|
|
29
|
+
background: { service_worker: "bg.js" },
|
|
30
|
+
}));
|
|
31
|
+
writeFileSync(join(dir, "bg.js"), background(zoom));
|
|
32
|
+
return {
|
|
33
|
+
args: [`--disable-extensions-except=${dir}`, `--load-extension=${dir}`],
|
|
34
|
+
cleanup: () => rmSync(dir, { recursive: true, force: true }),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=page-zoom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-zoom.js","sourceRoot":"","sources":["../../src/browser/page-zoom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAwBjC,2EAA2E;AAC3E,kFAAkF;AAClF,8DAA8D;AAC9D,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC;eACtB,IAAI;;;;;;;;;CASlB,CAAC;AAEF;;mCAEmC;AACnC,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAC5D,aAAa,CACX,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,EAC1B,IAAI,CAAC,SAAS,CAAC;QACb,gBAAgB,EAAE,CAAC;QACnB,IAAI,EAAE,sBAAsB;QAC5B,OAAO,EAAE,KAAK;QACd,WAAW,EAAE,CAAC,MAAM,CAAC;QACrB,gBAAgB,EAAE,CAAC,YAAY,CAAC;QAChC,UAAU,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE;KACxC,CAAC,CACH,CAAC;IACF,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,OAAO;QACL,IAAI,EAAE,CAAC,+BAA+B,GAAG,EAAE,EAAE,oBAAoB,GAAG,EAAE,CAAC;QACvE,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;KAC7D,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"play-button.d.ts","sourceRoot":"","sources":["../../src/browser/play-button.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,wBAAwB,CAAC;AACpD,eAAO,MAAM,YAAY,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"play-button.d.ts","sourceRoot":"","sources":["../../src/browser/play-button.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,wBAAwB,CAAC;AACpD,eAAO,MAAM,YAAY,uBAAuB,CAAC;AAsEjD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,GACd,MAAM,CAGR"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"play-button.js","sourceRoot":"","sources":["../../src/browser/play-button.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,qBAAqB,CAAC;AACpD,MAAM,CAAC,MAAM,YAAY,GAAG,oBAAoB,CAAC;AAEjD;;;GAGG;AACH,SAAS,gBAAgB,
|
|
1
|
+
{"version":3,"file":"play-button.js","sourceRoot":"","sources":["../../src/browser/play-button.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,qBAAqB,CAAC;AACpD,MAAM,CAAC,MAAM,YAAY,GAAG,oBAAoB,CAAC;AAEjD;;;GAGG;AACH,SAAS,gBAAgB,CAAC,OAAe,EAAE,EAAU,EAAE,OAAe;IACpE,wDAAwD;IACxD,IAAI,MAAM,KAAK,MAAM,CAAC,MAAM;QAAE,OAAO;IACrC,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,IAAI,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YAAE,OAAO;QACxC,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG;YACnB,gBAAgB;YAChB,UAAU;YACV,2EAA2E;YAC3E,iEAAiE;YACjE,UAAU;YACV,4BAA4B;YAC5B,oBAAoB;YACpB,qBAAqB;YACrB,cAAc;YACd,oBAAoB;YACpB,UAAU;YACV,6BAA6B;YAC7B,gCAAgC;YAChC,YAAY;YACZ,qBAAqB;YACrB,uCAAuC;YACvC,yDAAyD;SAC1D,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC7C,GAAG,CAAC,KAAK,CAAC,OAAO,GAAG;YAClB,WAAW;YACX,gBAAgB;YAChB,qBAAqB;YACrB,oBAAoB;YACpB,wBAAwB;YACxB,YAAY;YACZ,aAAa;YACb,mBAAmB;YACnB,oBAAoB;YACpB,YAAY;YACZ,gBAAgB;SACjB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,GAAG,CAAC,WAAW,GAAG,GAAG,CAAC;QACtB,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAC7C,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC;QAC5B,KAAK,CAAC,KAAK,CAAC,OAAO;YACjB,gEAAgE,CAAC;QACnE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,KAAK,GAAG,KAAK,CAAC;QAClB,MAAM,IAAI,GAAG,GAAG,EAAE;YAChB,IAAI,KAAK;gBAAE,OAAO;YAClB,KAAK,GAAG,IAAI,CAAC;YACb,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,MAAM,EAAE,GAAI,MAAgD,CAAC,OAAO,CAAC,CAAC;YACtE,IAAI,OAAO,EAAE,KAAK,UAAU;gBAAE,EAAE,EAAE,CAAC;QACrC,CAAC,CAAC;QACF,sEAAsE;QACtE,iEAAiE;QACjE,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACpC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC,CAAC;IACF,IAAI,QAAQ,CAAC,IAAI;QAAE,KAAK,EAAE,CAAC;;QACtB,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAe,EACf,EAAU,EACV,OAAe;IAEf,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5E,OAAO,sCAAsC,gBAAgB,CAAC,QAAQ,EAAE,KAAK,IAAI,SAAS,CAAC;AAC7F,CAAC"}
|
|
@@ -25,19 +25,34 @@ export declare class Runtime {
|
|
|
25
25
|
restoreCursor(page: Page): Promise<void>;
|
|
26
26
|
visit(page: Page, url: string, options?: GoToOptions): Promise<void>;
|
|
27
27
|
waitFor(page: Page, target: string, options?: WaitForOptions): Promise<void>;
|
|
28
|
-
|
|
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;
|
|
29
42
|
hover(page: Page, target: string): Promise<void>;
|
|
30
43
|
type(page: Page, target: string, text: string, options?: {
|
|
31
44
|
duration?: number;
|
|
32
45
|
}): Promise<void>;
|
|
33
46
|
clear(page: Page, target: string): Promise<void>;
|
|
34
47
|
select(page: Page, target: string, value: string): Promise<void>;
|
|
48
|
+
private _optionValue;
|
|
35
49
|
key(page: Page, key: string): Promise<void>;
|
|
36
50
|
mouse(page: Page, target: string | {
|
|
37
51
|
x: number;
|
|
38
52
|
y: number;
|
|
39
53
|
}): Promise<void>;
|
|
40
54
|
scroll(page: Page, target: string | number, options?: {
|
|
55
|
+
container?: string;
|
|
41
56
|
duration?: number;
|
|
42
57
|
}): Promise<void>;
|
|
43
58
|
zoomTo(page: Page, level: number, options?: {
|
|
@@ -64,6 +79,11 @@ export declare class Runtime {
|
|
|
64
79
|
private _moveTo;
|
|
65
80
|
/** Scroll a target into view using the configured margin/speed. */
|
|
66
81
|
private _scrollIntoView;
|
|
82
|
+
/** Fragile-selector lint: warn (once) when a target resolves to more than one
|
|
83
|
+
* element, then act on the first. Best-effort — it does not wait for the
|
|
84
|
+
* element, so it stays silent if the matches haven't rendered yet, and never
|
|
85
|
+
* throws (a genuine miss surfaces later at getHandle). */
|
|
86
|
+
private checkAmbiguous;
|
|
67
87
|
/**
|
|
68
88
|
* Block until the user resumes — by clicking the in-page ▶ Play button, or by
|
|
69
89
|
* 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,EAAE,KAAK,IAAI,EAAE,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/browser/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,WAAW,EAAsB,MAAM,WAAW,CAAC;AAC5E,OAAO,KAAK,EACV,YAAY,EACZ,cAAc,EACd,cAAc,EACf,MAAM,cAAc,CAAC;AAItB,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAQ3C,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAgBrD,qBAAa,OAAO;IAShB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IATtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,IAAI,CAAqC;IAGjD,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,kBAAkB,CAAS;gBAGhB,MAAM,EAAE,MAAM,cAAc,EAC5B,GAAG,EAAE,MAAM;IAG9B;6CACyC;IACzC,IAAI,SAAS,IAAI,SAAS,CAEzB;IAED,+DAA+D;IAC/D,cAAc,IAAI,IAAI;IAItB,0EAA0E;IACpE,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C;kEAC8D;IAC9D,UAAU,IAAI,IAAI;IAIlB;8EAC0E;IACpE,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxC,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAapE,OAAO,CACX,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC;IAchB;wFACoF;IAC9E,KAAK,CACT,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAOvB;;;;;OAKG;YACW,YAAY;IAmB1B;;iCAE6B;IAC7B,OAAO,CAAC,aAAa;IAYf,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQhD,IAAI,CACR,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAClC,OAAO,CAAC,IAAI,CAAC;IAgBV,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhD,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAoBxD,YAAY;IAwBpB,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3C,KAAK,CACT,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,GAAG;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GACxC,OAAO,CAAC,IAAI,CAAC;IAYV,MAAM,CACV,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,OAAO,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GACtD,OAAO,CAAC,IAAI,CAAC;IAeV,MAAM,CACV,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GACnD,OAAO,CAAC,IAAI,CAAC;IAyBV,SAAS,CACb,IAAI,EAAE,IAAI,EACV,OAAO,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAClC,OAAO,CAAC,IAAI,CAAC;IAsBhB;sFACkF;YACpE,MAAM;IAepB;;;;;;;;OAQG;YACW,WAAW;IAqBzB,iFAAiF;YACnE,OAAO;IAKrB,mEAAmE;IACnE,OAAO,CAAC,eAAe;IAKvB;;;+DAG2D;YAC7C,cAAc;IAe5B;;;;;OAKG;IACG,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAuD9D"}
|
package/dist/browser/runtime.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sleep, truncate } from "../utils.js";
|
|
2
|
-
import { resolveTarget } from "
|
|
3
|
-
import {
|
|
2
|
+
import { resolveTarget, parseOptionSpec } from "./targets.js";
|
|
3
|
+
import { RecordableError } from "../errors.js";
|
|
4
|
+
import { getElementCenter, getHandle, originToCoords, scrollIntoView, smoothScrollToTarget, } from "./dom.js";
|
|
4
5
|
import { Cursor } from "./cursor.js";
|
|
5
6
|
import { jitter, typingDuration, typingGaps, PRE_CLICK_MS } from "../timing.js";
|
|
6
7
|
import { playButtonScript, PLAY_BINDING, PLAY_BUTTON_ID, } from "./play-button.js";
|
|
@@ -64,6 +65,7 @@ export class Runtime {
|
|
|
64
65
|
async waitFor(page, target, options = {}) {
|
|
65
66
|
const { state = "visible", timeout = this.getCfg().visitTimeout } = options;
|
|
66
67
|
this.log("WaitFor", `${target} (${state})`);
|
|
68
|
+
await this.checkAmbiguous(page, target);
|
|
67
69
|
await page.waitForSelector(resolveTarget(target), {
|
|
68
70
|
timeout,
|
|
69
71
|
visible: state === "visible",
|
|
@@ -72,12 +74,50 @@ export class Runtime {
|
|
|
72
74
|
await this.injectCursor(page);
|
|
73
75
|
}
|
|
74
76
|
// ─── Interactions ──────────────────────────────────────────────────────────
|
|
77
|
+
/** Click a target. Returns the new tab's `Page` when `followNewTab` is set and the
|
|
78
|
+
* click opened one — the session then switches recording to it. Otherwise void. */
|
|
75
79
|
async click(page, target, options = {}) {
|
|
76
80
|
this.log("Click", target);
|
|
81
|
+
await this.checkAmbiguous(page, target);
|
|
82
|
+
if (options.followNewTab)
|
|
83
|
+
return this._clickNewTab(page, target, options);
|
|
77
84
|
await this._click(page, target, options);
|
|
78
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Click a link that opens a new tab and resolve to the new `Page` (or warn and
|
|
88
|
+
* return void if none appears). The popup listener is armed *before* the click so a
|
|
89
|
+
* fast open can't be missed. The new tab is *not* waited on here — the session seals
|
|
90
|
+
* the current segment first, then waits for the load off-camera so it's trimmed.
|
|
91
|
+
*/
|
|
92
|
+
async _clickNewTab(page, target, options) {
|
|
93
|
+
const timeout = options.timeout ?? this.getCfg().visitTimeout;
|
|
94
|
+
const popup = this._waitForPopup(page, timeout);
|
|
95
|
+
// The nav happens in the new tab, so don't wait for a same-tab navigation here.
|
|
96
|
+
await this._click(page, target, { ...options, waitForNav: false });
|
|
97
|
+
const newPage = await popup;
|
|
98
|
+
if (!newPage) {
|
|
99
|
+
this.log.warn(`click: no new tab opened for "${target}" — staying on the current tab`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
return newPage;
|
|
103
|
+
}
|
|
104
|
+
/** Resolve to the next popup `page.once("popup")` opens, or null after `timeout`.
|
|
105
|
+
* A late timer after the popup resolves is a harmless no-op; unref it so it can't
|
|
106
|
+
* hold the process open. */
|
|
107
|
+
_waitForPopup(page, timeout) {
|
|
108
|
+
return new Promise((resolve) => {
|
|
109
|
+
const onPopup = (p) => resolve(p ?? null);
|
|
110
|
+
page.once("popup", onPopup);
|
|
111
|
+
const timer = setTimeout(() => {
|
|
112
|
+
page.off("popup", onPopup);
|
|
113
|
+
resolve(null);
|
|
114
|
+
}, timeout);
|
|
115
|
+
timer.unref?.();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
79
118
|
async hover(page, target) {
|
|
80
119
|
this.log("Hover", target);
|
|
120
|
+
await this.checkAmbiguous(page, target);
|
|
81
121
|
if (this.getCfg().autoScroll)
|
|
82
122
|
await this._scrollIntoView(page, target);
|
|
83
123
|
const { x, y } = await getElementCenter(page, target);
|
|
@@ -85,6 +125,7 @@ export class Runtime {
|
|
|
85
125
|
}
|
|
86
126
|
async type(page, target, text, options = {}) {
|
|
87
127
|
this.log("Type", `${target} "${truncate(text)}"`);
|
|
128
|
+
await this.checkAmbiguous(page, target);
|
|
88
129
|
await this._click(page, target);
|
|
89
130
|
const total = options.duration ?? typingDuration(text, this.getCfg().typingSpeed);
|
|
90
131
|
const gaps = typingGaps(text, this.getCfg().typingSpeed, total);
|
|
@@ -99,6 +140,7 @@ export class Runtime {
|
|
|
99
140
|
}
|
|
100
141
|
async clear(page, target) {
|
|
101
142
|
this.log("Clear", target);
|
|
143
|
+
await this.checkAmbiguous(page, target);
|
|
102
144
|
await this._click(page, target);
|
|
103
145
|
const mod = process.platform === "darwin" ? "Meta" : "Control";
|
|
104
146
|
await page.keyboard.down(mod);
|
|
@@ -108,6 +150,7 @@ export class Runtime {
|
|
|
108
150
|
}
|
|
109
151
|
async select(page, target, value) {
|
|
110
152
|
this.log("Select", `${target} ${value}`);
|
|
153
|
+
await this.checkAmbiguous(page, target);
|
|
111
154
|
if (this.getCfg().autoScroll)
|
|
112
155
|
await this._scrollIntoView(page, target);
|
|
113
156
|
const { x, y } = await getElementCenter(page, target);
|
|
@@ -116,13 +159,38 @@ export class Runtime {
|
|
|
116
159
|
await sleep(jitter(PRE_CLICK_MS));
|
|
117
160
|
await this.cursor.clickEffect(page);
|
|
118
161
|
}
|
|
119
|
-
|
|
162
|
+
// Resolve the handle through getHandle (frame-aware) and act on it directly —
|
|
163
|
+
// page.select() / page.$eval() only see the main frame, so a <select> inside
|
|
164
|
+
// an iframe (e.g. a dialog) would miss it or hit a same-id placeholder.
|
|
165
|
+
const el = await getHandle(page, target);
|
|
166
|
+
await el.select(await this._optionValue(el, target, value));
|
|
167
|
+
}
|
|
168
|
+
// Map a select value-spec to the concrete option `value` Puppeteer expects.
|
|
169
|
+
// Literal values pass through; `:option-index/-label(...)` read the live
|
|
170
|
+
// `<select>` via the element's own frame.
|
|
171
|
+
async _optionValue(el, target, value) {
|
|
172
|
+
const spec = parseOptionSpec(value);
|
|
173
|
+
if (!spec)
|
|
174
|
+
return value;
|
|
175
|
+
const resolved = await el.evaluate((node, spec) => {
|
|
176
|
+
const opts = [...node.options];
|
|
177
|
+
const hit = "index" in spec
|
|
178
|
+
? opts[spec.index - 1]
|
|
179
|
+
: opts.find((o) => o.textContent?.trim() === spec.label);
|
|
180
|
+
return hit?.value;
|
|
181
|
+
}, spec);
|
|
182
|
+
if (resolved == null) {
|
|
183
|
+
throw new RecordableError("CONFIG_INVALID", `select("${target}", "${value}"): no matching <option>.`);
|
|
184
|
+
}
|
|
185
|
+
return resolved;
|
|
120
186
|
}
|
|
121
187
|
async key(page, key) {
|
|
122
188
|
this.log("Key", key);
|
|
123
189
|
await page.keyboard.press(key);
|
|
124
190
|
}
|
|
125
191
|
async mouse(page, target) {
|
|
192
|
+
if (typeof target === "string")
|
|
193
|
+
await this.checkAmbiguous(page, target);
|
|
126
194
|
const { x, y } = typeof target === "string"
|
|
127
195
|
? await getElementCenter(page, target)
|
|
128
196
|
: target;
|
|
@@ -132,7 +200,11 @@ export class Runtime {
|
|
|
132
200
|
// ─── Scrolling ─────────────────────────────────────────────────────────────
|
|
133
201
|
async scroll(page, target, options = {}) {
|
|
134
202
|
this.log("Scroll", String(target));
|
|
135
|
-
|
|
203
|
+
if (typeof target === "string" && target !== "top" && target !== "bottom")
|
|
204
|
+
await this.checkAmbiguous(page, target);
|
|
205
|
+
if (options.container)
|
|
206
|
+
await this.checkAmbiguous(page, options.container);
|
|
207
|
+
await smoothScrollToTarget(page, target, options.duration ?? this.getCfg().scrollDuration, options.container);
|
|
136
208
|
}
|
|
137
209
|
// ─── Zoom ──────────────────────────────────────────────────────────────────
|
|
138
210
|
async zoomTo(page, level, options = {}) {
|
|
@@ -217,6 +289,21 @@ export class Runtime {
|
|
|
217
289
|
const cfg = this.getCfg();
|
|
218
290
|
return scrollIntoView(page, target, cfg.scrollMargin, cfg.scrollSpeed);
|
|
219
291
|
}
|
|
292
|
+
/** Fragile-selector lint: warn (once) when a target resolves to more than one
|
|
293
|
+
* element, then act on the first. Best-effort — it does not wait for the
|
|
294
|
+
* element, so it stays silent if the matches haven't rendered yet, and never
|
|
295
|
+
* throws (a genuine miss surfaces later at getHandle). */
|
|
296
|
+
async checkAmbiguous(page, target) {
|
|
297
|
+
try {
|
|
298
|
+
const matches = await page.$$(resolveTarget(target));
|
|
299
|
+
if (matches.length > 1)
|
|
300
|
+
this.log.warn(`"${target}" matched ${matches.length} elements; using the first`);
|
|
301
|
+
await Promise.all(matches.map((m) => m.dispose()));
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
// ignore: resolution failures are reported where the element is fetched
|
|
305
|
+
}
|
|
306
|
+
}
|
|
220
307
|
// ─── Resume gate (▶ Play button) ─────────────────────────────────────────────
|
|
221
308
|
/**
|
|
222
309
|
* Block until the user resumes — by clicking the in-page ▶ Play button, or by
|