@humanjs/playwright 0.2.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mouse/index.ts","../src/index.ts"],"names":["box","bezierPath","humanizePath","resolvePersonality","createRng"],"mappings":"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","file":"index.cjs","sourcesContent":["import { bezierPath, humanizePath, type Personality, type Point, type Rng } from '@humanjs/core';\nimport type { Locator, Page } from 'playwright';\nimport type { Speed } from '../index';\n\n/** Runtime dependencies for a humanized mouse action. */\nexport interface MouseContext {\n readonly page: Page;\n readonly personality: Personality;\n readonly rng: Rng;\n readonly speed: Speed;\n /** Last known mouse position — used as the path start. */\n readonly getMousePosition: () => Point;\n /** Updates the last known mouse position after a move/click. */\n readonly setMousePosition: (point: Point) => void;\n}\n\n/** Result of a click action, returned to the caller for observability. */\nexport interface ClickResult {\n /** Coordinates the click landed at. */\n readonly target: Point;\n}\n\n/**\n * Executes a humanized click on a target locator.\n *\n * Steps:\n * 1. Resolve the target's bounding box.\n * 2. Pick a point inside it — Gaussian-centered so we never click dead-center,\n * which is itself a bot signal.\n * 3. Generate a Bezier path from the current mouse position to the target.\n * 4. Apply velocity profile + micro-jitter via `humanizePath`.\n * 5. Walk the mouse along the path with timing scaled by personality + speed.\n * 6. Click at the target coordinates.\n *\n * In `speed: 'instant'`, all humanization is bypassed and Playwright's\n * native `locator.click()` is used directly.\n */\nexport async function executeClick(\n target: Locator | string,\n ctx: MouseContext,\n): Promise<ClickResult> {\n const locator = typeof target === 'string' ? ctx.page.locator(target) : target;\n\n if (ctx.speed === 'instant') {\n // Read the bounding box BEFORE the click — the click may navigate away\n // or remove the element, after which `boundingBox()` returns null.\n const box = await locator.boundingBox();\n await locator.click();\n const center = box\n ? { x: box.x + box.width / 2, y: box.y + box.height / 2 }\n : ctx.getMousePosition();\n ctx.setMousePosition(center);\n return { target: center };\n }\n\n const box = await locator.boundingBox();\n if (!box) {\n throw new Error(\n `Cannot click: element not found or has no bounding box (target: ${describeTarget(target)})`,\n );\n }\n\n const targetPoint = pickClickPoint(box, ctx.rng);\n const startPoint = ctx.getMousePosition();\n\n const rawPath = bezierPath(startPoint, targetPoint, ctx.rng, {\n curvature: ctx.personality.mouse.curvature,\n });\n const path = humanizePath(rawPath, ctx.rng);\n\n await walkMouseAlongPath(ctx.page, path, ctx.personality, ctx.rng, ctx.speed);\n\n // Hover dwell — a real user briefly settles on the target before clicking.\n const preClickMs = computeDwellTime(\n ctx.personality.dwell.preClickMs,\n ctx.personality.dwell.preClickJitter,\n ctx.personality,\n ctx.speed,\n ctx.rng,\n );\n if (preClickMs > 0) await sleep(preClickMs);\n\n // Commit the new position BEFORE the click side-effect. If the click throws\n // (page closed, target removed mid-flight), the next action still starts\n // from the correct mouse position.\n ctx.setMousePosition(targetPoint);\n await ctx.page.mouse.click(targetPoint.x, targetPoint.y);\n\n // Post-action dwell — a beat after the click before the next action.\n const postActionMs = computeDwellTime(\n ctx.personality.dwell.postActionMs,\n ctx.personality.dwell.postActionJitter,\n ctx.personality,\n ctx.speed,\n ctx.rng,\n );\n if (postActionMs > 0) await sleep(postActionMs);\n\n return { target: targetPoint };\n}\n\n/** Bounding box returned by Playwright's `Locator.boundingBox()`. */\ninterface BoundingBox {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * Picks a click point inside the bounding box. Gaussian-centered so the\n * majority of clicks land near the visual center but with natural spread.\n * Clamped to never fall outside the element.\n */\nfunction pickClickPoint(box: BoundingBox, rng: Rng): Point {\n const cx = box.x + box.width / 2;\n const cy = box.y + box.height / 2;\n // Spread is 1/8 of each dimension — most clicks fall within the middle\n // half of the element, but we never click dead-center deterministically.\n const x = clamp(cx + rng.nextGaussian(0, box.width / 8), box.x, box.x + box.width);\n const y = clamp(cy + rng.nextGaussian(0, box.height / 8), box.y, box.y + box.height);\n return { x, y };\n}\n\n/**\n * Walks the mouse along the path with timing scaled by personality + speed.\n * Sleeps between moves so the path is visible to observers (and to whatever\n * timing-based detector is watching).\n */\nasync function walkMouseAlongPath(\n page: Page,\n path: readonly Point[],\n personality: Personality,\n rng: Rng,\n speed: Speed,\n): Promise<void> {\n if (path.length === 0) return;\n const totalTimeMs = computeTravelTime(path, personality, speed, rng);\n const stepDelayMs = path.length > 1 ? totalTimeMs / (path.length - 1) : 0;\n\n for (let i = 0; i < path.length; i++) {\n const point = path[i];\n if (!point) continue;\n await page.mouse.move(point.x, point.y);\n if (i < path.length - 1 && stepDelayMs > 0) {\n await sleep(stepDelayMs);\n }\n }\n}\n\n/**\n * Travel time in ms for the given path.\n *\n * base = (totalDistance / 1000) * Personality.mouse.travelTimeMs\n * jitter = base * Personality.mouse.travelTimeJitter * rand[-1, 1]\n * total = (base + jitter) * Personality.speed * speedModeFactor\n */\nfunction computeTravelTime(\n path: readonly Point[],\n personality: Personality,\n speed: Speed,\n rng: Rng,\n): number {\n let distance = 0;\n for (let i = 1; i < path.length; i++) {\n const prev = path[i - 1];\n const curr = path[i];\n if (!prev || !curr) continue;\n distance += Math.hypot(curr.x - prev.x, curr.y - prev.y);\n }\n\n const baseTime = (distance / 1000) * personality.mouse.travelTimeMs;\n const jitterMag = baseTime * personality.mouse.travelTimeJitter;\n const jitter = rng.nextFloat(-jitterMag, jitterMag);\n const total = (baseTime + jitter) * personality.speed * speedModeFactor(speed);\n return Math.max(0, total);\n}\n\n/**\n * Dwell time in ms for a single pause.\n *\n * base = meanMs\n * jitter = base * jitterFraction * rand[-1, 1]\n * total = (base + jitter) * Personality.speed * speedModeFactor\n *\n * Returns 0 for zero/negative inputs so callers can skip the sleep entirely.\n */\nfunction computeDwellTime(\n meanMs: number,\n jitter: number,\n personality: Personality,\n speed: Speed,\n rng: Rng,\n): number {\n if (meanMs <= 0) return 0;\n const jitterMag = meanMs * jitter;\n const offset = rng.nextFloat(-jitterMag, jitterMag);\n return Math.max(0, (meanMs + offset) * personality.speed * speedModeFactor(speed));\n}\n\nfunction speedModeFactor(speed: Speed): number {\n switch (speed) {\n case 'fast':\n return 0.5;\n case 'instant':\n return 0;\n default:\n return 1;\n }\n}\n\nfunction describeTarget(target: Locator | string): string {\n return typeof target === 'string' ? target : (target.toString?.() ?? 'locator');\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return value < min ? min : value > max ? max : value;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import {\n type ActionResult,\n createRng,\n type HumanAction,\n type HumanPlugin,\n type Personality,\n type PersonalityConfig,\n type PluginContext,\n type Point,\n resolvePersonality,\n} from '@humanjs/core';\nimport type { Locator, Page } from 'playwright';\nimport { executeClick } from './mouse';\n\nexport type {\n ActionResult,\n ActionType,\n BezierPathOptions,\n DwellProfile,\n HumanAction,\n HumanizePathOptions,\n HumanPlugin,\n KnownActionType,\n MouseProfile,\n Personality,\n PersonalityConfig,\n PersonalityExtension,\n PluginContext,\n Point,\n PresetName,\n ReadingProfile,\n Rng,\n TypingProfile,\n} from '@humanjs/core';\n// Re-exports of the public core API so consumers have one import surface.\nexport {\n applyMicroJitter,\n applyVelocityProfile,\n bezierPath,\n blend,\n careful,\n createRng,\n distracted,\n fast,\n humanizePath,\n precise,\n resolvePersonality,\n} from '@humanjs/core';\n\n/**\n * How fast the humanized session runs.\n * - `'human'` — full humanization (default)\n * - `'fast'` — humanized but accelerated\n * - `'instant'` — bypass all humanization, straight Playwright\n */\nexport type Speed = 'fast' | 'human' | 'instant';\n\n/** Options for {@link createHuman}. */\nexport interface CreateHumanOptions {\n /** Personality preset, extension, or fully built personality. Defaults to `'careful'`. */\n readonly personality?: PersonalityConfig;\n /** Seed for the session's PRNG. Same seed produces identical trajectories. */\n readonly seed?: number | string;\n /** Speed mode. Defaults to `'human'`. */\n readonly speed?: Speed;\n /** Plugins installed on this session, invoked in registration order. */\n readonly plugins?: readonly HumanPlugin[];\n /**\n * Starting cursor position used as the origin of the first humanized path.\n * Defaults to `{ x: 0, y: 0 }`. Set this if you've already moved the cursor\n * (e.g. via `page.mouse.move`) before creating the session, so the first\n * click's path starts from the correct location.\n */\n readonly initialMousePosition?: Point;\n}\n\n/** A humanized Playwright session bound to a single `Page`. */\nexport interface Human {\n /** The resolved personality this session is using. */\n readonly personality: Personality;\n /** The speed mode this session was created with. */\n readonly speed: Speed;\n /**\n * Navigate to `url`. Plugins observe the action via `'goto'`; the underlying\n * `page.goto(url)` is awaited unchanged.\n */\n goto(url: string): Promise<void>;\n /**\n * Move the mouse along a humanized Bezier path to `target` and click.\n *\n * `target` accepts either a Playwright-compatible selector string (e.g.\n * `'button:has-text(\"Buy now\")'`) or a built `Locator`. The click point\n * inside the element is Gaussian-distributed around the center.\n *\n * In `speed: 'instant'`, all humanization is skipped and Playwright's\n * native `locator.click()` is used directly.\n */\n click(target: Locator | string): Promise<void>;\n}\n\n/**\n * Creates a humanized session bound to a Playwright `Page`.\n *\n * @example\n * ```ts\n * import { chromium } from 'playwright';\n * import { createHuman } from '@humanjs/playwright';\n *\n * const browser = await chromium.launch();\n * const page = await browser.newPage();\n *\n * const human = await createHuman(page, {\n * personality: 'careful',\n * seed: 'session-42',\n * });\n *\n * await human.goto('https://example.com');\n * ```\n */\nexport async function createHuman(page: Page, options: CreateHumanOptions = {}): Promise<Human> {\n const personality = resolvePersonality(options.personality ?? 'careful');\n const rng = createRng(options.seed);\n const speed = options.speed ?? 'human';\n const plugins = options.plugins ?? [];\n\n const context: PluginContext = { personality, rng };\n for (const plugin of plugins) {\n await plugin.install?.(context);\n }\n\n async function performAction<T>(action: HumanAction, actionFn: () => Promise<T>): Promise<T> {\n for (const plugin of plugins) {\n await plugin.beforeAction?.(action);\n }\n const startedAt = Date.now();\n try {\n const value = await actionFn();\n const result: ActionResult = {\n type: action.type,\n durationMs: Date.now() - startedAt,\n };\n for (const plugin of plugins) {\n await plugin.afterAction?.(action, result);\n }\n return value;\n } catch (error) {\n for (const plugin of plugins) {\n await plugin.onError?.(action, error);\n }\n throw error;\n }\n }\n\n let lastMousePosition: Point = options.initialMousePosition ?? { x: 0, y: 0 };\n\n return {\n personality,\n speed,\n async goto(url) {\n await performAction({ type: 'goto', params: { url } }, async () => {\n await page.goto(url);\n });\n },\n async click(target) {\n const description = typeof target === 'string' ? target : (target.toString?.() ?? 'locator');\n await performAction({ type: 'click', params: { target: description } }, async () => {\n await executeClick(target, {\n page,\n personality,\n rng,\n speed,\n getMousePosition: () => lastMousePosition,\n setMousePosition: (point) => {\n lastMousePosition = point;\n },\n });\n });\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/internal/timing.ts","../src/keyboard/index.ts","../src/internal/mouse-walk.ts","../src/mouse/index.ts","../src/reading/index.ts","../src/scroll/index.ts","../src/mouse-helper/index.ts","../src/index.ts"],"names":["planTypeKeystrokes","box","bezierPath","humanizePath","countWords","computeReadingDwellMs","planReadingScan","clamp","planScroll","resolvePersonality","createRng","options"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Personality, Rng } from '@humanjs/core';\nimport type { Speed } from '..';\n\n/**\n * Sleeps for `ms` milliseconds. Resolves immediately if `ms <= 0` so callers\n * can hand the result of `computeDwellTime` straight through without guards.\n */\nexport function sleep(ms: number): Promise<void> {\n return ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();\n}\n\n/**\n * Per-mode timing multiplier applied on top of personality.speed.\n * - `'human'`: 1 — full humanization\n * - `'fast'`: 0.5 — accelerated humanization (still humanized, just brisk)\n * - `'instant'`: 0 — no delays (CI / snapshot mode)\n */\nexport function speedModeFactor(speed: Speed): number {\n switch (speed) {\n case 'fast':\n return 0.5;\n case 'instant':\n return 0;\n default:\n return 1;\n }\n}\n\n/**\n * Computes a dwell time in ms with deterministic jitter and personality scaling.\n *\n * base = meanMs\n * jitter = base × jitterFraction × rand[-1, 1]\n * total = (base + jitter) × personality.speed × speedModeFactor(speed)\n *\n * Returns 0 for non-positive inputs so callers can skip the sleep entirely.\n */\nexport function computeDwellTime(\n meanMs: number,\n jitter: number,\n personality: Personality,\n speed: Speed,\n rng: Rng,\n): number {\n if (meanMs <= 0) return 0;\n const jitterMag = meanMs * jitter;\n const offset = rng.nextFloat(-jitterMag, jitterMag);\n return Math.max(0, (meanMs + offset) * personality.speed * speedModeFactor(speed));\n}\n","import { type Personality, planTypeKeystrokes, type Rng } from '@humanjs/core';\nimport type { Locator, Page } from 'playwright';\nimport type { Speed } from '../index';\nimport { sleep, speedModeFactor } from '../internal/timing';\n\n/** Runtime dependencies for a humanized keyboard action. */\nexport interface KeyboardContext {\n readonly page: Page;\n readonly personality: Personality;\n readonly rng: Rng;\n readonly speed: Speed;\n}\n\n/** Result of a typing action, returned to the caller for observability. */\nexport interface TypeResult {\n /** Number of characters in the input string. */\n readonly characters: number;\n /** Number of typos injected (with or without correction). */\n readonly typos: number;\n /** Number of typo corrections via Backspace. */\n readonly corrections: number;\n}\n\n/**\n * Executes a humanized typing pass over `value` on `target`.\n *\n * Planning (which keys to press, with what delays, in what order) is delegated\n * to `@humanjs/core`'s `planTypeKeystrokes`. This module is the thin Playwright\n * dispatcher: it focuses the target, walks the plan, and chooses the best\n * Playwright API per key:\n *\n * - Named keys (`Enter`, `Backspace`, `Tab`) and ASCII chars: `keyboard.press`\n * — fires keydown/press/up, so handlers (autocomplete, validation) run.\n * - Non-ASCII characters: `keyboard.insertText` — fires `input` events but\n * not keyboard events, since `keyboard.press` is keyboard-layout-aware\n * and can't reliably synthesize characters like `é` or `🎉` on every layout.\n *\n * In `speed: 'instant'`, the whole humanized loop is bypassed in favor of\n * `locator.pressSequentially(value, { delay: 0 })` — events still fire,\n * humanization is skipped.\n */\nexport async function executeType(\n target: Locator | string,\n value: string,\n ctx: KeyboardContext,\n): Promise<TypeResult> {\n const locator = typeof target === 'string' ? ctx.page.locator(target) : target;\n\n if (value.length === 0) {\n return { characters: 0, typos: 0, corrections: 0 };\n }\n\n if (ctx.speed === 'instant') {\n await locator.pressSequentially(value, { delay: 0 });\n return { characters: value.length, typos: 0, corrections: 0 };\n }\n\n await locator.focus();\n\n const plan = planTypeKeystrokes(value, ctx.personality.typing, ctx.rng, {\n personalitySpeed: ctx.personality.speed,\n speedFactor: speedModeFactor(ctx.speed),\n });\n\n let typos = 0;\n let corrections = 0;\n\n for (const step of plan) {\n if (step.delayBeforeMs > 0) await sleep(step.delayBeforeMs);\n await dispatchKey(ctx.page, step.key);\n if (step.isTypo) typos++;\n if (step.isCorrection) corrections++;\n }\n\n return { characters: value.length, typos, corrections };\n}\n\n/**\n * Dispatches a single planned key. Multi-char strings are treated as named\n * keys (`Enter`, `Backspace`). Single ASCII chars use the layout-aware\n * `keyboard.press`. Single non-ASCII chars fall back to `insertText` so the\n * adapter works on any keyboard layout, at the cost of skipping per-key events.\n */\nasync function dispatchKey(page: Page, key: string): Promise<void> {\n if (key.length > 1 || key.charCodeAt(0) < 128) {\n await page.keyboard.press(key);\n } else {\n await page.keyboard.insertText(key);\n }\n}\n","import type { Point } from '@humanjs/core';\nimport type { Page } from 'playwright';\nimport { sleep } from './timing';\n\n/**\n * Walks the mouse along `path` over `durationMs`, evenly spacing the steps.\n * Generic helper used by both the click executor (which derives `durationMs`\n * from `personality.mouse.travelTimeMs`) and the read executor (which uses\n * the reading dwell duration so the cursor finishes its scan exactly as the\n * dwell elapses).\n *\n * - Empty path is a no-op.\n * - `durationMs <= 0` jumps through the path without inter-step delays\n * (matches `speed: 'instant'` behavior).\n */\nexport async function walkMouseAlongPath(\n page: Page,\n path: readonly Point[],\n durationMs: number,\n): Promise<void> {\n if (path.length === 0) return;\n const stepDelayMs = path.length > 1 && durationMs > 0 ? durationMs / (path.length - 1) : 0;\n\n for (let i = 0; i < path.length; i++) {\n const point = path[i];\n if (!point) continue;\n await page.mouse.move(point.x, point.y);\n if (i < path.length - 1 && stepDelayMs > 0) {\n await sleep(stepDelayMs);\n }\n }\n}\n","import { bezierPath, humanizePath, type Personality, type Point, type Rng } from '@humanjs/core';\nimport type { Locator, Page } from 'playwright';\nimport type { Speed } from '../index';\nimport { walkMouseAlongPath } from '../internal/mouse-walk';\nimport { computeDwellTime, sleep, speedModeFactor } from '../internal/timing';\n\n/** Runtime dependencies for a humanized mouse action. */\nexport interface MouseContext {\n readonly page: Page;\n readonly personality: Personality;\n readonly rng: Rng;\n readonly speed: Speed;\n /** Last known mouse position — used as the path start. */\n readonly getMousePosition: () => Point;\n /** Updates the last known mouse position after a move/click. */\n readonly setMousePosition: (point: Point) => void;\n}\n\n/** Result of a click action, returned to the caller for observability. */\nexport interface ClickResult {\n /** Coordinates the click landed at. */\n readonly target: Point;\n}\n\n/**\n * Executes a humanized click on a target locator.\n *\n * Steps:\n * 1. Resolve the target's bounding box.\n * 2. Pick a point inside it — Gaussian-centered so we never click dead-center,\n * which is itself a bot signal.\n * 3. Generate a Bezier path from the current mouse position to the target.\n * 4. Apply velocity profile + micro-jitter via `humanizePath`.\n * 5. Walk the mouse along the path with timing scaled by personality + speed.\n * 6. Click at the target coordinates.\n *\n * In `speed: 'instant'`, all humanization is bypassed and Playwright's\n * native `locator.click()` is used directly.\n */\nexport async function executeClick(\n target: Locator | string,\n ctx: MouseContext,\n): Promise<ClickResult> {\n const locator = typeof target === 'string' ? ctx.page.locator(target) : target;\n\n if (ctx.speed === 'instant') {\n // Read the bounding box BEFORE the click — the click may navigate away\n // or remove the element, after which `boundingBox()` returns null.\n const box = await locator.boundingBox();\n await locator.click();\n const center = box\n ? { x: box.x + box.width / 2, y: box.y + box.height / 2 }\n : ctx.getMousePosition();\n ctx.setMousePosition(center);\n return { target: center };\n }\n\n const box = await locator.boundingBox();\n if (!box) {\n throw new Error(\n `Cannot click: element not found or has no bounding box (target: ${describeTarget(target)})`,\n );\n }\n\n const targetPoint = pickClickPoint(box, ctx.rng);\n const startPoint = ctx.getMousePosition();\n\n const rawPath = bezierPath(startPoint, targetPoint, ctx.rng, {\n curvature: ctx.personality.mouse.curvature,\n });\n const path = humanizePath(rawPath, ctx.rng);\n\n const travelMs = computeTravelTime(path, ctx.personality, ctx.speed, ctx.rng);\n await walkMouseAlongPath(ctx.page, path, travelMs);\n\n // Hover dwell — a real user briefly settles on the target before clicking.\n const preClickMs = computeDwellTime(\n ctx.personality.dwell.preClickMs,\n ctx.personality.dwell.preClickJitter,\n ctx.personality,\n ctx.speed,\n ctx.rng,\n );\n if (preClickMs > 0) await sleep(preClickMs);\n\n // Commit the new position BEFORE the click side-effect. If the click throws\n // (page closed, target removed mid-flight), the next action still starts\n // from the correct mouse position.\n ctx.setMousePosition(targetPoint);\n await ctx.page.mouse.click(targetPoint.x, targetPoint.y);\n\n // Post-action dwell — a beat after the click before the next action.\n const postActionMs = computeDwellTime(\n ctx.personality.dwell.postActionMs,\n ctx.personality.dwell.postActionJitter,\n ctx.personality,\n ctx.speed,\n ctx.rng,\n );\n if (postActionMs > 0) await sleep(postActionMs);\n\n return { target: targetPoint };\n}\n\n/** Bounding box returned by Playwright's `Locator.boundingBox()`. */\ninterface BoundingBox {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * Picks a click point inside the bounding box. Gaussian-centered so the\n * majority of clicks land near the visual center but with natural spread.\n * Clamped to never fall outside the element.\n */\nfunction pickClickPoint(box: BoundingBox, rng: Rng): Point {\n const cx = box.x + box.width / 2;\n const cy = box.y + box.height / 2;\n // Spread is 1/8 of each dimension — most clicks fall within the middle\n // half of the element, but we never click dead-center deterministically.\n const x = clamp(cx + rng.nextGaussian(0, box.width / 8), box.x, box.x + box.width);\n const y = clamp(cy + rng.nextGaussian(0, box.height / 8), box.y, box.y + box.height);\n return { x, y };\n}\n\n/**\n * Travel time in ms for the given path.\n *\n * base = (totalDistance / 1000) * Personality.mouse.travelTimeMs\n * jitter = base * Personality.mouse.travelTimeJitter * rand[-1, 1]\n * total = (base + jitter) * Personality.speed * speedModeFactor\n */\nfunction computeTravelTime(\n path: readonly Point[],\n personality: Personality,\n speed: Speed,\n rng: Rng,\n): number {\n let distance = 0;\n for (let i = 1; i < path.length; i++) {\n const prev = path[i - 1];\n const curr = path[i];\n if (!prev || !curr) continue;\n distance += Math.hypot(curr.x - prev.x, curr.y - prev.y);\n }\n\n const baseTime = (distance / 1000) * personality.mouse.travelTimeMs;\n const jitterMag = baseTime * personality.mouse.travelTimeJitter;\n const jitter = rng.nextFloat(-jitterMag, jitterMag);\n const total = (baseTime + jitter) * personality.speed * speedModeFactor(speed);\n return Math.max(0, total);\n}\n\nfunction describeTarget(target: Locator | string): string {\n return typeof target === 'string' ? target : (target.toString?.() ?? 'locator');\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return value < min ? min : value > max ? max : value;\n}\n","import {\n computeReadingDwellMs,\n countWords,\n type Personality,\n type Point,\n planReadingScan,\n type ReadKind,\n type Rng,\n} from '@humanjs/core';\nimport type { Locator, Page } from 'playwright';\nimport type { Speed } from '../index';\nimport { walkMouseAlongPath } from '../internal/mouse-walk';\nimport { sleep, speedModeFactor } from '../internal/timing';\n\n/** Runtime dependencies for a humanized reading dwell. */\nexport interface ReadingContext {\n readonly page: Page;\n readonly personality: Personality;\n readonly rng: Rng;\n readonly speed: Speed;\n /**\n * Last known cursor position. Used as the starting point for an eye-scan\n * when `withMotion: true`. Required even when motion is off — the read\n * adapter needs to know where the cursor *would* start if motion is later\n * enabled mid-session.\n */\n readonly getMousePosition: () => Point;\n /** Updates the tracked cursor position after a scan completes. */\n readonly setMousePosition: (point: Point) => void;\n}\n\n/**\n * What to read:\n * - `string`: a Playwright-compatible selector (matches `click()` / `type()`).\n * The element's `innerText` is resolved and word-counted.\n * - `Locator`: same, but you already have a Locator handle.\n * - `{ text }`: literal text (you have the string in hand, no selector).\n * - `{ words }`: pre-counted (bypass extraction entirely).\n */\nexport type ReadTarget = string | Locator | { readonly text: string } | { readonly words: number };\n\nexport interface ReadOptions {\n /**\n * Reading mode. Built-in multipliers on top of `personality.reading.wpm`:\n * - `'prose'`: 1.0× (default for non-code targets)\n * - `'code'`: 0.4× (default when target is a `<pre>` or `<code>` element)\n * - `'scan'`: 1.8× (skim mode, must be explicit)\n *\n * **Smart defaults:** when `kind` is omitted AND the target is a Locator/\n * selector, the adapter inspects the resolved element's tag — `<pre>` and\n * `<code>` auto-detect as `'code'`; everything else as `'prose'`. An\n * explicit `kind` always wins over auto-detection.\n */\n readonly kind?: ReadKind;\n /** Override the effective WPM multiplier directly. Wins over `kind`. */\n readonly wpmMultiplier?: number;\n /**\n * For selector/Locator targets: scroll the element into the viewport\n * before the dwell. A user can't read what isn't on screen. Defaults to\n * `false` — in most flows the caller has already scrolled there.\n */\n readonly scrollIntoView?: boolean;\n /**\n * For selector/Locator targets: trace a humanized cursor path through the\n * target's bounding box while the dwell elapses, like an eye tracking\n * lines of text. Off by default — opt in when you want the humanization\n * signal (or when reading-time mouseover behavior matters, e.g. tooltips\n * that activate while a real user's cursor moves over the content).\n *\n * The scan path is generated by `planReadingScan` from `@humanjs/core`\n * (same deterministic-by-seed contract as the rest of the library) and\n * walked over the computed dwell duration, so motion finishes exactly as\n * the read does. The cursor's tracked position is updated to the scan's\n * endpoint so the next click starts from where the eye landed.\n *\n * Ignored when the target is `{ text }` or `{ words }` (no bounding box).\n */\n readonly withMotion?: boolean;\n}\n\n/** Outcome of a read, returned to the caller for observability. */\nexport interface ReadResult {\n /** Number of words counted (after whitespace splitting / from caller's `{ words }`). */\n readonly words: number;\n /** Sleep duration in ms. Zero in `speed: 'instant'` or for zero-word inputs. */\n readonly durationMs: number;\n /** Final reading kind used (after auto-detection + explicit-override resolution). */\n readonly kind: ReadKind;\n}\n\n/**\n * Executes a humanized reading dwell.\n *\n * Flow:\n * 1. Resolve target → words + (optional) Locator\n * 2. Optionally scroll into view\n * 3. Resolve `kind` (explicit → auto-detected from tag → `'prose'`)\n * 4. Compute dwell via `computeReadingDwellMs` (jittered + scaled)\n * 5. Sleep\n *\n * Eye-scan cursor motion during the dwell (`withMotion`) is planned for a\n * follow-up release — see CLAUDE.md \"v1 must-have features.\" The base\n * dwell ships now because it completes the brand's third pillar.\n */\nexport async function executeRead(\n target: ReadTarget,\n ctx: ReadingContext,\n options: ReadOptions = {},\n): Promise<ReadResult> {\n // ── Resolve target → words (+ optional Locator for tag detection / scroll) ─\n let words = 0;\n let locator: Locator | undefined;\n\n if (typeof target === 'string') {\n locator = ctx.page.locator(target);\n } else if ('words' in target) {\n words = target.words;\n } else if ('text' in target) {\n words = countWords(target.text);\n } else {\n // Only Locator remains in the union after the discriminator checks above.\n locator = target;\n }\n\n let autoDetectedKind: ReadKind | undefined;\n\n if (locator) {\n if (options.scrollIntoView) {\n await locator.scrollIntoViewIfNeeded();\n }\n const text = await locator.innerText().catch(() => '');\n words = countWords(text);\n\n // Auto-detect kind from tag only when caller didn't specify one.\n if (options.kind === undefined) {\n autoDetectedKind = await detectKindFromTag(locator);\n }\n }\n\n const kind = options.kind ?? autoDetectedKind ?? 'prose';\n\n const durationMs = computeReadingDwellMs(words, ctx.personality.reading, ctx.rng, {\n kind,\n wpmMultiplier: options.wpmMultiplier,\n personalitySpeed: ctx.personality.speed,\n speedFactor: speedModeFactor(ctx.speed),\n });\n\n if (options.withMotion && locator && durationMs > 0) {\n // Walk a humanized scan path across the rendered text over the dwell\n // duration. `walkMouseAlongPath` paces the steps so motion ends exactly\n // as the read does — no extra sleep needed.\n const box = await locator.boundingBox().catch(() => null);\n if (box) {\n // Per-line rects make sweeps land on actual text edges, not the\n // element's full width — important for `<pre>` / `<code>` blocks\n // where individual lines are far narrower than the container.\n const lineRects = await getLineRects(locator).catch(() => []);\n const path = planReadingScan(box, ctx.rng, {\n start: ctx.getMousePosition(),\n lineRects: lineRects.length > 0 ? lineRects : undefined,\n });\n await walkMouseAlongPath(ctx.page, path, durationMs);\n const final = path[path.length - 1];\n if (final) ctx.setMousePosition(final);\n return { words, durationMs, kind };\n }\n // No box → fall through to a plain sleep (rare but possible if the\n // element disappeared between innerText() and boundingBox()).\n }\n\n if (durationMs > 0) await sleep(durationMs);\n\n return { words, durationMs, kind };\n}\n\n// ────────── helpers ──────────\n\n/**\n * Inspects the element's tag for the smart default. Only the narrowest\n * heuristic (literal `<pre>` / `<code>` tagName) — CSS class sniffing would\n * be too magical. Returns undefined for anything else, so the caller's\n * resolution chain falls back to `'prose'`.\n */\n/**\n * Returns one rect per visible line of *text* inside the element, in\n * viewport coordinates (same space as `locator.boundingBox()`).\n *\n * Walks text-node descendants and collects each text node's line rects via\n * `Range.getClientRects()`. This is the only way to get tight per-line\n * geometry when the target contains block-level descendants — calling\n * `selectNodeContents` over a `<ul>` or `<div>` would return the block's\n * content box (full container width), not the rendered text edges.\n *\n * Rects on the same visual line (e.g. text broken by `<strong>` / `<em>`)\n * are merged so the planner sweeps each line as a single L→R stroke\n * rather than zigzagging through inline siblings.\n */\nasync function getLineRects(\n locator: Locator,\n): Promise<Array<{ x: number; y: number; width: number; height: number }>> {\n const result = await locator.evaluate((el) => {\n type Rect = { x: number; y: number; width: number; height: number };\n // `4` === `NodeFilter.SHOW_TEXT`. Hardcoded so we don't depend on the\n // global `NodeFilter` being in the Node-side TS lib.\n const walker = el.ownerDocument.createTreeWalker(el, 4);\n const rects: Rect[] = [];\n let node = walker.nextNode();\n while (node) {\n const text = node.textContent ?? '';\n if (text.trim().length > 0) {\n const range = el.ownerDocument.createRange();\n range.selectNodeContents(node);\n for (const r of Array.from(range.getClientRects())) {\n if (r.width > 0 && r.height > 0) {\n rects.push({ x: r.x, y: r.y, width: r.width, height: r.height });\n }\n }\n }\n node = walker.nextNode();\n }\n\n // Merge rects that sit on the same visual line — text broken by inline\n // siblings (\"hello <strong>bold</strong> world\") produces three rects\n // with the same y. Merged, that becomes a single L→R stroke across the\n // full line of text.\n rects.sort((a, b) => a.y - b.y || a.x - b.x);\n const merged: Rect[] = [];\n for (const r of rects) {\n const last = merged[merged.length - 1];\n if (last && Math.abs(last.y - r.y) < 1 && r.x - (last.x + last.width) < 6) {\n const right = Math.max(last.x + last.width, r.x + r.width);\n const bottom = Math.max(last.y + last.height, r.y + r.height);\n last.width = right - last.x;\n last.height = bottom - last.y;\n } else {\n merged.push({ ...r });\n }\n }\n return merged;\n });\n // Defensive: validate the evaluate result is an array of rect-shaped\n // objects before handing it to the planner. Cheap, and keeps the planner\n // from ever seeing junk from a mocked evaluate or an exotic test harness.\n if (!Array.isArray(result)) return [];\n return result.filter(\n (r): r is { x: number; y: number; width: number; height: number } =>\n r != null &&\n typeof r === 'object' &&\n typeof (r as { x?: unknown }).x === 'number' &&\n typeof (r as { y?: unknown }).y === 'number' &&\n typeof (r as { width?: unknown }).width === 'number' &&\n typeof (r as { height?: unknown }).height === 'number',\n );\n}\n\nasync function detectKindFromTag(locator: Locator): Promise<ReadKind | undefined> {\n // `evaluate` runs the function in the browser context; Playwright's types\n // give the callback a DOM Element here without needing the lib.dom ref\n // on the Node-side tsconfig.\n const tag = await locator.evaluate((el) => el.tagName?.toLowerCase() ?? '').catch(() => '');\n if (tag === 'pre' || tag === 'code') return 'code';\n return undefined;\n}\n","import { type Personality, planScroll, type Rng, type ScrollSegment } from '@humanjs/core';\nimport type { Locator, Page } from 'playwright';\nimport type { Speed } from '../index';\nimport { sleep, speedModeFactor } from '../internal/timing';\n\n/** Runtime dependencies for a humanized scroll. */\nexport interface ScrollContext {\n readonly page: Page;\n readonly personality: Personality;\n readonly rng: Rng;\n readonly speed: Speed;\n}\n\n/**\n * What / where to scroll. The chosen axis (`'y'` by default, `'x'` via\n * `options.axis`) decides whether targets resolve along the vertical or\n * horizontal axis.\n *\n * - `'natural'`: scroll one viewport along the chosen axis (default).\n * - `'end'`: scroll to the far edge (bottom for `'y'`, right for `'x'`).\n * - `'top'`: scroll to the origin (top for `'y'`, left for `'x'`).\n * - `string`: a Playwright-compatible selector — scroll until in view.\n * - `Locator`: same, but you already have a Locator handle.\n * - `{ by: number }`: relative pixel delta (negative = up / left).\n * - `{ to: number }`: absolute scroll position on the chosen axis.\n */\nexport type ScrollTarget =\n | 'natural'\n | 'end'\n | 'top'\n | string\n | Locator\n | { readonly by: number }\n | { readonly to: number };\n\nexport interface ScrollOptions {\n /**\n * Force an overshoot + correction even if the personality wouldn't choose\n * one. Useful when you want the humanization signal regardless of\n * personality (e.g. demos).\n */\n readonly overshoot?: boolean;\n /**\n * Disable mid-scroll micro-pauses. Defaults to `true` (pauses enabled).\n * Set `false` for the smoothest possible motion.\n */\n readonly withPauses?: boolean;\n /**\n * For element targets only: where to align the element along the chosen\n * axis when the scroll ends. Mirrors `scrollIntoView({ block })`.\n * Defaults to `'start'`.\n *\n * - `'start'`: element's leading edge aligns with the viewport's leading\n * edge (top for `axis: 'y'`, left for `axis: 'x'`)\n * - `'center'`: element centers in the viewport\n * - `'end'`: element's trailing edge aligns with the viewport's trailing\n * edge (bottom for `'y'`, right for `'x'`)\n * - `'nearest'`: do the minimum scroll — stay put if the element is\n * already fully visible, otherwise scroll to the closest edge\n */\n readonly block?: 'start' | 'center' | 'end' | 'nearest';\n /**\n * Scroll inside a scrollable container instead of the page. Accepts a\n * Playwright-compatible selector or a built `Locator`. Every scroll\n * semantic (`'natural'`, `'top'`, `'end'`, `{ by }`, `{ to }`, element\n * targets, `block` alignment) applies relative to the container.\n *\n * In humanized speed modes, the cursor parks over the container's\n * center so the visible cursor reads as \"human hand on the wheel\"\n * (when paired with `installMouseHelper`), and each planned segment\n * advances the container's `scrollLeft` / `scrollTop` directly. Direct\n * property assignment is more reliable than dispatching wheel events\n * into nested overflow containers — Playwright's synthetic wheel\n * events don't always route past the cursor element. In `'instant'`\n * mode, the container's scroll position is set with a single\n * `scrollTo` call.\n *\n * Common use: chat threads, modal bodies, infinite-scroll feeds, any\n * `<div style=\"overflow-y: auto\">` that owns its own scrollbar.\n */\n readonly within?: string | Locator;\n /**\n * Which axis to scroll along. Defaults to `'y'` (vertical).\n *\n * Set `'x'` for horizontal scrolling — carousels, kanban boards,\n * sideways galleries. Every target shape still works: `'natural'`\n * scrolls one viewport-width right, `'top'` jumps to scrollLeft 0,\n * `'end'` to (scrollWidth - clientWidth), `{ by }` / `{ to }` apply to\n * the X-axis, and element targets scroll until the element is visible\n * horizontally.\n */\n readonly axis?: 'x' | 'y';\n}\n\n/** Outcome of a scroll, returned to the caller for observability. */\nexport interface ScrollResult {\n /** Starting scroll position along the chosen axis (scrollY or scrollX). */\n readonly from: number;\n /** Final scroll position the scroll aimed for, along the chosen axis. */\n readonly to: number;\n /** Signed pixel distance (`to - from`). */\n readonly distance: number;\n /** Total elapsed dwell + motion time in ms. Zero in `speed: 'instant'`. */\n readonly durationMs: number;\n}\n\n/** Reserved string targets that select a behavior rather than an element. */\nconst RESERVED_TARGETS = new Set(['natural', 'end', 'top']);\n\n/**\n * Generic axis description — the planner doesn't care whether it's driving\n * `window.scrollY` or an element's `scrollTop`. Both look like the same\n * 1-D problem: a current position, a viewport length, and a max scrollable\n * extent.\n */\ninterface ScrollGeometry {\n /** Current scroll position. */\n readonly current: number;\n /** Visible length (viewport height for window, clientHeight for container). */\n readonly viewport: number;\n /** Total scrollable extent (docHeight for window, scrollHeight for container). */\n readonly total: number;\n /**\n * For container scrolls: viewport-relative center coordinates the cursor\n * should park at so an `installMouseHelper` overlay reads as \"human hand\n * on the wheel\" during the scroll. Undefined for window scrolls (no\n * cursor parking — the page scrolls without a per-target hover).\n */\n readonly hover?: { x: number; y: number };\n}\n\n/**\n * Executes a humanized scroll on either the page or a scrollable container,\n * along the chosen axis.\n *\n * Flow:\n * 1. Resolve the scope: page (default) or a container if `within` is set.\n * 2. Read current position + viewport + total geometry for the chosen axis.\n * 3. Resolve target → final position on that axis.\n * 4. Plan segments via `planScroll`.\n * 5. Walk the segments — for page scrolls, dispatch `page.mouse.wheel`\n * events on the chosen axis. For containers, advance the element's\n * `scrollLeft` / `scrollTop` directly (wheel events don't always\n * route into nested overflow containers). Containers also park the\n * cursor at their center so an `installMouseHelper` overlay reads\n * as \"hand on the wheel.\"\n *\n * In `speed: 'instant'`, all humanization is bypassed:\n * - Page scrolls call `window.scrollTo(...)` with the new position on the\n * chosen axis and the existing position on the other.\n * - Container scrolls evaluate `el.scrollTo(...)` with the same shape.\n */\nexport async function executeScroll(\n target: ScrollTarget | undefined,\n ctx: ScrollContext,\n options: ScrollOptions = {},\n): Promise<ScrollResult> {\n const { page, personality, rng, speed } = ctx;\n const speedFactor = speedModeFactor(speed);\n const axis: 'x' | 'y' = options.axis ?? 'y';\n\n const container = resolveWithin(options.within, ctx);\n const geom = container\n ? await readContainerGeometry(container, axis)\n : await readWindowGeometry(page, axis);\n if (!geom) {\n // Container not found / not scrollable — nothing to do.\n return { from: 0, to: 0, distance: 0, durationMs: 0 };\n }\n\n const from = geom.current;\n const targetPos = await resolveTarget(target, ctx, geom, container, axis, options.block);\n const to = clamp(targetPos, 0, Math.max(0, geom.total - geom.viewport));\n const distance = to - from;\n\n if (distance === 0) {\n return { from, to, distance: 0, durationMs: 0 };\n }\n\n if (speed === 'instant') {\n if (container) {\n await container.evaluate(\n (el, args) => {\n const a = args as { axis: 'x' | 'y'; pos: number };\n if (a.axis === 'x') el.scrollTo(a.pos, el.scrollTop);\n else el.scrollTo(el.scrollLeft, a.pos);\n },\n { axis, pos: to },\n );\n } else {\n await page.evaluate(\n (args) => {\n if (args.axis === 'x') window.scrollTo(args.pos, window.scrollY);\n else window.scrollTo(window.scrollX, args.pos);\n },\n { axis, pos: to },\n );\n }\n return { from, to, distance, durationMs: 0 };\n }\n\n const segments = planScroll(from, to, personality.scroll, rng, {\n forceOvershoot: options.overshoot,\n withPauses: options.withPauses,\n personalitySpeed: personality.speed,\n speedFactor,\n });\n\n // For container scrolls, park the cursor over the container so the\n // visible cursor (when `installMouseHelper` is in use) reads as \"human\n // hand on the wheel.\" Actual scrolling goes through direct\n // `scrollLeft` / `scrollTop` assignment in `walkSegments` — Playwright's\n // synthetic wheel events don't reliably route into nested overflow\n // containers.\n if (container && geom.hover) {\n await page.mouse.move(geom.hover.x, geom.hover.y);\n }\n\n const startedAt = Date.now();\n await walkSegments(page, segments, axis, container);\n const durationMs = Date.now() - startedAt;\n\n return { from, to, distance, durationMs };\n}\n\n/** Coerces the `within` option into a Locator (or null when unset). */\nfunction resolveWithin(within: ScrollOptions['within'], ctx: ScrollContext): Locator | null {\n if (!within) return null;\n return typeof within === 'string' ? ctx.page.locator(within) : within;\n}\n\n/** Reads the page's current scroll geometry for the chosen axis. */\nasync function readWindowGeometry(page: Page, axis: 'x' | 'y'): Promise<ScrollGeometry> {\n const g = await page.evaluate((a: 'x' | 'y') => {\n if (a === 'x') {\n return {\n current: window.scrollX,\n viewport: window.innerWidth,\n total: Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0),\n };\n }\n return {\n current: window.scrollY,\n viewport: window.innerHeight,\n total: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0),\n };\n }, axis);\n return { current: g.current, viewport: g.viewport, total: g.total };\n}\n\n/**\n * Reads container scroll geometry for the chosen axis plus the center of\n * its viewport-relative bounding box (for cursor parking). Returns `null`\n * if the element resolves to nothing.\n */\nasync function readContainerGeometry(\n container: Locator,\n axis: 'x' | 'y',\n): Promise<ScrollGeometry | null> {\n return container\n .evaluate((el, a: 'x' | 'y') => {\n const rect = el.getBoundingClientRect();\n const isX = a === 'x';\n return {\n current: isX ? el.scrollLeft : el.scrollTop,\n viewport: isX ? el.clientWidth : el.clientHeight,\n total: isX ? el.scrollWidth : el.scrollHeight,\n hover: {\n x: rect.left + rect.width / 2,\n y: rect.top + rect.height / 2,\n },\n } as const;\n }, axis)\n .catch(() => null);\n}\n\n/**\n * Resolves a `ScrollTarget` to an absolute scroll position on the active\n * axis (window scroll or container scroll, vertical or horizontal).\n */\nasync function resolveTarget(\n target: ScrollTarget | undefined,\n ctx: ScrollContext,\n geom: ScrollGeometry,\n container: Locator | null,\n axis: 'x' | 'y',\n block: 'start' | 'center' | 'end' | 'nearest' = 'start',\n): Promise<number> {\n if (target === undefined || target === 'natural') return geom.current + geom.viewport;\n if (target === 'end') return geom.total;\n if (target === 'top') return 0;\n if (typeof target === 'object' && 'by' in target) return geom.current + target.by;\n if (typeof target === 'object' && 'to' in target) return target.to;\n\n // Element target — selector string or Locator.\n const elementLocator =\n typeof target === 'string' && !RESERVED_TARGETS.has(target)\n ? ctx.page.locator(target)\n : typeof target === 'string'\n ? null\n : target;\n if (!elementLocator) return geom.current + geom.viewport;\n\n return container\n ? resolveElementWithinContainer(elementLocator, container, geom, axis, block)\n : resolveElementInWindow(elementLocator, geom, axis, block);\n}\n\n/**\n * Computes the scroll position needed to align a window-level element per\n * `block` along the chosen axis. `rect.x` / `rect.y` are viewport-relative;\n * absolute position is `geom.current + rect.{axis}`.\n */\nasync function resolveElementInWindow(\n elementLocator: Locator,\n geom: ScrollGeometry,\n axis: 'x' | 'y',\n block: 'start' | 'center' | 'end' | 'nearest',\n): Promise<number> {\n const rect = await elementLocator.boundingBox().catch(() => null);\n if (!rect) return geom.current;\n const relStart = axis === 'x' ? rect.x : rect.y;\n const length = axis === 'x' ? rect.width : rect.height;\n const absoluteStart = geom.current + relStart;\n const absoluteEnd = absoluteStart + length;\n if (block === 'start') return absoluteStart;\n if (block === 'end') return absoluteEnd - geom.viewport;\n if (block === 'nearest') {\n if (relStart >= 0 && relStart + length <= geom.viewport) return geom.current;\n if (relStart < 0) return absoluteStart;\n return absoluteEnd - geom.viewport;\n }\n return absoluteStart - (geom.viewport - length) / 2;\n}\n\n/**\n * Computes the container's `scrollTop` / `scrollLeft` needed to align an\n * element per `block` along the chosen axis. Element offset from the\n * container's content origin =\n * `(element.rect.{axis} - container.rect.{axis}) + container.scroll{axis}`\n * regardless of whether the container is the element's positioning ancestor.\n */\nasync function resolveElementWithinContainer(\n elementLocator: Locator,\n container: Locator,\n geom: ScrollGeometry,\n axis: 'x' | 'y',\n block: 'start' | 'center' | 'end' | 'nearest',\n): Promise<number> {\n const rects = await container\n .evaluate(\n (containerEl, args: { sel: string | null; axis: 'x' | 'y' }) => {\n const elementEl = args.sel ? document.querySelector(args.sel) : null;\n // The element may not be a child of the container — handle that too.\n const targetEl = elementEl ?? (containerEl.querySelector(':scope > *') as Element | null);\n if (!targetEl) return null;\n const cRect = containerEl.getBoundingClientRect();\n const eRect = targetEl.getBoundingClientRect();\n return args.axis === 'x'\n ? { relStart: eRect.left - cRect.left, length: eRect.width }\n : { relStart: eRect.top - cRect.top, length: eRect.height };\n },\n { sel: await locatorSelector(elementLocator), axis },\n )\n .catch(() => null);\n if (!rects) return geom.current;\n // Offset from container's content origin = (visual delta) + scroll position.\n const offsetStart = rects.relStart + geom.current;\n const offsetEnd = offsetStart + rects.length;\n if (block === 'start') return offsetStart;\n if (block === 'end') return offsetEnd - geom.viewport;\n if (block === 'nearest') {\n if (rects.relStart >= 0 && rects.relStart + rects.length <= geom.viewport) {\n return geom.current;\n }\n if (rects.relStart < 0) return offsetStart;\n return offsetEnd - geom.viewport;\n }\n return offsetStart - (geom.viewport - rects.length) / 2;\n}\n\n/**\n * Best-effort recovery of the selector string a Locator wraps. Playwright's\n * `Locator.toString()` returns something like `locator('css=#foo')` — we\n * strip the wrapper to get the inner selector. Falls back to `null` when\n * the format isn't recognized; the caller treats that as \"no element.\"\n *\n * Fragile by design: depends on Playwright's internal `toString()` shape,\n * which isn't a stable public API. Used only when resolving element\n * targets *inside* a `within` container, since the executor needs to\n * `querySelector` from the container's perspective rather than the\n * document's. For chained Locators (e.g. `page.locator('foo').first()`)\n * the regex grabs the first quoted selector and ignores chained\n * refinements — good enough for the common case, but worth a richer\n * approach if Playwright ever changes the shape.\n */\nasync function locatorSelector(locator: Locator): Promise<string | null> {\n const s = locator.toString?.();\n if (typeof s !== 'string') return null;\n const match = /locator\\(['\"](.+?)['\"]/.exec(s);\n if (!match) return null;\n const raw = match[1] ?? '';\n // Strip an \"engine=\" prefix when present (Playwright sometimes prepends one).\n const eq = raw.indexOf('=');\n return eq > 0 && /^[a-z]+$/.test(raw.slice(0, eq)) ? raw.slice(eq + 1) : raw;\n}\n\n/**\n * Walks the planned segments. For page scrolls, dispatches wheel events\n * on the chosen axis — real wheel events trigger every page-level wheel\n * handler, which is part of HumanJS's \"dispatch what a human dispatches\"\n * promise.\n *\n * For container scrolls, advances `scrollLeft` / `scrollTop` directly via\n * `container.evaluate`. Playwright's `page.mouse.wheel` doesn't reliably\n * route into nested overflow containers (the event hits the element under\n * the cursor but may scroll a parent or no element instead). Direct\n * property assignment is deterministic — it always scrolls the target —\n * and the brand promise stays intact because the planner still owns the\n * bell-curve cadence, mid-scroll pauses, and overshoot.\n */\nasync function walkSegments(\n page: Page,\n segments: readonly ScrollSegment[],\n axis: 'x' | 'y',\n container: Locator | null,\n): Promise<void> {\n for (const segment of segments) {\n if (segment.delayBeforeMs > 0) await sleep(segment.delayBeforeMs);\n if (segment.delta === 0) continue;\n if (container) {\n await container.evaluate(\n (el, args) => {\n const a = args as { axis: 'x' | 'y'; delta: number };\n // Direct property assignment is more reliable than `scrollBy()`\n // — some flex/grid layouts have edge cases where scrollBy\n // becomes a no-op despite the element being scrollable.\n if (a.axis === 'x') el.scrollLeft += a.delta;\n else el.scrollTop += a.delta;\n },\n { axis, delta: segment.delta },\n );\n } else if (axis === 'x') {\n await page.mouse.wheel(segment.delta, 0);\n } else {\n await page.mouse.wheel(0, segment.delta);\n }\n }\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return value < min ? min : value > max ? max : value;\n}\n","/// <reference lib=\"dom\" />\n\nimport type { BrowserContext, Page } from 'playwright';\n\n/**\n * Canonical HumanJS cursor path — kept in lockstep with the SVG used by the\n * web app's `HumanCursorIcon`. Inlined here so the playwright package has no\n * dependency on `@humanjs/web` or DOM assets.\n */\nconst CURSOR_PATH = 'M 0 0 L 16 6 L 8 9.5 L 5 19 Z';\n\n/** Options for {@link installMouseHelper}. */\nexport interface InstallMouseHelperOptions {\n /** Cursor fill color. Defaults to the HumanJS amber `#f5a55c`. */\n readonly color?: string;\n /** Cursor visual size in pixels. Defaults to 22. */\n readonly size?: number;\n /**\n * Render a ripple at each `mousedown` position so clicks read on video.\n * Defaults to `true`.\n */\n readonly showClicks?: boolean;\n /** Halo opacity behind the cursor. Defaults to `0.18`. */\n readonly haloOpacity?: number;\n}\n\n/**\n * Installs a visual cursor overlay that follows every `mousemove` on each\n * page in the target. Real synthetic motion from Playwright (e.g.\n * `human.click()`, `human.read(..., { withMotion: true })`) is *already*\n * happening — the page just doesn't render a system cursor for it. This\n * helper injects a HumanJS-styled SVG cursor that listens to mousemove\n * events and follows them, making the motion visible in headed demos and\n * screen recordings.\n *\n * Re-runs on every navigation via `addInitScript`, so the overlay survives\n * page reloads. Idempotent — guard flag on `window` prevents double-install.\n *\n * Accepts either a `Page` (overlay applies to that page) or a\n * `BrowserContext` (overlay applies to every page in the context, including\n * pages opened later).\n *\n * @example\n * ```ts\n * const browser = await chromium.launch({ headless: false });\n * const context = await browser.newContext();\n * await installMouseHelper(context);\n * const page = await context.newPage();\n * // human-driven actions are now visible in the page\n * ```\n */\nexport async function installMouseHelper(\n target: BrowserContext | Page,\n options: InstallMouseHelperOptions = {},\n): Promise<void> {\n const config: HelperConfig = {\n color: options.color ?? '#f5a55c',\n stroke: '#020203',\n size: options.size ?? 22,\n showClicks: options.showClicks ?? true,\n haloOpacity: options.haloOpacity ?? 0.18,\n path: CURSOR_PATH,\n };\n\n // Register for every future navigation. Survives goto, reload, and frame\n // attachment without needing to re-install.\n await target.addInitScript(installScript, config);\n\n // ALSO inject into any pages that already exist. Without this, a page that\n // has already loaded (via goto or setContent) won't get the overlay until\n // the next navigation — and `page.setContent()` in particular replaces the\n // document, wiping anything the initial init-script run appended. Pages\n // opened later still get the overlay via the registered init script.\n const pages: Page[] = 'pages' in target ? target.pages() : [target];\n await Promise.all(\n pages.map((page) => page.evaluate(installScript, config).catch(() => undefined)),\n );\n}\n\ninterface HelperConfig {\n color: string;\n stroke: string;\n size: number;\n showClicks: boolean;\n haloOpacity: number;\n path: string;\n}\n\n/**\n * Serialized into the page and re-run on every navigation. No closures —\n * everything threads through `config`. `window` and `document` are the\n * page's globals at runtime, not the Node-side types.\n */\nfunction installScript(config: HelperConfig) {\n const guardKey = '__humanjsMouseHelperInstalled';\n const w = window as Window & { [guardKey]?: boolean };\n if (w[guardKey]) return;\n w[guardKey] = true;\n\n const attach = () => {\n const cursor = document.createElement('div');\n cursor.setAttribute('aria-hidden', 'true');\n cursor.setAttribute('data-humanjs-cursor', 'true');\n cursor.style.cssText = [\n 'position: fixed',\n 'left: 0',\n 'top: 0',\n `width: ${config.size}px`,\n `height: ${config.size + 4}px`,\n 'pointer-events: none',\n 'z-index: 2147483647',\n // Start visible at (0, 0) so the cursor is on screen from the moment\n // the page loads — without this the helper looks like nothing happened\n // until the first mousemove arrives.\n 'opacity: 1',\n 'transform: translate(0px, 0px)',\n // CSS interpolates between successive `mousemove` updates so the\n // cursor reads as continuous motion instead of discrete hops. Slightly\n // longer than the path-walker's typical step interval (~30–80ms) so\n // each tween is still settling when the next move lands → no pauses.\n 'transition: transform 110ms ease-out, opacity 0.18s ease-out',\n 'will-change: transform',\n ].join('; ');\n const haloRadius = Math.round(config.size * 0.6);\n cursor.innerHTML = `\n <svg width=\"${config.size}\" height=\"${config.size + 4}\" viewBox=\"0 0 22 24\" style=\"overflow: visible;\">\n <circle cx=\"0\" cy=\"0\" r=\"${haloRadius}\" fill=\"${config.color}\" opacity=\"${config.haloOpacity}\" />\n <path d=\"${config.path}\" fill=\"${config.color}\" stroke=\"${config.stroke}\" stroke-width=\"0.7\" stroke-linejoin=\"round\" />\n </svg>\n `;\n document.body.appendChild(cursor);\n\n let lastX = 0;\n let lastY = 0;\n const onMove = (e: MouseEvent) => {\n lastX = e.clientX;\n lastY = e.clientY;\n cursor.style.transform = `translate(${lastX}px, ${lastY}px)`;\n cursor.style.opacity = '1';\n };\n // Listen on both window and document so we catch synthetic mouse events\n // dispatched at either level (Playwright sends them via CDP, and the\n // target frame can vary by event source).\n window.addEventListener('mousemove', onMove, { capture: true, passive: true });\n document.addEventListener('mousemove', onMove, { capture: true, passive: true });\n document.addEventListener(\n 'mouseleave',\n () => {\n cursor.style.opacity = '0';\n },\n { capture: true, passive: true },\n );\n\n if (config.showClicks) {\n const styleEl = document.createElement('style');\n styleEl.textContent =\n '@keyframes humanjs-ripple { 0% { transform: translate(-50%, -50%) scale(0.4); opacity: 0.9; } 100% { transform: translate(-50%, -50%) scale(2); opacity: 0; } }';\n document.head.appendChild(styleEl);\n\n window.addEventListener(\n 'mousedown',\n () => {\n const ripple = document.createElement('div');\n ripple.style.cssText = [\n 'position: fixed',\n `left: ${lastX}px`,\n `top: ${lastY}px`,\n 'width: 28px',\n 'height: 28px',\n 'border-radius: 50%',\n `border: 1.5px solid ${config.color}`,\n 'pointer-events: none',\n 'z-index: 2147483646',\n 'animation: humanjs-ripple 0.45s ease-out forwards',\n ].join('; ');\n document.body.appendChild(ripple);\n window.setTimeout(() => ripple.remove(), 500);\n },\n { capture: true, passive: true },\n );\n }\n };\n\n if (document.body) attach();\n else document.addEventListener('DOMContentLoaded', attach, { once: true });\n}\n","import {\n type ActionResult,\n createRng,\n type HumanAction,\n type HumanPlugin,\n type Personality,\n type PersonalityConfig,\n type PluginContext,\n type Point,\n resolvePersonality,\n} from '@humanjs/core';\nimport type { Locator, Page } from 'playwright';\nimport { executeType } from './keyboard';\nimport { executeClick } from './mouse';\nimport { executeRead, type ReadOptions, type ReadResult, type ReadTarget } from './reading';\nimport { executeScroll, type ScrollOptions, type ScrollResult, type ScrollTarget } from './scroll';\n\nexport type {\n ActionResult,\n ActionType,\n BezierPathOptions,\n ComputeReadingDwellOptions,\n DwellProfile,\n HumanAction,\n HumanizePathOptions,\n HumanPlugin,\n Keystroke,\n KnownActionType,\n MouseProfile,\n Personality,\n PersonalityConfig,\n PersonalityExtension,\n PlanTypingOptions,\n PluginContext,\n Point,\n PresetName,\n ReadingProfile,\n ReadKind,\n Rng,\n ScrollProfile,\n ScrollSegment,\n TypingProfile,\n} from '@humanjs/core';\n// Re-exports of the public core API so consumers have one import surface.\nexport {\n applyMicroJitter,\n applyVelocityProfile,\n bezierPath,\n blend,\n careful,\n computeReadingDwellMs,\n countWords,\n createRng,\n distracted,\n fast,\n humanizePath,\n planScroll,\n planTypeKeystrokes,\n precise,\n resolvePersonality,\n} from '@humanjs/core';\nexport type { InstallMouseHelperOptions } from './mouse-helper';\nexport { installMouseHelper } from './mouse-helper';\nexport type { ReadOptions, ReadResult, ReadTarget } from './reading';\nexport type { ScrollOptions, ScrollResult, ScrollTarget } from './scroll';\n\n/**\n * How fast the humanized session runs.\n * - `'human'` — full humanization (default)\n * - `'fast'` — humanized but accelerated\n * - `'instant'` — bypass all humanization, straight Playwright\n */\nexport type Speed = 'fast' | 'human' | 'instant';\n\n/** Options for {@link createHuman}. */\nexport interface CreateHumanOptions {\n /** Personality preset, extension, or fully built personality. Defaults to `'careful'`. */\n readonly personality?: PersonalityConfig;\n /** Seed for the session's PRNG. Same seed produces identical trajectories. */\n readonly seed?: number | string;\n /** Speed mode. Defaults to `'human'`. */\n readonly speed?: Speed;\n /** Plugins installed on this session, invoked in registration order. */\n readonly plugins?: readonly HumanPlugin[];\n /**\n * Starting cursor position used as the origin of the first humanized path.\n * Defaults to `{ x: 0, y: 0 }`. Set this if you've already moved the cursor\n * (e.g. via `page.mouse.move`) before creating the session, so the first\n * click's path starts from the correct location.\n */\n readonly initialMousePosition?: Point;\n}\n\n/** A humanized Playwright session bound to a single `Page`. */\nexport interface Human {\n /** The resolved personality this session is using. */\n readonly personality: Personality;\n /** The speed mode this session was created with. */\n readonly speed: Speed;\n /**\n * Navigate to `url`. Plugins observe the action via `'goto'`; the underlying\n * `page.goto(url)` is awaited unchanged.\n */\n goto(url: string): Promise<void>;\n /**\n * Move the mouse along a humanized Bezier path to `target` and click.\n *\n * `target` accepts either a Playwright-compatible selector string (e.g.\n * `'button:has-text(\"Buy now\")'`) or a built `Locator`. The click point\n * inside the element is Gaussian-distributed around the center.\n *\n * In `speed: 'instant'`, all humanization is skipped and Playwright's\n * native `locator.click()` is used directly.\n */\n click(target: Locator | string): Promise<void>;\n /**\n * Type `value` into `target` with humanized per-key timing, optional typo\n * injection (with backspace recovery), and occasional think-pauses.\n *\n * Per-key `keydown`/`press`/`up` events fire for each character, so\n * handlers like autocomplete dropdowns still receive every keystroke.\n *\n * In `speed: 'instant'`, falls back to `locator.pressSequentially` with\n * zero inter-key delay — events still fire, but humanization is skipped.\n */\n type(target: Locator | string, value: string): Promise<void>;\n /**\n * Dwell as if reading `target` — the third pillar of humanization after\n * the cursor and the keyboard. Real users pause to read; HumanJS models\n * that pause from word count + the personality's reading WPM (+ jitter).\n *\n * **Targets:**\n * - `string`: a Playwright-compatible selector (matches `click()`/`type()`).\n * - `Locator`: a pre-built Locator.\n * - `{ text }`: literal text in hand (no DOM lookup).\n * - `{ words }`: pre-counted — skip text extraction entirely.\n *\n * **Smart defaults** (only when the caller doesn't override):\n * - `kind` auto-detects as `'code'` for `<pre>` and `<code>` tags;\n * everything else falls back to `'prose'`. Explicit `kind` always wins.\n * - `scrollIntoView: false` — most flows already scrolled to the content.\n *\n * Plugins observe `'read'` actions with `{ target, words, kind }` in params.\n * The text content itself is never echoed — passwords, tokens, and other\n * sensitive strings stay out of telemetry by default.\n *\n * In `speed: 'instant'`, dwell collapses to 0 ms but the action still fires\n * so observability stays consistent.\n *\n * Returns a {@link ReadResult} with the word count, final kind (after\n * auto-detection), and total dwell duration — useful for assertions in\n * tests or for surfacing reading metadata to a UI.\n */\n read(target: ReadTarget, options?: ReadOptions): Promise<ReadResult>;\n /**\n * Scroll the page (or a scrollable container) humanly. Multi-segment\n * motion with a bell-curve velocity profile, optional mid-scroll\n * micro-pauses, and (for the `distracted` personality) the occasional\n * overshoot + correction.\n *\n * **Targets:**\n * - `'natural'` (default): scroll one viewport in the chosen axis\n * - `'end'` / `'top'`: jump to the document/container edges, humanized\n * - `string`: a Playwright-compatible selector — scroll until in view\n * - `Locator`: same, but with a pre-built handle\n * - `{ by: n }`: relative pixel delta (negative = up / left)\n * - `{ to: n }`: absolute scroll position on the chosen axis\n *\n * **Options:** `axis: 'x' | 'y'` (default `'y'`) picks the direction;\n * `within: selector | Locator` scopes the scroll to a scrollable\n * container; `block: 'start' | 'center' | 'end' | 'nearest'` aligns\n * element targets; `overshoot` / `withPauses` toggle individual\n * humanization signals.\n *\n * Plugins observe `'scroll'` actions with `{ target }` in\n * `beforeAction`'s params (a human-readable description of the target).\n * The full {@link ScrollResult} (`from` / `to` / `distance` /\n * `durationMs`) is available via `afterAction`'s `result` argument.\n *\n * In `speed: 'instant'`, the page (or container) is moved with a single\n * `scrollTo` call. No wheel events, no segments — but the action still\n * fires for observability.\n *\n * Returns a {@link ScrollResult} for assertions in tests.\n */\n scroll(target?: ScrollTarget, options?: ScrollOptions): Promise<ScrollResult>;\n}\n\n/**\n * Creates a humanized session bound to a Playwright `Page`.\n *\n * @example\n * ```ts\n * import { chromium } from 'playwright';\n * import { createHuman } from '@humanjs/playwright';\n *\n * const browser = await chromium.launch();\n * const page = await browser.newPage();\n *\n * const human = await createHuman(page, {\n * personality: 'careful',\n * seed: 'session-42',\n * });\n *\n * await human.goto('https://example.com');\n * ```\n */\nexport async function createHuman(page: Page, options: CreateHumanOptions = {}): Promise<Human> {\n const personality = resolvePersonality(options.personality ?? 'careful');\n const rng = createRng(options.seed);\n const speed = options.speed ?? 'human';\n const plugins = options.plugins ?? [];\n\n const context: PluginContext = { personality, rng };\n for (const plugin of plugins) {\n await plugin.install?.(context);\n }\n\n async function performAction<T>(action: HumanAction, actionFn: () => Promise<T>): Promise<T> {\n for (const plugin of plugins) {\n await plugin.beforeAction?.(action);\n }\n const startedAt = Date.now();\n try {\n const value = await actionFn();\n const result: ActionResult = {\n type: action.type,\n durationMs: Date.now() - startedAt,\n };\n for (const plugin of plugins) {\n await plugin.afterAction?.(action, result);\n }\n return value;\n } catch (error) {\n for (const plugin of plugins) {\n await plugin.onError?.(action, error);\n }\n throw error;\n }\n }\n\n let lastMousePosition: Point = options.initialMousePosition ?? { x: 0, y: 0 };\n\n return {\n personality,\n speed,\n async goto(url) {\n await performAction({ type: 'goto', params: { url } }, async () => {\n await page.goto(url);\n });\n },\n async click(target) {\n const description = typeof target === 'string' ? target : (target.toString?.() ?? 'locator');\n await performAction({ type: 'click', params: { target: description } }, async () => {\n await executeClick(target, {\n page,\n personality,\n rng,\n speed,\n getMousePosition: () => lastMousePosition,\n setMousePosition: (point) => {\n lastMousePosition = point;\n },\n });\n });\n },\n async type(target, value) {\n const description = typeof target === 'string' ? target : (target.toString?.() ?? 'locator');\n // `value` itself is intentionally not echoed into params — typed input may\n // be sensitive (passwords, tokens). Expose length only for observability.\n await performAction(\n { type: 'type', params: { target: description, length: value.length } },\n async () => {\n await executeType(target, value, { page, personality, rng, speed });\n },\n );\n },\n async read(target, options) {\n const description = describeReadTarget(target);\n // Same privacy posture as `type`: never echo arbitrary content into\n // action params. `target` description, words (when known up front), and\n // kind are inert metadata; the text itself never lands here.\n return performAction(\n {\n type: 'read',\n params: {\n target: description,\n kind: options?.kind,\n },\n },\n () =>\n executeRead(\n target,\n {\n page,\n personality,\n rng,\n speed,\n // Read shares the session's tracked cursor position so an eye\n // scan starts from where the last click left off, and the next\n // click starts from where the scan ended.\n getMousePosition: () => lastMousePosition,\n setMousePosition: (point) => {\n lastMousePosition = point;\n },\n },\n options,\n ),\n );\n },\n async scroll(target, options) {\n const description = describeScrollTarget(target);\n return performAction(\n {\n type: 'scroll',\n params: { target: description },\n },\n () => executeScroll(target, { page, personality, rng, speed }, options),\n );\n },\n };\n}\n\n/**\n * Human-readable description of a scroll target for action params. Strings\n * (presets + selectors) pass through; objects describe their shape; the\n * Locator branch falls back to `toString()`.\n */\nfunction describeScrollTarget(target: ScrollTarget | undefined): string {\n if (target === undefined) return 'natural';\n if (typeof target === 'string') return target;\n if ('by' in target) return `by:${target.by}`;\n if ('to' in target) return `to:${target.to}`;\n return target.toString?.() ?? 'locator';\n}\n\n/**\n * Human-readable description of a read target for action params. Echoes\n * selectors and `{ words }` (both inert); abbreviates literal text to its\n * length so we never expose content even via accidental logging.\n */\nfunction describeReadTarget(target: ReadTarget): string {\n if (typeof target === 'string') return target;\n if ('words' in target && typeof target.words === 'number') return `${target.words} words`;\n if ('text' in target && typeof target.text === 'string') {\n return `text:${target.text.length} chars`;\n }\n return target.toString?.() ?? 'locator';\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,196 @@
1
- import { PersonalityConfig, HumanPlugin, Point, Personality } from '@humanjs/core';
2
- export { ActionResult, ActionType, BezierPathOptions, DwellProfile, HumanAction, HumanPlugin, HumanizePathOptions, KnownActionType, MouseProfile, Personality, PersonalityConfig, PersonalityExtension, PluginContext, Point, PresetName, ReadingProfile, Rng, TypingProfile, applyMicroJitter, applyVelocityProfile, bezierPath, blend, careful, createRng, distracted, fast, humanizePath, precise, resolvePersonality } from '@humanjs/core';
3
- import { Locator, Page } from 'playwright';
1
+ import { ReadKind, PersonalityConfig, HumanPlugin, Point, Personality } from '@humanjs/core';
2
+ export { ActionResult, ActionType, BezierPathOptions, ComputeReadingDwellOptions, DwellProfile, HumanAction, HumanPlugin, HumanizePathOptions, Keystroke, KnownActionType, MouseProfile, Personality, PersonalityConfig, PersonalityExtension, PlanTypingOptions, PluginContext, Point, PresetName, ReadKind, ReadingProfile, Rng, ScrollProfile, ScrollSegment, TypingProfile, applyMicroJitter, applyVelocityProfile, bezierPath, blend, careful, computeReadingDwellMs, countWords, createRng, distracted, fast, humanizePath, planScroll, planTypeKeystrokes, precise, resolvePersonality } from '@humanjs/core';
3
+ import { Locator, BrowserContext, Page } from 'playwright';
4
+
5
+ /**
6
+ * What to read:
7
+ * - `string`: a Playwright-compatible selector (matches `click()` / `type()`).
8
+ * The element's `innerText` is resolved and word-counted.
9
+ * - `Locator`: same, but you already have a Locator handle.
10
+ * - `{ text }`: literal text (you have the string in hand, no selector).
11
+ * - `{ words }`: pre-counted (bypass extraction entirely).
12
+ */
13
+ type ReadTarget = string | Locator | {
14
+ readonly text: string;
15
+ } | {
16
+ readonly words: number;
17
+ };
18
+ interface ReadOptions {
19
+ /**
20
+ * Reading mode. Built-in multipliers on top of `personality.reading.wpm`:
21
+ * - `'prose'`: 1.0× (default for non-code targets)
22
+ * - `'code'`: 0.4× (default when target is a `<pre>` or `<code>` element)
23
+ * - `'scan'`: 1.8× (skim mode, must be explicit)
24
+ *
25
+ * **Smart defaults:** when `kind` is omitted AND the target is a Locator/
26
+ * selector, the adapter inspects the resolved element's tag — `<pre>` and
27
+ * `<code>` auto-detect as `'code'`; everything else as `'prose'`. An
28
+ * explicit `kind` always wins over auto-detection.
29
+ */
30
+ readonly kind?: ReadKind;
31
+ /** Override the effective WPM multiplier directly. Wins over `kind`. */
32
+ readonly wpmMultiplier?: number;
33
+ /**
34
+ * For selector/Locator targets: scroll the element into the viewport
35
+ * before the dwell. A user can't read what isn't on screen. Defaults to
36
+ * `false` — in most flows the caller has already scrolled there.
37
+ */
38
+ readonly scrollIntoView?: boolean;
39
+ /**
40
+ * For selector/Locator targets: trace a humanized cursor path through the
41
+ * target's bounding box while the dwell elapses, like an eye tracking
42
+ * lines of text. Off by default — opt in when you want the humanization
43
+ * signal (or when reading-time mouseover behavior matters, e.g. tooltips
44
+ * that activate while a real user's cursor moves over the content).
45
+ *
46
+ * The scan path is generated by `planReadingScan` from `@humanjs/core`
47
+ * (same deterministic-by-seed contract as the rest of the library) and
48
+ * walked over the computed dwell duration, so motion finishes exactly as
49
+ * the read does. The cursor's tracked position is updated to the scan's
50
+ * endpoint so the next click starts from where the eye landed.
51
+ *
52
+ * Ignored when the target is `{ text }` or `{ words }` (no bounding box).
53
+ */
54
+ readonly withMotion?: boolean;
55
+ }
56
+ /** Outcome of a read, returned to the caller for observability. */
57
+ interface ReadResult {
58
+ /** Number of words counted (after whitespace splitting / from caller's `{ words }`). */
59
+ readonly words: number;
60
+ /** Sleep duration in ms. Zero in `speed: 'instant'` or for zero-word inputs. */
61
+ readonly durationMs: number;
62
+ /** Final reading kind used (after auto-detection + explicit-override resolution). */
63
+ readonly kind: ReadKind;
64
+ }
65
+
66
+ /**
67
+ * What / where to scroll. The chosen axis (`'y'` by default, `'x'` via
68
+ * `options.axis`) decides whether targets resolve along the vertical or
69
+ * horizontal axis.
70
+ *
71
+ * - `'natural'`: scroll one viewport along the chosen axis (default).
72
+ * - `'end'`: scroll to the far edge (bottom for `'y'`, right for `'x'`).
73
+ * - `'top'`: scroll to the origin (top for `'y'`, left for `'x'`).
74
+ * - `string`: a Playwright-compatible selector — scroll until in view.
75
+ * - `Locator`: same, but you already have a Locator handle.
76
+ * - `{ by: number }`: relative pixel delta (negative = up / left).
77
+ * - `{ to: number }`: absolute scroll position on the chosen axis.
78
+ */
79
+ type ScrollTarget = 'natural' | 'end' | 'top' | string | Locator | {
80
+ readonly by: number;
81
+ } | {
82
+ readonly to: number;
83
+ };
84
+ interface ScrollOptions {
85
+ /**
86
+ * Force an overshoot + correction even if the personality wouldn't choose
87
+ * one. Useful when you want the humanization signal regardless of
88
+ * personality (e.g. demos).
89
+ */
90
+ readonly overshoot?: boolean;
91
+ /**
92
+ * Disable mid-scroll micro-pauses. Defaults to `true` (pauses enabled).
93
+ * Set `false` for the smoothest possible motion.
94
+ */
95
+ readonly withPauses?: boolean;
96
+ /**
97
+ * For element targets only: where to align the element along the chosen
98
+ * axis when the scroll ends. Mirrors `scrollIntoView({ block })`.
99
+ * Defaults to `'start'`.
100
+ *
101
+ * - `'start'`: element's leading edge aligns with the viewport's leading
102
+ * edge (top for `axis: 'y'`, left for `axis: 'x'`)
103
+ * - `'center'`: element centers in the viewport
104
+ * - `'end'`: element's trailing edge aligns with the viewport's trailing
105
+ * edge (bottom for `'y'`, right for `'x'`)
106
+ * - `'nearest'`: do the minimum scroll — stay put if the element is
107
+ * already fully visible, otherwise scroll to the closest edge
108
+ */
109
+ readonly block?: 'start' | 'center' | 'end' | 'nearest';
110
+ /**
111
+ * Scroll inside a scrollable container instead of the page. Accepts a
112
+ * Playwright-compatible selector or a built `Locator`. Every scroll
113
+ * semantic (`'natural'`, `'top'`, `'end'`, `{ by }`, `{ to }`, element
114
+ * targets, `block` alignment) applies relative to the container.
115
+ *
116
+ * In humanized speed modes, the cursor parks over the container's
117
+ * center so the visible cursor reads as "human hand on the wheel"
118
+ * (when paired with `installMouseHelper`), and each planned segment
119
+ * advances the container's `scrollLeft` / `scrollTop` directly. Direct
120
+ * property assignment is more reliable than dispatching wheel events
121
+ * into nested overflow containers — Playwright's synthetic wheel
122
+ * events don't always route past the cursor element. In `'instant'`
123
+ * mode, the container's scroll position is set with a single
124
+ * `scrollTo` call.
125
+ *
126
+ * Common use: chat threads, modal bodies, infinite-scroll feeds, any
127
+ * `<div style="overflow-y: auto">` that owns its own scrollbar.
128
+ */
129
+ readonly within?: string | Locator;
130
+ /**
131
+ * Which axis to scroll along. Defaults to `'y'` (vertical).
132
+ *
133
+ * Set `'x'` for horizontal scrolling — carousels, kanban boards,
134
+ * sideways galleries. Every target shape still works: `'natural'`
135
+ * scrolls one viewport-width right, `'top'` jumps to scrollLeft 0,
136
+ * `'end'` to (scrollWidth - clientWidth), `{ by }` / `{ to }` apply to
137
+ * the X-axis, and element targets scroll until the element is visible
138
+ * horizontally.
139
+ */
140
+ readonly axis?: 'x' | 'y';
141
+ }
142
+ /** Outcome of a scroll, returned to the caller for observability. */
143
+ interface ScrollResult {
144
+ /** Starting scroll position along the chosen axis (scrollY or scrollX). */
145
+ readonly from: number;
146
+ /** Final scroll position the scroll aimed for, along the chosen axis. */
147
+ readonly to: number;
148
+ /** Signed pixel distance (`to - from`). */
149
+ readonly distance: number;
150
+ /** Total elapsed dwell + motion time in ms. Zero in `speed: 'instant'`. */
151
+ readonly durationMs: number;
152
+ }
153
+
154
+ /** Options for {@link installMouseHelper}. */
155
+ interface InstallMouseHelperOptions {
156
+ /** Cursor fill color. Defaults to the HumanJS amber `#f5a55c`. */
157
+ readonly color?: string;
158
+ /** Cursor visual size in pixels. Defaults to 22. */
159
+ readonly size?: number;
160
+ /**
161
+ * Render a ripple at each `mousedown` position so clicks read on video.
162
+ * Defaults to `true`.
163
+ */
164
+ readonly showClicks?: boolean;
165
+ /** Halo opacity behind the cursor. Defaults to `0.18`. */
166
+ readonly haloOpacity?: number;
167
+ }
168
+ /**
169
+ * Installs a visual cursor overlay that follows every `mousemove` on each
170
+ * page in the target. Real synthetic motion from Playwright (e.g.
171
+ * `human.click()`, `human.read(..., { withMotion: true })`) is *already*
172
+ * happening — the page just doesn't render a system cursor for it. This
173
+ * helper injects a HumanJS-styled SVG cursor that listens to mousemove
174
+ * events and follows them, making the motion visible in headed demos and
175
+ * screen recordings.
176
+ *
177
+ * Re-runs on every navigation via `addInitScript`, so the overlay survives
178
+ * page reloads. Idempotent — guard flag on `window` prevents double-install.
179
+ *
180
+ * Accepts either a `Page` (overlay applies to that page) or a
181
+ * `BrowserContext` (overlay applies to every page in the context, including
182
+ * pages opened later).
183
+ *
184
+ * @example
185
+ * ```ts
186
+ * const browser = await chromium.launch({ headless: false });
187
+ * const context = await browser.newContext();
188
+ * await installMouseHelper(context);
189
+ * const page = await context.newPage();
190
+ * // human-driven actions are now visible in the page
191
+ * ```
192
+ */
193
+ declare function installMouseHelper(target: BrowserContext | Page, options?: InstallMouseHelperOptions): Promise<void>;
4
194
 
5
195
  /**
6
196
  * How fast the humanized session runs.
@@ -49,6 +239,77 @@ interface Human {
49
239
  * native `locator.click()` is used directly.
50
240
  */
51
241
  click(target: Locator | string): Promise<void>;
242
+ /**
243
+ * Type `value` into `target` with humanized per-key timing, optional typo
244
+ * injection (with backspace recovery), and occasional think-pauses.
245
+ *
246
+ * Per-key `keydown`/`press`/`up` events fire for each character, so
247
+ * handlers like autocomplete dropdowns still receive every keystroke.
248
+ *
249
+ * In `speed: 'instant'`, falls back to `locator.pressSequentially` with
250
+ * zero inter-key delay — events still fire, but humanization is skipped.
251
+ */
252
+ type(target: Locator | string, value: string): Promise<void>;
253
+ /**
254
+ * Dwell as if reading `target` — the third pillar of humanization after
255
+ * the cursor and the keyboard. Real users pause to read; HumanJS models
256
+ * that pause from word count + the personality's reading WPM (+ jitter).
257
+ *
258
+ * **Targets:**
259
+ * - `string`: a Playwright-compatible selector (matches `click()`/`type()`).
260
+ * - `Locator`: a pre-built Locator.
261
+ * - `{ text }`: literal text in hand (no DOM lookup).
262
+ * - `{ words }`: pre-counted — skip text extraction entirely.
263
+ *
264
+ * **Smart defaults** (only when the caller doesn't override):
265
+ * - `kind` auto-detects as `'code'` for `<pre>` and `<code>` tags;
266
+ * everything else falls back to `'prose'`. Explicit `kind` always wins.
267
+ * - `scrollIntoView: false` — most flows already scrolled to the content.
268
+ *
269
+ * Plugins observe `'read'` actions with `{ target, words, kind }` in params.
270
+ * The text content itself is never echoed — passwords, tokens, and other
271
+ * sensitive strings stay out of telemetry by default.
272
+ *
273
+ * In `speed: 'instant'`, dwell collapses to 0 ms but the action still fires
274
+ * so observability stays consistent.
275
+ *
276
+ * Returns a {@link ReadResult} with the word count, final kind (after
277
+ * auto-detection), and total dwell duration — useful for assertions in
278
+ * tests or for surfacing reading metadata to a UI.
279
+ */
280
+ read(target: ReadTarget, options?: ReadOptions): Promise<ReadResult>;
281
+ /**
282
+ * Scroll the page (or a scrollable container) humanly. Multi-segment
283
+ * motion with a bell-curve velocity profile, optional mid-scroll
284
+ * micro-pauses, and (for the `distracted` personality) the occasional
285
+ * overshoot + correction.
286
+ *
287
+ * **Targets:**
288
+ * - `'natural'` (default): scroll one viewport in the chosen axis
289
+ * - `'end'` / `'top'`: jump to the document/container edges, humanized
290
+ * - `string`: a Playwright-compatible selector — scroll until in view
291
+ * - `Locator`: same, but with a pre-built handle
292
+ * - `{ by: n }`: relative pixel delta (negative = up / left)
293
+ * - `{ to: n }`: absolute scroll position on the chosen axis
294
+ *
295
+ * **Options:** `axis: 'x' | 'y'` (default `'y'`) picks the direction;
296
+ * `within: selector | Locator` scopes the scroll to a scrollable
297
+ * container; `block: 'start' | 'center' | 'end' | 'nearest'` aligns
298
+ * element targets; `overshoot` / `withPauses` toggle individual
299
+ * humanization signals.
300
+ *
301
+ * Plugins observe `'scroll'` actions with `{ target }` in
302
+ * `beforeAction`'s params (a human-readable description of the target).
303
+ * The full {@link ScrollResult} (`from` / `to` / `distance` /
304
+ * `durationMs`) is available via `afterAction`'s `result` argument.
305
+ *
306
+ * In `speed: 'instant'`, the page (or container) is moved with a single
307
+ * `scrollTo` call. No wheel events, no segments — but the action still
308
+ * fires for observability.
309
+ *
310
+ * Returns a {@link ScrollResult} for assertions in tests.
311
+ */
312
+ scroll(target?: ScrollTarget, options?: ScrollOptions): Promise<ScrollResult>;
52
313
  }
53
314
  /**
54
315
  * Creates a humanized session bound to a Playwright `Page`.
@@ -71,4 +332,4 @@ interface Human {
71
332
  */
72
333
  declare function createHuman(page: Page, options?: CreateHumanOptions): Promise<Human>;
73
334
 
74
- export { type CreateHumanOptions, type Human, type Speed, createHuman };
335
+ export { type CreateHumanOptions, type Human, type InstallMouseHelperOptions, type ReadOptions, type ReadResult, type ReadTarget, type ScrollOptions, type ScrollResult, type ScrollTarget, type Speed, createHuman, installMouseHelper };