@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/README.md CHANGED
@@ -20,15 +20,156 @@ const browser = await chromium.launch();
20
20
  const page = await browser.newPage();
21
21
 
22
22
  const human = await createHuman(page, {
23
- personality: 'careful', // careful | fast | distracted | precise
24
- seed: 'session-42', // deterministic for tests
25
- speed: 'human', // human | fast | instant
23
+ personality: 'careful', // careful | fast | distracted | precise
24
+ seed: 'session-42', // deterministic for tests
25
+ speed: 'human', // human | fast | instant
26
26
  });
27
27
 
28
28
  await human.goto('https://example.com');
29
+
30
+ // Mouse: real Bezier path, velocity profile, pre-click hover dwell.
31
+ await human.click('button:has-text("Sign in")');
32
+
33
+ // Keyboard: per-key rhythm, optional QWERTY typos, Backspace recovery,
34
+ // occasional mid-word think pauses. The typed string is *not* echoed to
35
+ // plugin params — `params.length` only, by design.
36
+ await human.type('input[name="email"]', 'gonzalo@example.com');
37
+ ```
38
+
39
+ ### Speed modes
40
+
41
+ - `'human'` (default) — full humanization on every action.
42
+ - `'fast'` — humanized but accelerated.
43
+ - `'instant'` — bypass humanization entirely; uses Playwright's native methods. Per-key events still fire for `type()`. Right for CI.
44
+
45
+ ### Determinism
46
+
47
+ Pass a `seed` and every random decision (path curvature, typo placement, keystroke jitter) becomes reproducible. Same seed + same personality + same value = same keystrokes.
48
+
49
+ ### Reading
50
+
51
+ ```ts
52
+ await human.read('p.welcome');
53
+ ```
54
+
55
+ `human.read()` dwells like a real reader — pause-time scaled by the target's word count and the personality's reading WPM (with personality-controlled jitter).
56
+
57
+ **Target options:**
58
+
59
+ - `string` — Playwright-compatible selector
60
+ - `Locator` — a pre-built Locator
61
+ - `{ text: '...' }` — literal text, no DOM lookup
62
+ - `{ words: 42 }` — pre-counted; skips text extraction entirely
63
+
64
+ **Reading kinds** scale the dwell on top of `personality.reading.wpm`:
65
+
66
+ - `'prose'` (1.0×) — default for non-code targets
67
+ - `'code'` (0.4×) — slower; auto-detected when the target is a `<pre>` or `<code>` element
68
+ - `'scan'` (1.8×) — explicit skim mode
69
+
70
+ ```ts
71
+ await human.read('.article-body'); // prose, default
72
+ await human.read('pre.snippet'); // 'code' auto-detected from <pre>
73
+ await human.read('ul.changelog', { kind: 'scan' }); // explicit skim
29
74
  ```
30
75
 
31
- See [humanjs.dev](https://humanjs.dev) for full documentation.
76
+ Explicit `kind` always wins over auto-detection.
77
+
78
+ **Visible eye-scan motion** during the dwell:
79
+
80
+ ```ts
81
+ await human.read('article', { withMotion: true });
82
+ ```
83
+
84
+ The cursor walks a humanized L→R sweep through every line of rendered text and emits a small return-saccade between lines — same `mousemove` events a real reader would dispatch (so reading-time tooltip / hover handlers fire). Off by default.
85
+
86
+ For demos and screen recordings, pair `withMotion` with `installMouseHelper(page)` to render a visible cursor that follows the synthetic motion:
87
+
88
+ ```ts
89
+ import { createHuman, installMouseHelper } from '@humanjs/playwright';
90
+
91
+ const page = await context.newPage();
92
+ await page.goto('https://example.com/article');
93
+ await installMouseHelper(page);
94
+
95
+ const human = await createHuman(page, { personality: 'careful' });
96
+ await human.read('article', { withMotion: true });
97
+ ```
98
+
99
+ **Returns** a `ReadResult`:
100
+
101
+ ```ts
102
+ const { words, durationMs, kind } = await human.read('main');
103
+ ```
104
+
105
+ Useful for test assertions or surfacing reading metadata in a UI.
106
+
107
+ **Privacy**: the read text is never echoed to plugin params. `read` actions surface only `{ target, kind }` plus inert length metadata — the content itself stays out of telemetry by design, same posture as `human.type()`.
108
+
109
+ ### Scrolling
110
+
111
+ ```ts
112
+ await human.scroll(); // ~one viewport down, humanized
113
+ ```
114
+
115
+ `human.scroll()` produces multi-segment scroll motion with a bell-curve velocity profile (slow start, fast middle, slow end), optional mid-scroll micro-pauses, and — for the `distracted` personality — occasional overshoot + correction. Page scrolls dispatch real `wheel` events; container scrolls advance the element's scroll position directly (more reliable inside nested overflow containers).
116
+
117
+ **Target options:**
118
+
119
+ ```ts
120
+ await human.scroll(); // 'natural' — ~one viewport
121
+ await human.scroll('top'); // to the top
122
+ await human.scroll('end'); // to the bottom
123
+ await human.scroll({ by: 800 }); // relative pixel delta (negative = up)
124
+ await human.scroll({ to: 1500 }); // absolute scroll position on the chosen axis
125
+ await human.scroll('#pricing'); // by selector — scroll until in view
126
+ await human.scroll(locator); // by Locator
127
+ ```
128
+
129
+ **Element-target alignment** matches native `scrollIntoView`:
130
+
131
+ ```ts
132
+ await human.scroll('#hero', { block: 'center' }); // 'start' | 'center' | 'end' | 'nearest'
133
+ ```
134
+
135
+ `'nearest'` is a useful default for "make sure this element is visible without moving more than necessary" — it stays put if the element is already fully in view, otherwise scrolls to the closest edge.
136
+
137
+ **Scroll inside a scrollable container**, not the page:
138
+
139
+ ```ts
140
+ await human.scroll('end', { within: '#messages' }); // chat thread to latest
141
+ await human.scroll('#newest-item', { within: '.feed', block: 'end' });
142
+ await human.scroll({ by: -200 }, { within: modalBody }); // scroll up inside a modal
143
+ ```
144
+
145
+ Every target shape (`'natural'`, `'top'`, `'end'`, selectors, `{ by }`, `{ to }`) applies relative to the container. In humanized mode the cursor parks over the container's center (so an `installMouseHelper` overlay reads as "human hand on the wheel") and each segment advances the container's `scrollLeft` / `scrollTop` directly — more reliable than wheel events inside nested overflow containers. In `'instant'` mode the container's scroll position is set with a single `scrollTo` call.
146
+
147
+ **Horizontal scroll** via `axis: 'x'` — same target shapes apply to the X axis:
148
+
149
+ ```ts
150
+ await human.scroll('end', { axis: 'x' }); // to the right edge
151
+ await human.scroll({ by: 400 }, { axis: 'x' }); // 400px right
152
+ await human.scroll('#card-5', { axis: 'x', block: 'center' }); // carousel to a card
153
+ await human.scroll('end', { within: '#kanban', axis: 'x' }); // kanban board to the right end
154
+ ```
155
+
156
+ Defaults to `'y'`. Combine with `within` for horizontal scrolling inside a container (carousels, kanban boards, sideways galleries).
157
+
158
+ **Force overshoot** even when the personality wouldn't choose one — useful for demos and screen recordings where the humanization signal needs to read clearly:
159
+
160
+ ```ts
161
+ await human.scroll('#footer', { overshoot: true });
162
+ ```
163
+
164
+ **Returns** a `ScrollResult`:
165
+
166
+ ```ts
167
+ const { from, to, distance, durationMs } = await human.scroll('end');
168
+ ```
169
+
170
+ In `speed: 'instant'`, the page jumps directly via `window.scrollTo` — no wheel events — but the action still fires for observability.
171
+
172
+ See [humanjs.dev](https://humanjs.dev) for the full feature set and personality reference.
32
173
 
33
174
  ## License
34
175