arn-browser 0.0.4 → 0.0.6
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/package.json +6 -1
- package/src/human-cursor/HumanCursor.js +516 -0
- package/src/human-cursor/bezier.js +248 -0
- package/src/human-cursor/index.d.ts +161 -0
- package/src/human-cursor/index.js +9 -0
- package/src/human-cursor/randomizer.js +149 -0
- package/src/human-cursor/tweening.js +260 -0
- package/src/utility/launchBrowser.d.ts +28 -1
- package/src/utility/launchBrowser.js +53 -22
- package/src/utility/multilogin_token_manager.js +26 -48
- package/src/utility/proxy-utility/custom-proxy.js +1 -45
- package/src/utility/proxy-utility/proxy-helper.d.ts +1 -1
- package/src/utility/proxy-utility/proxy-helper.js +21 -44
- package/rowser_automation_env.js +0 -32
package/package.json
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arn-browser",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "A lightweight, browser autmation helper.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"src"
|
|
9
|
+
],
|
|
7
10
|
"directories": {
|
|
8
11
|
"test": "test"
|
|
9
12
|
},
|
|
10
13
|
"dependencies": {
|
|
11
14
|
"@aws-sdk/client-ec2": "^3.946.0",
|
|
12
15
|
"@ghostery/adblocker": "^2.13.0",
|
|
16
|
+
"arn-knexjs": "^0.0.1",
|
|
13
17
|
"camoufox-js": "^0.8.4",
|
|
18
|
+
"dotenv": "^17.2.3",
|
|
14
19
|
"fingerprint-injector": "^2.1.78",
|
|
15
20
|
"https-proxy-agent": "^7.0.6",
|
|
16
21
|
"node-cache": "^5.1.2",
|
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HumanCursor - Human-like cursor movements for Playwright
|
|
3
|
+
* Drop-in replacement for Page with human cursor for click/fill/type/hover
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { HumanizeMouseTrajectory } from './bezier.js';
|
|
7
|
+
import { generateRandomCurveParameters, calculateAbsoluteOffset } from './randomizer.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Random integer between min and max (inclusive)
|
|
11
|
+
*/
|
|
12
|
+
function randInt(min, max) {
|
|
13
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Sleep for specified milliseconds
|
|
18
|
+
*/
|
|
19
|
+
function sleep(ms) {
|
|
20
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates a HumanLocator that wraps a Playwright Locator
|
|
25
|
+
* with human-like cursor movement for actions
|
|
26
|
+
*/
|
|
27
|
+
function createHumanLocator(cursor, locator) {
|
|
28
|
+
// Methods that need human cursor movement
|
|
29
|
+
const humanMethods = {
|
|
30
|
+
async click(options = {}) {
|
|
31
|
+
// If humanize is disabled, use native Playwright click
|
|
32
|
+
if (!cursor.config.humanize) {
|
|
33
|
+
return await locator.click(options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await cursor._moveToLocator(locator, options);
|
|
37
|
+
const clicks = options.clickCount || 1;
|
|
38
|
+
const button = options.button || 'left';
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < clicks; i++) {
|
|
41
|
+
if (options.delay) {
|
|
42
|
+
await cursor._page.mouse.down({ button });
|
|
43
|
+
await sleep(options.delay);
|
|
44
|
+
await cursor._page.mouse.up({ button });
|
|
45
|
+
} else {
|
|
46
|
+
await cursor._page.mouse.click(
|
|
47
|
+
cursor.originCoordinates[0],
|
|
48
|
+
cursor.originCoordinates[1],
|
|
49
|
+
{ button }
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (i < clicks - 1) {
|
|
53
|
+
await sleep(randInt(170, 280));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
async dblclick(options = {}) {
|
|
59
|
+
if (!cursor.config.humanize) {
|
|
60
|
+
return await locator.dblclick(options);
|
|
61
|
+
}
|
|
62
|
+
await cursor._moveToLocator(locator, options);
|
|
63
|
+
await cursor._page.mouse.dblclick(
|
|
64
|
+
cursor.originCoordinates[0],
|
|
65
|
+
cursor.originCoordinates[1]
|
|
66
|
+
);
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async fill(value, options = {}) {
|
|
70
|
+
if (!cursor.config.humanize) {
|
|
71
|
+
return await locator.fill(value, options);
|
|
72
|
+
}
|
|
73
|
+
// Humanize: Move to element, click, then fill instantly
|
|
74
|
+
await this.click(options);
|
|
75
|
+
return await locator.fill(value, options);
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
async fillSequentially(value, options = {}) {
|
|
79
|
+
if (!cursor.config.humanize) {
|
|
80
|
+
return await locator.fill(value, options);
|
|
81
|
+
}
|
|
82
|
+
await this.click(options);
|
|
83
|
+
await cursor._page.keyboard.press('Control+A');
|
|
84
|
+
await sleep(randInt(50, 100));
|
|
85
|
+
// Human type with delays
|
|
86
|
+
await cursor._type(value, options);
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
async type(text, options = {}) {
|
|
90
|
+
if (!cursor.config.humanize) {
|
|
91
|
+
return await locator.type(text, options);
|
|
92
|
+
}
|
|
93
|
+
await this.click(options);
|
|
94
|
+
await cursor._type(text, options);
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
async hover(options = {}) {
|
|
98
|
+
if (!cursor.config.humanize) {
|
|
99
|
+
return await locator.hover(options);
|
|
100
|
+
}
|
|
101
|
+
await cursor._moveToLocator(locator, options);
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
async press(key, options = {}) {
|
|
105
|
+
if (!cursor.config.humanize) {
|
|
106
|
+
return await locator.press(key, options);
|
|
107
|
+
}
|
|
108
|
+
await this.click(options);
|
|
109
|
+
await cursor._page.keyboard.press(key);
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
async check(options = {}) {
|
|
113
|
+
if (!cursor.config.humanize) {
|
|
114
|
+
return await locator.check(options);
|
|
115
|
+
}
|
|
116
|
+
if (!(await locator.isChecked())) {
|
|
117
|
+
await this.click(options);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
async uncheck(options = {}) {
|
|
122
|
+
if (!cursor.config.humanize) {
|
|
123
|
+
return await locator.uncheck(options);
|
|
124
|
+
}
|
|
125
|
+
if (await locator.isChecked()) {
|
|
126
|
+
await this.click(options);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
async setChecked(checked, options = {}) {
|
|
131
|
+
if (!cursor.config.humanize) {
|
|
132
|
+
return await locator.setChecked(checked, options);
|
|
133
|
+
}
|
|
134
|
+
if (checked) {
|
|
135
|
+
await this.check(options);
|
|
136
|
+
} else {
|
|
137
|
+
await this.uncheck(options);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
async pressSequentially(text, options = {}) {
|
|
142
|
+
if (!cursor.config.humanize) {
|
|
143
|
+
return await locator.pressSequentially(text, options);
|
|
144
|
+
}
|
|
145
|
+
await this.click(options);
|
|
146
|
+
await cursor._type(text, options);
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
// Chainable methods that return new HumanLocators
|
|
150
|
+
filter(options) {
|
|
151
|
+
return createHumanLocator(cursor, locator.filter(options));
|
|
152
|
+
},
|
|
153
|
+
first() {
|
|
154
|
+
return createHumanLocator(cursor, locator.first());
|
|
155
|
+
},
|
|
156
|
+
last() {
|
|
157
|
+
return createHumanLocator(cursor, locator.last());
|
|
158
|
+
},
|
|
159
|
+
nth(index) {
|
|
160
|
+
return createHumanLocator(cursor, locator.nth(index));
|
|
161
|
+
},
|
|
162
|
+
locator(selector) {
|
|
163
|
+
return createHumanLocator(cursor, locator.locator(selector));
|
|
164
|
+
},
|
|
165
|
+
getByText(text, options) {
|
|
166
|
+
return createHumanLocator(cursor, locator.getByText(text, options));
|
|
167
|
+
},
|
|
168
|
+
getByRole(role, options) {
|
|
169
|
+
return createHumanLocator(cursor, locator.getByRole(role, options));
|
|
170
|
+
},
|
|
171
|
+
getByLabel(text, options) {
|
|
172
|
+
return createHumanLocator(cursor, locator.getByLabel(text, options));
|
|
173
|
+
},
|
|
174
|
+
getByPlaceholder(text, options) {
|
|
175
|
+
return createHumanLocator(cursor, locator.getByPlaceholder(text, options));
|
|
176
|
+
},
|
|
177
|
+
getByTestId(testId) {
|
|
178
|
+
return createHumanLocator(cursor, locator.getByTestId(testId));
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Use Proxy to pass through all other methods to original locator
|
|
183
|
+
return new Proxy(locator, {
|
|
184
|
+
get(target, prop) {
|
|
185
|
+
// Human methods override
|
|
186
|
+
if (prop in humanMethods) {
|
|
187
|
+
return humanMethods[prop];
|
|
188
|
+
}
|
|
189
|
+
// Pass through to original locator
|
|
190
|
+
const value = target[prop];
|
|
191
|
+
if (typeof value === 'function') {
|
|
192
|
+
return value.bind(target);
|
|
193
|
+
}
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Create a HumanCursor that acts as a drop-in replacement for Page
|
|
201
|
+
* All page methods work, but click/fill/type/check/hover use human cursor
|
|
202
|
+
*
|
|
203
|
+
* @param {import('playwright').Page} page - Playwright Page object
|
|
204
|
+
* @param {Object} options - Configuration options
|
|
205
|
+
* @returns {import('playwright').Page & HumanCursorMethods}
|
|
206
|
+
*/
|
|
207
|
+
export function createCursor(page, options = {}) {
|
|
208
|
+
const config = {
|
|
209
|
+
humanize: options.humanize ?? true, // Enable human cursor by default
|
|
210
|
+
maxTime: options.maxTime ?? 1.5,
|
|
211
|
+
minTime: options.minTime ?? 0.5,
|
|
212
|
+
showCursor: options.showCursor ?? true // Show cursor by default
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// Internal state
|
|
216
|
+
const cursor = {
|
|
217
|
+
_page: page,
|
|
218
|
+
originCoordinates: [0, 0],
|
|
219
|
+
config,
|
|
220
|
+
|
|
221
|
+
async _moveToLocator(locator, options = {}) {
|
|
222
|
+
await locator.scrollIntoViewIfNeeded();
|
|
223
|
+
const bounds = await locator.boundingBox();
|
|
224
|
+
|
|
225
|
+
if (!bounds) {
|
|
226
|
+
throw new Error('Element not found or not visible');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let xOffset, yOffset;
|
|
230
|
+
if (options.position) {
|
|
231
|
+
xOffset = options.position.x;
|
|
232
|
+
yOffset = options.position.y;
|
|
233
|
+
} else {
|
|
234
|
+
xOffset = bounds.width * (randInt(25, 75) / 100);
|
|
235
|
+
yOffset = bounds.height * (randInt(25, 75) / 100);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const destination = [bounds.x + xOffset, bounds.y + yOffset];
|
|
239
|
+
return await this._moveToPoint(destination, options);
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
async _moveToPoint(destination, options = {}) {
|
|
243
|
+
const viewport = page.viewportSize() || { width: 1280, height: 720 };
|
|
244
|
+
|
|
245
|
+
const params = generateRandomCurveParameters(
|
|
246
|
+
viewport,
|
|
247
|
+
this.originCoordinates,
|
|
248
|
+
destination
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
if (options.steady) {
|
|
252
|
+
params.offsetBoundaryX = 10;
|
|
253
|
+
params.offsetBoundaryY = 10;
|
|
254
|
+
params.distortionMean = 1.2;
|
|
255
|
+
params.distortionStdDev = 1.2;
|
|
256
|
+
params.distortionFrequency = 1;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const curve = new HumanizeMouseTrajectory(
|
|
260
|
+
this.originCoordinates,
|
|
261
|
+
destination,
|
|
262
|
+
{
|
|
263
|
+
offsetBoundaryX: params.offsetBoundaryX,
|
|
264
|
+
offsetBoundaryY: params.offsetBoundaryY,
|
|
265
|
+
knotsCount: params.knotsCount,
|
|
266
|
+
distortionMean: params.distortionMean,
|
|
267
|
+
distortionStdDev: params.distortionStdDev,
|
|
268
|
+
distortionFrequency: params.distortionFrequency,
|
|
269
|
+
tweening: params.tween,
|
|
270
|
+
targetPoints: params.targetPoints
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const distance = Math.sqrt(
|
|
275
|
+
Math.pow(destination[0] - this.originCoordinates[0], 2) +
|
|
276
|
+
Math.pow(destination[1] - this.originCoordinates[1], 2)
|
|
277
|
+
);
|
|
278
|
+
const totalTime = Math.min(
|
|
279
|
+
config.maxTime,
|
|
280
|
+
Math.max(config.minTime, distance / 1000)
|
|
281
|
+
) * 1000;
|
|
282
|
+
const baseDelay = totalTime / curve.points.length;
|
|
283
|
+
const speedMultiplier = options.moveSpeed || 1.0;
|
|
284
|
+
|
|
285
|
+
for (const point of curve.points) {
|
|
286
|
+
await page.mouse.move(point[0], point[1]);
|
|
287
|
+
if (baseDelay > 0) {
|
|
288
|
+
await sleep((baseDelay / speedMultiplier) + Math.random() * 2);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
this.originCoordinates = destination;
|
|
293
|
+
return destination;
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
async _type(text, options = {}) {
|
|
297
|
+
const minDelay = options.minDelay ?? 50;
|
|
298
|
+
const maxDelay = options.maxDelay ?? 150;
|
|
299
|
+
|
|
300
|
+
for (const char of text) {
|
|
301
|
+
await page.keyboard.type(char);
|
|
302
|
+
await sleep(randInt(minDelay, maxDelay));
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
async showCursor() {
|
|
307
|
+
await page.evaluate(() => {
|
|
308
|
+
if (window.__humanCursorDot) return;
|
|
309
|
+
|
|
310
|
+
let cursor;
|
|
311
|
+
function updateCursor(event) {
|
|
312
|
+
const x = event.clientX;
|
|
313
|
+
const y = event.clientY;
|
|
314
|
+
|
|
315
|
+
if (!cursor) {
|
|
316
|
+
// Create cursor container
|
|
317
|
+
cursor = document.createElement('div');
|
|
318
|
+
cursor.id = '__human_cursor';
|
|
319
|
+
cursor.style.cssText = `
|
|
320
|
+
position: fixed;
|
|
321
|
+
pointer-events: none;
|
|
322
|
+
z-index: 2147483647;
|
|
323
|
+
transition: left 0.05s ease-out, top 0.05s ease-out;
|
|
324
|
+
`;
|
|
325
|
+
|
|
326
|
+
// Outer ring (animated)
|
|
327
|
+
const ring = document.createElement('div');
|
|
328
|
+
ring.style.cssText = `
|
|
329
|
+
width: 28px;
|
|
330
|
+
height: 28px;
|
|
331
|
+
border: 2px solid rgba(59, 130, 246, 0.6);
|
|
332
|
+
border-radius: 50%;
|
|
333
|
+
box-sizing: border-box;
|
|
334
|
+
position: absolute;
|
|
335
|
+
top: -14px;
|
|
336
|
+
left: -14px;
|
|
337
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
338
|
+
`;
|
|
339
|
+
|
|
340
|
+
// Inner dot
|
|
341
|
+
const dot = document.createElement('div');
|
|
342
|
+
dot.style.cssText = `
|
|
343
|
+
width: 10px;
|
|
344
|
+
height: 10px;
|
|
345
|
+
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
|
|
346
|
+
border-radius: 50%;
|
|
347
|
+
position: absolute;
|
|
348
|
+
top: -5px;
|
|
349
|
+
left: -5px;
|
|
350
|
+
box-shadow: 0 0 12px rgba(59, 130, 246, 0.8), 0 0 4px rgba(255, 255, 255, 0.9);
|
|
351
|
+
`;
|
|
352
|
+
|
|
353
|
+
// Click indicator
|
|
354
|
+
const clickRing = document.createElement('div');
|
|
355
|
+
clickRing.id = '__human_cursor_click';
|
|
356
|
+
clickRing.style.cssText = `
|
|
357
|
+
width: 36px;
|
|
358
|
+
height: 36px;
|
|
359
|
+
border: 2px solid rgba(239, 68, 68, 0.8);
|
|
360
|
+
border-radius: 50%;
|
|
361
|
+
box-sizing: border-box;
|
|
362
|
+
position: absolute;
|
|
363
|
+
top: -18px;
|
|
364
|
+
left: -18px;
|
|
365
|
+
opacity: 0;
|
|
366
|
+
transform: scale(0.5);
|
|
367
|
+
transition: opacity 0.15s, transform 0.15s;
|
|
368
|
+
`;
|
|
369
|
+
|
|
370
|
+
// Add pulse animation
|
|
371
|
+
const style = document.createElement('style');
|
|
372
|
+
style.textContent = `
|
|
373
|
+
@keyframes pulse {
|
|
374
|
+
0%, 100% { transform: scale(1); opacity: 0.6; }
|
|
375
|
+
50% { transform: scale(1.2); opacity: 0.3; }
|
|
376
|
+
}
|
|
377
|
+
`;
|
|
378
|
+
document.head.appendChild(style);
|
|
379
|
+
|
|
380
|
+
cursor.appendChild(ring);
|
|
381
|
+
cursor.appendChild(dot);
|
|
382
|
+
cursor.appendChild(clickRing);
|
|
383
|
+
document.body.appendChild(cursor);
|
|
384
|
+
|
|
385
|
+
// Click effect
|
|
386
|
+
document.addEventListener('mousedown', () => {
|
|
387
|
+
const click = document.getElementById('__human_cursor_click');
|
|
388
|
+
if (click) {
|
|
389
|
+
click.style.opacity = '1';
|
|
390
|
+
click.style.transform = 'scale(1)';
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
document.addEventListener('mouseup', () => {
|
|
394
|
+
const click = document.getElementById('__human_cursor_click');
|
|
395
|
+
if (click) {
|
|
396
|
+
click.style.opacity = '0';
|
|
397
|
+
click.style.transform = 'scale(0.5)';
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
cursor.style.left = x + 'px';
|
|
403
|
+
cursor.style.top = y + 'px';
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
document.addEventListener('mousemove', updateCursor);
|
|
407
|
+
window.__humanCursorDot = true;
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
// Methods that return HumanLocators
|
|
413
|
+
const locatorMethods = {
|
|
414
|
+
locator(selector) {
|
|
415
|
+
return createHumanLocator(cursor, page.locator(selector));
|
|
416
|
+
},
|
|
417
|
+
getByText(text, opts) {
|
|
418
|
+
return createHumanLocator(cursor, page.getByText(text, opts));
|
|
419
|
+
},
|
|
420
|
+
getByRole(role, opts) {
|
|
421
|
+
return createHumanLocator(cursor, page.getByRole(role, opts));
|
|
422
|
+
},
|
|
423
|
+
getByLabel(text, opts) {
|
|
424
|
+
return createHumanLocator(cursor, page.getByLabel(text, opts));
|
|
425
|
+
},
|
|
426
|
+
getByPlaceholder(text, opts) {
|
|
427
|
+
return createHumanLocator(cursor, page.getByPlaceholder(text, opts));
|
|
428
|
+
},
|
|
429
|
+
getByAltText(text, opts) {
|
|
430
|
+
return createHumanLocator(cursor, page.getByAltText(text, opts));
|
|
431
|
+
},
|
|
432
|
+
getByTitle(text, opts) {
|
|
433
|
+
return createHumanLocator(cursor, page.getByTitle(text, opts));
|
|
434
|
+
},
|
|
435
|
+
getByTestId(testId) {
|
|
436
|
+
return createHumanLocator(cursor, page.getByTestId(testId));
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// Wrap goto to auto-show cursor after navigation
|
|
441
|
+
const originalGoto = page.goto.bind(page);
|
|
442
|
+
const wrappedGoto = async (...args) => {
|
|
443
|
+
const result = await originalGoto(...args);
|
|
444
|
+
if (config.showCursor) {
|
|
445
|
+
await cursor.showCursor();
|
|
446
|
+
}
|
|
447
|
+
return result;
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// Use Proxy to create drop-in page replacement
|
|
451
|
+
return new Proxy(page, {
|
|
452
|
+
get(target, prop) {
|
|
453
|
+
// Override goto to auto-show cursor
|
|
454
|
+
if (prop === 'goto') {
|
|
455
|
+
return wrappedGoto;
|
|
456
|
+
}
|
|
457
|
+
// Locator methods return HumanLocators
|
|
458
|
+
if (prop in locatorMethods) {
|
|
459
|
+
return locatorMethods[prop];
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Override Page methods to use HumanLocator
|
|
463
|
+
if (['click', 'dblclick', 'fill', 'hover', 'type', 'press', 'check', 'uncheck'].includes(prop)) {
|
|
464
|
+
return async (selector, ...args) => {
|
|
465
|
+
const humanLocator = createHumanLocator(cursor, page.locator(selector));
|
|
466
|
+
return await humanLocator[prop](...args);
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
if (prop === 'fillSequentially') {
|
|
470
|
+
return async (selector, ...args) => {
|
|
471
|
+
const humanLocator = createHumanLocator(cursor, page.locator(selector));
|
|
472
|
+
return await humanLocator.fillSequentially(...args);
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
if (prop === 'pressSequentially') {
|
|
476
|
+
return async (selector, ...args) => {
|
|
477
|
+
const humanLocator = createHumanLocator(cursor, page.locator(selector));
|
|
478
|
+
return await humanLocator.pressSequentially(...args);
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Expose cursor utilities
|
|
483
|
+
if (prop === 'showCursor') {
|
|
484
|
+
return cursor.showCursor.bind(cursor);
|
|
485
|
+
}
|
|
486
|
+
if (prop === 'originCoordinates') {
|
|
487
|
+
return cursor.originCoordinates;
|
|
488
|
+
}
|
|
489
|
+
if (prop === 'setPosition') {
|
|
490
|
+
return (x, y) => { cursor.originCoordinates = [x, y]; };
|
|
491
|
+
}
|
|
492
|
+
// Pass through everything else to original page
|
|
493
|
+
const value = target[prop];
|
|
494
|
+
if (typeof value === 'function') {
|
|
495
|
+
return value.bind(target);
|
|
496
|
+
}
|
|
497
|
+
return value;
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Legacy class export for backwards compatibility
|
|
503
|
+
export class HumanCursor {
|
|
504
|
+
constructor(page, options = {}) {
|
|
505
|
+
this.page = page;
|
|
506
|
+
this.originCoordinates = [0, 0];
|
|
507
|
+
this.options = {
|
|
508
|
+
maxTime: options.maxTime ?? 1.5,
|
|
509
|
+
minTime: options.minTime ?? 0.5,
|
|
510
|
+
showCursor: options.showCursor ?? true // Show cursor by default
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
// Return proxy instead of class instance
|
|
514
|
+
return createCursor(page, options);
|
|
515
|
+
}
|
|
516
|
+
}
|