@humanjs/playwright 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2,6 +2,512 @@
2
2
 
3
3
  var core = require('@humanjs/core');
4
4
 
5
+ // src/index.ts
6
+
7
+ // src/internal/timing.ts
8
+ function sleep(ms) {
9
+ return ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
10
+ }
11
+ function speedModeFactor(speed) {
12
+ switch (speed) {
13
+ case "fast":
14
+ return 0.5;
15
+ case "instant":
16
+ return 0;
17
+ default:
18
+ return 1;
19
+ }
20
+ }
21
+ function computeDwellTime(meanMs, jitter, personality, speed, rng) {
22
+ if (meanMs <= 0) return 0;
23
+ const jitterMag = meanMs * jitter;
24
+ const offset = rng.nextFloat(-jitterMag, jitterMag);
25
+ return Math.max(0, (meanMs + offset) * personality.speed * speedModeFactor(speed));
26
+ }
27
+
28
+ // src/keyboard/index.ts
29
+ async function executeType(target, value, ctx) {
30
+ const locator = typeof target === "string" ? ctx.page.locator(target) : target;
31
+ if (value.length === 0) {
32
+ return { characters: 0, typos: 0, corrections: 0 };
33
+ }
34
+ if (ctx.speed === "instant") {
35
+ await locator.pressSequentially(value, { delay: 0 });
36
+ return { characters: value.length, typos: 0, corrections: 0 };
37
+ }
38
+ await locator.focus();
39
+ const plan = core.planTypeKeystrokes(value, ctx.personality.typing, ctx.rng, {
40
+ personalitySpeed: ctx.personality.speed,
41
+ speedFactor: speedModeFactor(ctx.speed)
42
+ });
43
+ let typos = 0;
44
+ let corrections = 0;
45
+ for (const step of plan) {
46
+ if (step.delayBeforeMs > 0) await sleep(step.delayBeforeMs);
47
+ await dispatchKey(ctx.page, step.key);
48
+ if (step.isTypo) typos++;
49
+ if (step.isCorrection) corrections++;
50
+ }
51
+ return { characters: value.length, typos, corrections };
52
+ }
53
+ async function dispatchKey(page, key) {
54
+ if (key.length > 1 || key.charCodeAt(0) < 128) {
55
+ await page.keyboard.press(key);
56
+ } else {
57
+ await page.keyboard.insertText(key);
58
+ }
59
+ }
60
+
61
+ // src/internal/mouse-walk.ts
62
+ async function walkMouseAlongPath(page, path, durationMs) {
63
+ if (path.length === 0) return;
64
+ const stepDelayMs = path.length > 1 && durationMs > 0 ? durationMs / (path.length - 1) : 0;
65
+ for (let i = 0; i < path.length; i++) {
66
+ const point = path[i];
67
+ if (!point) continue;
68
+ await page.mouse.move(point.x, point.y);
69
+ if (i < path.length - 1 && stepDelayMs > 0) {
70
+ await sleep(stepDelayMs);
71
+ }
72
+ }
73
+ }
74
+
75
+ // src/mouse/index.ts
76
+ async function executeClick(target, ctx) {
77
+ const locator = typeof target === "string" ? ctx.page.locator(target) : target;
78
+ if (ctx.speed === "instant") {
79
+ const box2 = await locator.boundingBox();
80
+ await locator.click();
81
+ const center = box2 ? { x: box2.x + box2.width / 2, y: box2.y + box2.height / 2 } : ctx.getMousePosition();
82
+ ctx.setMousePosition(center);
83
+ return { target: center };
84
+ }
85
+ const box = await locator.boundingBox();
86
+ if (!box) {
87
+ throw new Error(
88
+ `Cannot click: element not found or has no bounding box (target: ${describeTarget(target)})`
89
+ );
90
+ }
91
+ const targetPoint = pickClickPoint(box, ctx.rng);
92
+ const startPoint = ctx.getMousePosition();
93
+ const rawPath = core.bezierPath(startPoint, targetPoint, ctx.rng, {
94
+ curvature: ctx.personality.mouse.curvature
95
+ });
96
+ const path = core.humanizePath(rawPath, ctx.rng);
97
+ const travelMs = computeTravelTime(path, ctx.personality, ctx.speed, ctx.rng);
98
+ await walkMouseAlongPath(ctx.page, path, travelMs);
99
+ const preClickMs = computeDwellTime(
100
+ ctx.personality.dwell.preClickMs,
101
+ ctx.personality.dwell.preClickJitter,
102
+ ctx.personality,
103
+ ctx.speed,
104
+ ctx.rng
105
+ );
106
+ if (preClickMs > 0) await sleep(preClickMs);
107
+ ctx.setMousePosition(targetPoint);
108
+ await ctx.page.mouse.click(targetPoint.x, targetPoint.y);
109
+ const postActionMs = computeDwellTime(
110
+ ctx.personality.dwell.postActionMs,
111
+ ctx.personality.dwell.postActionJitter,
112
+ ctx.personality,
113
+ ctx.speed,
114
+ ctx.rng
115
+ );
116
+ if (postActionMs > 0) await sleep(postActionMs);
117
+ return { target: targetPoint };
118
+ }
119
+ function pickClickPoint(box, rng) {
120
+ const cx = box.x + box.width / 2;
121
+ const cy = box.y + box.height / 2;
122
+ const x = clamp(cx + rng.nextGaussian(0, box.width / 8), box.x, box.x + box.width);
123
+ const y = clamp(cy + rng.nextGaussian(0, box.height / 8), box.y, box.y + box.height);
124
+ return { x, y };
125
+ }
126
+ function computeTravelTime(path, personality, speed, rng) {
127
+ let distance = 0;
128
+ for (let i = 1; i < path.length; i++) {
129
+ const prev = path[i - 1];
130
+ const curr = path[i];
131
+ if (!prev || !curr) continue;
132
+ distance += Math.hypot(curr.x - prev.x, curr.y - prev.y);
133
+ }
134
+ const baseTime = distance / 1e3 * personality.mouse.travelTimeMs;
135
+ const jitterMag = baseTime * personality.mouse.travelTimeJitter;
136
+ const jitter = rng.nextFloat(-jitterMag, jitterMag);
137
+ const total = (baseTime + jitter) * personality.speed * speedModeFactor(speed);
138
+ return Math.max(0, total);
139
+ }
140
+ function describeTarget(target) {
141
+ return typeof target === "string" ? target : target.toString?.() ?? "locator";
142
+ }
143
+ function clamp(value, min, max) {
144
+ return value < min ? min : value > max ? max : value;
145
+ }
146
+ async function executeRead(target, ctx, options = {}) {
147
+ let words = 0;
148
+ let locator;
149
+ if (typeof target === "string") {
150
+ locator = ctx.page.locator(target);
151
+ } else if ("words" in target) {
152
+ words = target.words;
153
+ } else if ("text" in target) {
154
+ words = core.countWords(target.text);
155
+ } else {
156
+ locator = target;
157
+ }
158
+ let autoDetectedKind;
159
+ if (locator) {
160
+ if (options.scrollIntoView) {
161
+ await locator.scrollIntoViewIfNeeded();
162
+ }
163
+ const text = await locator.innerText().catch(() => "");
164
+ words = core.countWords(text);
165
+ if (options.kind === void 0) {
166
+ autoDetectedKind = await detectKindFromTag(locator);
167
+ }
168
+ }
169
+ const kind = options.kind ?? autoDetectedKind ?? "prose";
170
+ const durationMs = core.computeReadingDwellMs(words, ctx.personality.reading, ctx.rng, {
171
+ kind,
172
+ wpmMultiplier: options.wpmMultiplier,
173
+ personalitySpeed: ctx.personality.speed,
174
+ speedFactor: speedModeFactor(ctx.speed)
175
+ });
176
+ if (options.withMotion && locator && durationMs > 0) {
177
+ const box = await locator.boundingBox().catch(() => null);
178
+ if (box) {
179
+ const lineRects = await getLineRects(locator).catch(() => []);
180
+ const path = core.planReadingScan(box, ctx.rng, {
181
+ start: ctx.getMousePosition(),
182
+ lineRects: lineRects.length > 0 ? lineRects : void 0
183
+ });
184
+ await walkMouseAlongPath(ctx.page, path, durationMs);
185
+ const final = path[path.length - 1];
186
+ if (final) ctx.setMousePosition(final);
187
+ return { words, durationMs, kind };
188
+ }
189
+ }
190
+ if (durationMs > 0) await sleep(durationMs);
191
+ return { words, durationMs, kind };
192
+ }
193
+ async function getLineRects(locator) {
194
+ const result = await locator.evaluate((el) => {
195
+ const walker = el.ownerDocument.createTreeWalker(el, 4);
196
+ const rects = [];
197
+ let node = walker.nextNode();
198
+ while (node) {
199
+ const text = node.textContent ?? "";
200
+ if (text.trim().length > 0) {
201
+ const range = el.ownerDocument.createRange();
202
+ range.selectNodeContents(node);
203
+ for (const r of Array.from(range.getClientRects())) {
204
+ if (r.width > 0 && r.height > 0) {
205
+ rects.push({ x: r.x, y: r.y, width: r.width, height: r.height });
206
+ }
207
+ }
208
+ }
209
+ node = walker.nextNode();
210
+ }
211
+ rects.sort((a, b) => a.y - b.y || a.x - b.x);
212
+ const merged = [];
213
+ for (const r of rects) {
214
+ const last = merged[merged.length - 1];
215
+ if (last && Math.abs(last.y - r.y) < 1 && r.x - (last.x + last.width) < 6) {
216
+ const right = Math.max(last.x + last.width, r.x + r.width);
217
+ const bottom = Math.max(last.y + last.height, r.y + r.height);
218
+ last.width = right - last.x;
219
+ last.height = bottom - last.y;
220
+ } else {
221
+ merged.push({ ...r });
222
+ }
223
+ }
224
+ return merged;
225
+ });
226
+ if (!Array.isArray(result)) return [];
227
+ return result.filter(
228
+ (r) => r != null && typeof r === "object" && typeof r.x === "number" && typeof r.y === "number" && typeof r.width === "number" && typeof r.height === "number"
229
+ );
230
+ }
231
+ async function detectKindFromTag(locator) {
232
+ const tag = await locator.evaluate((el) => el.tagName?.toLowerCase() ?? "").catch(() => "");
233
+ if (tag === "pre" || tag === "code") return "code";
234
+ return void 0;
235
+ }
236
+ var RESERVED_TARGETS = /* @__PURE__ */ new Set(["natural", "end", "top"]);
237
+ async function executeScroll(target, ctx, options = {}) {
238
+ const { page, personality, rng, speed } = ctx;
239
+ const speedFactor = speedModeFactor(speed);
240
+ const axis = options.axis ?? "y";
241
+ const container = resolveWithin(options.within, ctx);
242
+ const geom = container ? await readContainerGeometry(container, axis) : await readWindowGeometry(page, axis);
243
+ if (!geom) {
244
+ return { from: 0, to: 0, distance: 0, durationMs: 0 };
245
+ }
246
+ const from = geom.current;
247
+ const targetPos = await resolveTarget(target, ctx, geom, container, axis, options.block);
248
+ const to = clamp2(targetPos, 0, Math.max(0, geom.total - geom.viewport));
249
+ const distance = to - from;
250
+ if (distance === 0) {
251
+ return { from, to, distance: 0, durationMs: 0 };
252
+ }
253
+ if (speed === "instant") {
254
+ if (container) {
255
+ await container.evaluate(
256
+ (el, args) => {
257
+ const a = args;
258
+ if (a.axis === "x") el.scrollTo(a.pos, el.scrollTop);
259
+ else el.scrollTo(el.scrollLeft, a.pos);
260
+ },
261
+ { axis, pos: to }
262
+ );
263
+ } else {
264
+ await page.evaluate(
265
+ (args) => {
266
+ if (args.axis === "x") window.scrollTo(args.pos, window.scrollY);
267
+ else window.scrollTo(window.scrollX, args.pos);
268
+ },
269
+ { axis, pos: to }
270
+ );
271
+ }
272
+ return { from, to, distance, durationMs: 0 };
273
+ }
274
+ const segments = core.planScroll(from, to, personality.scroll, rng, {
275
+ forceOvershoot: options.overshoot,
276
+ withPauses: options.withPauses,
277
+ personalitySpeed: personality.speed,
278
+ speedFactor
279
+ });
280
+ if (container && geom.hover) {
281
+ await page.mouse.move(geom.hover.x, geom.hover.y);
282
+ }
283
+ const startedAt = Date.now();
284
+ await walkSegments(page, segments, axis, container);
285
+ const durationMs = Date.now() - startedAt;
286
+ return { from, to, distance, durationMs };
287
+ }
288
+ function resolveWithin(within, ctx) {
289
+ if (!within) return null;
290
+ return typeof within === "string" ? ctx.page.locator(within) : within;
291
+ }
292
+ async function readWindowGeometry(page, axis) {
293
+ const g = await page.evaluate((a) => {
294
+ if (a === "x") {
295
+ return {
296
+ current: window.scrollX,
297
+ viewport: window.innerWidth,
298
+ total: Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0)
299
+ };
300
+ }
301
+ return {
302
+ current: window.scrollY,
303
+ viewport: window.innerHeight,
304
+ total: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0)
305
+ };
306
+ }, axis);
307
+ return { current: g.current, viewport: g.viewport, total: g.total };
308
+ }
309
+ async function readContainerGeometry(container, axis) {
310
+ return container.evaluate((el, a) => {
311
+ const rect = el.getBoundingClientRect();
312
+ const isX = a === "x";
313
+ return {
314
+ current: isX ? el.scrollLeft : el.scrollTop,
315
+ viewport: isX ? el.clientWidth : el.clientHeight,
316
+ total: isX ? el.scrollWidth : el.scrollHeight,
317
+ hover: {
318
+ x: rect.left + rect.width / 2,
319
+ y: rect.top + rect.height / 2
320
+ }
321
+ };
322
+ }, axis).catch(() => null);
323
+ }
324
+ async function resolveTarget(target, ctx, geom, container, axis, block = "start") {
325
+ if (target === void 0 || target === "natural") return geom.current + geom.viewport;
326
+ if (target === "end") return geom.total;
327
+ if (target === "top") return 0;
328
+ if (typeof target === "object" && "by" in target) return geom.current + target.by;
329
+ if (typeof target === "object" && "to" in target) return target.to;
330
+ const elementLocator = typeof target === "string" && !RESERVED_TARGETS.has(target) ? ctx.page.locator(target) : typeof target === "string" ? null : target;
331
+ if (!elementLocator) return geom.current + geom.viewport;
332
+ return container ? resolveElementWithinContainer(elementLocator, container, geom, axis, block) : resolveElementInWindow(elementLocator, geom, axis, block);
333
+ }
334
+ async function resolveElementInWindow(elementLocator, geom, axis, block) {
335
+ const rect = await elementLocator.boundingBox().catch(() => null);
336
+ if (!rect) return geom.current;
337
+ const relStart = axis === "x" ? rect.x : rect.y;
338
+ const length = axis === "x" ? rect.width : rect.height;
339
+ const absoluteStart = geom.current + relStart;
340
+ const absoluteEnd = absoluteStart + length;
341
+ if (block === "start") return absoluteStart;
342
+ if (block === "end") return absoluteEnd - geom.viewport;
343
+ if (block === "nearest") {
344
+ if (relStart >= 0 && relStart + length <= geom.viewport) return geom.current;
345
+ if (relStart < 0) return absoluteStart;
346
+ return absoluteEnd - geom.viewport;
347
+ }
348
+ return absoluteStart - (geom.viewport - length) / 2;
349
+ }
350
+ async function resolveElementWithinContainer(elementLocator, container, geom, axis, block) {
351
+ const rects = await container.evaluate(
352
+ (containerEl, args) => {
353
+ const elementEl = args.sel ? document.querySelector(args.sel) : null;
354
+ const targetEl = elementEl ?? containerEl.querySelector(":scope > *");
355
+ if (!targetEl) return null;
356
+ const cRect = containerEl.getBoundingClientRect();
357
+ const eRect = targetEl.getBoundingClientRect();
358
+ return args.axis === "x" ? { relStart: eRect.left - cRect.left, length: eRect.width } : { relStart: eRect.top - cRect.top, length: eRect.height };
359
+ },
360
+ { sel: await locatorSelector(elementLocator), axis }
361
+ ).catch(() => null);
362
+ if (!rects) return geom.current;
363
+ const offsetStart = rects.relStart + geom.current;
364
+ const offsetEnd = offsetStart + rects.length;
365
+ if (block === "start") return offsetStart;
366
+ if (block === "end") return offsetEnd - geom.viewport;
367
+ if (block === "nearest") {
368
+ if (rects.relStart >= 0 && rects.relStart + rects.length <= geom.viewport) {
369
+ return geom.current;
370
+ }
371
+ if (rects.relStart < 0) return offsetStart;
372
+ return offsetEnd - geom.viewport;
373
+ }
374
+ return offsetStart - (geom.viewport - rects.length) / 2;
375
+ }
376
+ async function locatorSelector(locator) {
377
+ const s = locator.toString?.();
378
+ if (typeof s !== "string") return null;
379
+ const match = /locator\(['"](.+?)['"]/.exec(s);
380
+ if (!match) return null;
381
+ const raw = match[1] ?? "";
382
+ const eq = raw.indexOf("=");
383
+ return eq > 0 && /^[a-z]+$/.test(raw.slice(0, eq)) ? raw.slice(eq + 1) : raw;
384
+ }
385
+ async function walkSegments(page, segments, axis, container) {
386
+ for (const segment of segments) {
387
+ if (segment.delayBeforeMs > 0) await sleep(segment.delayBeforeMs);
388
+ if (segment.delta === 0) continue;
389
+ if (container) {
390
+ await container.evaluate(
391
+ (el, args) => {
392
+ const a = args;
393
+ if (a.axis === "x") el.scrollLeft += a.delta;
394
+ else el.scrollTop += a.delta;
395
+ },
396
+ { axis, delta: segment.delta }
397
+ );
398
+ } else if (axis === "x") {
399
+ await page.mouse.wheel(segment.delta, 0);
400
+ } else {
401
+ await page.mouse.wheel(0, segment.delta);
402
+ }
403
+ }
404
+ }
405
+ function clamp2(value, min, max) {
406
+ return value < min ? min : value > max ? max : value;
407
+ }
408
+
409
+ // src/mouse-helper/index.ts
410
+ var CURSOR_PATH = "M 0 0 L 16 6 L 8 9.5 L 5 19 Z";
411
+ async function installMouseHelper(target, options = {}) {
412
+ const config = {
413
+ color: options.color ?? "#f5a55c",
414
+ stroke: "#020203",
415
+ size: options.size ?? 22,
416
+ showClicks: options.showClicks ?? true,
417
+ haloOpacity: options.haloOpacity ?? 0.18,
418
+ path: CURSOR_PATH
419
+ };
420
+ await target.addInitScript(installScript, config);
421
+ const pages = "pages" in target ? target.pages() : [target];
422
+ await Promise.all(
423
+ pages.map((page) => page.evaluate(installScript, config).catch(() => void 0))
424
+ );
425
+ }
426
+ function installScript(config) {
427
+ const guardKey = "__humanjsMouseHelperInstalled";
428
+ const w = window;
429
+ if (w[guardKey]) return;
430
+ w[guardKey] = true;
431
+ const attach = () => {
432
+ const cursor = document.createElement("div");
433
+ cursor.setAttribute("aria-hidden", "true");
434
+ cursor.setAttribute("data-humanjs-cursor", "true");
435
+ cursor.style.cssText = [
436
+ "position: fixed",
437
+ "left: 0",
438
+ "top: 0",
439
+ `width: ${config.size}px`,
440
+ `height: ${config.size + 4}px`,
441
+ "pointer-events: none",
442
+ "z-index: 2147483647",
443
+ // Start visible at (0, 0) so the cursor is on screen from the moment
444
+ // the page loads — without this the helper looks like nothing happened
445
+ // until the first mousemove arrives.
446
+ "opacity: 1",
447
+ "transform: translate(0px, 0px)",
448
+ // CSS interpolates between successive `mousemove` updates so the
449
+ // cursor reads as continuous motion instead of discrete hops. Slightly
450
+ // longer than the path-walker's typical step interval (~30–80ms) so
451
+ // each tween is still settling when the next move lands → no pauses.
452
+ "transition: transform 110ms ease-out, opacity 0.18s ease-out",
453
+ "will-change: transform"
454
+ ].join("; ");
455
+ const haloRadius = Math.round(config.size * 0.6);
456
+ cursor.innerHTML = `
457
+ <svg width="${config.size}" height="${config.size + 4}" viewBox="0 0 22 24" style="overflow: visible;">
458
+ <circle cx="0" cy="0" r="${haloRadius}" fill="${config.color}" opacity="${config.haloOpacity}" />
459
+ <path d="${config.path}" fill="${config.color}" stroke="${config.stroke}" stroke-width="0.7" stroke-linejoin="round" />
460
+ </svg>
461
+ `;
462
+ document.body.appendChild(cursor);
463
+ let lastX = 0;
464
+ let lastY = 0;
465
+ const onMove = (e) => {
466
+ lastX = e.clientX;
467
+ lastY = e.clientY;
468
+ cursor.style.transform = `translate(${lastX}px, ${lastY}px)`;
469
+ cursor.style.opacity = "1";
470
+ };
471
+ window.addEventListener("mousemove", onMove, { capture: true, passive: true });
472
+ document.addEventListener("mousemove", onMove, { capture: true, passive: true });
473
+ document.addEventListener(
474
+ "mouseleave",
475
+ () => {
476
+ cursor.style.opacity = "0";
477
+ },
478
+ { capture: true, passive: true }
479
+ );
480
+ if (config.showClicks) {
481
+ const styleEl = document.createElement("style");
482
+ styleEl.textContent = "@keyframes humanjs-ripple { 0% { transform: translate(-50%, -50%) scale(0.4); opacity: 0.9; } 100% { transform: translate(-50%, -50%) scale(2); opacity: 0; } }";
483
+ document.head.appendChild(styleEl);
484
+ window.addEventListener(
485
+ "mousedown",
486
+ () => {
487
+ const ripple = document.createElement("div");
488
+ ripple.style.cssText = [
489
+ "position: fixed",
490
+ `left: ${lastX}px`,
491
+ `top: ${lastY}px`,
492
+ "width: 28px",
493
+ "height: 28px",
494
+ "border-radius: 50%",
495
+ `border: 1.5px solid ${config.color}`,
496
+ "pointer-events: none",
497
+ "z-index: 2147483646",
498
+ "animation: humanjs-ripple 0.45s ease-out forwards"
499
+ ].join("; ");
500
+ document.body.appendChild(ripple);
501
+ window.setTimeout(() => ripple.remove(), 500);
502
+ },
503
+ { capture: true, passive: true }
504
+ );
505
+ }
506
+ };
507
+ if (document.body) attach();
508
+ else document.addEventListener("DOMContentLoaded", attach, { once: true });
509
+ }
510
+
5
511
  // src/index.ts
6
512
  async function createHuman(page, options = {}) {
7
513
  const personality = core.resolvePersonality(options.personality ?? "careful");
@@ -34,6 +540,7 @@ async function createHuman(page, options = {}) {
34
540
  throw error;
35
541
  }
36
542
  }
543
+ let lastMousePosition = options.initialMousePosition ?? { x: 0, y: 0 };
37
544
  return {
38
545
  personality,
39
546
  speed,
@@ -41,10 +548,100 @@ async function createHuman(page, options = {}) {
41
548
  await performAction({ type: "goto", params: { url } }, async () => {
42
549
  await page.goto(url);
43
550
  });
551
+ },
552
+ async click(target) {
553
+ const description = typeof target === "string" ? target : target.toString?.() ?? "locator";
554
+ await performAction({ type: "click", params: { target: description } }, async () => {
555
+ await executeClick(target, {
556
+ page,
557
+ personality,
558
+ rng,
559
+ speed,
560
+ getMousePosition: () => lastMousePosition,
561
+ setMousePosition: (point) => {
562
+ lastMousePosition = point;
563
+ }
564
+ });
565
+ });
566
+ },
567
+ async type(target, value) {
568
+ const description = typeof target === "string" ? target : target.toString?.() ?? "locator";
569
+ await performAction(
570
+ { type: "type", params: { target: description, length: value.length } },
571
+ async () => {
572
+ await executeType(target, value, { page, personality, rng, speed });
573
+ }
574
+ );
575
+ },
576
+ async read(target, options2) {
577
+ const description = describeReadTarget(target);
578
+ return performAction(
579
+ {
580
+ type: "read",
581
+ params: {
582
+ target: description,
583
+ kind: options2?.kind
584
+ }
585
+ },
586
+ () => executeRead(
587
+ target,
588
+ {
589
+ page,
590
+ personality,
591
+ rng,
592
+ speed,
593
+ // Read shares the session's tracked cursor position so an eye
594
+ // scan starts from where the last click left off, and the next
595
+ // click starts from where the scan ended.
596
+ getMousePosition: () => lastMousePosition,
597
+ setMousePosition: (point) => {
598
+ lastMousePosition = point;
599
+ }
600
+ },
601
+ options2
602
+ )
603
+ );
604
+ },
605
+ async scroll(target, options2) {
606
+ const description = describeScrollTarget(target);
607
+ return performAction(
608
+ {
609
+ type: "scroll",
610
+ params: { target: description }
611
+ },
612
+ () => executeScroll(target, { page, personality, rng, speed }, options2)
613
+ );
44
614
  }
45
615
  };
46
616
  }
617
+ function describeScrollTarget(target) {
618
+ if (target === void 0) return "natural";
619
+ if (typeof target === "string") return target;
620
+ if ("by" in target) return `by:${target.by}`;
621
+ if ("to" in target) return `to:${target.to}`;
622
+ return target.toString?.() ?? "locator";
623
+ }
624
+ function describeReadTarget(target) {
625
+ if (typeof target === "string") return target;
626
+ if ("words" in target && typeof target.words === "number") return `${target.words} words`;
627
+ if ("text" in target && typeof target.text === "string") {
628
+ return `text:${target.text.length} chars`;
629
+ }
630
+ return target.toString?.() ?? "locator";
631
+ }
47
632
 
633
+ Object.defineProperty(exports, "applyMicroJitter", {
634
+ enumerable: true,
635
+ get: function () { return core.applyMicroJitter; }
636
+ });
637
+ Object.defineProperty(exports, "applyVelocityProfile", {
638
+ enumerable: true,
639
+ get: function () { return core.applyVelocityProfile; }
640
+ });
641
+ Object.defineProperty(exports, "bezierPath", {
642
+ enumerable: true,
643
+ get: function () { return core.bezierPath; }
644
+ });
48
645
  Object.defineProperty(exports, "blend", {
49
646
  enumerable: true,
50
647
  get: function () { return core.blend; }
@@ -53,6 +650,14 @@ Object.defineProperty(exports, "careful", {
53
650
  enumerable: true,
54
651
  get: function () { return core.careful; }
55
652
  });
653
+ Object.defineProperty(exports, "computeReadingDwellMs", {
654
+ enumerable: true,
655
+ get: function () { return core.computeReadingDwellMs; }
656
+ });
657
+ Object.defineProperty(exports, "countWords", {
658
+ enumerable: true,
659
+ get: function () { return core.countWords; }
660
+ });
56
661
  Object.defineProperty(exports, "createRng", {
57
662
  enumerable: true,
58
663
  get: function () { return core.createRng; }
@@ -65,6 +670,18 @@ Object.defineProperty(exports, "fast", {
65
670
  enumerable: true,
66
671
  get: function () { return core.fast; }
67
672
  });
673
+ Object.defineProperty(exports, "humanizePath", {
674
+ enumerable: true,
675
+ get: function () { return core.humanizePath; }
676
+ });
677
+ Object.defineProperty(exports, "planScroll", {
678
+ enumerable: true,
679
+ get: function () { return core.planScroll; }
680
+ });
681
+ Object.defineProperty(exports, "planTypeKeystrokes", {
682
+ enumerable: true,
683
+ get: function () { return core.planTypeKeystrokes; }
684
+ });
68
685
  Object.defineProperty(exports, "precise", {
69
686
  enumerable: true,
70
687
  get: function () { return core.precise; }
@@ -74,5 +691,6 @@ Object.defineProperty(exports, "resolvePersonality", {
74
691
  get: function () { return core.resolvePersonality; }
75
692
  });
76
693
  exports.createHuman = createHuman;
694
+ exports.installMouseHelper = installMouseHelper;
77
695
  //# sourceMappingURL=index.cjs.map
78
696
  //# sourceMappingURL=index.cjs.map