@humanjs/playwright 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -2
- package/dist/index.cjs +605 -206
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +274 -8
- package/dist/index.d.ts +274 -8
- package/dist/index.js +605 -206
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -68,6 +68,73 @@ async function dispatchKey(page, key) {
|
|
|
68
68
|
await page.keyboard.insertText(key);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
async function executePaste(target, value, ctx) {
|
|
72
|
+
if (value.length === 0) return { characters: 0 };
|
|
73
|
+
const locator = typeof target === "string" ? ctx.page.locator(target) : target;
|
|
74
|
+
await locator.focus();
|
|
75
|
+
await ctx.page.keyboard.insertText(value);
|
|
76
|
+
return { characters: value.length };
|
|
77
|
+
}
|
|
78
|
+
async function executePress(key, ctx) {
|
|
79
|
+
const dispatched = resolveChord(key);
|
|
80
|
+
await ctx.page.keyboard.press(dispatched);
|
|
81
|
+
return { dispatched };
|
|
82
|
+
}
|
|
83
|
+
function resolveChord(key) {
|
|
84
|
+
const parts = key.split("+").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
85
|
+
if (parts.length === 0) {
|
|
86
|
+
throw new Error(`Invalid key: ${JSON.stringify(key)} \u2014 empty or only separators`);
|
|
87
|
+
}
|
|
88
|
+
const keyToken = parts[parts.length - 1];
|
|
89
|
+
if (keyToken === void 0) {
|
|
90
|
+
throw new Error(`Invalid key: ${JSON.stringify(key)} \u2014 missing key`);
|
|
91
|
+
}
|
|
92
|
+
const modifierTokens = parts.slice(0, -1);
|
|
93
|
+
const modifiers = [];
|
|
94
|
+
for (const token of modifierTokens) {
|
|
95
|
+
const resolved = resolveModifier(token);
|
|
96
|
+
if (resolved === null) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Invalid key modifier: ${JSON.stringify(token)} in ${JSON.stringify(key)}. Use one of: Mod/CmdOrCtrl/CommandOrControl, Cmd/Command/Meta/Win/Super, Ctrl/Control, Alt/Option/Opt, Shift.`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
modifiers.push(resolved);
|
|
102
|
+
}
|
|
103
|
+
return [...modifiers, normalizeKey(keyToken)].join("+");
|
|
104
|
+
}
|
|
105
|
+
function resolveModifier(token) {
|
|
106
|
+
const lower = token.toLowerCase();
|
|
107
|
+
switch (lower) {
|
|
108
|
+
case "mod":
|
|
109
|
+
case "cmdorctrl":
|
|
110
|
+
case "commandorcontrol":
|
|
111
|
+
return isMac() ? "Meta" : "Control";
|
|
112
|
+
case "cmd":
|
|
113
|
+
case "command":
|
|
114
|
+
case "meta":
|
|
115
|
+
case "win":
|
|
116
|
+
case "super":
|
|
117
|
+
return "Meta";
|
|
118
|
+
case "ctrl":
|
|
119
|
+
case "control":
|
|
120
|
+
return "Control";
|
|
121
|
+
case "alt":
|
|
122
|
+
case "option":
|
|
123
|
+
case "opt":
|
|
124
|
+
return "Alt";
|
|
125
|
+
case "shift":
|
|
126
|
+
return "Shift";
|
|
127
|
+
default:
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function normalizeKey(key) {
|
|
132
|
+
if (key.length === 1) return key.toUpperCase();
|
|
133
|
+
return key.charAt(0).toUpperCase() + key.slice(1);
|
|
134
|
+
}
|
|
135
|
+
function isMac() {
|
|
136
|
+
return process.platform === "darwin";
|
|
137
|
+
}
|
|
71
138
|
|
|
72
139
|
// src/internal/mouse-walk.ts
|
|
73
140
|
async function walkMouseAlongPath(page, path, durationMs) {
|
|
@@ -82,31 +149,198 @@ async function walkMouseAlongPath(page, path, durationMs) {
|
|
|
82
149
|
}
|
|
83
150
|
}
|
|
84
151
|
}
|
|
152
|
+
var RESERVED_TARGETS = /* @__PURE__ */ new Set(["natural", "end", "top"]);
|
|
153
|
+
async function executeScroll(target, ctx, options = {}) {
|
|
154
|
+
const { page, personality, rng, speed } = ctx;
|
|
155
|
+
const speedFactor = speedModeFactor(speed);
|
|
156
|
+
const axis = options.axis ?? "y";
|
|
157
|
+
const container = resolveWithin(options.within, ctx);
|
|
158
|
+
const geom = container ? await readContainerGeometry(container, axis) : await readWindowGeometry(page, axis);
|
|
159
|
+
if (!geom) {
|
|
160
|
+
return { from: 0, to: 0, distance: 0, durationMs: 0 };
|
|
161
|
+
}
|
|
162
|
+
const from = geom.current;
|
|
163
|
+
const targetPos = await resolveTarget(target, ctx, geom, container, axis, options.block);
|
|
164
|
+
const to = clamp(targetPos, 0, Math.max(0, geom.total - geom.viewport));
|
|
165
|
+
const distance = to - from;
|
|
166
|
+
if (distance === 0) {
|
|
167
|
+
return { from, to, distance: 0, durationMs: 0 };
|
|
168
|
+
}
|
|
169
|
+
if (speed === "instant") {
|
|
170
|
+
if (container) {
|
|
171
|
+
await container.evaluate(
|
|
172
|
+
(el, args) => {
|
|
173
|
+
const a = args;
|
|
174
|
+
if (a.axis === "x") el.scrollTo(a.pos, el.scrollTop);
|
|
175
|
+
else el.scrollTo(el.scrollLeft, a.pos);
|
|
176
|
+
},
|
|
177
|
+
{ axis, pos: to }
|
|
178
|
+
);
|
|
179
|
+
} else {
|
|
180
|
+
await page.evaluate(
|
|
181
|
+
(args) => {
|
|
182
|
+
if (args.axis === "x") window.scrollTo(args.pos, window.scrollY);
|
|
183
|
+
else window.scrollTo(window.scrollX, args.pos);
|
|
184
|
+
},
|
|
185
|
+
{ axis, pos: to }
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return { from, to, distance, durationMs: 0 };
|
|
189
|
+
}
|
|
190
|
+
const segments = core.planScroll(from, to, personality.scroll, rng, {
|
|
191
|
+
forceOvershoot: options.overshoot,
|
|
192
|
+
withPauses: options.withPauses,
|
|
193
|
+
personalitySpeed: personality.speed,
|
|
194
|
+
speedFactor
|
|
195
|
+
});
|
|
196
|
+
if (container && geom.hover) {
|
|
197
|
+
await page.mouse.move(geom.hover.x, geom.hover.y);
|
|
198
|
+
}
|
|
199
|
+
const startedAt = Date.now();
|
|
200
|
+
await walkSegments(page, segments, axis, container);
|
|
201
|
+
const durationMs = Date.now() - startedAt;
|
|
202
|
+
return { from, to, distance, durationMs };
|
|
203
|
+
}
|
|
204
|
+
function resolveWithin(within, ctx) {
|
|
205
|
+
if (!within) return null;
|
|
206
|
+
return typeof within === "string" ? ctx.page.locator(within) : within;
|
|
207
|
+
}
|
|
208
|
+
async function readWindowGeometry(page, axis) {
|
|
209
|
+
const g = await page.evaluate((a) => {
|
|
210
|
+
if (a === "x") {
|
|
211
|
+
return {
|
|
212
|
+
current: window.scrollX,
|
|
213
|
+
viewport: window.innerWidth,
|
|
214
|
+
total: Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0)
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
current: window.scrollY,
|
|
219
|
+
viewport: window.innerHeight,
|
|
220
|
+
total: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0)
|
|
221
|
+
};
|
|
222
|
+
}, axis);
|
|
223
|
+
return { current: g.current, viewport: g.viewport, total: g.total };
|
|
224
|
+
}
|
|
225
|
+
async function readContainerGeometry(container, axis) {
|
|
226
|
+
return container.evaluate((el, a) => {
|
|
227
|
+
const rect = el.getBoundingClientRect();
|
|
228
|
+
const isX = a === "x";
|
|
229
|
+
return {
|
|
230
|
+
current: isX ? el.scrollLeft : el.scrollTop,
|
|
231
|
+
viewport: isX ? el.clientWidth : el.clientHeight,
|
|
232
|
+
total: isX ? el.scrollWidth : el.scrollHeight,
|
|
233
|
+
hover: {
|
|
234
|
+
x: rect.left + rect.width / 2,
|
|
235
|
+
y: rect.top + rect.height / 2
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}, axis).catch(() => null);
|
|
239
|
+
}
|
|
240
|
+
async function resolveTarget(target, ctx, geom, container, axis, block = "start") {
|
|
241
|
+
if (target === void 0 || target === "natural") return geom.current + geom.viewport;
|
|
242
|
+
if (target === "end") return geom.total;
|
|
243
|
+
if (target === "top") return 0;
|
|
244
|
+
if (typeof target === "object" && "by" in target) return geom.current + target.by;
|
|
245
|
+
if (typeof target === "object" && "to" in target) return target.to;
|
|
246
|
+
const elementLocator = typeof target === "string" && !RESERVED_TARGETS.has(target) ? ctx.page.locator(target) : typeof target === "string" ? null : target;
|
|
247
|
+
if (!elementLocator) return geom.current + geom.viewport;
|
|
248
|
+
return container ? resolveElementWithinContainer(elementLocator, container, geom, axis, block) : resolveElementInWindow(elementLocator, geom, axis, block);
|
|
249
|
+
}
|
|
250
|
+
async function resolveElementInWindow(elementLocator, geom, axis, block) {
|
|
251
|
+
const rect = await elementLocator.boundingBox().catch(() => null);
|
|
252
|
+
if (!rect) return geom.current;
|
|
253
|
+
const relStart = axis === "x" ? rect.x : rect.y;
|
|
254
|
+
const length = axis === "x" ? rect.width : rect.height;
|
|
255
|
+
const absoluteStart = geom.current + relStart;
|
|
256
|
+
const absoluteEnd = absoluteStart + length;
|
|
257
|
+
if (block === "start") return absoluteStart;
|
|
258
|
+
if (block === "end") return absoluteEnd - geom.viewport;
|
|
259
|
+
if (block === "nearest") {
|
|
260
|
+
if (relStart >= 0 && relStart + length <= geom.viewport) return geom.current;
|
|
261
|
+
if (relStart < 0) return absoluteStart;
|
|
262
|
+
return absoluteEnd - geom.viewport;
|
|
263
|
+
}
|
|
264
|
+
return absoluteStart - (geom.viewport - length) / 2;
|
|
265
|
+
}
|
|
266
|
+
async function resolveElementWithinContainer(elementLocator, container, geom, axis, block) {
|
|
267
|
+
const rects = await container.evaluate(
|
|
268
|
+
(containerEl, args) => {
|
|
269
|
+
const elementEl = args.sel ? document.querySelector(args.sel) : null;
|
|
270
|
+
const targetEl = elementEl ?? containerEl.querySelector(":scope > *");
|
|
271
|
+
if (!targetEl) return null;
|
|
272
|
+
const cRect = containerEl.getBoundingClientRect();
|
|
273
|
+
const eRect = targetEl.getBoundingClientRect();
|
|
274
|
+
return args.axis === "x" ? { relStart: eRect.left - cRect.left, length: eRect.width } : { relStart: eRect.top - cRect.top, length: eRect.height };
|
|
275
|
+
},
|
|
276
|
+
{ sel: await locatorSelector(elementLocator), axis }
|
|
277
|
+
).catch(() => null);
|
|
278
|
+
if (!rects) return geom.current;
|
|
279
|
+
const offsetStart = rects.relStart + geom.current;
|
|
280
|
+
const offsetEnd = offsetStart + rects.length;
|
|
281
|
+
if (block === "start") return offsetStart;
|
|
282
|
+
if (block === "end") return offsetEnd - geom.viewport;
|
|
283
|
+
if (block === "nearest") {
|
|
284
|
+
if (rects.relStart >= 0 && rects.relStart + rects.length <= geom.viewport) {
|
|
285
|
+
return geom.current;
|
|
286
|
+
}
|
|
287
|
+
if (rects.relStart < 0) return offsetStart;
|
|
288
|
+
return offsetEnd - geom.viewport;
|
|
289
|
+
}
|
|
290
|
+
return offsetStart - (geom.viewport - rects.length) / 2;
|
|
291
|
+
}
|
|
292
|
+
async function locatorSelector(locator) {
|
|
293
|
+
const s = locator.toString?.();
|
|
294
|
+
if (typeof s !== "string") return null;
|
|
295
|
+
const match = /locator\(['"](.+?)['"]/.exec(s);
|
|
296
|
+
if (!match) return null;
|
|
297
|
+
const raw = match[1] ?? "";
|
|
298
|
+
const eq = raw.indexOf("=");
|
|
299
|
+
return eq > 0 && /^[a-z]+$/.test(raw.slice(0, eq)) ? raw.slice(eq + 1) : raw;
|
|
300
|
+
}
|
|
301
|
+
async function walkSegments(page, segments, axis, container) {
|
|
302
|
+
for (const segment of segments) {
|
|
303
|
+
if (segment.delayBeforeMs > 0) await sleep(segment.delayBeforeMs);
|
|
304
|
+
if (segment.delta === 0) continue;
|
|
305
|
+
if (container) {
|
|
306
|
+
await container.evaluate(
|
|
307
|
+
(el, args) => {
|
|
308
|
+
const a = args;
|
|
309
|
+
if (a.axis === "x") el.scrollLeft += a.delta;
|
|
310
|
+
else el.scrollTop += a.delta;
|
|
311
|
+
},
|
|
312
|
+
{ axis, delta: segment.delta }
|
|
313
|
+
);
|
|
314
|
+
} else if (axis === "x") {
|
|
315
|
+
await page.mouse.wheel(segment.delta, 0);
|
|
316
|
+
} else {
|
|
317
|
+
await page.mouse.wheel(0, segment.delta);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function clamp(value, min, max) {
|
|
322
|
+
return value < min ? min : value > max ? max : value;
|
|
323
|
+
}
|
|
85
324
|
|
|
86
325
|
// src/mouse/index.ts
|
|
87
|
-
async function executeClick(target, ctx) {
|
|
88
|
-
const
|
|
326
|
+
async function executeClick(target, ctx, options = {}) {
|
|
327
|
+
const button = options.button ?? "left";
|
|
89
328
|
if (ctx.speed === "instant") {
|
|
329
|
+
if (isPoint(target)) {
|
|
330
|
+
await ctx.page.mouse.click(target.x, target.y, { button });
|
|
331
|
+
ctx.setMousePosition(target);
|
|
332
|
+
return { target };
|
|
333
|
+
}
|
|
334
|
+
const locator = typeof target === "string" ? ctx.page.locator(target) : target;
|
|
90
335
|
const box2 = await locator.boundingBox();
|
|
91
|
-
await locator.click();
|
|
336
|
+
await locator.click({ button });
|
|
92
337
|
const center = box2 ? { x: box2.x + box2.width / 2, y: box2.y + box2.height / 2 } : ctx.getMousePosition();
|
|
93
338
|
ctx.setMousePosition(center);
|
|
94
339
|
return { target: center };
|
|
95
340
|
}
|
|
96
|
-
const box = await
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
`Cannot click: element not found or has no bounding box (target: ${describeTarget(target)})`
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
const targetPoint = pickClickPoint(box, ctx.rng);
|
|
103
|
-
const startPoint = ctx.getMousePosition();
|
|
104
|
-
const rawPath = core.bezierPath(startPoint, targetPoint, ctx.rng, {
|
|
105
|
-
curvature: ctx.personality.mouse.curvature
|
|
106
|
-
});
|
|
107
|
-
const path = core.humanizePath(rawPath, ctx.rng);
|
|
108
|
-
const travelMs = computeTravelTime(path, ctx.personality, ctx.speed, ctx.rng);
|
|
109
|
-
await walkMouseAlongPath(ctx.page, path, travelMs);
|
|
341
|
+
const { point: targetPoint, box } = await resolveTargetPointAndBox(target, ctx, "click");
|
|
342
|
+
await maybeMisclickBeat(ctx, box, targetPoint);
|
|
343
|
+
await walkBezierTo(targetPoint, ctx);
|
|
110
344
|
const preClickMs = computeDwellTime(
|
|
111
345
|
ctx.personality.dwell.preClickMs,
|
|
112
346
|
ctx.personality.dwell.preClickJitter,
|
|
@@ -116,7 +350,7 @@ async function executeClick(target, ctx) {
|
|
|
116
350
|
);
|
|
117
351
|
if (preClickMs > 0) await sleep(preClickMs);
|
|
118
352
|
ctx.setMousePosition(targetPoint);
|
|
119
|
-
await ctx.page.mouse.click(targetPoint.x, targetPoint.y);
|
|
353
|
+
await ctx.page.mouse.click(targetPoint.x, targetPoint.y, { button });
|
|
120
354
|
const postActionMs = computeDwellTime(
|
|
121
355
|
ctx.personality.dwell.postActionMs,
|
|
122
356
|
ctx.personality.dwell.postActionJitter,
|
|
@@ -127,11 +361,248 @@ async function executeClick(target, ctx) {
|
|
|
127
361
|
if (postActionMs > 0) await sleep(postActionMs);
|
|
128
362
|
return { target: targetPoint };
|
|
129
363
|
}
|
|
130
|
-
function
|
|
364
|
+
async function executeHover(target, ctx) {
|
|
365
|
+
if (ctx.speed === "instant") {
|
|
366
|
+
const box = await readBoxWithAutoScroll(target, ctx, "hover");
|
|
367
|
+
const center = { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
|
368
|
+
await ctx.page.mouse.move(center.x, center.y);
|
|
369
|
+
ctx.setMousePosition(center);
|
|
370
|
+
return { target: center };
|
|
371
|
+
}
|
|
372
|
+
const targetPoint = await moveToTarget(target, ctx);
|
|
373
|
+
const dwellMs = computeDwellTime(
|
|
374
|
+
ctx.personality.dwell.preClickMs,
|
|
375
|
+
ctx.personality.dwell.preClickJitter,
|
|
376
|
+
ctx.personality,
|
|
377
|
+
ctx.speed,
|
|
378
|
+
ctx.rng
|
|
379
|
+
);
|
|
380
|
+
if (dwellMs > 0) await sleep(dwellMs);
|
|
381
|
+
ctx.setMousePosition(targetPoint);
|
|
382
|
+
return { target: targetPoint };
|
|
383
|
+
}
|
|
384
|
+
async function executeDrag(from, to, ctx) {
|
|
385
|
+
const scrollYBeforeResolve = await readScrollY(ctx.page);
|
|
386
|
+
let { point: fromPoint, box: fromBox } = await resolveTargetPointAndBox(from, ctx, "drag");
|
|
387
|
+
let { point: toPoint, box: toBox } = await resolveTargetPointAndBox(to, ctx, "drag");
|
|
388
|
+
const resolveScrollDelta = await readScrollY(ctx.page) - scrollYBeforeResolve;
|
|
389
|
+
if (resolveScrollDelta !== 0) {
|
|
390
|
+
if (isPoint(from)) fromPoint = { x: fromPoint.x, y: fromPoint.y - resolveScrollDelta };
|
|
391
|
+
if (isPoint(to)) toPoint = { x: toPoint.x, y: toPoint.y - resolveScrollDelta };
|
|
392
|
+
}
|
|
393
|
+
if (ctx.speed === "instant") {
|
|
394
|
+
await ctx.page.mouse.move(fromPoint.x, fromPoint.y);
|
|
395
|
+
await ctx.page.mouse.down();
|
|
396
|
+
await ctx.page.mouse.move(toPoint.x, toPoint.y);
|
|
397
|
+
await ctx.page.mouse.up();
|
|
398
|
+
ctx.setMousePosition(toPoint);
|
|
399
|
+
return { from: fromPoint, to: toPoint };
|
|
400
|
+
}
|
|
401
|
+
if (fromBox && toBox) {
|
|
402
|
+
const scrollDelta = computeCurveScrollDelta(
|
|
403
|
+
fromPoint,
|
|
404
|
+
toPoint,
|
|
405
|
+
ctx.page.viewportSize(),
|
|
406
|
+
ctx.personality.mouse.curvature
|
|
407
|
+
);
|
|
408
|
+
if (scrollDelta !== 0) {
|
|
409
|
+
await executeScroll({ by: scrollDelta }, ctx, {});
|
|
410
|
+
const refreshedFrom = await resolveTargetPointAndBox(from, ctx, "drag");
|
|
411
|
+
fromPoint = refreshedFrom.point;
|
|
412
|
+
fromBox = refreshedFrom.box;
|
|
413
|
+
const refreshedTo = await resolveTargetPointAndBox(to, ctx, "drag");
|
|
414
|
+
toPoint = refreshedTo.point;
|
|
415
|
+
toBox = refreshedTo.box;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
await maybeMisclickBeat(ctx, fromBox, fromPoint);
|
|
419
|
+
await walkBezierTo(fromPoint, ctx);
|
|
420
|
+
ctx.setMousePosition(fromPoint);
|
|
421
|
+
const preDragMs = computeDwellTime(
|
|
422
|
+
ctx.personality.dwell.preClickMs,
|
|
423
|
+
ctx.personality.dwell.preClickJitter,
|
|
424
|
+
ctx.personality,
|
|
425
|
+
ctx.speed,
|
|
426
|
+
ctx.rng
|
|
427
|
+
);
|
|
428
|
+
if (preDragMs > 0) await sleep(preDragMs);
|
|
429
|
+
await ctx.page.mouse.down();
|
|
430
|
+
await maybeMisclickBeat(ctx, toBox, toPoint);
|
|
431
|
+
await walkBezierTo(toPoint, ctx);
|
|
432
|
+
await ctx.page.mouse.up();
|
|
433
|
+
ctx.setMousePosition(toPoint);
|
|
434
|
+
const postActionMs = computeDwellTime(
|
|
435
|
+
ctx.personality.dwell.postActionMs,
|
|
436
|
+
ctx.personality.dwell.postActionJitter,
|
|
437
|
+
ctx.personality,
|
|
438
|
+
ctx.speed,
|
|
439
|
+
ctx.rng
|
|
440
|
+
);
|
|
441
|
+
if (postActionMs > 0) await sleep(postActionMs);
|
|
442
|
+
return { from: fromPoint, to: toPoint };
|
|
443
|
+
}
|
|
444
|
+
async function executeMove(target, ctx) {
|
|
445
|
+
const point = await resolveTargetPoint(target, ctx, "move");
|
|
446
|
+
if (ctx.speed === "instant") {
|
|
447
|
+
await ctx.page.mouse.move(point.x, point.y);
|
|
448
|
+
ctx.setMousePosition(point);
|
|
449
|
+
return { target: point };
|
|
450
|
+
}
|
|
451
|
+
await walkBezierTo(point, ctx);
|
|
452
|
+
ctx.setMousePosition(point);
|
|
453
|
+
return { target: point };
|
|
454
|
+
}
|
|
455
|
+
async function moveToTarget(target, ctx) {
|
|
456
|
+
const box = await readBoxWithAutoScroll(target, ctx, "hover");
|
|
457
|
+
const targetPoint = pickClickPoint(box, ctx.rng, ctx.personality.mouse.clickSpread);
|
|
458
|
+
await walkBezierTo(targetPoint, ctx);
|
|
459
|
+
return targetPoint;
|
|
460
|
+
}
|
|
461
|
+
async function walkBezierTo(to, ctx) {
|
|
462
|
+
const startPoint = ctx.getMousePosition();
|
|
463
|
+
const rawPath = core.bezierPath(startPoint, to, ctx.rng, {
|
|
464
|
+
curvature: ctx.personality.mouse.curvature
|
|
465
|
+
});
|
|
466
|
+
const path = core.humanizePath(rawPath, ctx.rng);
|
|
467
|
+
const travelMs = computeTravelTime(path, ctx.personality, ctx.speed, ctx.rng);
|
|
468
|
+
await walkMouseAlongPath(ctx.page, path, travelMs);
|
|
469
|
+
}
|
|
470
|
+
async function resolveTargetPoint(target, ctx, action) {
|
|
471
|
+
if (isPoint(target)) return target;
|
|
472
|
+
return resolveLocatorPoint(target, ctx, action);
|
|
473
|
+
}
|
|
474
|
+
async function resolveTargetPointAndBox(target, ctx, action) {
|
|
475
|
+
if (isPoint(target)) return { point: target, box: null };
|
|
476
|
+
const box = await readBoxWithAutoScroll(target, ctx, action);
|
|
477
|
+
const point = pickClickPoint(box, ctx.rng, ctx.personality.mouse.clickSpread);
|
|
478
|
+
return { point, box };
|
|
479
|
+
}
|
|
480
|
+
async function resolveLocatorPoint(target, ctx, action) {
|
|
481
|
+
const box = await readBoxWithAutoScroll(target, ctx, action);
|
|
482
|
+
return pickClickPoint(box, ctx.rng, ctx.personality.mouse.clickSpread);
|
|
483
|
+
}
|
|
484
|
+
async function readBoxWithAutoScroll(target, ctx, action) {
|
|
485
|
+
const locator = typeof target === "string" ? ctx.page.locator(target) : target;
|
|
486
|
+
let box = await locator.boundingBox();
|
|
487
|
+
if (!box) {
|
|
488
|
+
throw new Error(
|
|
489
|
+
`Cannot ${action}: element not found or has no bounding box (target: ${describeTarget(target)})`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
const viewport = ctx.page.viewportSize();
|
|
493
|
+
if (viewport && !isBoxCenterInViewport(box, viewport)) {
|
|
494
|
+
if (ctx.speed === "instant") {
|
|
495
|
+
await locator.scrollIntoViewIfNeeded();
|
|
496
|
+
} else {
|
|
497
|
+
await executeScroll(locator, ctx, { block: "center" });
|
|
498
|
+
}
|
|
499
|
+
box = await locator.boundingBox();
|
|
500
|
+
if (!box) {
|
|
501
|
+
throw new Error(
|
|
502
|
+
`Cannot ${action}: element disappeared after scrolling into view (target: ${describeTarget(target)})`
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return box;
|
|
507
|
+
}
|
|
508
|
+
function isBoxCenterInViewport(box, viewport) {
|
|
509
|
+
const cx = box.x + box.width / 2;
|
|
510
|
+
const cy = box.y + box.height / 2;
|
|
511
|
+
return cx >= 0 && cx <= viewport.width && cy >= 0 && cy <= viewport.height;
|
|
512
|
+
}
|
|
513
|
+
async function readScrollY(page) {
|
|
514
|
+
if (typeof page.evaluate !== "function") return 0;
|
|
515
|
+
return page.evaluate(() => window.scrollY);
|
|
516
|
+
}
|
|
517
|
+
var CURVE_VIEWPORT_MARGIN = 20;
|
|
518
|
+
function computeCurveScrollDelta(from, to, viewport, curvature) {
|
|
519
|
+
if (!viewport) return 0;
|
|
520
|
+
const distance = Math.hypot(to.x - from.x, to.y - from.y);
|
|
521
|
+
const perpendicularExtent = distance * curvature;
|
|
522
|
+
const minY = Math.min(from.y, to.y) - perpendicularExtent;
|
|
523
|
+
const maxY = Math.max(from.y, to.y) + perpendicularExtent;
|
|
524
|
+
const topOverflow = -minY + CURVE_VIEWPORT_MARGIN;
|
|
525
|
+
const bottomOverflow = maxY + CURVE_VIEWPORT_MARGIN - viewport.height;
|
|
526
|
+
if (bottomOverflow > 0 && bottomOverflow >= topOverflow) return bottomOverflow;
|
|
527
|
+
if (topOverflow > 0) return -topOverflow;
|
|
528
|
+
return 0;
|
|
529
|
+
}
|
|
530
|
+
function isPoint(target) {
|
|
531
|
+
return typeof target === "object" && target !== null && !("boundingBox" in target) && typeof target.x === "number" && typeof target.y === "number";
|
|
532
|
+
}
|
|
533
|
+
var MISCLICK_OFFSET_MIN = 5;
|
|
534
|
+
var MISCLICK_OFFSET_MAX = 15;
|
|
535
|
+
async function maybeMisclickBeat(ctx, box, targetPoint) {
|
|
536
|
+
if (!ctx.rng.chance(ctx.personality.mouse.misclickProbability)) return;
|
|
537
|
+
if (cursorAlreadyOnTarget(ctx.getMousePosition(), box, targetPoint)) return;
|
|
538
|
+
const viewport = ctx.page.viewportSize();
|
|
539
|
+
const misclickPoint = box ? pickMisclickOutsideBox(box, ctx.rng, viewport) : pickMisclickAroundPoint(targetPoint, ctx.rng, viewport);
|
|
540
|
+
if (misclickPoint === null) return;
|
|
541
|
+
await walkBezierTo(misclickPoint, ctx);
|
|
542
|
+
ctx.setMousePosition(misclickPoint);
|
|
543
|
+
const realizeMs = computeDwellTime(
|
|
544
|
+
ctx.personality.dwell.preClickMs,
|
|
545
|
+
ctx.personality.dwell.preClickJitter,
|
|
546
|
+
ctx.personality,
|
|
547
|
+
ctx.speed,
|
|
548
|
+
ctx.rng
|
|
549
|
+
);
|
|
550
|
+
if (realizeMs > 0) await sleep(realizeMs);
|
|
551
|
+
}
|
|
552
|
+
function cursorAlreadyOnTarget(current, box, targetPoint) {
|
|
553
|
+
if (box) {
|
|
554
|
+
return current.x >= box.x && current.x <= box.x + box.width && current.y >= box.y && current.y <= box.y + box.height;
|
|
555
|
+
}
|
|
556
|
+
const dx = current.x - targetPoint.x;
|
|
557
|
+
const dy = current.y - targetPoint.y;
|
|
558
|
+
return Math.hypot(dx, dy) < MISCLICK_OFFSET_MIN;
|
|
559
|
+
}
|
|
560
|
+
function pickMisclickOutsideBox(box, rng, viewport) {
|
|
561
|
+
const edge = rng.nextInt(0, 4);
|
|
562
|
+
const offset = rng.nextFloat(MISCLICK_OFFSET_MIN, MISCLICK_OFFSET_MAX);
|
|
563
|
+
const along = rng.nextFloat(0.2, 0.8);
|
|
564
|
+
let x;
|
|
565
|
+
let y;
|
|
566
|
+
if (edge === 0) {
|
|
567
|
+
x = box.x + box.width * along;
|
|
568
|
+
y = box.y - offset;
|
|
569
|
+
} else if (edge === 1) {
|
|
570
|
+
x = box.x + box.width + offset;
|
|
571
|
+
y = box.y + box.height * along;
|
|
572
|
+
} else if (edge === 2) {
|
|
573
|
+
x = box.x + box.width * along;
|
|
574
|
+
y = box.y + box.height + offset;
|
|
575
|
+
} else {
|
|
576
|
+
x = box.x - offset;
|
|
577
|
+
y = box.y + box.height * along;
|
|
578
|
+
}
|
|
579
|
+
if (viewport) {
|
|
580
|
+
x = clamp2(x, 0, viewport.width - 1);
|
|
581
|
+
y = clamp2(y, 0, viewport.height - 1);
|
|
582
|
+
}
|
|
583
|
+
const insideBox = x >= box.x && x <= box.x + box.width && y >= box.y && y <= box.y + box.height;
|
|
584
|
+
if (insideBox) return null;
|
|
585
|
+
return { x, y };
|
|
586
|
+
}
|
|
587
|
+
function pickMisclickAroundPoint(target, rng, viewport) {
|
|
588
|
+
const angle = rng.nextFloat(0, Math.PI * 2);
|
|
589
|
+
const distance = rng.nextFloat(MISCLICK_OFFSET_MIN, MISCLICK_OFFSET_MAX);
|
|
590
|
+
let x = target.x + Math.cos(angle) * distance;
|
|
591
|
+
let y = target.y + Math.sin(angle) * distance;
|
|
592
|
+
if (viewport) {
|
|
593
|
+
x = clamp2(x, 0, viewport.width - 1);
|
|
594
|
+
y = clamp2(y, 0, viewport.height - 1);
|
|
595
|
+
}
|
|
596
|
+
if (x === target.x && y === target.y) return null;
|
|
597
|
+
return { x, y };
|
|
598
|
+
}
|
|
599
|
+
function pickClickPoint(box, rng, clickSpread) {
|
|
131
600
|
const cx = box.x + box.width / 2;
|
|
132
601
|
const cy = box.y + box.height / 2;
|
|
133
|
-
const
|
|
134
|
-
const
|
|
602
|
+
const sigmaX = box.width * clickSpread;
|
|
603
|
+
const sigmaY = box.height * clickSpread;
|
|
604
|
+
const x = clamp2(cx + rng.nextGaussian(0, sigmaX), box.x, box.x + box.width);
|
|
605
|
+
const y = clamp2(cy + rng.nextGaussian(0, sigmaY), box.y, box.y + box.height);
|
|
135
606
|
return { x, y };
|
|
136
607
|
}
|
|
137
608
|
function computeTravelTime(path, personality, speed, rng) {
|
|
@@ -149,9 +620,10 @@ function computeTravelTime(path, personality, speed, rng) {
|
|
|
149
620
|
return Math.max(0, total);
|
|
150
621
|
}
|
|
151
622
|
function describeTarget(target) {
|
|
623
|
+
if (isPoint(target)) return `point(${target.x}, ${target.y})`;
|
|
152
624
|
return typeof target === "string" ? target : target.toString?.() ?? "locator";
|
|
153
625
|
}
|
|
154
|
-
function
|
|
626
|
+
function clamp2(value, min, max) {
|
|
155
627
|
return value < min ? min : value > max ? max : value;
|
|
156
628
|
}
|
|
157
629
|
async function executeRead(target, ctx, options = {}) {
|
|
@@ -635,178 +1107,6 @@ async function startCapture(page, options = {}) {
|
|
|
635
1107
|
}
|
|
636
1108
|
};
|
|
637
1109
|
}
|
|
638
|
-
var RESERVED_TARGETS = /* @__PURE__ */ new Set(["natural", "end", "top"]);
|
|
639
|
-
async function executeScroll(target, ctx, options = {}) {
|
|
640
|
-
const { page, personality, rng, speed } = ctx;
|
|
641
|
-
const speedFactor = speedModeFactor(speed);
|
|
642
|
-
const axis = options.axis ?? "y";
|
|
643
|
-
const container = resolveWithin(options.within, ctx);
|
|
644
|
-
const geom = container ? await readContainerGeometry(container, axis) : await readWindowGeometry(page, axis);
|
|
645
|
-
if (!geom) {
|
|
646
|
-
return { from: 0, to: 0, distance: 0, durationMs: 0 };
|
|
647
|
-
}
|
|
648
|
-
const from = geom.current;
|
|
649
|
-
const targetPos = await resolveTarget(target, ctx, geom, container, axis, options.block);
|
|
650
|
-
const to = clamp2(targetPos, 0, Math.max(0, geom.total - geom.viewport));
|
|
651
|
-
const distance = to - from;
|
|
652
|
-
if (distance === 0) {
|
|
653
|
-
return { from, to, distance: 0, durationMs: 0 };
|
|
654
|
-
}
|
|
655
|
-
if (speed === "instant") {
|
|
656
|
-
if (container) {
|
|
657
|
-
await container.evaluate(
|
|
658
|
-
(el, args) => {
|
|
659
|
-
const a = args;
|
|
660
|
-
if (a.axis === "x") el.scrollTo(a.pos, el.scrollTop);
|
|
661
|
-
else el.scrollTo(el.scrollLeft, a.pos);
|
|
662
|
-
},
|
|
663
|
-
{ axis, pos: to }
|
|
664
|
-
);
|
|
665
|
-
} else {
|
|
666
|
-
await page.evaluate(
|
|
667
|
-
(args) => {
|
|
668
|
-
if (args.axis === "x") window.scrollTo(args.pos, window.scrollY);
|
|
669
|
-
else window.scrollTo(window.scrollX, args.pos);
|
|
670
|
-
},
|
|
671
|
-
{ axis, pos: to }
|
|
672
|
-
);
|
|
673
|
-
}
|
|
674
|
-
return { from, to, distance, durationMs: 0 };
|
|
675
|
-
}
|
|
676
|
-
const segments = core.planScroll(from, to, personality.scroll, rng, {
|
|
677
|
-
forceOvershoot: options.overshoot,
|
|
678
|
-
withPauses: options.withPauses,
|
|
679
|
-
personalitySpeed: personality.speed,
|
|
680
|
-
speedFactor
|
|
681
|
-
});
|
|
682
|
-
if (container && geom.hover) {
|
|
683
|
-
await page.mouse.move(geom.hover.x, geom.hover.y);
|
|
684
|
-
}
|
|
685
|
-
const startedAt = Date.now();
|
|
686
|
-
await walkSegments(page, segments, axis, container);
|
|
687
|
-
const durationMs = Date.now() - startedAt;
|
|
688
|
-
return { from, to, distance, durationMs };
|
|
689
|
-
}
|
|
690
|
-
function resolveWithin(within, ctx) {
|
|
691
|
-
if (!within) return null;
|
|
692
|
-
return typeof within === "string" ? ctx.page.locator(within) : within;
|
|
693
|
-
}
|
|
694
|
-
async function readWindowGeometry(page, axis) {
|
|
695
|
-
const g = await page.evaluate((a) => {
|
|
696
|
-
if (a === "x") {
|
|
697
|
-
return {
|
|
698
|
-
current: window.scrollX,
|
|
699
|
-
viewport: window.innerWidth,
|
|
700
|
-
total: Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0)
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
return {
|
|
704
|
-
current: window.scrollY,
|
|
705
|
-
viewport: window.innerHeight,
|
|
706
|
-
total: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0)
|
|
707
|
-
};
|
|
708
|
-
}, axis);
|
|
709
|
-
return { current: g.current, viewport: g.viewport, total: g.total };
|
|
710
|
-
}
|
|
711
|
-
async function readContainerGeometry(container, axis) {
|
|
712
|
-
return container.evaluate((el, a) => {
|
|
713
|
-
const rect = el.getBoundingClientRect();
|
|
714
|
-
const isX = a === "x";
|
|
715
|
-
return {
|
|
716
|
-
current: isX ? el.scrollLeft : el.scrollTop,
|
|
717
|
-
viewport: isX ? el.clientWidth : el.clientHeight,
|
|
718
|
-
total: isX ? el.scrollWidth : el.scrollHeight,
|
|
719
|
-
hover: {
|
|
720
|
-
x: rect.left + rect.width / 2,
|
|
721
|
-
y: rect.top + rect.height / 2
|
|
722
|
-
}
|
|
723
|
-
};
|
|
724
|
-
}, axis).catch(() => null);
|
|
725
|
-
}
|
|
726
|
-
async function resolveTarget(target, ctx, geom, container, axis, block = "start") {
|
|
727
|
-
if (target === void 0 || target === "natural") return geom.current + geom.viewport;
|
|
728
|
-
if (target === "end") return geom.total;
|
|
729
|
-
if (target === "top") return 0;
|
|
730
|
-
if (typeof target === "object" && "by" in target) return geom.current + target.by;
|
|
731
|
-
if (typeof target === "object" && "to" in target) return target.to;
|
|
732
|
-
const elementLocator = typeof target === "string" && !RESERVED_TARGETS.has(target) ? ctx.page.locator(target) : typeof target === "string" ? null : target;
|
|
733
|
-
if (!elementLocator) return geom.current + geom.viewport;
|
|
734
|
-
return container ? resolveElementWithinContainer(elementLocator, container, geom, axis, block) : resolveElementInWindow(elementLocator, geom, axis, block);
|
|
735
|
-
}
|
|
736
|
-
async function resolveElementInWindow(elementLocator, geom, axis, block) {
|
|
737
|
-
const rect = await elementLocator.boundingBox().catch(() => null);
|
|
738
|
-
if (!rect) return geom.current;
|
|
739
|
-
const relStart = axis === "x" ? rect.x : rect.y;
|
|
740
|
-
const length = axis === "x" ? rect.width : rect.height;
|
|
741
|
-
const absoluteStart = geom.current + relStart;
|
|
742
|
-
const absoluteEnd = absoluteStart + length;
|
|
743
|
-
if (block === "start") return absoluteStart;
|
|
744
|
-
if (block === "end") return absoluteEnd - geom.viewport;
|
|
745
|
-
if (block === "nearest") {
|
|
746
|
-
if (relStart >= 0 && relStart + length <= geom.viewport) return geom.current;
|
|
747
|
-
if (relStart < 0) return absoluteStart;
|
|
748
|
-
return absoluteEnd - geom.viewport;
|
|
749
|
-
}
|
|
750
|
-
return absoluteStart - (geom.viewport - length) / 2;
|
|
751
|
-
}
|
|
752
|
-
async function resolveElementWithinContainer(elementLocator, container, geom, axis, block) {
|
|
753
|
-
const rects = await container.evaluate(
|
|
754
|
-
(containerEl, args) => {
|
|
755
|
-
const elementEl = args.sel ? document.querySelector(args.sel) : null;
|
|
756
|
-
const targetEl = elementEl ?? containerEl.querySelector(":scope > *");
|
|
757
|
-
if (!targetEl) return null;
|
|
758
|
-
const cRect = containerEl.getBoundingClientRect();
|
|
759
|
-
const eRect = targetEl.getBoundingClientRect();
|
|
760
|
-
return args.axis === "x" ? { relStart: eRect.left - cRect.left, length: eRect.width } : { relStart: eRect.top - cRect.top, length: eRect.height };
|
|
761
|
-
},
|
|
762
|
-
{ sel: await locatorSelector(elementLocator), axis }
|
|
763
|
-
).catch(() => null);
|
|
764
|
-
if (!rects) return geom.current;
|
|
765
|
-
const offsetStart = rects.relStart + geom.current;
|
|
766
|
-
const offsetEnd = offsetStart + rects.length;
|
|
767
|
-
if (block === "start") return offsetStart;
|
|
768
|
-
if (block === "end") return offsetEnd - geom.viewport;
|
|
769
|
-
if (block === "nearest") {
|
|
770
|
-
if (rects.relStart >= 0 && rects.relStart + rects.length <= geom.viewport) {
|
|
771
|
-
return geom.current;
|
|
772
|
-
}
|
|
773
|
-
if (rects.relStart < 0) return offsetStart;
|
|
774
|
-
return offsetEnd - geom.viewport;
|
|
775
|
-
}
|
|
776
|
-
return offsetStart - (geom.viewport - rects.length) / 2;
|
|
777
|
-
}
|
|
778
|
-
async function locatorSelector(locator) {
|
|
779
|
-
const s = locator.toString?.();
|
|
780
|
-
if (typeof s !== "string") return null;
|
|
781
|
-
const match = /locator\(['"](.+?)['"]/.exec(s);
|
|
782
|
-
if (!match) return null;
|
|
783
|
-
const raw = match[1] ?? "";
|
|
784
|
-
const eq = raw.indexOf("=");
|
|
785
|
-
return eq > 0 && /^[a-z]+$/.test(raw.slice(0, eq)) ? raw.slice(eq + 1) : raw;
|
|
786
|
-
}
|
|
787
|
-
async function walkSegments(page, segments, axis, container) {
|
|
788
|
-
for (const segment of segments) {
|
|
789
|
-
if (segment.delayBeforeMs > 0) await sleep(segment.delayBeforeMs);
|
|
790
|
-
if (segment.delta === 0) continue;
|
|
791
|
-
if (container) {
|
|
792
|
-
await container.evaluate(
|
|
793
|
-
(el, args) => {
|
|
794
|
-
const a = args;
|
|
795
|
-
if (a.axis === "x") el.scrollLeft += a.delta;
|
|
796
|
-
else el.scrollTop += a.delta;
|
|
797
|
-
},
|
|
798
|
-
{ axis, delta: segment.delta }
|
|
799
|
-
);
|
|
800
|
-
} else if (axis === "x") {
|
|
801
|
-
await page.mouse.wheel(segment.delta, 0);
|
|
802
|
-
} else {
|
|
803
|
-
await page.mouse.wheel(0, segment.delta);
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
function clamp2(value, min, max) {
|
|
808
|
-
return value < min ? min : value > max ? max : value;
|
|
809
|
-
}
|
|
810
1110
|
|
|
811
1111
|
// src/mouse-helper/index.ts
|
|
812
1112
|
var CURSOR_PATH = "M 0 0 L 16 6 L 8 9.5 L 5 19 Z";
|
|
@@ -971,6 +1271,23 @@ async function createHuman(page, options = {}) {
|
|
|
971
1271
|
}
|
|
972
1272
|
}
|
|
973
1273
|
let lastMousePosition = options.initialMousePosition ?? { x: 0, y: 0 };
|
|
1274
|
+
const mouseCtx = () => ({
|
|
1275
|
+
page,
|
|
1276
|
+
personality,
|
|
1277
|
+
rng,
|
|
1278
|
+
speed,
|
|
1279
|
+
getMousePosition: () => lastMousePosition,
|
|
1280
|
+
setMousePosition: (point) => {
|
|
1281
|
+
lastMousePosition = point;
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1284
|
+
const describeMouseTarget = (target) => {
|
|
1285
|
+
if (typeof target === "string") return target;
|
|
1286
|
+
if ("x" in target && "y" in target && typeof target.x === "number") {
|
|
1287
|
+
return `point(${target.x}, ${target.y})`;
|
|
1288
|
+
}
|
|
1289
|
+
return target.toString?.() ?? "locator";
|
|
1290
|
+
};
|
|
974
1291
|
return {
|
|
975
1292
|
personality,
|
|
976
1293
|
speed,
|
|
@@ -980,29 +1297,65 @@ async function createHuman(page, options = {}) {
|
|
|
980
1297
|
});
|
|
981
1298
|
},
|
|
982
1299
|
async click(target) {
|
|
983
|
-
const description =
|
|
1300
|
+
const description = describeMouseTarget(target);
|
|
984
1301
|
await performAction({ type: "click", params: { target: description } }, async () => {
|
|
985
|
-
await executeClick(target,
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1302
|
+
await executeClick(target, mouseCtx());
|
|
1303
|
+
});
|
|
1304
|
+
},
|
|
1305
|
+
async rightClick(target) {
|
|
1306
|
+
const description = describeMouseTarget(target);
|
|
1307
|
+
await performAction({ type: "rightClick", params: { target: description } }, async () => {
|
|
1308
|
+
await executeClick(target, mouseCtx(), { button: "right" });
|
|
1309
|
+
});
|
|
1310
|
+
},
|
|
1311
|
+
async hover(target) {
|
|
1312
|
+
const description = describeMouseTarget(target);
|
|
1313
|
+
await performAction({ type: "hover", params: { target: description } }, async () => {
|
|
1314
|
+
await executeHover(target, mouseCtx());
|
|
1315
|
+
});
|
|
1316
|
+
},
|
|
1317
|
+
async move(target) {
|
|
1318
|
+
const description = describeMouseTarget(target);
|
|
1319
|
+
await performAction({ type: "move", params: { target: description } }, async () => {
|
|
1320
|
+
await executeMove(target, mouseCtx());
|
|
1321
|
+
});
|
|
1322
|
+
},
|
|
1323
|
+
async drag(from, to) {
|
|
1324
|
+
const fromDesc = describeMouseTarget(from);
|
|
1325
|
+
const toDesc = describeMouseTarget(to);
|
|
1326
|
+
await performAction({ type: "drag", params: { from: fromDesc, to: toDesc } }, async () => {
|
|
1327
|
+
await executeDrag(from, to, mouseCtx());
|
|
995
1328
|
});
|
|
996
1329
|
},
|
|
997
1330
|
async type(target, value) {
|
|
998
|
-
const description =
|
|
1331
|
+
const description = describeMouseTarget(target);
|
|
999
1332
|
await performAction(
|
|
1000
1333
|
{ type: "type", params: { target: description, length: value.length } },
|
|
1001
1334
|
async () => {
|
|
1335
|
+
if (speed !== "instant" && value.length > 0) {
|
|
1336
|
+
await executeClick(target, mouseCtx());
|
|
1337
|
+
}
|
|
1002
1338
|
await executeType(target, value, { page, personality, rng, speed });
|
|
1003
1339
|
}
|
|
1004
1340
|
);
|
|
1005
1341
|
},
|
|
1342
|
+
async paste(target, value) {
|
|
1343
|
+
const description = describeMouseTarget(target);
|
|
1344
|
+
await performAction(
|
|
1345
|
+
{ type: "paste", params: { target: description, length: value.length } },
|
|
1346
|
+
async () => {
|
|
1347
|
+
if (speed !== "instant" && value.length > 0) {
|
|
1348
|
+
await executeClick(target, mouseCtx());
|
|
1349
|
+
}
|
|
1350
|
+
await executePaste(target, value, { page, personality, rng, speed });
|
|
1351
|
+
}
|
|
1352
|
+
);
|
|
1353
|
+
},
|
|
1354
|
+
async press(key) {
|
|
1355
|
+
await performAction({ type: "press", params: { key } }, async () => {
|
|
1356
|
+
await executePress(key, { page, personality, rng, speed });
|
|
1357
|
+
});
|
|
1358
|
+
},
|
|
1006
1359
|
async read(target, options2) {
|
|
1007
1360
|
const description = describeReadTarget(target);
|
|
1008
1361
|
return performAction(
|
|
@@ -1086,6 +1439,52 @@ async function createHuman(page, options = {}) {
|
|
|
1086
1439
|
speed,
|
|
1087
1440
|
events
|
|
1088
1441
|
});
|
|
1442
|
+
},
|
|
1443
|
+
// ────────────────────────────────────────────────────────────────────
|
|
1444
|
+
// Thin re-exports of common Playwright `Page` methods. See the `Human`
|
|
1445
|
+
// interface for the rationale; implementations forward unchanged.
|
|
1446
|
+
// ────────────────────────────────────────────────────────────────────
|
|
1447
|
+
screenshot(opts) {
|
|
1448
|
+
return page.screenshot(opts);
|
|
1449
|
+
},
|
|
1450
|
+
pageText() {
|
|
1451
|
+
return page.innerText("body");
|
|
1452
|
+
},
|
|
1453
|
+
content() {
|
|
1454
|
+
return page.content();
|
|
1455
|
+
},
|
|
1456
|
+
url() {
|
|
1457
|
+
return page.url();
|
|
1458
|
+
},
|
|
1459
|
+
title() {
|
|
1460
|
+
return page.title();
|
|
1461
|
+
},
|
|
1462
|
+
async reload(opts) {
|
|
1463
|
+
await performAction({ type: "reload", params: {} }, async () => {
|
|
1464
|
+
await page.reload(opts);
|
|
1465
|
+
});
|
|
1466
|
+
},
|
|
1467
|
+
async goBack(opts) {
|
|
1468
|
+
await performAction({ type: "goBack", params: {} }, async () => {
|
|
1469
|
+
await page.goBack(opts);
|
|
1470
|
+
});
|
|
1471
|
+
},
|
|
1472
|
+
async goForward(opts) {
|
|
1473
|
+
await performAction({ type: "goForward", params: {} }, async () => {
|
|
1474
|
+
await page.goForward(opts);
|
|
1475
|
+
});
|
|
1476
|
+
},
|
|
1477
|
+
waitForLoadState(state, opts) {
|
|
1478
|
+
return page.waitForLoadState(state, opts);
|
|
1479
|
+
},
|
|
1480
|
+
waitForURL(url, opts) {
|
|
1481
|
+
return page.waitForURL(url, opts);
|
|
1482
|
+
},
|
|
1483
|
+
setViewportSize(size) {
|
|
1484
|
+
return page.setViewportSize(size);
|
|
1485
|
+
},
|
|
1486
|
+
pdf(opts) {
|
|
1487
|
+
return page.pdf(opts);
|
|
1089
1488
|
}
|
|
1090
1489
|
};
|
|
1091
1490
|
}
|