screenci 0.0.18 → 0.0.20
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.d.ts +2 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -15
- package/dist/cli.js.map +1 -1
- package/dist/e2e/instrument.e2e.js +21 -2
- package/dist/e2e/instrument.e2e.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/src/autoZoom.d.ts +5 -1
- package/dist/src/autoZoom.d.ts.map +1 -1
- package/dist/src/autoZoom.js +30 -1
- package/dist/src/autoZoom.js.map +1 -1
- package/dist/src/cue.d.ts +0 -63
- package/dist/src/cue.d.ts.map +1 -1
- package/dist/src/cue.js +0 -84
- package/dist/src/cue.js.map +1 -1
- package/dist/src/events.d.ts +19 -8
- package/dist/src/events.d.ts.map +1 -1
- package/dist/src/events.js +3 -13
- package/dist/src/events.js.map +1 -1
- package/dist/src/instrument.d.ts.map +1 -1
- package/dist/src/instrument.js +305 -131
- package/dist/src/instrument.js.map +1 -1
- package/dist/src/recordingData.d.ts +14 -7
- package/dist/src/recordingData.d.ts.map +1 -1
- package/dist/src/scroll.d.ts +47 -0
- package/dist/src/scroll.d.ts.map +1 -0
- package/dist/src/scroll.js +347 -0
- package/dist/src/scroll.js.map +1 -0
- package/dist/src/types.d.ts +11 -16
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +1 -1
- package/dist/src/types.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/dist/src/instrument.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { logger } from './logger.js';
|
|
2
|
-
import { isInsideAutoZoom, getZoomDuration, getPostZoomInOutDelay, getLastZoomLocation, setLastZoomLocation, } from './autoZoom.js';
|
|
2
|
+
import { isInsideAutoZoom, getZoomDuration, getZoomEasing, getPostZoomInOutDelay, getLastZoomLocation, setLastZoomLocation, } from './autoZoom.js';
|
|
3
3
|
import { isInsideHide } from './hide.js';
|
|
4
|
+
import { ZoomScrollHandler } from './scroll.js';
|
|
4
5
|
let activeClickRecorder = null;
|
|
5
6
|
export function setActiveClickRecorder(recorder) {
|
|
6
7
|
activeClickRecorder = recorder;
|
|
@@ -106,6 +107,18 @@ const FRAME_LOCATOR_SELF_RETURN_METHODS = [
|
|
|
106
107
|
'last',
|
|
107
108
|
'nth',
|
|
108
109
|
];
|
|
110
|
+
function canUseDirectMouseClickAfterScroll(options) {
|
|
111
|
+
if (!options)
|
|
112
|
+
return true;
|
|
113
|
+
const unsupported = [
|
|
114
|
+
'force',
|
|
115
|
+
'modifiers',
|
|
116
|
+
'noWaitAfter',
|
|
117
|
+
'timeout',
|
|
118
|
+
'trial',
|
|
119
|
+
];
|
|
120
|
+
return unsupported.every((key) => options[key] === undefined);
|
|
121
|
+
}
|
|
109
122
|
// Per-page storage for the most recently captured DOM click event data.
|
|
110
123
|
// Reset to null before each instrumented click; set by the exposeFunction callback.
|
|
111
124
|
const pendingClickData = new WeakMap();
|
|
@@ -145,43 +158,10 @@ export function scrollIntoViewAsync(locator, options = {}) {
|
|
|
145
158
|
}));
|
|
146
159
|
}), { behavior, block, timeout, postScrollTimeout });
|
|
147
160
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Scrolls the locator into view only if it is at least partially outside the
|
|
150
|
-
* viewport, then returns the (possibly updated) bounding box as an ElementRect.
|
|
151
|
-
* Returns undefined if the bounding box cannot be determined.
|
|
152
|
-
*/
|
|
153
|
-
async function scrollIntoViewIfNeeded(locator, block = 'center') {
|
|
154
|
-
const page = locator.page();
|
|
155
|
-
const bb = await locator.boundingBox();
|
|
156
|
-
if (!bb)
|
|
157
|
-
return undefined;
|
|
158
|
-
const viewportSize = page.viewportSize();
|
|
159
|
-
if (viewportSize) {
|
|
160
|
-
const isFullyInViewport = bb.x >= 0 &&
|
|
161
|
-
bb.y >= 0 &&
|
|
162
|
-
bb.x + bb.width <= viewportSize.width &&
|
|
163
|
-
bb.y + bb.height <= viewportSize.height;
|
|
164
|
-
if (!isFullyInViewport) {
|
|
165
|
-
await scrollIntoViewAsync(locator, { block });
|
|
166
|
-
const newBb = await locator.boundingBox();
|
|
167
|
-
if (newBb)
|
|
168
|
-
return {
|
|
169
|
-
x: newBb.x,
|
|
170
|
-
y: newBb.y,
|
|
171
|
-
width: newBb.width,
|
|
172
|
-
height: newBb.height,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
logger.warn('[screenci] Unable to determine viewport size; skipping auto-scroll check.');
|
|
178
|
-
}
|
|
179
|
-
return { x: bb.x, y: bb.y, width: bb.width, height: bb.height };
|
|
180
|
-
}
|
|
181
161
|
const CURSOR_STEP_MS = 1000 / 60;
|
|
182
162
|
/**
|
|
183
163
|
* Physically moves the mouse from its current tracked position to (targetX,
|
|
184
|
-
* targetY) over `duration` ms using `easing`, then returns a
|
|
164
|
+
* targetY) over `duration` ms using `easing`, then returns a FocusChangeEvent
|
|
185
165
|
* whose startMs is `eventStartMs` (which may predate this call, e.g. when a
|
|
186
166
|
* scroll consumed part of moveDuration). When duration ≤ 0 the cursor is
|
|
187
167
|
* snapped directly to the target with a single move call.
|
|
@@ -209,32 +189,40 @@ async function animateMouseMove(page, mouseMoveInternal, targetX, targetY, durat
|
|
|
209
189
|
mousePositions.set(page, { x: targetX, y: targetY });
|
|
210
190
|
const endMs = Date.now();
|
|
211
191
|
return {
|
|
212
|
-
type: '
|
|
192
|
+
type: 'focusChange',
|
|
213
193
|
startMs: eventStartMs,
|
|
214
194
|
endMs,
|
|
215
|
-
duration: Math.max(0, endMs - eventStartMs),
|
|
216
195
|
x: targetX,
|
|
217
196
|
y: targetY,
|
|
218
197
|
easing,
|
|
219
198
|
...(elementRect !== undefined ? { elementRect } : {}),
|
|
220
199
|
};
|
|
221
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);
|
|
210
|
+
}
|
|
222
211
|
/**
|
|
223
212
|
* Performs all click mechanics (scroll-check, zoom handling, cursor animation,
|
|
224
213
|
* click, post-click move) and returns the collected timing/position data.
|
|
225
214
|
* Returns null if coordinates could not be determined (no DOM event and no
|
|
226
215
|
* locator bounding box).
|
|
227
216
|
*/
|
|
228
|
-
async function performClickActions(locator, doClick, clickOptions, position, moveDuration, moveSpeed, beforeClickPause = CLICK_DURATION_MS / 2, moveEasing = 'ease-in-out', postClickPause = CLICK_DURATION_MS / 2, postClickMove) {
|
|
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) {
|
|
229
218
|
const page = locator.page();
|
|
230
219
|
pendingClickData.set(page, null);
|
|
231
220
|
const halfClickDuration = CLICK_DURATION_MS / 2;
|
|
232
221
|
const mouseMoveInternal = originalMouseMoves.get(page) ?? page.mouse.move.bind(page.mouse);
|
|
233
|
-
// Capture before any setLastZoomLocation call changes the state.
|
|
234
|
-
const isFirstAutoZoomEvent = isInsideAutoZoom() && getLastZoomLocation() === null;
|
|
235
222
|
const moveStartTime = Date.now();
|
|
236
|
-
const
|
|
237
|
-
const scrollElapsedMs =
|
|
223
|
+
const scrollResult = await new ZoomScrollHandler(autoZoomOptions).scroll(locator);
|
|
224
|
+
const { locatorRect, scrollElapsedMs } = scrollResult;
|
|
225
|
+
const isFirstAutoZoomEvent = scrollResult.isFirstAutoZoomInteraction;
|
|
238
226
|
if (!locatorRect) {
|
|
239
227
|
logger.warn('[screenci] Unable to get locator bounding box; skipping auto-scroll check.');
|
|
240
228
|
}
|
|
@@ -270,11 +258,6 @@ async function performClickActions(locator, doClick, clickOptions, position, mov
|
|
|
270
258
|
y: locatorRect.height / 2,
|
|
271
259
|
}
|
|
272
260
|
: undefined;
|
|
273
|
-
// No physical mouse movement happens during the scroll. moveStartTime is
|
|
274
|
-
// captured before the scroll so the recorded event spans scroll-start →
|
|
275
|
-
// animation-end, making the cursor appear to move during the scroll in the
|
|
276
|
-
// video. After the scroll, the remaining duration drives the physical easing
|
|
277
|
-
// animation via the shared helper.
|
|
278
261
|
if (targetPos && locatorRect) {
|
|
279
262
|
const targetX = locatorRect.x + targetPos.x;
|
|
280
263
|
const targetY = locatorRect.y + targetPos.y;
|
|
@@ -284,19 +267,40 @@ async function performClickActions(locator, doClick, clickOptions, position, mov
|
|
|
284
267
|
defaultDuration: 1000,
|
|
285
268
|
context: 'click move',
|
|
286
269
|
});
|
|
287
|
-
|
|
288
|
-
|
|
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));
|
|
276
|
+
}
|
|
277
|
+
const moveEndTime = Date.now();
|
|
278
|
+
innerEvents.push({
|
|
279
|
+
type: 'focusChange',
|
|
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
|
+
}
|
|
289
292
|
}
|
|
290
293
|
else {
|
|
291
294
|
assertDurationOrSpeed(moveDuration, moveSpeed, 'click move');
|
|
292
|
-
const remainingMs = Math.max(0,
|
|
295
|
+
const remainingMs = Math.max(0, moveSpeed === undefined ? (moveDuration ?? 1000) : 0);
|
|
293
296
|
if (remainingMs > 0) {
|
|
294
297
|
await new Promise((resolve) => setTimeout(resolve, remainingMs));
|
|
295
298
|
}
|
|
296
299
|
}
|
|
300
|
+
const zoomDur = isInsideAutoZoom() && locatorRect ? (getZoomDuration() ?? 0) : 0;
|
|
297
301
|
const effectiveBeforeClickPause = isFirstAutoZoomEvent
|
|
298
|
-
? Math.max(beforeClickPause, getPostZoomInOutDelay() ?? 0)
|
|
299
|
-
: beforeClickPause;
|
|
302
|
+
? Math.max(beforeClickPause, getPostZoomInOutDelay() ?? 0, zoomDur)
|
|
303
|
+
: Math.max(beforeClickPause, zoomDur);
|
|
300
304
|
await new Promise((resolve) => setTimeout(resolve, effectiveBeforeClickPause));
|
|
301
305
|
await new Promise((resolve) => setTimeout(resolve, halfClickDuration));
|
|
302
306
|
// Note click can take some time, but better to show it before than after
|
|
@@ -308,11 +312,47 @@ async function performClickActions(locator, doClick, clickOptions, position, mov
|
|
|
308
312
|
endMs: clickTime,
|
|
309
313
|
easing: 'ease-in-out',
|
|
310
314
|
});
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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 {
|
|
334
|
+
await doClick({
|
|
335
|
+
...clickOptions,
|
|
336
|
+
...(targetPos ? { position: targetPos } : {}),
|
|
337
|
+
});
|
|
338
|
+
}
|
|
315
339
|
const domClickData = pendingClickData.get(page);
|
|
340
|
+
if (domClickData) {
|
|
341
|
+
const lastMouseMoveIndex = innerEvents.findIndex((e) => e.type === 'focusChange' || e.type === 'mouseMove');
|
|
342
|
+
if (lastMouseMoveIndex !== -1) {
|
|
343
|
+
const existingMove = innerEvents[lastMouseMoveIndex];
|
|
344
|
+
if (existingMove?.type === 'focusChange' ||
|
|
345
|
+
existingMove?.type === 'mouseMove') {
|
|
346
|
+
innerEvents[lastMouseMoveIndex] = {
|
|
347
|
+
...existingMove,
|
|
348
|
+
x: domClickData.x,
|
|
349
|
+
y: domClickData.y,
|
|
350
|
+
elementRect: domClickData.targetRect,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
mousePositions.set(page, { x: domClickData.x, y: domClickData.y });
|
|
355
|
+
}
|
|
316
356
|
const mouseUpEnd = Date.now() + halfClickDuration;
|
|
317
357
|
innerEvents.push({
|
|
318
358
|
type: 'mouseUp',
|
|
@@ -386,26 +426,23 @@ async function performClickActions(locator, doClick, clickOptions, position, mov
|
|
|
386
426
|
}
|
|
387
427
|
const postClickMoveEndMs = Date.now();
|
|
388
428
|
innerEvents.push({
|
|
389
|
-
type: '
|
|
429
|
+
type: 'focusChange',
|
|
390
430
|
startMs: postClickMoveStartMs,
|
|
391
431
|
endMs: postClickMoveEndMs,
|
|
392
|
-
duration: Math.max(0, postClickMoveEndMs - postClickMoveStartMs),
|
|
393
432
|
x: targetX,
|
|
394
433
|
y: targetY,
|
|
395
434
|
easing,
|
|
396
|
-
zoomFollow: false,
|
|
397
435
|
});
|
|
398
436
|
mousePositions.set(page, { x: targetX, y: targetY });
|
|
399
437
|
}
|
|
400
438
|
}
|
|
401
439
|
let elementRect;
|
|
402
|
-
if (
|
|
403
|
-
elementRect = locatorRect;
|
|
404
|
-
}
|
|
405
|
-
else if (domClickData) {
|
|
406
|
-
console.warn('[screenci] using DOM click data as fallback for elementRect');
|
|
440
|
+
if (domClickData) {
|
|
407
441
|
elementRect = domClickData.targetRect;
|
|
408
442
|
}
|
|
443
|
+
else if (locatorRect) {
|
|
444
|
+
elementRect = locatorRect ?? undefined;
|
|
445
|
+
}
|
|
409
446
|
if (elementRect) {
|
|
410
447
|
return {
|
|
411
448
|
elementRect,
|
|
@@ -417,15 +454,17 @@ async function performClickActions(locator, doClick, clickOptions, position, mov
|
|
|
417
454
|
return null;
|
|
418
455
|
}
|
|
419
456
|
}
|
|
420
|
-
async function prepareAutoZoomForLocator(locator, eventType) {
|
|
421
|
-
const
|
|
422
|
-
const zoomDur = isInsideAutoZoom
|
|
423
|
-
if (isInsideAutoZoom
|
|
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) {
|
|
424
463
|
await sleep(zoomDur);
|
|
425
464
|
}
|
|
426
|
-
const locatorRect = await
|
|
427
|
-
if (isInsideAutoZoom
|
|
428
|
-
if (
|
|
465
|
+
const { locatorRect, isFirstAutoZoomInteraction } = await scrollHandler.scroll(locator);
|
|
466
|
+
if (scrollHandler.isInsideAutoZoom && locatorRect) {
|
|
467
|
+
if (isFirstAutoZoomInteraction && zoomDur > 0) {
|
|
429
468
|
await sleep(zoomDur);
|
|
430
469
|
}
|
|
431
470
|
setLastZoomLocation({
|
|
@@ -435,25 +474,36 @@ async function prepareAutoZoomForLocator(locator, eventType) {
|
|
|
435
474
|
eventType,
|
|
436
475
|
});
|
|
437
476
|
}
|
|
438
|
-
return locatorRect;
|
|
477
|
+
return { locatorRect, isFirstAutoZoomInteraction };
|
|
439
478
|
}
|
|
440
|
-
async function performSimpleAction(locator, doAction, options, subType, clickOpt, position, recordMousePress = subType === 'tap') {
|
|
479
|
+
async function performSimpleAction(locator, doAction, options, subType, clickOpt, autoZoomOptions, position, recordMousePress = subType === 'tap') {
|
|
441
480
|
await sleep(PRE_ACTION_SLEEP);
|
|
442
481
|
let innerEvents = [];
|
|
443
482
|
let elementRect;
|
|
444
483
|
if (clickOpt !== undefined) {
|
|
445
484
|
const { moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove, } = clickOpt;
|
|
446
|
-
const clickActionResult = await performClickActions(locator, doAction, {}, position, moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove);
|
|
485
|
+
const clickActionResult = await performClickActions(locator, doAction, {}, autoZoomOptions, position, moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove);
|
|
447
486
|
innerEvents = clickActionResult?.innerEvents ?? [];
|
|
448
487
|
elementRect = clickActionResult?.elementRect;
|
|
449
488
|
}
|
|
450
489
|
else {
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const
|
|
455
|
-
if (
|
|
456
|
-
await sleep(
|
|
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();
|
|
497
|
+
innerEvents.push({
|
|
498
|
+
type: 'focusChange',
|
|
499
|
+
startMs: focusStart,
|
|
500
|
+
endMs: focusEnd,
|
|
501
|
+
x: locatorRect.x + locatorRect.width / 2,
|
|
502
|
+
y: locatorRect.y + locatorRect.height / 2,
|
|
503
|
+
easing: getZoomEasing() ?? 'ease-in-out',
|
|
504
|
+
focusOnly: true,
|
|
505
|
+
elementRect: locatorRect,
|
|
506
|
+
});
|
|
457
507
|
}
|
|
458
508
|
const targetPosition = locatorRect
|
|
459
509
|
? {
|
|
@@ -467,7 +517,7 @@ async function performSimpleAction(locator, doAction, options, subType, clickOpt
|
|
|
467
517
|
...(targetPosition ? { position: targetPosition } : {}),
|
|
468
518
|
});
|
|
469
519
|
const endTime = Date.now();
|
|
470
|
-
elementRect = locatorRect;
|
|
520
|
+
elementRect = locatorRect ?? undefined;
|
|
471
521
|
if (recordMousePress) {
|
|
472
522
|
const midTime = (startTime + endTime) / 2;
|
|
473
523
|
innerEvents.push({
|
|
@@ -494,9 +544,9 @@ async function performSimpleAction(locator, doAction, options, subType, clickOpt
|
|
|
494
544
|
activeClickRecorder.addInput(subType, elementRect, innerEvents);
|
|
495
545
|
}
|
|
496
546
|
}
|
|
497
|
-
async function recordedClick(locator, doClick, clickOptions, position, moveDuration, moveSpeed, beforeClickPause = CLICK_DURATION_MS / 2, moveEasing = 'ease-in-out', postClickPause = CLICK_DURATION_MS / 2, postClickMove) {
|
|
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) {
|
|
498
548
|
await sleep(PRE_ACTION_SLEEP);
|
|
499
|
-
const result = await performClickActions(locator, doClick, clickOptions, position, moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove);
|
|
549
|
+
const result = await performClickActions(locator, doClick, clickOptions, autoZoomOptions, position, moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove);
|
|
500
550
|
const clickWaitStart = Date.now();
|
|
501
551
|
await sleep(POST_ACTION_SLEEP);
|
|
502
552
|
const clickWaitEnd = Date.now();
|
|
@@ -536,7 +586,7 @@ export function instrumentLocator(locator) {
|
|
|
536
586
|
instrumented.add(locator);
|
|
537
587
|
const originalClick = locator.click.bind(locator);
|
|
538
588
|
locator.click = async (options) => {
|
|
539
|
-
const { moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove, position, steps: _steps, ...clickOptions } = options ?? {};
|
|
589
|
+
const { moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove, autoZoomOptions, position, steps: _steps, ...clickOptions } = options ?? {};
|
|
540
590
|
if (isInsideHide()) {
|
|
541
591
|
return originalClick({
|
|
542
592
|
...clickOptions,
|
|
@@ -544,11 +594,11 @@ export function instrumentLocator(locator) {
|
|
|
544
594
|
});
|
|
545
595
|
}
|
|
546
596
|
assertDurationOrSpeed(moveDuration, moveSpeed, 'click move');
|
|
547
|
-
return recordedClick(locator, (options) => originalClick(options), clickOptions, position, moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove);
|
|
597
|
+
return recordedClick(locator, (options) => originalClick(options), clickOptions, autoZoomOptions, position, moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove);
|
|
548
598
|
};
|
|
549
599
|
const originalPressSequentially = locator.pressSequentially.bind(locator);
|
|
550
600
|
locator.pressSequentially = async (text, options) => {
|
|
551
|
-
const { click: _click, hideMouse: _hideMouse, position: _position, ...pressOptions } = options ?? {};
|
|
601
|
+
const { click: _click, autoZoomOptions, hideMouse: _hideMouse, position: _position, ...pressOptions } = options ?? {};
|
|
552
602
|
if (isInsideHide()) {
|
|
553
603
|
return originalPressSequentially(text, pressOptions);
|
|
554
604
|
}
|
|
@@ -560,19 +610,24 @@ export function instrumentLocator(locator) {
|
|
|
560
610
|
const clickOpt = options.click;
|
|
561
611
|
const position = options.position;
|
|
562
612
|
const { moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove, ...clickOptions } = clickOpt;
|
|
563
|
-
const clickActionResult = await performClickActions(locator, (options) => originalClick(options), clickOptions, position, moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove);
|
|
613
|
+
const clickActionResult = await performClickActions(locator, (options) => originalClick(options), clickOptions, options.autoZoomOptions, position, moveDuration, moveSpeed, beforeClickPause, moveEasing, postClickPause, postClickMove);
|
|
564
614
|
innerEvents.push(...(clickActionResult?.innerEvents ?? []));
|
|
565
615
|
elementRect = clickActionResult?.elementRect;
|
|
566
616
|
}
|
|
567
617
|
else {
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
if (isFirstAutoZoomEvent) {
|
|
618
|
+
const { locatorRect, isFirstAutoZoomInteraction } = await prepareAutoZoomForLocator(locator, 'fill', options?.autoZoomOptions);
|
|
619
|
+
if (isFirstAutoZoomInteraction) {
|
|
571
620
|
const postDelay = getPostZoomInOutDelay() ?? 0;
|
|
572
621
|
if (postDelay > 0)
|
|
573
622
|
await sleep(postDelay);
|
|
574
623
|
}
|
|
575
624
|
elementRect = locatorRect;
|
|
625
|
+
if (isInsideAutoZoom() && locatorRect) {
|
|
626
|
+
const zoomDur = getZoomDuration() ?? 0;
|
|
627
|
+
if (zoomDur > 0) {
|
|
628
|
+
await sleep(zoomDur);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
576
631
|
}
|
|
577
632
|
// Hide cursor while typing (will be shown again on next mouse move)
|
|
578
633
|
const page = locator.page();
|
|
@@ -605,47 +660,176 @@ export function instrumentLocator(locator) {
|
|
|
605
660
|
const originalFill = locator.fill.bind(locator);
|
|
606
661
|
locator.fill = async (value, options) => {
|
|
607
662
|
if (isInsideHide()) {
|
|
608
|
-
const { duration: _duration, click: _click, position: _position, hideMouse: _hideMouse, ...fillOptions } = options ?? {};
|
|
663
|
+
const { duration: _duration, click: _click, position: _position, hideMouse: _hideMouse, autoZoomOptions: _autoZoomOptions, ...fillOptions } = options ?? {};
|
|
609
664
|
return originalFill(value, fillOptions);
|
|
610
665
|
}
|
|
611
|
-
|
|
666
|
+
if (options?.click !== undefined) {
|
|
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();
|
|
720
|
+
const innerEvents = [];
|
|
721
|
+
const fillOptions = options ?? {};
|
|
722
|
+
const locatorRect = await locator.boundingBox();
|
|
723
|
+
let elementRect = locatorRect ?? undefined;
|
|
724
|
+
if (fillOptions.hideMouse === true) {
|
|
725
|
+
const cursorVisible = mouseVisibilities.get(page) ?? true;
|
|
726
|
+
if (cursorVisible) {
|
|
727
|
+
mouseVisibilities.set(page, false);
|
|
728
|
+
const hideMs = Date.now();
|
|
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();
|
|
740
|
+
await locator.evaluate((element) => {
|
|
741
|
+
if (element instanceof HTMLInputElement ||
|
|
742
|
+
element instanceof HTMLTextAreaElement) {
|
|
743
|
+
element.focus();
|
|
744
|
+
element.select();
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (element instanceof HTMLElement && element.isContentEditable) {
|
|
748
|
+
element.focus();
|
|
749
|
+
const selection = element.ownerDocument.getSelection();
|
|
750
|
+
if (!selection)
|
|
751
|
+
return;
|
|
752
|
+
const range = element.ownerDocument.createRange();
|
|
753
|
+
range.selectNodeContents(element);
|
|
754
|
+
selection.removeAllRanges();
|
|
755
|
+
selection.addRange(range);
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
const focusEnd = Date.now();
|
|
759
|
+
innerEvents.push({
|
|
760
|
+
type: 'focusChange',
|
|
761
|
+
startMs: focusStart,
|
|
762
|
+
endMs: focusEnd,
|
|
763
|
+
x: correctedRect.x + correctedRect.width / 2,
|
|
764
|
+
y: correctedRect.y + correctedRect.height / 2,
|
|
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;
|
|
612
792
|
const delay = value.length > 0 ? duration / value.length : 0;
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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)) {
|
|
804
|
+
activeClickRecorder.addInput('pressSequentially', elementRect, innerEvents);
|
|
805
|
+
}
|
|
806
|
+
return;
|
|
623
807
|
};
|
|
624
808
|
const originalTap = locator.tap.bind(locator);
|
|
625
809
|
locator.tap = async (options) => {
|
|
626
810
|
const clickOpt = options?.click;
|
|
627
|
-
const { click: _click, position, ...tapOpts } = options ?? {};
|
|
628
|
-
return performSimpleAction(locator, (options) => originalTap(options), tapOpts, 'tap', clickOpt, position);
|
|
811
|
+
const { click: _click, position, autoZoomOptions, ...tapOpts } = options ?? {};
|
|
812
|
+
return performSimpleAction(locator, (options) => originalTap(options), tapOpts, 'tap', clickOpt, autoZoomOptions, position);
|
|
629
813
|
};
|
|
630
814
|
const originalCheck = locator.check.bind(locator);
|
|
631
815
|
locator.check = async (options) => {
|
|
632
816
|
const clickOpt = options?.click;
|
|
633
817
|
const position = options?.position;
|
|
634
|
-
const { click: _click, ...checkOpts } = options ?? {};
|
|
818
|
+
const { click: _click, autoZoomOptions, ...checkOpts } = options ?? {};
|
|
635
819
|
if (isInsideHide()) {
|
|
636
820
|
return originalCheck(checkOpts);
|
|
637
821
|
}
|
|
638
|
-
return performSimpleAction(locator, (options) => originalCheck(options), checkOpts, 'check', clickOpt, position, false);
|
|
822
|
+
return performSimpleAction(locator, (options) => originalCheck(options), checkOpts, 'check', clickOpt, autoZoomOptions, position, false);
|
|
639
823
|
};
|
|
640
824
|
const originalUncheck = locator.uncheck.bind(locator);
|
|
641
825
|
locator.uncheck = async (options) => {
|
|
642
826
|
const clickOpt = options?.click;
|
|
643
827
|
const position = options?.position;
|
|
644
|
-
const { click: _click, ...uncheckOpts } = options ?? {};
|
|
828
|
+
const { click: _click, autoZoomOptions, ...uncheckOpts } = options ?? {};
|
|
645
829
|
if (isInsideHide()) {
|
|
646
830
|
return originalUncheck(uncheckOpts);
|
|
647
831
|
}
|
|
648
|
-
return performSimpleAction(locator, (options) => originalUncheck(options), uncheckOpts, 'uncheck', clickOpt, position, false);
|
|
832
|
+
return performSimpleAction(locator, (options) => originalUncheck(options), uncheckOpts, 'uncheck', clickOpt, autoZoomOptions, position, false);
|
|
649
833
|
};
|
|
650
834
|
locator.setChecked = async (checked, options) => {
|
|
651
835
|
if (checked) {
|
|
@@ -658,14 +842,14 @@ export function instrumentLocator(locator) {
|
|
|
658
842
|
const originalSelectOption = locator.selectOption.bind(locator);
|
|
659
843
|
locator.selectOption = async (values, options) => {
|
|
660
844
|
const clickOpt = options?.click;
|
|
661
|
-
const { click: _click, position, ...selectOpts } = options ?? {};
|
|
845
|
+
const { click: _click, position, autoZoomOptions, ...selectOpts } = options ?? {};
|
|
662
846
|
if (isInsideHide()) {
|
|
663
847
|
return originalSelectOption(values, selectOpts);
|
|
664
848
|
}
|
|
665
849
|
let result = [];
|
|
666
850
|
await performSimpleAction(locator, (options) => originalSelectOption(values, options).then((res) => {
|
|
667
851
|
result = res;
|
|
668
|
-
}), selectOpts, 'select', clickOpt, position, false);
|
|
852
|
+
}), selectOpts, 'select', clickOpt, autoZoomOptions, position, false);
|
|
669
853
|
return result;
|
|
670
854
|
};
|
|
671
855
|
const originalHover = locator.hover.bind(locator);
|
|
@@ -675,10 +859,8 @@ export function instrumentLocator(locator) {
|
|
|
675
859
|
const page = locator.page();
|
|
676
860
|
const mouseMoveInternal = originalMouseMoves.get(page) ?? page.mouse.move.bind(page.mouse);
|
|
677
861
|
const moveStartTime = Date.now();
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
: 'center');
|
|
681
|
-
const scrollElapsedMs = Date.now() - moveStartTime;
|
|
862
|
+
const scrollResult = await new ZoomScrollHandler().scroll(locator);
|
|
863
|
+
const { locatorRect } = scrollResult;
|
|
682
864
|
const innerEvents = [];
|
|
683
865
|
const targetPos = position ??
|
|
684
866
|
(locatorRect
|
|
@@ -693,8 +875,7 @@ export function instrumentLocator(locator) {
|
|
|
693
875
|
defaultDuration: 1000,
|
|
694
876
|
context: 'hover move',
|
|
695
877
|
});
|
|
696
|
-
|
|
697
|
-
innerEvents.push(await animateMouseMove(page, mouseMoveInternal, targetX, targetY, effectiveDuration, moveEasing, moveStartTime, locatorRect));
|
|
878
|
+
innerEvents.push(await animateMouseMove(page, mouseMoveInternal, targetX, targetY, resolveMoveDuration(scrollResult, resolvedDuration), moveEasing, resolveMoveStartMs(scrollResult, moveStartTime), locatorRect));
|
|
698
879
|
}
|
|
699
880
|
const waitStartMs = Date.now();
|
|
700
881
|
await originalHover({
|
|
@@ -721,10 +902,8 @@ export function instrumentLocator(locator) {
|
|
|
721
902
|
const page = locator.page();
|
|
722
903
|
const mouseMoveInternal = originalMouseMoves.get(page) ?? page.mouse.move.bind(page.mouse);
|
|
723
904
|
const moveStartTime = Date.now();
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
: 'center');
|
|
727
|
-
const scrollElapsedMs = Date.now() - moveStartTime;
|
|
905
|
+
const scrollResult = await new ZoomScrollHandler().scroll(locator);
|
|
906
|
+
const { locatorRect } = scrollResult;
|
|
728
907
|
const innerEvents = [];
|
|
729
908
|
const targetPos = locatorRect
|
|
730
909
|
? { x: locatorRect.width / 2, y: locatorRect.height / 2 }
|
|
@@ -738,8 +917,7 @@ export function instrumentLocator(locator) {
|
|
|
738
917
|
defaultDuration: 1000,
|
|
739
918
|
context: 'selectText move',
|
|
740
919
|
});
|
|
741
|
-
|
|
742
|
-
innerEvents.push(await animateMouseMove(page, mouseMoveInternal, targetX, targetY, effectiveDuration, moveEasing, moveStartTime, locatorRect));
|
|
920
|
+
innerEvents.push(await animateMouseMove(page, mouseMoveInternal, targetX, targetY, resolveMoveDuration(scrollResult, resolvedDuration), moveEasing, resolveMoveStartMs(scrollResult, moveStartTime), locatorRect));
|
|
743
921
|
}
|
|
744
922
|
await sleep(beforeClickPause);
|
|
745
923
|
await originalSelectText(selectOpts);
|
|
@@ -778,10 +956,8 @@ export function instrumentLocator(locator) {
|
|
|
778
956
|
const page = locator.page();
|
|
779
957
|
const mouseMoveInternal = originalMouseMoves.get(page) ?? page.mouse.move.bind(page.mouse);
|
|
780
958
|
const moveStartTime = Date.now();
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
: 'center');
|
|
784
|
-
const scrollElapsedMs = Date.now() - moveStartTime;
|
|
959
|
+
const scrollResult = await new ZoomScrollHandler().scroll(locator);
|
|
960
|
+
const { locatorRect: sourceRect } = scrollResult;
|
|
785
961
|
const targetBb = await target.boundingBox();
|
|
786
962
|
const targetRect = targetBb
|
|
787
963
|
? {
|
|
@@ -810,8 +986,7 @@ export function instrumentLocator(locator) {
|
|
|
810
986
|
defaultDuration: 1000,
|
|
811
987
|
context: 'dragTo move',
|
|
812
988
|
});
|
|
813
|
-
|
|
814
|
-
innerEvents.push(await animateMouseMove(page, mouseMoveInternal, toX, toY, effectiveDuration, moveEasing, moveStartTime, sourceRect));
|
|
989
|
+
innerEvents.push(await animateMouseMove(page, mouseMoveInternal, toX, toY, resolveMoveDuration(scrollResult, resolvedDuration), moveEasing, resolveMoveStartMs(scrollResult, moveStartTime), sourceRect));
|
|
815
990
|
}
|
|
816
991
|
// 2. preDragPause + mouseDown
|
|
817
992
|
await sleep(preDragPause);
|
|
@@ -953,15 +1128,14 @@ export async function instrumentPage(page) {
|
|
|
953
1128
|
activeClickRecorder.addInput('mouseShow', undefined, [showEvent]);
|
|
954
1129
|
}
|
|
955
1130
|
const moveEvent = {
|
|
956
|
-
type: '
|
|
1131
|
+
type: 'focusChange',
|
|
957
1132
|
startMs,
|
|
958
1133
|
endMs,
|
|
959
|
-
duration,
|
|
960
1134
|
x,
|
|
961
1135
|
y,
|
|
962
1136
|
...(duration > 0 ? { easing } : {}),
|
|
963
1137
|
};
|
|
964
|
-
activeClickRecorder.addInput('
|
|
1138
|
+
activeClickRecorder.addInput('focusChange', undefined, [moveEvent]);
|
|
965
1139
|
}
|
|
966
1140
|
};
|
|
967
1141
|
mouseVisibilities.set(page, true);
|