screenci 0.0.21 → 0.0.22
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/dist/cli.js +1 -1
- package/dist/e2e/instrument.e2e.js +98 -68
- package/dist/e2e/instrument.e2e.js.map +1 -1
- package/dist/src/autoZoom.d.ts +34 -15
- package/dist/src/autoZoom.d.ts.map +1 -1
- package/dist/src/autoZoom.js +100 -69
- package/dist/src/autoZoom.js.map +1 -1
- package/dist/src/changeFocus.d.ts +129 -0
- package/dist/src/changeFocus.d.ts.map +1 -0
- package/dist/src/changeFocus.js +885 -0
- package/dist/src/changeFocus.js.map +1 -0
- package/dist/src/defaults.d.ts +2 -17
- package/dist/src/defaults.d.ts.map +1 -1
- package/dist/src/defaults.js +8 -16
- package/dist/src/defaults.js.map +1 -1
- package/dist/src/easing.d.ts +6 -0
- package/dist/src/easing.d.ts.map +1 -0
- package/dist/src/easing.js +30 -0
- package/dist/src/easing.js.map +1 -0
- package/dist/src/errors.d.ts +10 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +26 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/events.d.ts +34 -12
- package/dist/src/events.d.ts.map +1 -1
- package/dist/src/events.js +111 -30
- package/dist/src/events.js.map +1 -1
- package/dist/src/instrument.d.ts +0 -6
- package/dist/src/instrument.d.ts.map +1 -1
- package/dist/src/instrument.js +382 -812
- package/dist/src/instrument.js.map +1 -1
- package/dist/src/mouse.d.ts +128 -0
- package/dist/src/mouse.d.ts.map +1 -0
- package/dist/src/mouse.js +268 -0
- package/dist/src/mouse.js.map +1 -0
- package/dist/src/recordingData.d.ts +29 -4
- package/dist/src/recordingData.d.ts.map +1 -1
- package/dist/src/types.d.ts +4 -7
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/zoom.d.ts +69 -0
- package/dist/src/zoom.d.ts.map +1 -0
- package/dist/src/zoom.js +90 -0
- package/dist/src/zoom.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/dist/src/scroll.d.ts +0 -36
- package/dist/src/scroll.d.ts.map +0 -1
- package/dist/src/scroll.js +0 -274
- package/dist/src/scroll.js.map +0 -1
package/dist/src/instrument.js
CHANGED
|
@@ -1,76 +1,14 @@
|
|
|
1
|
-
import { logger } from './logger.js';
|
|
2
|
-
import { isInsideAutoZoom, getZoomDuration, getZoomEasing, getPostZoomInOutDelay, getLastZoomLocation, setLastZoomLocation, } from './autoZoom.js';
|
|
3
1
|
import { isInsideHide } from './hide.js';
|
|
4
|
-
import {
|
|
2
|
+
import { changeFocus } from './changeFocus.js';
|
|
3
|
+
import { CLICK_DURATION_MS, assertDurationOrSpeed, buildMouseDownEvent, buildMouseUpEvent, getOriginalLocatorCheck, getOriginalLocatorClick, getOriginalLocatorSelect, getOriginalLocatorTap, getOriginalLocatorUncheck, getOriginalMouseDown, getOriginalMouseHide, getMousePosition, getOriginalMouseShow, getOriginalMouseUp, isMouseVisible, performMouseClickAction, performMouseDown, performMouseHide, performMouseMove, performMouseShow, performMouseUp, resolveMouseMoveDuration, setOriginalLocatorCheck, setOriginalLocatorClick, setOriginalLocatorSelect, setOriginalLocatorTap, setOriginalLocatorUncheck, setMouseVisible, setOriginalMouseClick, setOriginalMouseDown, setOriginalMouseHide, setOriginalMouseMove, setOriginalMouseShow, setOriginalMouseUp, } from './mouse.js';
|
|
5
4
|
let activeClickRecorder = null;
|
|
6
5
|
export function setActiveClickRecorder(recorder) {
|
|
7
6
|
activeClickRecorder = recorder;
|
|
8
7
|
}
|
|
9
|
-
/**
|
|
10
|
-
* Evaluate a polynomial easing function at normalized time t ∈ [0, 1].
|
|
11
|
-
* Returns the eased progress value (0–1).
|
|
12
|
-
*/
|
|
13
|
-
function evaluateEasingAtT(t, easing) {
|
|
14
|
-
if (t <= 0)
|
|
15
|
-
return 0;
|
|
16
|
-
if (t >= 1)
|
|
17
|
-
return 1;
|
|
18
|
-
switch (easing) {
|
|
19
|
-
case 'linear':
|
|
20
|
-
return t;
|
|
21
|
-
case 'ease-in':
|
|
22
|
-
return t * t * t;
|
|
23
|
-
case 'ease-out':
|
|
24
|
-
return 1 - (1 - t) * (1 - t) * (1 - t);
|
|
25
|
-
case 'ease-in-out':
|
|
26
|
-
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
27
|
-
case 'ease-in-strong':
|
|
28
|
-
return t * t * t * t;
|
|
29
|
-
case 'ease-out-strong':
|
|
30
|
-
return 1 - (1 - t) * (1 - t) * (1 - t) * (1 - t);
|
|
31
|
-
case 'ease-in-out-strong':
|
|
32
|
-
return t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
|
|
33
|
-
default: {
|
|
34
|
-
const _ = easing;
|
|
35
|
-
throw new Error(`Unknown easing: ${_}`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
/** Tracked cursor position per page, updated after each animated move. */
|
|
40
|
-
const mousePositions = new WeakMap();
|
|
41
|
-
/** Tracks cursor visibility per page (true = visible). Defaults to true. */
|
|
42
|
-
const mouseVisibilities = new WeakMap();
|
|
43
|
-
/** Stores the original (un-instrumented) mouse.move per page so internal
|
|
44
|
-
* cursor animations don't emit addInput recorder events. */
|
|
45
|
-
const originalMouseMoves = new WeakMap();
|
|
46
8
|
const instrumented = new WeakSet();
|
|
47
9
|
function sleep(ms) {
|
|
48
10
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
49
11
|
}
|
|
50
|
-
const CLICK_DURATION_MS = 200;
|
|
51
|
-
const PRE_ACTION_SLEEP = 50;
|
|
52
|
-
const POST_ACTION_SLEEP = 250;
|
|
53
|
-
function assertDurationOrSpeed(duration, speed, context) {
|
|
54
|
-
if (duration !== undefined && speed !== undefined) {
|
|
55
|
-
throw new Error(`[screenci] ${context} accepts either duration or speed, not both.`);
|
|
56
|
-
}
|
|
57
|
-
if (duration !== undefined && (!Number.isFinite(duration) || duration < 0)) {
|
|
58
|
-
throw new Error(`[screenci] ${context} duration must be a finite number >= 0.`);
|
|
59
|
-
}
|
|
60
|
-
if (speed !== undefined && (!Number.isFinite(speed) || speed <= 0)) {
|
|
61
|
-
throw new Error(`[screenci] ${context} speed must be a finite number > 0.`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
function resolveMouseMoveDuration(page, targetX, targetY, options) {
|
|
65
|
-
const { duration, speed, defaultDuration, context } = options;
|
|
66
|
-
assertDurationOrSpeed(duration, speed, context);
|
|
67
|
-
if (speed !== undefined) {
|
|
68
|
-
const startPos = mousePositions.get(page) ?? { x: 0, y: 0 };
|
|
69
|
-
const distancePx = Math.hypot(targetX - startPos.x, targetY - startPos.y);
|
|
70
|
-
return (distancePx / speed) * 1000;
|
|
71
|
-
}
|
|
72
|
-
return duration ?? defaultDuration ?? 0;
|
|
73
|
-
}
|
|
74
12
|
const LOCATOR_RETURN_METHODS = [
|
|
75
13
|
'locator',
|
|
76
14
|
'getByAltText',
|
|
@@ -107,294 +45,129 @@ const FRAME_LOCATOR_SELF_RETURN_METHODS = [
|
|
|
107
45
|
'last',
|
|
108
46
|
'nth',
|
|
109
47
|
];
|
|
110
|
-
function
|
|
111
|
-
if (
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
'modifiers',
|
|
116
|
-
'noWaitAfter',
|
|
117
|
-
'timeout',
|
|
118
|
-
'trial',
|
|
119
|
-
];
|
|
120
|
-
return unsupported.every((key) => options[key] === undefined);
|
|
121
|
-
}
|
|
122
|
-
// Per-page storage for the most recently captured DOM click event data.
|
|
123
|
-
// Reset to null before each instrumented click; set by the exposeFunction callback.
|
|
124
|
-
const pendingClickData = new WeakMap();
|
|
125
|
-
export function scrollIntoViewAsync(locator, options = {}) {
|
|
126
|
-
const { behavior = 'smooth', block = 'center', timeout = 5000, postScrollTimeout = 500, } = options;
|
|
127
|
-
return locator.evaluate((element, opts) => new Promise((resolve) => {
|
|
128
|
-
let settled = false;
|
|
129
|
-
const finish = () => {
|
|
130
|
-
if (settled)
|
|
131
|
-
return;
|
|
132
|
-
settled = true;
|
|
133
|
-
clearTimeout(fallback);
|
|
134
|
-
setTimeout(resolve, opts.postScrollTimeout);
|
|
135
|
-
};
|
|
136
|
-
const fallback = setTimeout(finish, opts.timeout);
|
|
137
|
-
// scrollend fires once the smooth-scroll animation completes (Chrome 114+).
|
|
138
|
-
// It captures scroll on any ancestor element via the capture phase.
|
|
139
|
-
window.addEventListener('scrollend', finish, {
|
|
140
|
-
capture: true,
|
|
141
|
-
once: true,
|
|
142
|
-
});
|
|
143
|
-
element.scrollIntoView({ behavior: opts.behavior, block: opts.block });
|
|
144
|
-
// If the element is already in the target position no scroll occurs and
|
|
145
|
-
// scrollend never fires. Detect this: if no scroll event appears within
|
|
146
|
-
// two animation frames the element was already in place.
|
|
147
|
-
let didScroll = false;
|
|
148
|
-
window.addEventListener('scroll', () => {
|
|
149
|
-
didScroll = true;
|
|
150
|
-
}, {
|
|
151
|
-
capture: true,
|
|
152
|
-
passive: true,
|
|
153
|
-
once: true,
|
|
154
|
-
});
|
|
155
|
-
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
156
|
-
if (!didScroll)
|
|
157
|
-
finish();
|
|
158
|
-
}));
|
|
159
|
-
}), { behavior, block, timeout, postScrollTimeout });
|
|
48
|
+
function getRecordedInnerEventEndMs(event) {
|
|
49
|
+
if (event.type === 'focusChange') {
|
|
50
|
+
return Math.max(...(event.mouse ? [event.mouse.endMs] : []), ...(event.scroll ? [event.scroll.endMs] : []), ...(event.zoom ? [event.zoom.endMs] : []));
|
|
51
|
+
}
|
|
52
|
+
return event.endMs;
|
|
160
53
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
54
|
+
function resolveLocatorMouseAction(locator, interactionType) {
|
|
55
|
+
switch (interactionType) {
|
|
56
|
+
case 'click': {
|
|
57
|
+
const action = getOriginalLocatorClick(locator);
|
|
58
|
+
if (action)
|
|
59
|
+
return { doClick: action, supportsTrial: true };
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case 'tap': {
|
|
63
|
+
const action = getOriginalLocatorTap(locator);
|
|
64
|
+
if (action)
|
|
65
|
+
return { doClick: action, supportsTrial: true };
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case 'check': {
|
|
69
|
+
const action = getOriginalLocatorCheck(locator);
|
|
70
|
+
if (action)
|
|
71
|
+
return { doClick: action, supportsTrial: true };
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case 'uncheck': {
|
|
75
|
+
const action = getOriginalLocatorUncheck(locator);
|
|
76
|
+
if (action)
|
|
77
|
+
return { doClick: action, supportsTrial: true };
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case 'select': {
|
|
81
|
+
const action = getOriginalLocatorSelect(locator);
|
|
82
|
+
if (action) {
|
|
83
|
+
return {
|
|
84
|
+
doClick: (options) => action(null, options).then(() => { }),
|
|
85
|
+
supportsTrial: false,
|
|
86
|
+
};
|
|
183
87
|
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
default: {
|
|
91
|
+
const _ = interactionType;
|
|
92
|
+
throw new Error(`Unknown mouse click interaction type: ${_}`);
|
|
184
93
|
}
|
|
185
94
|
}
|
|
186
|
-
|
|
187
|
-
await mouseMoveInternal(targetX, targetY);
|
|
188
|
-
}
|
|
189
|
-
mousePositions.set(page, { x: targetX, y: targetY });
|
|
190
|
-
const endMs = Date.now();
|
|
191
|
-
return {
|
|
192
|
-
type: 'focusChange',
|
|
193
|
-
startMs: eventStartMs,
|
|
194
|
-
endMs,
|
|
195
|
-
x: targetX,
|
|
196
|
-
y: targetY,
|
|
197
|
-
easing,
|
|
198
|
-
...(elementRect !== undefined ? { elementRect } : {}),
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
function resolveMoveStartMs(scrollResult, fallbackStartMs) {
|
|
202
|
-
return scrollResult.shouldScrollBeforeMouseMove
|
|
203
|
-
? scrollResult.scrollEndMs
|
|
204
|
-
: fallbackStartMs;
|
|
205
|
-
}
|
|
206
|
-
function resolveMoveDuration(scrollResult, resolvedDuration) {
|
|
207
|
-
return scrollResult.shouldScrollBeforeMouseMove
|
|
208
|
-
? resolvedDuration
|
|
209
|
-
: Math.max(0, resolvedDuration - scrollResult.scrollElapsedMs);
|
|
95
|
+
throw new Error(`[screenci] Missing original locator action for '${interactionType}'.`);
|
|
210
96
|
}
|
|
211
|
-
|
|
212
|
-
* Performs all click mechanics (scroll-check, zoom handling, cursor animation,
|
|
213
|
-
* click, post-click move) and returns the collected timing/position data.
|
|
214
|
-
* Returns null if coordinates could not be determined (no DOM event and no
|
|
215
|
-
* locator bounding box).
|
|
216
|
-
*/
|
|
217
|
-
async function performClickActions(locator, doClick, clickOptions, autoZoomOptions, position, moveDuration, moveSpeed, beforeClickPause = CLICK_DURATION_MS / 2, moveEasing = 'ease-in-out', postClickPause = CLICK_DURATION_MS / 2, postClickMove) {
|
|
97
|
+
async function performAction(mouseMoveRequest, locator, doClick, supportsTrial, mode, autoZoomOptions, position, beforeClickPause = 0, postClickPause = 0, postClickMove, shouldHideMouse = false) {
|
|
218
98
|
const page = locator.page();
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
const { locatorRect, scrollElapsedMs } = scrollResult;
|
|
225
|
-
const isFirstAutoZoomEvent = scrollResult.isFirstAutoZoomInteraction;
|
|
226
|
-
if (!locatorRect) {
|
|
227
|
-
logger.warn('[screenci] Unable to get locator bounding box; skipping auto-scroll check.');
|
|
228
|
-
}
|
|
229
|
-
const innerEvents = [];
|
|
230
|
-
// If inside autoZoom: optionally await zoom duration for camera pan then
|
|
231
|
-
// update the zoom location tracker.
|
|
232
|
-
if (isInsideAutoZoom() && locatorRect) {
|
|
233
|
-
const targetX = position
|
|
234
|
-
? locatorRect.x + position.x
|
|
235
|
-
: locatorRect.x + locatorRect.width / 2;
|
|
236
|
-
const targetY = position
|
|
237
|
-
? locatorRect.y + position.y
|
|
238
|
-
: locatorRect.y + locatorRect.height / 2;
|
|
239
|
-
const lastLoc = getLastZoomLocation();
|
|
240
|
-
if (lastLoc !== null) {
|
|
241
|
-
const zoomDur = getZoomDuration() ?? 0;
|
|
242
|
-
if (zoomDur > 0) {
|
|
243
|
-
await sleep(zoomDur);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
setLastZoomLocation({
|
|
247
|
-
x: targetX,
|
|
248
|
-
y: targetY,
|
|
249
|
-
elementRect: locatorRect,
|
|
250
|
-
eventType: 'click',
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
const targetPos = position
|
|
254
|
-
? position
|
|
255
|
-
: locatorRect
|
|
99
|
+
const focusChange = await changeFocus(locator, autoZoomOptions, mouseMoveRequest);
|
|
100
|
+
const elementRect = focusChange.elementRect;
|
|
101
|
+
const innerEvents = [focusChange];
|
|
102
|
+
const targetPosition = position ??
|
|
103
|
+
(elementRect
|
|
256
104
|
? {
|
|
257
|
-
x:
|
|
258
|
-
y:
|
|
259
|
-
}
|
|
260
|
-
: undefined;
|
|
261
|
-
if (targetPos && locatorRect) {
|
|
262
|
-
const targetX = locatorRect.x + targetPos.x;
|
|
263
|
-
const targetY = locatorRect.y + targetPos.y;
|
|
264
|
-
const resolvedDuration = resolveMouseMoveDuration(page, targetX, targetY, {
|
|
265
|
-
duration: moveDuration,
|
|
266
|
-
speed: moveSpeed,
|
|
267
|
-
defaultDuration: 1000,
|
|
268
|
-
context: 'click move',
|
|
269
|
-
});
|
|
270
|
-
if (scrollElapsedMs > 0 && !scrollResult.shouldScrollBeforeMouseMove) {
|
|
271
|
-
await mouseMoveInternal(targetX, targetY);
|
|
272
|
-
mousePositions.set(page, { x: targetX, y: targetY });
|
|
273
|
-
const remainingDuration = Math.max(0, resolvedDuration - scrollElapsedMs);
|
|
274
|
-
if (remainingDuration > 0) {
|
|
275
|
-
await new Promise((resolve) => setTimeout(resolve, remainingDuration));
|
|
105
|
+
x: elementRect.width / 2,
|
|
106
|
+
y: elementRect.height / 2,
|
|
276
107
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
startMs: moveStartTime,
|
|
281
|
-
endMs: moveEndTime,
|
|
282
|
-
x: targetX,
|
|
283
|
-
y: targetY,
|
|
284
|
-
easing: moveEasing,
|
|
285
|
-
focusOnly: false,
|
|
286
|
-
elementRect: locatorRect,
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
else {
|
|
290
|
-
innerEvents.push(await animateMouseMove(page, mouseMoveInternal, targetX, targetY, resolveMoveDuration(scrollResult, resolvedDuration), moveEasing, resolveMoveStartMs(scrollResult, moveStartTime), locatorRect));
|
|
291
|
-
}
|
|
108
|
+
: undefined);
|
|
109
|
+
if (!elementRect || !targetPosition) {
|
|
110
|
+
throw new Error('[screenci] performAction requires an element rect and target position.');
|
|
292
111
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const remainingMs = Math.max(0, moveSpeed === undefined ? (moveDuration ?? 1000) : 0);
|
|
296
|
-
if (remainingMs > 0) {
|
|
297
|
-
await new Promise((resolve) => setTimeout(resolve, remainingMs));
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
const zoomDur = isInsideAutoZoom() && locatorRect ? (getZoomDuration() ?? 0) : 0;
|
|
301
|
-
const effectiveBeforeClickPause = isFirstAutoZoomEvent
|
|
302
|
-
? Math.max(beforeClickPause, getPostZoomInOutDelay() ?? 0, zoomDur)
|
|
303
|
-
: Math.max(beforeClickPause, zoomDur);
|
|
304
|
-
await new Promise((resolve) => setTimeout(resolve, effectiveBeforeClickPause));
|
|
305
|
-
await new Promise((resolve) => setTimeout(resolve, halfClickDuration));
|
|
306
|
-
// Note click can take some time, but better to show it before than after
|
|
307
|
-
const clickTime = Date.now();
|
|
308
|
-
const mouseDownStart = clickTime - halfClickDuration;
|
|
309
|
-
innerEvents.push({
|
|
310
|
-
type: 'mouseDown',
|
|
311
|
-
startMs: mouseDownStart,
|
|
312
|
-
endMs: clickTime,
|
|
313
|
-
easing: 'ease-in-out',
|
|
314
|
-
});
|
|
315
|
-
if (scrollElapsedMs > 0 &&
|
|
316
|
-
targetPos &&
|
|
317
|
-
locatorRect &&
|
|
318
|
-
canUseDirectMouseClickAfterScroll(clickOptions)) {
|
|
319
|
-
const mouseClickOptions = {
|
|
320
|
-
...(clickOptions?.button !== undefined
|
|
321
|
-
? { button: clickOptions.button }
|
|
322
|
-
: {}),
|
|
323
|
-
...(clickOptions?.clickCount !== undefined
|
|
324
|
-
? { clickCount: clickOptions.clickCount }
|
|
325
|
-
: {}),
|
|
326
|
-
};
|
|
327
|
-
await page.mouse.down(mouseClickOptions);
|
|
328
|
-
if (clickOptions?.delay) {
|
|
329
|
-
await new Promise((resolve) => setTimeout(resolve, clickOptions.delay));
|
|
330
|
-
}
|
|
331
|
-
await page.mouse.up(mouseClickOptions);
|
|
332
|
-
}
|
|
333
|
-
else {
|
|
112
|
+
await sleep(beforeClickPause);
|
|
113
|
+
if (!mouseMoveRequest) {
|
|
334
114
|
await doClick({
|
|
335
|
-
...
|
|
336
|
-
...(
|
|
115
|
+
...(supportsTrial ? { trial: true } : {}),
|
|
116
|
+
...(mode === 'singleDuring' ? { position: targetPosition } : {}),
|
|
337
117
|
});
|
|
118
|
+
await sleep(postClickPause);
|
|
119
|
+
return {
|
|
120
|
+
elementRect,
|
|
121
|
+
innerEvents,
|
|
122
|
+
};
|
|
338
123
|
}
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
endMs: mouseUpEnd,
|
|
361
|
-
easing: 'ease-in-out',
|
|
362
|
-
});
|
|
363
|
-
await new Promise((resolve) => setTimeout(resolve, halfClickDuration));
|
|
364
|
-
await new Promise((resolve) => setTimeout(resolve, postClickPause));
|
|
365
|
-
// Animate mouse cursor in the specified direction after the click completes,
|
|
366
|
-
// capturing start/end times and final position for the recorded event.
|
|
124
|
+
const clickActionBase = {
|
|
125
|
+
locator,
|
|
126
|
+
doClick,
|
|
127
|
+
supportsTrial,
|
|
128
|
+
targetX: elementRect.x + targetPosition.x,
|
|
129
|
+
targetY: elementRect.y + targetPosition.y,
|
|
130
|
+
clickOptions: { position: targetPosition },
|
|
131
|
+
};
|
|
132
|
+
const clickActionOptions = mode === 'singleDuring'
|
|
133
|
+
? {
|
|
134
|
+
...clickActionBase,
|
|
135
|
+
mode,
|
|
136
|
+
}
|
|
137
|
+
: {
|
|
138
|
+
...clickActionBase,
|
|
139
|
+
mode,
|
|
140
|
+
shouldHideMouse,
|
|
141
|
+
};
|
|
142
|
+
const { events, elementRect: actionElementRect } = await performMouseClickAction(clickActionOptions);
|
|
143
|
+
innerEvents.push(...events);
|
|
144
|
+
await sleep(postClickPause);
|
|
367
145
|
if (postClickMove !== undefined) {
|
|
368
|
-
const currentPos =
|
|
146
|
+
const currentPos = getMousePosition(page) ?? { x: 0, y: 0 };
|
|
369
147
|
let targetX;
|
|
370
148
|
let targetY;
|
|
371
149
|
if ('direction' in postClickMove) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
break;
|
|
394
|
-
default: {
|
|
395
|
-
const _ = postClickMove.direction;
|
|
396
|
-
throw new Error(`Unknown postClickMove direction: ${_}`);
|
|
397
|
-
}
|
|
150
|
+
const padding = postClickMove.padding ?? 0;
|
|
151
|
+
switch (postClickMove.direction) {
|
|
152
|
+
case 'up':
|
|
153
|
+
targetX = currentPos.x;
|
|
154
|
+
targetY = elementRect.y - padding;
|
|
155
|
+
break;
|
|
156
|
+
case 'down':
|
|
157
|
+
targetX = currentPos.x;
|
|
158
|
+
targetY = elementRect.y + elementRect.height + padding;
|
|
159
|
+
break;
|
|
160
|
+
case 'left':
|
|
161
|
+
targetX = elementRect.x - padding;
|
|
162
|
+
targetY = currentPos.y;
|
|
163
|
+
break;
|
|
164
|
+
case 'right':
|
|
165
|
+
targetX = elementRect.x + elementRect.width + padding;
|
|
166
|
+
targetY = currentPos.y;
|
|
167
|
+
break;
|
|
168
|
+
default: {
|
|
169
|
+
const _ = postClickMove.direction;
|
|
170
|
+
throw new Error(`Unknown postClickMove direction: ${_}`);
|
|
398
171
|
}
|
|
399
172
|
}
|
|
400
173
|
}
|
|
@@ -410,154 +183,33 @@ async function performClickActions(locator, doClick, clickOptions, autoZoomOptio
|
|
|
410
183
|
defaultDuration: undefined,
|
|
411
184
|
context: 'postClickMove',
|
|
412
185
|
});
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const easedT = evaluateEasingAtT(t, easing);
|
|
420
|
-
const x = startPos.x + easedT * (targetX - startPos.x);
|
|
421
|
-
const y = startPos.y + easedT * (targetY - startPos.y);
|
|
422
|
-
await mouseMoveInternal(x, y);
|
|
423
|
-
if (i < steps) {
|
|
424
|
-
await new Promise((resolve) => setTimeout(resolve, stepMs));
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
const postClickMoveEndMs = Date.now();
|
|
428
|
-
innerEvents.push({
|
|
429
|
-
type: 'focusChange',
|
|
430
|
-
startMs: postClickMoveStartMs,
|
|
431
|
-
endMs: postClickMoveEndMs,
|
|
432
|
-
x: targetX,
|
|
433
|
-
y: targetY,
|
|
186
|
+
const startMs = Date.now();
|
|
187
|
+
await performMouseMove({
|
|
188
|
+
page,
|
|
189
|
+
targetX,
|
|
190
|
+
targetY,
|
|
191
|
+
duration,
|
|
434
192
|
easing,
|
|
435
193
|
});
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
let elementRect;
|
|
440
|
-
if (domClickData) {
|
|
441
|
-
elementRect = domClickData.targetRect;
|
|
442
|
-
}
|
|
443
|
-
else if (locatorRect) {
|
|
444
|
-
elementRect = locatorRect ?? undefined;
|
|
445
|
-
}
|
|
446
|
-
if (elementRect) {
|
|
447
|
-
return {
|
|
448
|
-
elementRect,
|
|
449
|
-
innerEvents,
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
else {
|
|
453
|
-
logger.warn('[screenci] Failed to capture click coordinates from both DOM event and locator bounding box.');
|
|
454
|
-
return null;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
async function prepareAutoZoomForLocator(locator, eventType, autoZoomOptions) {
|
|
458
|
-
const scrollHandler = new ZoomScrollHandler(autoZoomOptions);
|
|
459
|
-
const zoomDur = scrollHandler.isInsideAutoZoom ? (getZoomDuration() ?? 0) : 0;
|
|
460
|
-
if (scrollHandler.isInsideAutoZoom &&
|
|
461
|
-
scrollHandler.hadPreviousZoomLocation &&
|
|
462
|
-
zoomDur > 0) {
|
|
463
|
-
await sleep(zoomDur);
|
|
464
|
-
}
|
|
465
|
-
const { locatorRect, isFirstAutoZoomInteraction } = await scrollHandler.scroll(locator);
|
|
466
|
-
if (scrollHandler.isInsideAutoZoom && locatorRect) {
|
|
467
|
-
if (isFirstAutoZoomInteraction && zoomDur > 0) {
|
|
468
|
-
await sleep(zoomDur);
|
|
469
|
-
}
|
|
470
|
-
setLastZoomLocation({
|
|
471
|
-
x: locatorRect.x + locatorRect.width / 2,
|
|
472
|
-
y: locatorRect.y + locatorRect.height / 2,
|
|
473
|
-
elementRect: locatorRect,
|
|
474
|
-
eventType,
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
return { locatorRect, isFirstAutoZoomInteraction };
|
|
478
|
-
}
|
|
479
|
-
async function performSimpleAction(locator, doAction, options, subType, clickOpt, autoZoomOptions, position, recordMousePress = subType === 'tap') {
|
|
480
|
-
await sleep(PRE_ACTION_SLEEP);
|
|
481
|
-
let innerEvents = [];
|
|
482
|
-
let elementRect;
|
|
483
|
-
if (clickOpt !== undefined) {
|
|
484
|
-
const { moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove, } = clickOpt;
|
|
485
|
-
const clickActionResult = await performClickActions(locator, doAction, {}, autoZoomOptions, position, moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove);
|
|
486
|
-
innerEvents = clickActionResult?.innerEvents ?? [];
|
|
487
|
-
elementRect = clickActionResult?.elementRect;
|
|
488
|
-
}
|
|
489
|
-
else {
|
|
490
|
-
const locatorRect = await locator.boundingBox();
|
|
491
|
-
if (isInsideAutoZoom() && locatorRect) {
|
|
492
|
-
const focusStart = Date.now();
|
|
493
|
-
const zoomDur = getZoomDuration() ?? 0;
|
|
494
|
-
if (zoomDur > 0)
|
|
495
|
-
await sleep(zoomDur);
|
|
496
|
-
const focusEnd = Date.now();
|
|
194
|
+
const endMs = Date.now();
|
|
497
195
|
innerEvents.push({
|
|
498
196
|
type: 'focusChange',
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
const targetPosition = locatorRect
|
|
509
|
-
? {
|
|
510
|
-
x: locatorRect.width / 2,
|
|
511
|
-
y: locatorRect.height / 2,
|
|
512
|
-
}
|
|
513
|
-
: undefined;
|
|
514
|
-
const startTime = Date.now();
|
|
515
|
-
await doAction({
|
|
516
|
-
...options,
|
|
517
|
-
...(targetPosition ? { position: targetPosition } : {}),
|
|
518
|
-
});
|
|
519
|
-
const endTime = Date.now();
|
|
520
|
-
elementRect = locatorRect ?? undefined;
|
|
521
|
-
if (recordMousePress) {
|
|
522
|
-
const midTime = (startTime + endTime) / 2;
|
|
523
|
-
innerEvents.push({
|
|
524
|
-
type: 'mouseDown',
|
|
525
|
-
startMs: startTime,
|
|
526
|
-
endMs: midTime,
|
|
527
|
-
});
|
|
528
|
-
innerEvents.push({
|
|
529
|
-
type: 'mouseUp',
|
|
530
|
-
startMs: midTime,
|
|
531
|
-
endMs: endTime,
|
|
197
|
+
x: targetX,
|
|
198
|
+
y: targetY,
|
|
199
|
+
startMs,
|
|
200
|
+
endMs,
|
|
201
|
+
mouse: {
|
|
202
|
+
startMs,
|
|
203
|
+
endMs,
|
|
204
|
+
...(duration > 0 ? { easing } : {}),
|
|
205
|
+
},
|
|
532
206
|
});
|
|
533
207
|
}
|
|
534
208
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
type: 'mouseWait',
|
|
540
|
-
startMs: simpleWaitStart,
|
|
541
|
-
endMs: simpleWaitEnd,
|
|
542
|
-
});
|
|
543
|
-
if (activeClickRecorder && innerEvents.length > 0) {
|
|
544
|
-
activeClickRecorder.addInput(subType, elementRect, innerEvents);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
async function recordedClick(locator, doClick, clickOptions, autoZoomOptions, position, moveDuration, moveSpeed, beforeClickPause = CLICK_DURATION_MS / 2, moveEasing = 'ease-in-out', postClickPause = CLICK_DURATION_MS / 2, postClickMove) {
|
|
548
|
-
await sleep(PRE_ACTION_SLEEP);
|
|
549
|
-
const result = await performClickActions(locator, doClick, clickOptions, autoZoomOptions, position, moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove);
|
|
550
|
-
const clickWaitStart = Date.now();
|
|
551
|
-
await sleep(POST_ACTION_SLEEP);
|
|
552
|
-
const clickWaitEnd = Date.now();
|
|
553
|
-
if (activeClickRecorder && result) {
|
|
554
|
-
result.innerEvents.push({
|
|
555
|
-
type: 'mouseWait',
|
|
556
|
-
startMs: clickWaitStart,
|
|
557
|
-
endMs: clickWaitEnd,
|
|
558
|
-
});
|
|
559
|
-
activeClickRecorder.addInput('click', result.elementRect, result.innerEvents);
|
|
560
|
-
}
|
|
209
|
+
return {
|
|
210
|
+
elementRect: actionElementRect ?? elementRect,
|
|
211
|
+
innerEvents,
|
|
212
|
+
};
|
|
561
213
|
}
|
|
562
214
|
function instrumentLocatorMethods(obj) {
|
|
563
215
|
for (const method of LOCATOR_RETURN_METHODS) {
|
|
@@ -585,6 +237,7 @@ export function instrumentLocator(locator) {
|
|
|
585
237
|
return locator;
|
|
586
238
|
instrumented.add(locator);
|
|
587
239
|
const originalClick = locator.click.bind(locator);
|
|
240
|
+
setOriginalLocatorClick(locator, originalClick);
|
|
588
241
|
locator.click = async (options) => {
|
|
589
242
|
const { moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove, autoZoomOptions, position, steps: _steps, ...clickOptions } = options ?? {};
|
|
590
243
|
if (isInsideHide()) {
|
|
@@ -594,66 +247,36 @@ export function instrumentLocator(locator) {
|
|
|
594
247
|
});
|
|
595
248
|
}
|
|
596
249
|
assertDurationOrSpeed(moveDuration, moveSpeed, 'click move');
|
|
597
|
-
|
|
250
|
+
const { doClick, supportsTrial } = resolveLocatorMouseAction(locator, 'click');
|
|
251
|
+
const result = await performAction({
|
|
252
|
+
targetPosInElement: position ?? { x: 0, y: 0 },
|
|
253
|
+
...(moveDuration !== undefined ? { duration: moveDuration } : {}),
|
|
254
|
+
...(moveSpeed !== undefined ? { speed: moveSpeed } : {}),
|
|
255
|
+
easing: moveEasing ?? 'ease-in-out',
|
|
256
|
+
}, locator, doClick, supportsTrial, 'singleDuring', autoZoomOptions, position, beforeClickPause, postClickPause, postClickMove, false);
|
|
257
|
+
if (activeClickRecorder && result) {
|
|
258
|
+
activeClickRecorder.addInput('click', undefined, result.innerEvents);
|
|
259
|
+
}
|
|
598
260
|
};
|
|
599
261
|
const originalPressSequentially = locator.pressSequentially.bind(locator);
|
|
600
262
|
locator.pressSequentially = async (text, options) => {
|
|
263
|
+
const clickOpt = options?.click ?? {};
|
|
601
264
|
const { click: _click, autoZoomOptions, hideMouse: _hideMouse, position: _position, ...pressOptions } = options ?? {};
|
|
602
265
|
if (isInsideHide()) {
|
|
603
266
|
return originalPressSequentially(text, pressOptions);
|
|
604
267
|
}
|
|
605
|
-
await sleep(PRE_ACTION_SLEEP);
|
|
606
268
|
const innerEvents = [];
|
|
607
|
-
let elementRect;
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
else {
|
|
618
|
-
const { locatorRect, isFirstAutoZoomInteraction } = await prepareAutoZoomForLocator(locator, 'fill', options?.autoZoomOptions);
|
|
619
|
-
if (isFirstAutoZoomInteraction) {
|
|
620
|
-
const postDelay = getPostZoomInOutDelay() ?? 0;
|
|
621
|
-
if (postDelay > 0)
|
|
622
|
-
await sleep(postDelay);
|
|
623
|
-
}
|
|
624
|
-
elementRect = locatorRect;
|
|
625
|
-
if (isInsideAutoZoom() && locatorRect) {
|
|
626
|
-
const zoomDur = getZoomDuration() ?? 0;
|
|
627
|
-
if (zoomDur > 0) {
|
|
628
|
-
await sleep(zoomDur);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
// Hide cursor while typing (will be shown again on next mouse move)
|
|
633
|
-
const page = locator.page();
|
|
634
|
-
const shouldHideMouse = options?.hideMouse === true;
|
|
635
|
-
if (shouldHideMouse) {
|
|
636
|
-
const cursorVisible = mouseVisibilities.get(page) ?? true;
|
|
637
|
-
if (cursorVisible) {
|
|
638
|
-
mouseVisibilities.set(page, false);
|
|
639
|
-
const hideMs = Date.now();
|
|
640
|
-
innerEvents.push({
|
|
641
|
-
type: 'mouseHide',
|
|
642
|
-
startMs: hideMs,
|
|
643
|
-
endMs: hideMs,
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
await originalPressSequentially(text, pressOptions);
|
|
648
|
-
const pressWaitStart = Date.now();
|
|
649
|
-
await sleep(POST_ACTION_SLEEP);
|
|
650
|
-
const pressWaitEnd = Date.now();
|
|
269
|
+
let elementRect = undefined;
|
|
270
|
+
const { moveDuration, moveSpeed, beforeClickPause, moveEasing = 'ease-in-out', postClickPause, postClickMove, } = clickOpt;
|
|
271
|
+
const clickActionResult = await performAction({
|
|
272
|
+
targetPosInElement: options?.position ?? { x: 0, y: 0 },
|
|
273
|
+
...(moveDuration !== undefined ? { duration: moveDuration } : {}),
|
|
274
|
+
...(moveSpeed !== undefined ? { speed: moveSpeed } : {}),
|
|
275
|
+
easing: moveEasing,
|
|
276
|
+
}, locator, async () => originalPressSequentially(text, pressOptions), false, 'singleBefore', autoZoomOptions, options?.position, beforeClickPause ?? CLICK_DURATION_MS / 2, postClickPause ?? CLICK_DURATION_MS / 2, postClickMove, true);
|
|
277
|
+
innerEvents.push(...(clickActionResult?.innerEvents ?? []));
|
|
278
|
+
elementRect = clickActionResult?.elementRect;
|
|
651
279
|
if (activeClickRecorder) {
|
|
652
|
-
innerEvents.push({
|
|
653
|
-
type: 'mouseWait',
|
|
654
|
-
startMs: pressWaitStart,
|
|
655
|
-
endMs: pressWaitEnd,
|
|
656
|
-
});
|
|
657
280
|
activeClickRecorder.addInput('pressSequentially', elementRect, innerEvents);
|
|
658
281
|
}
|
|
659
282
|
};
|
|
@@ -663,80 +286,16 @@ export function instrumentLocator(locator) {
|
|
|
663
286
|
const { duration: _duration, click: _click, position: _position, hideMouse: _hideMouse, autoZoomOptions: _autoZoomOptions, ...fillOptions } = options ?? {};
|
|
664
287
|
return originalFill(value, fillOptions);
|
|
665
288
|
}
|
|
666
|
-
|
|
667
|
-
await sleep(PRE_ACTION_SLEEP);
|
|
668
|
-
const clickActionResult = await performClickActions(locator, (clickOptions) => originalClick(clickOptions), {}, options.autoZoomOptions, options.position, options.click.moveDuration, options.click.moveSpeed, options.click.beforeClickPause, options.click.moveEasing, options.click.postClickPause, options.click.postClickMove);
|
|
669
|
-
const innerEvents = [...(clickActionResult?.innerEvents ?? [])];
|
|
670
|
-
const elementRect = clickActionResult?.elementRect;
|
|
671
|
-
const page = locator.page();
|
|
672
|
-
if (options.hideMouse === true) {
|
|
673
|
-
const cursorVisible = mouseVisibilities.get(page) ?? true;
|
|
674
|
-
if (cursorVisible) {
|
|
675
|
-
mouseVisibilities.set(page, false);
|
|
676
|
-
const hideMs = Date.now();
|
|
677
|
-
innerEvents.push({
|
|
678
|
-
type: 'mouseHide',
|
|
679
|
-
startMs: hideMs,
|
|
680
|
-
endMs: hideMs,
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
await locator.evaluate((element) => {
|
|
685
|
-
if (element instanceof HTMLInputElement ||
|
|
686
|
-
element instanceof HTMLTextAreaElement) {
|
|
687
|
-
element.focus();
|
|
688
|
-
element.select();
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
if (element instanceof HTMLElement && element.isContentEditable) {
|
|
692
|
-
element.focus();
|
|
693
|
-
const selection = element.ownerDocument.getSelection();
|
|
694
|
-
if (!selection)
|
|
695
|
-
return;
|
|
696
|
-
const range = element.ownerDocument.createRange();
|
|
697
|
-
range.selectNodeContents(element);
|
|
698
|
-
selection.removeAllRanges();
|
|
699
|
-
selection.addRange(range);
|
|
700
|
-
}
|
|
701
|
-
});
|
|
702
|
-
const duration = options.duration ?? 1000;
|
|
703
|
-
const delay = value.length > 0 ? duration / value.length : 0;
|
|
704
|
-
await page.keyboard.type(value, { delay });
|
|
705
|
-
const fillWaitStart = Date.now();
|
|
706
|
-
await sleep(POST_ACTION_SLEEP);
|
|
707
|
-
const fillWaitEnd = Date.now();
|
|
708
|
-
innerEvents.push({
|
|
709
|
-
type: 'mouseWait',
|
|
710
|
-
startMs: fillWaitStart,
|
|
711
|
-
endMs: fillWaitEnd,
|
|
712
|
-
});
|
|
713
|
-
if (activeClickRecorder) {
|
|
714
|
-
activeClickRecorder.addInput('pressSequentially', elementRect, innerEvents);
|
|
715
|
-
}
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
await sleep(PRE_ACTION_SLEEP);
|
|
719
|
-
const page = locator.page();
|
|
289
|
+
const clickOpt = options?.click ?? {};
|
|
720
290
|
const innerEvents = [];
|
|
721
|
-
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
innerEvents.push({
|
|
730
|
-
type: 'mouseHide',
|
|
731
|
-
startMs: hideMs,
|
|
732
|
-
endMs: hideMs,
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
if (isInsideAutoZoom() && locatorRect) {
|
|
737
|
-
const correctedScrollResult = await new ZoomScrollHandler(options?.autoZoomOptions).scroll(locator);
|
|
738
|
-
const correctedRect = correctedScrollResult.locatorRect ?? locatorRect;
|
|
739
|
-
const focusStart = Date.now();
|
|
291
|
+
let elementRect = undefined;
|
|
292
|
+
const { moveDuration, moveSpeed, beforeClickPause, moveEasing = 'ease-in-out', postClickPause, postClickMove, } = clickOpt;
|
|
293
|
+
const clickActionResult = await performAction({
|
|
294
|
+
targetPosInElement: options?.position ?? { x: 0, y: 0 },
|
|
295
|
+
...(moveDuration !== undefined ? { duration: moveDuration } : {}),
|
|
296
|
+
...(moveSpeed !== undefined ? { speed: moveSpeed } : {}),
|
|
297
|
+
easing: moveEasing,
|
|
298
|
+
}, locator, async () => {
|
|
740
299
|
await locator.evaluate((element) => {
|
|
741
300
|
if (element instanceof HTMLInputElement ||
|
|
742
301
|
element instanceof HTMLTextAreaElement) {
|
|
@@ -755,63 +314,43 @@ export function instrumentLocator(locator) {
|
|
|
755
314
|
selection.addRange(range);
|
|
756
315
|
}
|
|
757
316
|
});
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
easing: getZoomEasing() ?? 'ease-in-out',
|
|
766
|
-
focusOnly: true,
|
|
767
|
-
elementRect: correctedRect,
|
|
768
|
-
});
|
|
769
|
-
elementRect = correctedRect;
|
|
770
|
-
}
|
|
771
|
-
else {
|
|
772
|
-
await locator.evaluate((element) => {
|
|
773
|
-
if (element instanceof HTMLInputElement ||
|
|
774
|
-
element instanceof HTMLTextAreaElement) {
|
|
775
|
-
element.focus();
|
|
776
|
-
element.select();
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
|
-
if (element instanceof HTMLElement && element.isContentEditable) {
|
|
780
|
-
element.focus();
|
|
781
|
-
const selection = element.ownerDocument.getSelection();
|
|
782
|
-
if (!selection)
|
|
783
|
-
return;
|
|
784
|
-
const range = element.ownerDocument.createRange();
|
|
785
|
-
range.selectNodeContents(element);
|
|
786
|
-
selection.removeAllRanges();
|
|
787
|
-
selection.addRange(range);
|
|
788
|
-
}
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
const duration = fillOptions.duration ?? 1000;
|
|
792
|
-
const delay = value.length > 0 ? duration / value.length : 0;
|
|
793
|
-
await page.keyboard.type(value, { delay });
|
|
794
|
-
const fillWaitStart = Date.now();
|
|
795
|
-
await sleep(POST_ACTION_SLEEP);
|
|
796
|
-
const fillWaitEnd = Date.now();
|
|
797
|
-
innerEvents.push({
|
|
798
|
-
type: 'mouseWait',
|
|
799
|
-
startMs: fillWaitStart,
|
|
800
|
-
endMs: fillWaitEnd,
|
|
801
|
-
});
|
|
802
|
-
if (activeClickRecorder &&
|
|
803
|
-
(isInsideAutoZoom() || fillOptions.hideMouse === true)) {
|
|
317
|
+
const duration = options?.duration ?? 1000;
|
|
318
|
+
const delay = value.length > 0 ? duration / value.length : 0;
|
|
319
|
+
await locator.page().keyboard.type(value, { delay });
|
|
320
|
+
}, false, 'singleBefore', options?.autoZoomOptions, options?.position, beforeClickPause ?? CLICK_DURATION_MS / 2, postClickPause ?? CLICK_DURATION_MS / 2, postClickMove, true);
|
|
321
|
+
innerEvents.push(...(clickActionResult?.innerEvents ?? []));
|
|
322
|
+
elementRect = clickActionResult?.elementRect;
|
|
323
|
+
if (activeClickRecorder) {
|
|
804
324
|
activeClickRecorder.addInput('pressSequentially', elementRect, innerEvents);
|
|
805
325
|
}
|
|
806
|
-
return;
|
|
807
326
|
};
|
|
808
327
|
const originalTap = locator.tap.bind(locator);
|
|
328
|
+
setOriginalLocatorTap(locator, originalTap);
|
|
809
329
|
locator.tap = async (options) => {
|
|
810
330
|
const clickOpt = options?.click;
|
|
811
331
|
const { click: _click, position, autoZoomOptions, ...tapOpts } = options ?? {};
|
|
812
|
-
|
|
332
|
+
if (isInsideHide()) {
|
|
333
|
+
return originalTap(tapOpts);
|
|
334
|
+
}
|
|
335
|
+
const { doClick, supportsTrial } = resolveLocatorMouseAction(locator, 'tap');
|
|
336
|
+
const result = await performAction(clickOpt
|
|
337
|
+
? {
|
|
338
|
+
targetPosInElement: position ?? { x: 0, y: 0 },
|
|
339
|
+
...(clickOpt.moveDuration !== undefined
|
|
340
|
+
? { duration: clickOpt.moveDuration }
|
|
341
|
+
: {}),
|
|
342
|
+
...(clickOpt.moveSpeed !== undefined
|
|
343
|
+
? { speed: clickOpt.moveSpeed }
|
|
344
|
+
: {}),
|
|
345
|
+
easing: clickOpt.moveEasing ?? 'ease-in-out',
|
|
346
|
+
}
|
|
347
|
+
: undefined, locator, doClick, supportsTrial, 'singleDuring', autoZoomOptions, position, clickOpt?.beforeClickPause, clickOpt?.postClickPause, clickOpt?.postClickMove, false);
|
|
348
|
+
if (activeClickRecorder && result) {
|
|
349
|
+
activeClickRecorder.addInput('tap', result.elementRect, result.innerEvents);
|
|
350
|
+
}
|
|
813
351
|
};
|
|
814
352
|
const originalCheck = locator.check.bind(locator);
|
|
353
|
+
setOriginalLocatorCheck(locator, originalCheck);
|
|
815
354
|
locator.check = async (options) => {
|
|
816
355
|
const clickOpt = options?.click;
|
|
817
356
|
const position = options?.position;
|
|
@@ -819,9 +358,25 @@ export function instrumentLocator(locator) {
|
|
|
819
358
|
if (isInsideHide()) {
|
|
820
359
|
return originalCheck(checkOpts);
|
|
821
360
|
}
|
|
822
|
-
|
|
361
|
+
const { doClick, supportsTrial } = resolveLocatorMouseAction(locator, 'check');
|
|
362
|
+
const result = await performAction(clickOpt
|
|
363
|
+
? {
|
|
364
|
+
targetPosInElement: position ?? { x: 0, y: 0 },
|
|
365
|
+
...(clickOpt.moveDuration !== undefined
|
|
366
|
+
? { duration: clickOpt.moveDuration }
|
|
367
|
+
: {}),
|
|
368
|
+
...(clickOpt.moveSpeed !== undefined
|
|
369
|
+
? { speed: clickOpt.moveSpeed }
|
|
370
|
+
: {}),
|
|
371
|
+
easing: clickOpt.moveEasing ?? 'ease-in-out',
|
|
372
|
+
}
|
|
373
|
+
: undefined, locator, doClick, supportsTrial, 'singleDuring', autoZoomOptions, position, clickOpt?.beforeClickPause, clickOpt?.postClickPause, clickOpt?.postClickMove, false);
|
|
374
|
+
if (activeClickRecorder && result) {
|
|
375
|
+
activeClickRecorder.addInput('check', result.elementRect, result.innerEvents);
|
|
376
|
+
}
|
|
823
377
|
};
|
|
824
378
|
const originalUncheck = locator.uncheck.bind(locator);
|
|
379
|
+
setOriginalLocatorUncheck(locator, originalUncheck);
|
|
825
380
|
locator.uncheck = async (options) => {
|
|
826
381
|
const clickOpt = options?.click;
|
|
827
382
|
const position = options?.position;
|
|
@@ -829,7 +384,22 @@ export function instrumentLocator(locator) {
|
|
|
829
384
|
if (isInsideHide()) {
|
|
830
385
|
return originalUncheck(uncheckOpts);
|
|
831
386
|
}
|
|
832
|
-
|
|
387
|
+
const { doClick, supportsTrial } = resolveLocatorMouseAction(locator, 'uncheck');
|
|
388
|
+
const result = await performAction(clickOpt
|
|
389
|
+
? {
|
|
390
|
+
targetPosInElement: position ?? { x: 0, y: 0 },
|
|
391
|
+
...(clickOpt.moveDuration !== undefined
|
|
392
|
+
? { duration: clickOpt.moveDuration }
|
|
393
|
+
: {}),
|
|
394
|
+
...(clickOpt.moveSpeed !== undefined
|
|
395
|
+
? { speed: clickOpt.moveSpeed }
|
|
396
|
+
: {}),
|
|
397
|
+
easing: clickOpt.moveEasing ?? 'ease-in-out',
|
|
398
|
+
}
|
|
399
|
+
: undefined, locator, doClick, supportsTrial, 'singleDuring', autoZoomOptions, position, clickOpt?.beforeClickPause, clickOpt?.postClickPause, clickOpt?.postClickMove, false);
|
|
400
|
+
if (activeClickRecorder && result) {
|
|
401
|
+
activeClickRecorder.addInput('uncheck', result.elementRect, result.innerEvents);
|
|
402
|
+
}
|
|
833
403
|
};
|
|
834
404
|
locator.setChecked = async (checked, options) => {
|
|
835
405
|
if (checked) {
|
|
@@ -840,47 +410,61 @@ export function instrumentLocator(locator) {
|
|
|
840
410
|
}
|
|
841
411
|
};
|
|
842
412
|
const originalSelectOption = locator.selectOption.bind(locator);
|
|
413
|
+
let currentSelectValues = null;
|
|
414
|
+
let currentSelectOptions;
|
|
415
|
+
let currentSelectResult = [];
|
|
416
|
+
setOriginalLocatorSelect(locator, (_values, actionOptions) => originalSelectOption(currentSelectValues, {
|
|
417
|
+
...currentSelectOptions,
|
|
418
|
+
...actionOptions,
|
|
419
|
+
}).then((res) => {
|
|
420
|
+
currentSelectResult = res;
|
|
421
|
+
return res;
|
|
422
|
+
}));
|
|
843
423
|
locator.selectOption = async (values, options) => {
|
|
844
424
|
const clickOpt = options?.click;
|
|
845
425
|
const { click: _click, position, autoZoomOptions, ...selectOpts } = options ?? {};
|
|
846
426
|
if (isInsideHide()) {
|
|
847
427
|
return originalSelectOption(values, selectOpts);
|
|
848
428
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
429
|
+
currentSelectValues = values;
|
|
430
|
+
currentSelectOptions = selectOpts;
|
|
431
|
+
currentSelectResult = [];
|
|
432
|
+
const { doClick, supportsTrial } = resolveLocatorMouseAction(locator, 'select');
|
|
433
|
+
const actionResult = await performAction(clickOpt
|
|
434
|
+
? {
|
|
435
|
+
targetPosInElement: position ?? { x: 0, y: 0 },
|
|
436
|
+
...(clickOpt.moveDuration !== undefined
|
|
437
|
+
? { duration: clickOpt.moveDuration }
|
|
438
|
+
: {}),
|
|
439
|
+
...(clickOpt.moveSpeed !== undefined
|
|
440
|
+
? { speed: clickOpt.moveSpeed }
|
|
441
|
+
: {}),
|
|
442
|
+
easing: clickOpt.moveEasing ?? 'ease-in-out',
|
|
443
|
+
}
|
|
444
|
+
: undefined, locator, doClick, supportsTrial, 'singleDuring', autoZoomOptions, position, clickOpt?.beforeClickPause, clickOpt?.postClickPause, clickOpt?.postClickMove);
|
|
445
|
+
if (activeClickRecorder && actionResult) {
|
|
446
|
+
activeClickRecorder.addInput('select', actionResult.elementRect, actionResult.innerEvents);
|
|
447
|
+
}
|
|
448
|
+
return currentSelectResult;
|
|
854
449
|
};
|
|
855
450
|
const originalHover = locator.hover.bind(locator);
|
|
856
451
|
locator.hover = async (options) => {
|
|
857
452
|
const { moveDuration, moveSpeed, easing: moveEasing = 'ease-in-out', hoverDuration = 1000, position, ...hoverOptions } = options ?? {};
|
|
858
453
|
assertDurationOrSpeed(moveDuration, moveSpeed, 'hover move');
|
|
859
|
-
const page = locator.page();
|
|
860
|
-
const mouseMoveInternal = originalMouseMoves.get(page) ?? page.mouse.move.bind(page.mouse);
|
|
861
|
-
const moveStartTime = Date.now();
|
|
862
|
-
const scrollResult = await new ZoomScrollHandler().scroll(locator);
|
|
863
|
-
const { locatorRect } = scrollResult;
|
|
864
454
|
const innerEvents = [];
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
speed: moveSpeed,
|
|
875
|
-
defaultDuration: 1000,
|
|
876
|
-
context: 'hover move',
|
|
877
|
-
});
|
|
878
|
-
innerEvents.push(await animateMouseMove(page, mouseMoveInternal, targetX, targetY, resolveMoveDuration(scrollResult, resolvedDuration), moveEasing, resolveMoveStartMs(scrollResult, moveStartTime), locatorRect));
|
|
879
|
-
}
|
|
455
|
+
const mouseMovePlan = {
|
|
456
|
+
targetPosInElement: position,
|
|
457
|
+
...(moveDuration !== undefined ? { duration: moveDuration } : {}),
|
|
458
|
+
...(moveSpeed !== undefined ? { speed: moveSpeed } : {}),
|
|
459
|
+
easing: moveEasing,
|
|
460
|
+
};
|
|
461
|
+
const hoverFocusChange = await changeFocus(locator, options?.autoZoomOptions, mouseMovePlan);
|
|
462
|
+
const locatorRect = hoverFocusChange.elementRect;
|
|
463
|
+
innerEvents.push(hoverFocusChange);
|
|
880
464
|
const waitStartMs = Date.now();
|
|
881
465
|
await originalHover({
|
|
882
466
|
...hoverOptions,
|
|
883
|
-
...(
|
|
467
|
+
...(position ? { position } : {}),
|
|
884
468
|
});
|
|
885
469
|
if (hoverDuration > 0) {
|
|
886
470
|
await sleep(hoverDuration);
|
|
@@ -895,55 +479,35 @@ export function instrumentLocator(locator) {
|
|
|
895
479
|
activeClickRecorder.addInput('hover', locatorRect, innerEvents);
|
|
896
480
|
}
|
|
897
481
|
};
|
|
482
|
+
const originalScrollIntoViewIfNeeded = locator.scrollIntoViewIfNeeded.bind(locator);
|
|
483
|
+
locator.scrollIntoViewIfNeeded = async (options) => {
|
|
484
|
+
if (isInsideHide()) {
|
|
485
|
+
return originalScrollIntoViewIfNeeded(options);
|
|
486
|
+
}
|
|
487
|
+
const { easing = 'ease-in-out', duration, amount, centering, } = options ?? {};
|
|
488
|
+
await changeFocus(locator, {
|
|
489
|
+
easing,
|
|
490
|
+
...(duration !== undefined ? { duration } : {}),
|
|
491
|
+
...(amount !== undefined ? { amount } : {}),
|
|
492
|
+
...(centering !== undefined ? { centering } : {}),
|
|
493
|
+
});
|
|
494
|
+
};
|
|
898
495
|
const originalSelectText = locator.selectText.bind(locator);
|
|
899
496
|
locator.selectText = async (options) => {
|
|
900
|
-
const { moveDuration, moveSpeed, easing: moveEasing = 'ease-in-out', beforeClickPause = CLICK_DURATION_MS / 2,
|
|
497
|
+
const { moveDuration, moveSpeed, easing: moveEasing = 'ease-in-out', beforeClickPause = CLICK_DURATION_MS / 2, ...selectOpts } = options ?? {};
|
|
901
498
|
assertDurationOrSpeed(moveDuration, moveSpeed, 'selectText move');
|
|
902
499
|
const page = locator.page();
|
|
903
|
-
const mouseMoveInternal = originalMouseMoves.get(page) ?? page.mouse.move.bind(page.mouse);
|
|
904
|
-
const moveStartTime = Date.now();
|
|
905
|
-
const scrollResult = await new ZoomScrollHandler().scroll(locator);
|
|
906
|
-
const { locatorRect } = scrollResult;
|
|
907
500
|
const innerEvents = [];
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
:
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
context: 'selectText move',
|
|
919
|
-
});
|
|
920
|
-
innerEvents.push(await animateMouseMove(page, mouseMoveInternal, targetX, targetY, resolveMoveDuration(scrollResult, resolvedDuration), moveEasing, resolveMoveStartMs(scrollResult, moveStartTime), locatorRect));
|
|
921
|
-
}
|
|
922
|
-
await sleep(beforeClickPause);
|
|
923
|
-
await originalSelectText(selectOpts);
|
|
924
|
-
// Backtrack triple-click events from the moment originalSelectText resolves.
|
|
925
|
-
// Clamp start so events don't precede the prior animation (produces a visible
|
|
926
|
-
// pre-click pause in the recording, which is acceptable).
|
|
927
|
-
// All timestamps use a single base + integer * segmentMs to avoid FP drift.
|
|
928
|
-
const selectEndMs = Date.now();
|
|
929
|
-
const lastEventEndMs = innerEvents.at(-1)?.endMs ?? 0;
|
|
930
|
-
const tripleClickStartMs = Math.max(lastEventEndMs, selectEndMs - selectDuration);
|
|
931
|
-
const segmentMs = selectDuration / 6;
|
|
932
|
-
for (let i = 0; i < 3; i++) {
|
|
933
|
-
const seg = i * 2;
|
|
934
|
-
innerEvents.push({
|
|
935
|
-
type: 'mouseDown',
|
|
936
|
-
startMs: tripleClickStartMs + seg * segmentMs,
|
|
937
|
-
endMs: tripleClickStartMs + (seg + 1) * segmentMs,
|
|
938
|
-
easing: 'ease-in-out',
|
|
939
|
-
});
|
|
940
|
-
innerEvents.push({
|
|
941
|
-
type: 'mouseUp',
|
|
942
|
-
startMs: tripleClickStartMs + (seg + 1) * segmentMs,
|
|
943
|
-
endMs: tripleClickStartMs + (seg + 2) * segmentMs,
|
|
944
|
-
easing: 'ease-in-out',
|
|
945
|
-
});
|
|
946
|
-
}
|
|
501
|
+
const selectActionResult = await performAction({
|
|
502
|
+
targetPosInElement: { x: 0, y: 0 },
|
|
503
|
+
...(moveDuration !== undefined ? { duration: moveDuration } : {}),
|
|
504
|
+
...(moveSpeed !== undefined ? { speed: moveSpeed } : {}),
|
|
505
|
+
easing: moveEasing,
|
|
506
|
+
}, locator, async () => {
|
|
507
|
+
await originalSelectText(selectOpts);
|
|
508
|
+
}, false, 'tripleBefore', undefined, undefined, beforeClickPause, undefined, undefined, false);
|
|
509
|
+
const locatorRect = selectActionResult?.elementRect;
|
|
510
|
+
innerEvents.push(...(selectActionResult?.innerEvents ?? []));
|
|
947
511
|
if (activeClickRecorder && innerEvents.length > 0) {
|
|
948
512
|
activeClickRecorder.addInput('selectText', locatorRect, innerEvents);
|
|
949
513
|
}
|
|
@@ -952,12 +516,8 @@ export function instrumentLocator(locator) {
|
|
|
952
516
|
const { moveDuration, moveSpeed, moveEasing = 'ease-in-out', preDragPause = CLICK_DURATION_MS / 2, dragDuration, dragSpeed, dragEasing = 'ease-in-out', sourcePosition, targetPosition, } = options ?? {};
|
|
953
517
|
assertDurationOrSpeed(moveDuration, moveSpeed, 'dragTo move');
|
|
954
518
|
assertDurationOrSpeed(dragDuration, dragSpeed, 'dragTo drag');
|
|
955
|
-
await sleep(PRE_ACTION_SLEEP);
|
|
956
519
|
const page = locator.page();
|
|
957
|
-
const
|
|
958
|
-
const moveStartTime = Date.now();
|
|
959
|
-
const scrollResult = await new ZoomScrollHandler().scroll(locator);
|
|
960
|
-
const { locatorRect: sourceRect } = scrollResult;
|
|
520
|
+
const sourceRectPreview = await locator.boundingBox();
|
|
961
521
|
const targetBb = await target.boundingBox();
|
|
962
522
|
const targetRect = targetBb
|
|
963
523
|
? {
|
|
@@ -968,37 +528,31 @@ export function instrumentLocator(locator) {
|
|
|
968
528
|
}
|
|
969
529
|
: undefined;
|
|
970
530
|
const innerEvents = [];
|
|
971
|
-
const sourcePos = sourcePosition ??
|
|
972
|
-
(sourceRect
|
|
973
|
-
? { x: sourceRect.width / 2, y: sourceRect.height / 2 }
|
|
974
|
-
: undefined);
|
|
975
531
|
const targetPos = targetPosition ??
|
|
976
532
|
(targetRect
|
|
977
533
|
? { x: targetRect.width / 2, y: targetRect.height / 2 }
|
|
978
534
|
: undefined);
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
context: 'dragTo move',
|
|
988
|
-
});
|
|
989
|
-
innerEvents.push(await animateMouseMove(page, mouseMoveInternal, toX, toY, resolveMoveDuration(scrollResult, resolvedDuration), moveEasing, resolveMoveStartMs(scrollResult, moveStartTime), sourceRect));
|
|
535
|
+
const sourceFocusChange = await changeFocus(locator, undefined, {
|
|
536
|
+
targetPosInElement: sourcePosition,
|
|
537
|
+
...(moveDuration !== undefined ? { duration: moveDuration } : {}),
|
|
538
|
+
...(moveSpeed !== undefined ? { speed: moveSpeed } : {}),
|
|
539
|
+
easing: moveEasing,
|
|
540
|
+
});
|
|
541
|
+
if (sourceFocusChange.elementRect) {
|
|
542
|
+
innerEvents.push(sourceFocusChange);
|
|
990
543
|
}
|
|
991
544
|
// 2. preDragPause + mouseDown
|
|
992
545
|
await sleep(preDragPause);
|
|
993
546
|
const mouseDownStart = Date.now();
|
|
994
|
-
await
|
|
547
|
+
await performMouseDown({
|
|
548
|
+
mouseDownInternal: getOriginalMouseDown(page, page.mouse.down.bind(page.mouse)),
|
|
549
|
+
});
|
|
995
550
|
await sleep(CLICK_DURATION_MS / 2);
|
|
996
|
-
innerEvents.push({
|
|
997
|
-
type: 'mouseDown',
|
|
551
|
+
innerEvents.push(buildMouseDownEvent({
|
|
998
552
|
startMs: mouseDownStart,
|
|
999
553
|
endMs: Date.now(),
|
|
1000
554
|
easing: 'ease-in-out',
|
|
1001
|
-
});
|
|
555
|
+
}));
|
|
1002
556
|
// 3. Drag: animate cursor from source to target
|
|
1003
557
|
const dragStartTime = Date.now();
|
|
1004
558
|
if (targetPos && targetRect) {
|
|
@@ -1010,28 +564,36 @@ export function instrumentLocator(locator) {
|
|
|
1010
564
|
defaultDuration: 1000,
|
|
1011
565
|
context: 'dragTo drag',
|
|
1012
566
|
});
|
|
1013
|
-
|
|
567
|
+
await performMouseMove({
|
|
568
|
+
page,
|
|
569
|
+
targetX: toX,
|
|
570
|
+
targetY: toY,
|
|
571
|
+
duration: resolvedDuration,
|
|
572
|
+
easing: dragEasing,
|
|
573
|
+
});
|
|
574
|
+
innerEvents.push({
|
|
575
|
+
type: 'mouseMove',
|
|
576
|
+
startMs: dragStartTime,
|
|
577
|
+
endMs: Date.now(),
|
|
578
|
+
x: toX,
|
|
579
|
+
y: toY,
|
|
580
|
+
...(resolvedDuration > 0 ? { easing: dragEasing } : {}),
|
|
581
|
+
elementRect: targetRect,
|
|
582
|
+
});
|
|
1014
583
|
}
|
|
1015
584
|
// 4. mouseUp at target
|
|
1016
585
|
const mouseUpStart = Date.now();
|
|
1017
|
-
await
|
|
586
|
+
await performMouseUp({
|
|
587
|
+
mouseUpInternal: getOriginalMouseUp(page, page.mouse.up.bind(page.mouse)),
|
|
588
|
+
});
|
|
1018
589
|
await sleep(CLICK_DURATION_MS / 2);
|
|
1019
|
-
innerEvents.push({
|
|
1020
|
-
type: 'mouseUp',
|
|
590
|
+
innerEvents.push(buildMouseUpEvent({
|
|
1021
591
|
startMs: mouseUpStart,
|
|
1022
592
|
endMs: Date.now(),
|
|
1023
593
|
easing: 'ease-in-out',
|
|
1024
|
-
});
|
|
1025
|
-
const dragWaitStart = Date.now();
|
|
1026
|
-
await sleep(POST_ACTION_SLEEP);
|
|
1027
|
-
const dragWaitEnd = Date.now();
|
|
1028
|
-
innerEvents.push({
|
|
1029
|
-
type: 'mouseWait',
|
|
1030
|
-
startMs: dragWaitStart,
|
|
1031
|
-
endMs: dragWaitEnd,
|
|
1032
|
-
});
|
|
594
|
+
}));
|
|
1033
595
|
if (activeClickRecorder && innerEvents.length > 0) {
|
|
1034
|
-
activeClickRecorder.addInput('dragTo',
|
|
596
|
+
activeClickRecorder.addInput('dragTo', sourceFocusChange.elementRect, innerEvents);
|
|
1035
597
|
}
|
|
1036
598
|
};
|
|
1037
599
|
const originalPage = locator.page.bind(locator);
|
|
@@ -1057,23 +619,6 @@ export async function instrumentPage(page) {
|
|
|
1057
619
|
if (instrumented.has(page))
|
|
1058
620
|
return page;
|
|
1059
621
|
instrumented.add(page);
|
|
1060
|
-
// Expose a Node.js function to the browser that captures DOM click event data.
|
|
1061
|
-
// Called synchronously from the click handler before any navigation can occur.
|
|
1062
|
-
await page.exposeFunction('__screenciOnClick', (data) => {
|
|
1063
|
-
pendingClickData.set(page, data);
|
|
1064
|
-
});
|
|
1065
|
-
// Inject a capture listener on every page load (including after navigation).
|
|
1066
|
-
await page.addInitScript(() => {
|
|
1067
|
-
document.addEventListener('click', (e) => {
|
|
1068
|
-
const target = e.target;
|
|
1069
|
-
const r = target.getBoundingClientRect();
|
|
1070
|
-
window.__screenciOnClick({
|
|
1071
|
-
x: e.clientX,
|
|
1072
|
-
y: e.clientY,
|
|
1073
|
-
targetRect: { x: r.x, y: r.y, width: r.width, height: r.height },
|
|
1074
|
-
});
|
|
1075
|
-
}, { capture: true });
|
|
1076
|
-
});
|
|
1077
622
|
instrumentLocatorMethods(page);
|
|
1078
623
|
const originalPageFrameLocator = page.frameLocator.bind(page);
|
|
1079
624
|
page.frameLocator = (...args) => instrumentFrameLocator(originalPageFrameLocator(...args));
|
|
@@ -1085,7 +630,35 @@ export async function instrumentPage(page) {
|
|
|
1085
630
|
// Instrument page.mouse to record mouse moves and visibility toggles.
|
|
1086
631
|
const originalMouse = page.mouse;
|
|
1087
632
|
const originalMove = originalMouse.move.bind(originalMouse);
|
|
1088
|
-
|
|
633
|
+
const originalDown = originalMouse.down.bind(originalMouse);
|
|
634
|
+
const originalUp = originalMouse.up.bind(originalMouse);
|
|
635
|
+
const originalClickMethod = originalMouse.click;
|
|
636
|
+
const originalClick = typeof originalClickMethod === 'function'
|
|
637
|
+
? originalClickMethod.bind(originalMouse)
|
|
638
|
+
: async (x, y, options) => {
|
|
639
|
+
await originalMove(x, y);
|
|
640
|
+
await originalDown(options);
|
|
641
|
+
if (options?.delay) {
|
|
642
|
+
await sleep(options.delay);
|
|
643
|
+
}
|
|
644
|
+
await originalUp(options);
|
|
645
|
+
};
|
|
646
|
+
const originalShowMethod = originalMouse
|
|
647
|
+
.show;
|
|
648
|
+
const originalHideMethod = originalMouse
|
|
649
|
+
.hide;
|
|
650
|
+
const originalShow = typeof originalShowMethod === 'function'
|
|
651
|
+
? originalShowMethod.bind(originalMouse)
|
|
652
|
+
: () => { };
|
|
653
|
+
const originalHide = typeof originalHideMethod === 'function'
|
|
654
|
+
? originalHideMethod.bind(originalMouse)
|
|
655
|
+
: () => { };
|
|
656
|
+
setOriginalMouseMove(page, originalMove);
|
|
657
|
+
setOriginalMouseClick(page, originalClick);
|
|
658
|
+
setOriginalMouseDown(page, originalDown);
|
|
659
|
+
setOriginalMouseUp(page, originalUp);
|
|
660
|
+
setOriginalMouseShow(page, originalShow);
|
|
661
|
+
setOriginalMouseHide(page, originalHide);
|
|
1089
662
|
originalMouse.move = async (x, y, options) => {
|
|
1090
663
|
const duration = resolveMouseMoveDuration(page, x, y, {
|
|
1091
664
|
duration: options?.duration,
|
|
@@ -1095,30 +668,29 @@ export async function instrumentPage(page) {
|
|
|
1095
668
|
});
|
|
1096
669
|
const easing = options?.easing ?? 'ease-in-out';
|
|
1097
670
|
const startMs = Date.now();
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
const endMs = Date.now();
|
|
671
|
+
await performMouseMove({
|
|
672
|
+
page,
|
|
673
|
+
targetX: x,
|
|
674
|
+
targetY: y,
|
|
675
|
+
duration,
|
|
676
|
+
easing,
|
|
677
|
+
});
|
|
678
|
+
const moveEvent = {
|
|
679
|
+
type: 'focusChange',
|
|
680
|
+
startMs,
|
|
681
|
+
endMs: Date.now(),
|
|
682
|
+
x,
|
|
683
|
+
y,
|
|
684
|
+
mouse: {
|
|
685
|
+
startMs,
|
|
686
|
+
endMs: Date.now(),
|
|
687
|
+
...(duration > 0 ? { easing } : {}),
|
|
688
|
+
},
|
|
689
|
+
};
|
|
1118
690
|
if (activeClickRecorder) {
|
|
1119
691
|
// Auto-show cursor when moving after a typing auto-hide
|
|
1120
|
-
if (!(
|
|
1121
|
-
|
|
692
|
+
if (!isMouseVisible(page)) {
|
|
693
|
+
setMouseVisible(page, true);
|
|
1122
694
|
const showMs = startMs;
|
|
1123
695
|
const showEvent = {
|
|
1124
696
|
type: 'mouseShow',
|
|
@@ -1127,21 +699,16 @@ export async function instrumentPage(page) {
|
|
|
1127
699
|
};
|
|
1128
700
|
activeClickRecorder.addInput('mouseShow', undefined, [showEvent]);
|
|
1129
701
|
}
|
|
1130
|
-
const moveEvent = {
|
|
1131
|
-
type: 'focusChange',
|
|
1132
|
-
startMs,
|
|
1133
|
-
endMs,
|
|
1134
|
-
x,
|
|
1135
|
-
y,
|
|
1136
|
-
...(duration > 0 ? { easing } : {}),
|
|
1137
|
-
};
|
|
1138
702
|
activeClickRecorder.addInput('focusChange', undefined, [moveEvent]);
|
|
1139
703
|
}
|
|
1140
704
|
};
|
|
1141
|
-
|
|
705
|
+
setMouseVisible(page, true);
|
|
1142
706
|
originalMouse.show = () => {
|
|
1143
|
-
if (!(
|
|
1144
|
-
|
|
707
|
+
if (!isMouseVisible(page)) {
|
|
708
|
+
performMouseShow({
|
|
709
|
+
mouseShowInternal: getOriginalMouseShow(page, originalShow),
|
|
710
|
+
page,
|
|
711
|
+
});
|
|
1145
712
|
if (activeClickRecorder) {
|
|
1146
713
|
const timeMs = Date.now();
|
|
1147
714
|
const showEvent = {
|
|
@@ -1154,8 +721,11 @@ export async function instrumentPage(page) {
|
|
|
1154
721
|
}
|
|
1155
722
|
};
|
|
1156
723
|
originalMouse.hide = () => {
|
|
1157
|
-
if (
|
|
1158
|
-
|
|
724
|
+
if (isMouseVisible(page)) {
|
|
725
|
+
performMouseHide({
|
|
726
|
+
mouseHideInternal: getOriginalMouseHide(page, originalHide),
|
|
727
|
+
page,
|
|
728
|
+
});
|
|
1159
729
|
if (activeClickRecorder) {
|
|
1160
730
|
const timeMs = Date.now();
|
|
1161
731
|
const hideEvent = {
|