@skrillex1224/playwright-toolkit 2.1.285 → 2.1.286

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2489,28 +2489,69 @@ var resolveModeStrategy = (strategies = {}, mode = getToolkitMode(), fallbackMod
2489
2489
  };
2490
2490
 
2491
2491
  // src/internals/delegate.js
2492
- var normalizeMethodDefinition = (definition) => typeof definition === "string" ? { name: definition, enumerable: true } : {
2492
+ var normalizeArrayMethodDefinition = (definition) => typeof definition === "string" ? { name: definition, enumerable: true } : {
2493
2493
  name: String(definition?.name || "").trim(),
2494
2494
  enumerable: definition?.enumerable !== false
2495
2495
  };
2496
- var createMethodDescriptor = (namespace, methodName, enumerable, strategies) => ({
2496
+ var normalizeMethodDefinitions = (methods = {}) => {
2497
+ if (Array.isArray(methods)) {
2498
+ return methods.map(normalizeArrayMethodDefinition).filter((method) => method.name);
2499
+ }
2500
+ if (!methods || typeof methods !== "object") {
2501
+ return [];
2502
+ }
2503
+ return Object.entries(methods).map(([name, definition]) => ({
2504
+ name: String(name || "").trim(),
2505
+ enumerable: definition?.enumerable !== false
2506
+ })).filter((method) => method.name);
2507
+ };
2508
+ var normalizeDelegateResolution = (resolution) => {
2509
+ if (resolution && typeof resolution === "object" && ("delegate" in resolution || "label" in resolution)) {
2510
+ return {
2511
+ delegate: resolution.delegate ?? null,
2512
+ label: String(resolution.label || "current delegate").trim() || "current delegate"
2513
+ };
2514
+ }
2515
+ return {
2516
+ delegate: resolution,
2517
+ label: "current delegate"
2518
+ };
2519
+ };
2520
+ var createMethodDescriptor = (namespace, methodName, enumerable, resolveDelegate) => ({
2497
2521
  enumerable,
2498
2522
  value: (...args) => {
2499
- const { mode, delegate } = resolveModeStrategy(strategies);
2523
+ const { delegate, label } = normalizeDelegateResolution(resolveDelegate(methodName, args));
2500
2524
  if (typeof delegate?.[methodName] !== "function") {
2501
- throw new Error(`${namespace}.${methodName} is not available in ${mode} mode`);
2525
+ throw new Error(`${namespace}.${methodName} is not available in ${label}`);
2502
2526
  }
2503
2527
  return delegate[methodName](...args);
2504
2528
  }
2505
2529
  });
2506
- var createDelegatedFacade = (namespace, strategies = {}, methods = []) => {
2530
+ var withDelegatedProperties = (target = {}, {
2531
+ namespace = "Delegate",
2532
+ methods = {},
2533
+ resolveDelegate = () => null
2534
+ } = {}) => {
2507
2535
  const descriptors = Object.fromEntries(
2508
- methods.map(normalizeMethodDefinition).filter((method) => method.name).map((method) => [
2536
+ normalizeMethodDefinitions(methods).map((method) => [
2509
2537
  method.name,
2510
- createMethodDescriptor(namespace, method.name, method.enumerable, strategies)
2538
+ createMethodDescriptor(namespace, method.name, method.enumerable, resolveDelegate)
2511
2539
  ])
2512
2540
  );
2513
- return Object.defineProperties({}, descriptors);
2541
+ return Object.defineProperties(target, descriptors);
2542
+ };
2543
+ var createDelegatedFacade = (namespace, strategies = {}, methods = {}) => {
2544
+ return withDelegatedProperties({}, {
2545
+ namespace,
2546
+ methods,
2547
+ resolveDelegate: () => {
2548
+ const { mode, delegate } = resolveModeStrategy(strategies);
2549
+ return {
2550
+ delegate,
2551
+ label: `${mode} mode`
2552
+ };
2553
+ }
2554
+ });
2514
2555
  };
2515
2556
 
2516
2557
  // src/internals/anti-cheat/default.js
@@ -2624,13 +2665,14 @@ var antiCheatStrategies = {
2624
2665
  [Mode.Default]: DefaultAntiCheat,
2625
2666
  [Mode.CloakBrowser]: CloakBrowserAntiCheat
2626
2667
  };
2627
- var AntiCheat = createDelegatedFacade("AntiCheat", antiCheatStrategies, [
2628
- "getBaseConfig",
2629
- "getFingerprintGeneratorOptions",
2630
- "getLaunchArgs",
2631
- "getTlsFingerprintOptions",
2632
- "applyLocaleHeaders"
2633
- ]);
2668
+ var antiCheatFacadeMethods = Object.freeze({
2669
+ getBaseConfig: { enumerable: true },
2670
+ getFingerprintGeneratorOptions: { enumerable: true },
2671
+ getLaunchArgs: { enumerable: true },
2672
+ getTlsFingerprintOptions: { enumerable: true },
2673
+ applyLocaleHeaders: { enumerable: true }
2674
+ });
2675
+ var AntiCheat = createDelegatedFacade("AntiCheat", antiCheatStrategies, antiCheatFacadeMethods);
2634
2676
 
2635
2677
  // src/device-input.js
2636
2678
  var resolveDeviceFromPage = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
@@ -3182,1136 +3224,1240 @@ var DeviceView = {
3182
3224
  }
3183
3225
  };
3184
3226
 
3185
- // src/internals/humanize/desktop.js
3227
+ // src/internals/humanize/cloakbrowser.js
3228
+ import delay3 from "delay";
3229
+
3230
+ // src/internals/humanize/shared.js
3186
3231
  import delay2 from "delay";
3187
- import { createCursor } from "ghost-cursor-playwright";
3188
- var logger5 = createInternalLogger("Humanize");
3189
- var $CursorWeakMap = /* @__PURE__ */ new WeakMap();
3190
- function $GetCursor(page) {
3191
- const cursor = $CursorWeakMap.get(page);
3192
- if (!cursor) {
3193
- throw new Error("Cursor \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 Humanize.initializeCursor(page)");
3232
+ var jitterMs = (base, jitterPercent = 0.3) => {
3233
+ const jitter = Number(base || 0) * Number(jitterPercent || 0) * (Math.random() * 2 - 1);
3234
+ return Math.max(10, Math.round(Number(base || 0) + jitter));
3235
+ };
3236
+ var resolveElement = async (page, target, { throwOnMissing = true } = {}) => {
3237
+ if (target == null) return null;
3238
+ let element = target;
3239
+ if (typeof target === "string") {
3240
+ element = await page.$(target);
3194
3241
  }
3195
- return cursor;
3196
- }
3197
- var Humanize = {
3198
- /**
3199
- * 生成带抖动的毫秒数 - 基于基础值添加随机浮动 (±30% 默认)
3200
- * @param {number} base - 基础延迟 (ms)
3201
- * @param {number} [jitterPercent=0.3] - 抖动百分比 (0.3 = ±30%)
3202
- * @returns {number} 抖动后的毫秒数
3203
- */
3204
- jitterMs(base, jitterPercent = 0.3) {
3205
- const jitter = base * jitterPercent * (Math.random() * 2 - 1);
3206
- return Math.max(10, Math.round(base + jitter));
3207
- },
3208
- /**
3209
- * 初始化页面的 Ghost Cursor(必须在使用其他 cursor 相关方法前调用)
3210
- *
3211
- * @param {import('playwright').Page} page
3212
- * @returns {Promise<void>}
3213
- */
3214
- async initializeCursor(page) {
3215
- if ($CursorWeakMap.has(page)) {
3216
- logger5.debug("initializeCursor: cursor already exists, skipping");
3217
- return;
3242
+ if (!element) {
3243
+ if (throwOnMissing) {
3244
+ throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${String(target)}`);
3218
3245
  }
3219
- logger5.start("initializeCursor", "creating cursor");
3220
- const cursor = await createCursor(page);
3221
- $CursorWeakMap.set(page, cursor);
3222
- logger5.success("initializeCursor", "cursor initialized");
3223
- },
3224
- /**
3225
- * 人类化鼠标移动 - 使用 ghost-cursor 移动到指定位置或元素
3226
- *
3227
- * @param {import('playwright').Page} page
3228
- * @param {string|{x: number, y: number}|import('playwright').ElementHandle} target - CSS选择器、坐标对象或元素句柄
3229
- */
3230
- async humanMove(page, target) {
3231
- const cursor = $GetCursor(page);
3232
- logger5.start("humanMove", `target=${typeof target === "string" ? target : "element/coords"}`);
3233
- try {
3234
- if (typeof target === "string") {
3235
- const element = await page.$(target);
3236
- if (!element) {
3237
- logger5.warn(`humanMove: \u5143\u7D20\u4E0D\u5B58\u5728 ${target}`);
3238
- return false;
3239
- }
3240
- const box = await element.boundingBox();
3241
- if (!box) {
3242
- logger5.warn(`humanMove: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E ${target}`);
3243
- return false;
3244
- }
3245
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
3246
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
3247
- await cursor.actions.move({ x, y });
3248
- } else if (target && typeof target.x === "number" && typeof target.y === "number") {
3249
- await cursor.actions.move(target);
3250
- } else if (target && typeof target.boundingBox === "function") {
3251
- const box = await target.boundingBox();
3252
- if (box) {
3253
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
3254
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
3255
- await cursor.actions.move({ x, y });
3256
- }
3246
+ return null;
3247
+ }
3248
+ return element;
3249
+ };
3250
+ var waitJitter = (base, jitterPercent = 0.3) => delay2(jitterMs(base, jitterPercent));
3251
+
3252
+ // src/internals/humanize/mobile.js
3253
+ var logger5 = createInternalLogger("Humanize.Mobile");
3254
+ var initializedPages = /* @__PURE__ */ new WeakSet();
3255
+ var DEFAULT_TAP_TIMEOUT_MS = 2500;
3256
+ var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
3257
+ var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
3258
+ var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
3259
+ var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
3260
+ var describeTarget = (target) => {
3261
+ if (target == null) return "Current Position";
3262
+ return typeof target === "string" ? target : "ElementHandle";
3263
+ };
3264
+ var clipBoxToViewport = (box, viewport) => {
3265
+ if (!box || !viewport) return box;
3266
+ const left = clamp(box.x, 0, viewport.width);
3267
+ const top = clamp(box.y, 0, viewport.height);
3268
+ const right = clamp(box.x + box.width, 0, viewport.width);
3269
+ const bottom = clamp(box.y + box.height, 0, viewport.height);
3270
+ if (right - left > 1 && bottom - top > 1) {
3271
+ return {
3272
+ x: left,
3273
+ y: top,
3274
+ width: right - left,
3275
+ height: bottom - top
3276
+ };
3277
+ }
3278
+ return box;
3279
+ };
3280
+ var centerPointInBox = (box) => ({
3281
+ x: box.x + box.width / 2,
3282
+ y: box.y + box.height / 2
3283
+ });
3284
+ var withTimeout = async (operation, timeoutMs, label) => {
3285
+ const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
3286
+ let timeoutId = null;
3287
+ try {
3288
+ return await Promise.race([
3289
+ Promise.resolve().then(operation),
3290
+ new Promise((_, reject) => {
3291
+ timeoutId = setTimeout(() => {
3292
+ reject(new Error(`${label} timeout after ${safeTimeoutMs}ms`));
3293
+ }, safeTimeoutMs);
3294
+ })
3295
+ ]);
3296
+ } finally {
3297
+ if (timeoutId) clearTimeout(timeoutId);
3298
+ }
3299
+ };
3300
+ var checkElementVisibility = async (element) => {
3301
+ return element.evaluate((el) => {
3302
+ const targetStyle = window.getComputedStyle(el);
3303
+ if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
3304
+ return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
3305
+ }
3306
+ const rect = el.getBoundingClientRect();
3307
+ if (!rect || rect.width <= 0 || rect.height <= 0) {
3308
+ return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6", direction: "down" };
3309
+ }
3310
+ const viewW = window.innerWidth;
3311
+ const viewH = window.innerHeight;
3312
+ const centerY = rect.top + rect.height / 2;
3313
+ let clipLeft = 0;
3314
+ let clipRight = viewW;
3315
+ let clipTop = 0;
3316
+ let clipBottom = viewH;
3317
+ let isFixed = false;
3318
+ let positioning = null;
3319
+ for (let node = el; node && node !== document.body; node = node.parentElement) {
3320
+ const style = window.getComputedStyle(node);
3321
+ if (style && (style.position === "fixed" || style.position === "sticky")) {
3322
+ isFixed = true;
3323
+ positioning = style.position;
3324
+ break;
3257
3325
  }
3258
- logger5.success("humanMove");
3259
- return true;
3260
- } catch (error) {
3261
- logger5.fail("humanMove", error);
3262
- throw error;
3263
3326
  }
3264
- },
3265
- /**
3266
- * 渐进式滚动到元素可见(仅处理 Y 轴滚动)
3267
- * 返回 restore 方法,用于将滚动容器恢复到原位置
3268
- *
3269
- * @param {import('playwright').Page} page
3270
- * @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
3271
- * @param {Object} [options]
3272
- * @param {number} [options.maxSteps=20] - 最大滚动步数
3273
- * @param {number} [options.minStep=260] - 单次滚动最小步长
3274
- * @param {number} [options.maxStep=800] - 单次滚动最大步长
3275
- * @param {number} [options.maxDurationMs] - 最长耗时上限 (默认随 maxSteps 估算)
3276
- */
3277
- async humanScroll(page, target, options = {}) {
3278
- const {
3279
- maxSteps = 20,
3280
- minStep = 260,
3281
- maxStep = 800,
3282
- maxDurationMs = maxSteps * 220 + 800
3283
- } = options;
3284
- const targetDesc = typeof target === "string" ? target : "ElementHandle";
3285
- logger5.start("humanScroll", `target=${targetDesc}`);
3286
- let element;
3287
- if (typeof target === "string") {
3288
- element = await page.$(target);
3289
- if (!element) {
3290
- logger5.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${target}`);
3291
- return { element: null, didScroll: false };
3327
+ for (let node = el.parentElement; node && node !== document.body; node = node.parentElement) {
3328
+ const style = window.getComputedStyle(node);
3329
+ if (!style) continue;
3330
+ const clipsY = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowY);
3331
+ const clipsX = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowX);
3332
+ if (!clipsX && !clipsY) continue;
3333
+ const nodeRect = node.getBoundingClientRect();
3334
+ if (!nodeRect || nodeRect.width <= 0 || nodeRect.height <= 0) continue;
3335
+ if (clipsX) {
3336
+ clipLeft = Math.max(clipLeft, nodeRect.left);
3337
+ clipRight = Math.min(clipRight, nodeRect.right);
3338
+ }
3339
+ if (clipsY) {
3340
+ clipTop = Math.max(clipTop, nodeRect.top);
3341
+ clipBottom = Math.min(clipBottom, nodeRect.bottom);
3292
3342
  }
3293
- } else {
3294
- element = target;
3295
3343
  }
3296
- const cursor = $GetCursor(page);
3297
- let didScroll = false;
3298
- const checkVisibility = async () => {
3299
- return await element.evaluate((el) => {
3300
- const rect = el.getBoundingClientRect();
3301
- if (!rect || rect.width === 0 || rect.height === 0) {
3302
- return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6" };
3303
- }
3304
- const cx = rect.left + rect.width / 2;
3305
- const cy = rect.top + rect.height / 2;
3306
- const viewH = window.innerHeight;
3307
- const viewW = window.innerWidth;
3308
- let isFixed = false;
3309
- for (let node = el; node && node !== document.body; node = node.parentElement) {
3310
- const style = window.getComputedStyle(node);
3311
- if (style && style.position === "fixed") {
3312
- isFixed = true;
3313
- break;
3314
- }
3315
- }
3316
- if (cy < 0 || cy > viewH || cx < 0 || cx > viewW) {
3317
- const direction = cy < 0 ? "up" : cy > viewH ? "down" : "unknown";
3318
- return { code: "OUT_OF_VIEWPORT", reason: "\u4E0D\u5728\u89C6\u53E3\u5185", direction, cy, viewH, isFixed };
3319
- }
3320
- const pointElement = document.elementFromPoint(cx, cy);
3321
- if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
3322
- return {
3323
- code: "OBSTRUCTED",
3324
- reason: "\u88AB\u906E\u6321",
3325
- obstruction: {
3326
- tag: pointElement.tagName,
3327
- id: pointElement.id,
3328
- className: pointElement.className
3329
- },
3330
- cy,
3331
- // Return Center Y for smart direction calculation
3332
- viewH,
3333
- isFixed
3334
- };
3335
- }
3336
- return { code: "VISIBLE", isFixed };
3337
- });
3338
- };
3339
- const getScrollableRect2 = async () => {
3340
- return await element.evaluate((el) => {
3341
- const isScrollable = (node) => {
3342
- const style = window.getComputedStyle(node);
3343
- if (!style) return false;
3344
- const overflowY = style.overflowY;
3345
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3346
- return node.scrollHeight > node.clientHeight + 1;
3347
- };
3348
- let current = el;
3349
- while (current && current !== document.body) {
3350
- if (isScrollable(current)) {
3351
- const rect = current.getBoundingClientRect();
3352
- if (rect && rect.width > 0 && rect.height > 0) {
3353
- return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
3354
- }
3355
- }
3356
- current = current.parentElement;
3357
- }
3358
- return null;
3359
- });
3360
- };
3361
- const startTime = Date.now();
3362
- try {
3363
- for (let i = 0; i < maxSteps; i++) {
3364
- if (Date.now() - startTime > maxDurationMs) {
3365
- logger5.warn(`humanScroll | \u8D85\u65F6\u4FDD\u62A4\u89E6\u53D1 (${maxDurationMs}ms)`);
3366
- return { element, didScroll };
3367
- }
3368
- const status = await checkVisibility();
3369
- if (status.code === "VISIBLE") {
3370
- if (status.isFixed) {
3371
- logger5.info("humanScroll | fixed \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
3372
- } else {
3373
- logger5.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
3374
- }
3375
- logger5.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
3376
- return { element, didScroll };
3377
- }
3378
- logger5.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
3379
- if (status.code === "OBSTRUCTED" && status.obstruction) {
3380
- logger5.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
3381
- }
3382
- const scrollRect = await getScrollableRect2();
3383
- if (!scrollRect && status.isFixed) {
3384
- logger5.warn("humanScroll | fixed \u5BB9\u5668\u5185\u4E14\u65E0\u53EF\u6EDA\u52A8\u7956\u5148\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
3385
- return { element, didScroll };
3386
- }
3387
- const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
3388
- const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
3389
- let deltaY = 0;
3390
- if (status.code === "OUT_OF_VIEWPORT") {
3391
- if (status.direction === "down") {
3392
- deltaY = stepMin + Math.random() * (stepMax - stepMin);
3393
- } else if (status.direction === "up") {
3394
- deltaY = -(stepMin + Math.random() * (stepMax - stepMin));
3395
- } else {
3396
- deltaY = 100;
3397
- }
3398
- } else if (status.code === "OBSTRUCTED") {
3399
- const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
3400
- const isBottomHalf = status.cy > halfY;
3401
- const direction = isBottomHalf ? 1 : -1;
3402
- deltaY = direction * (stepMin + Math.random() * 50);
3403
- }
3404
- if (i === 0) {
3405
- const viewSize = page.viewportSize();
3406
- if (scrollRect) {
3407
- const safeX = scrollRect.x + scrollRect.width * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.width * 0.4);
3408
- const safeY = scrollRect.y + scrollRect.height * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.height * 0.4);
3409
- await cursor.actions.move({ x: safeX, y: safeY });
3410
- } else if (viewSize) {
3411
- const safeX = viewSize.width * 0.5 + (Math.random() - 0.5) * 80;
3412
- const safeY = viewSize.height * 0.5 + (Math.random() - 0.5) * 80;
3413
- await cursor.actions.move({ x: safeX, y: safeY });
3414
- }
3415
- }
3416
- await page.mouse.wheel(0, deltaY);
3417
- didScroll = true;
3418
- await delay2(this.jitterMs(20 + Math.random() * 40, 0.2));
3419
- }
3420
- logger5.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
3421
- return { element, didScroll };
3422
- } catch (error) {
3423
- logger5.fail("humanScroll", error);
3424
- throw error;
3344
+ const visibleLeft = Math.max(clipLeft, Math.min(clipRight, rect.left));
3345
+ const visibleRight = Math.max(clipLeft, Math.min(clipRight, rect.right));
3346
+ const visibleTop = Math.max(clipTop, Math.min(clipBottom, rect.top));
3347
+ const visibleBottom = Math.max(clipTop, Math.min(clipBottom, rect.bottom));
3348
+ const visibleWidth = visibleRight - visibleLeft;
3349
+ const visibleHeight = visibleBottom - visibleTop;
3350
+ const cx = visibleLeft + visibleWidth / 2;
3351
+ const cy = visibleTop + visibleHeight / 2;
3352
+ if (visibleWidth <= 1 || visibleHeight <= 1) {
3353
+ return {
3354
+ code: "OUT_OF_VIEWPORT",
3355
+ reason: "\u4E0D\u5728\u89C6\u53E3\u5185",
3356
+ direction: centerY < clipTop ? "up" : "down",
3357
+ cy: centerY,
3358
+ viewH,
3359
+ isFixed,
3360
+ positioning
3361
+ };
3425
3362
  }
3426
- },
3427
- /**
3428
- * 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
3429
- *
3430
- * @param {import('playwright').Page} page
3431
- * @param {string|import('playwright').ElementHandle} [target] - CSS 选择器或元素句柄。如果为空,则点击当前鼠标位置
3432
- * @param {Object} [options]
3433
- * @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
3434
- * @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
3435
- * @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
3436
- */
3437
- async humanClick(page, target, options = {}) {
3438
- const cursor = $GetCursor(page);
3439
- const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true, restore = false } = options;
3440
- const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
3441
- logger5.start("humanClick", `target=${targetDesc}`);
3442
- const restoreOnce = async () => {
3443
- if (restoreOnce.restored) return;
3444
- restoreOnce.restored = true;
3445
- if (typeof restoreOnce.do !== "function") return;
3446
- try {
3447
- await delay2(this.jitterMs(1e3));
3448
- await restoreOnce.do();
3449
- } catch (restoreError) {
3450
- logger5.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
3363
+ const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
3364
+ const commonAncestor = (a, b) => {
3365
+ for (let current = a; current && !isRootNode(current); current = current.parentElement) {
3366
+ if (current.contains(b)) return current;
3451
3367
  }
3368
+ return null;
3452
3369
  };
3453
- try {
3454
- if (target == null) {
3455
- await delay2(this.jitterMs(reactionDelay, 0.4));
3456
- await cursor.actions.click();
3457
- logger5.success("humanClick", "Clicked current position");
3370
+ const sameTapTarget = (pointElement) => {
3371
+ if (!pointElement) return false;
3372
+ if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
3458
3373
  return true;
3459
3374
  }
3460
- let element;
3461
- if (typeof target === "string") {
3462
- element = await page.$(target);
3463
- if (!element) {
3464
- if (throwOnMissing) {
3465
- throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${target}`);
3466
- }
3467
- logger5.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${target}`);
3468
- return false;
3469
- }
3470
- } else {
3471
- element = target;
3472
- }
3473
- if (scrollIfNeeded) {
3474
- const { restore: restoreFn, didScroll } = await this.humanScroll(page, element);
3475
- restoreOnce.do = didScroll && restore ? restoreFn : null;
3476
- }
3477
- const box = await element.boundingBox();
3478
- if (!box) {
3479
- await restoreOnce();
3480
- if (throwOnMissing) {
3481
- throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
3482
- }
3483
- logger5.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
3375
+ const common = commonAncestor(el, pointElement);
3376
+ if (!common) return false;
3377
+ const commonRect = common.getBoundingClientRect?.();
3378
+ if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
3379
+ const commonArea = commonRect.width * commonRect.height;
3380
+ const targetArea = Math.max(1, rect.width * rect.height);
3381
+ const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
3382
+ if (commonArea > maxSharedRegionArea) return false;
3383
+ if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
3484
3384
  return false;
3485
3385
  }
3486
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
3487
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
3488
- await cursor.actions.move({ x, y });
3489
- await delay2(this.jitterMs(reactionDelay, 0.4));
3490
- await cursor.actions.click();
3491
- await restoreOnce();
3492
- logger5.success("humanClick");
3493
- return true;
3494
- } catch (error) {
3495
- await restoreOnce();
3496
- logger5.fail("humanClick", error);
3497
- throw error;
3386
+ return common.contains(el) && common.contains(pointElement);
3387
+ };
3388
+ const describeElement = (node) => {
3389
+ if (!node) return null;
3390
+ const className = typeof node.className === "string" ? node.className : node.className && typeof node.className.baseVal === "string" ? node.className.baseVal : "";
3391
+ const rect2 = node.getBoundingClientRect?.();
3392
+ const style = window.getComputedStyle(node);
3393
+ return {
3394
+ tag: node.tagName,
3395
+ id: node.id || "",
3396
+ className,
3397
+ isFixed: Boolean(style && (style.position === "fixed" || style.position === "sticky")),
3398
+ positioning: style?.position || "",
3399
+ top: rect2 ? rect2.top : null,
3400
+ bottom: rect2 ? rect2.bottom : null,
3401
+ left: rect2 ? rect2.left : null,
3402
+ right: rect2 ? rect2.right : null
3403
+ };
3404
+ };
3405
+ const samplePoints = [
3406
+ { x: cx, y: cy },
3407
+ { x: visibleLeft + Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3408
+ { x: visibleRight - Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3409
+ { x: cx, y: visibleTop + Math.min(8, Math.max(1, visibleHeight * 0.25)) },
3410
+ { x: cx, y: visibleBottom - Math.min(8, Math.max(1, visibleHeight * 0.25)) }
3411
+ ];
3412
+ let obstruction = null;
3413
+ for (const point of samplePoints) {
3414
+ const pointElement = document.elementFromPoint(point.x, point.y);
3415
+ if (sameTapTarget(pointElement)) {
3416
+ return { code: "VISIBLE", isFixed, positioning };
3417
+ }
3418
+ obstruction = obstruction || describeElement(pointElement);
3498
3419
  }
3499
- },
3500
- /**
3501
- * 随机延迟一段毫秒数(带 ±30% 抖动)
3502
- * @param {number} baseMs - 基础延迟毫秒数
3503
- * @param {number} [jitterPercent=0.3] - 抖动百分比
3504
- */
3505
- async randomSleep(baseMs, jitterPercent = 0.3) {
3506
- const ms = this.jitterMs(baseMs, jitterPercent);
3507
- logger5.start("randomSleep", `base=${baseMs}, actual=${ms}ms`);
3508
- await delay2(ms);
3509
- logger5.success("randomSleep");
3510
- },
3511
- /**
3512
- * 模拟人类"注视"或"阅读"行为:鼠标在页面上随机微动
3513
- * @param {import('playwright').Page} page
3514
- * @param {number} [baseDurationMs=2500] - 基础持续时间 (±40% 抖动)
3515
- */
3516
- async simulateGaze(page, baseDurationMs = 2500) {
3517
- const cursor = $GetCursor(page);
3518
- const durationMs = this.jitterMs(baseDurationMs, 0.4);
3519
- logger5.start("simulateGaze", `duration=${durationMs}ms`);
3520
- const startTime = Date.now();
3521
- const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
3522
- while (Date.now() - startTime < durationMs) {
3523
- const x = 100 + Math.random() * (viewportSize.width - 200);
3524
- const y = 100 + Math.random() * (viewportSize.height - 200);
3525
- await cursor.actions.move({ x, y });
3526
- await delay2(this.jitterMs(600, 0.5));
3420
+ if (obstruction) {
3421
+ return {
3422
+ code: "OBSTRUCTED",
3423
+ reason: "\u88AB\u906E\u6321",
3424
+ direction: cy > viewH / 2 ? "down" : "up",
3425
+ obstruction,
3426
+ cy,
3427
+ viewH,
3428
+ isFixed,
3429
+ positioning
3430
+ };
3527
3431
  }
3528
- logger5.success("simulateGaze");
3529
- },
3530
- /**
3531
- * 人类化输入 - 带节奏变化(快-慢-停顿-偶尔加速)
3532
- * @param {import('playwright').Page} page
3533
- * @param {string} selector - 输入框选择器
3534
- * @param {string} text - 要输入的文本
3535
- * @param {Object} [options]
3536
- * @param {number} [options.baseDelay=180] - 基础按键延迟 (ms),实际 ±40% 抖动
3537
- * @param {number} [options.pauseProbability=0.08] - 停顿概率 (0-1)
3538
- * @param {number} [options.pauseBase=800] - 停顿时长基础值 (ms),实际 ±50% 抖动
3539
- */
3540
- async humanType(page, selector, text, options = {}) {
3541
- logger5.start("humanType", `selector=${selector}, textLen=${text.length}`);
3542
- const {
3543
- baseDelay = 180,
3544
- pauseProbability = 0.08,
3545
- pauseBase = 800
3546
- } = options;
3547
- try {
3548
- const locator = page.locator(selector);
3549
- await Humanize.humanClick(page, locator);
3550
- await delay2(this.jitterMs(200, 0.4));
3551
- for (let i = 0; i < text.length; i++) {
3552
- const char = text[i];
3553
- let charDelay;
3554
- if (char === " ") {
3555
- charDelay = this.jitterMs(baseDelay * 0.6, 0.3);
3556
- } else if (/[,.!?;:,。!?;:]/.test(char)) {
3557
- charDelay = this.jitterMs(baseDelay * 1.5, 0.4);
3558
- } else {
3559
- charDelay = this.jitterMs(baseDelay, 0.4);
3560
- }
3561
- await page.keyboard.type(char);
3562
- await delay2(charDelay);
3563
- if (Math.random() < pauseProbability && i < text.length - 1) {
3564
- const pauseTime = this.jitterMs(pauseBase, 0.5);
3565
- logger5.debug(`\u505C\u987F ${pauseTime}ms...`);
3566
- await delay2(pauseTime);
3432
+ return { code: "VISIBLE", isFixed, positioning };
3433
+ });
3434
+ };
3435
+ var activateElementFallback = async (element, point = null, options = {}) => {
3436
+ return element.evaluate((el, { innerOptions }) => {
3437
+ const isEditable = (node) => {
3438
+ if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
3439
+ if (node.isContentEditable) return true;
3440
+ if (node instanceof HTMLTextAreaElement) return !node.disabled && !node.readOnly;
3441
+ if (node instanceof HTMLInputElement) {
3442
+ return !node.disabled && !node.readOnly && typeof node.select === "function";
3443
+ }
3444
+ return false;
3445
+ };
3446
+ const findEditable = (node) => {
3447
+ for (let current = node; current && current !== document.body; current = current.parentElement) {
3448
+ if (isEditable(current)) return current;
3449
+ }
3450
+ return null;
3451
+ };
3452
+ const editable = findEditable(el);
3453
+ if (editable && typeof editable.focus === "function") {
3454
+ editable.focus({ preventScroll: true });
3455
+ if (innerOptions.editableOnly) {
3456
+ return { activated: true, method: "focus", tag: editable.tagName || "" };
3457
+ }
3458
+ }
3459
+ if (innerOptions.editableOnly) {
3460
+ return { activated: false, method: "none", tag: el?.tagName || "" };
3461
+ }
3462
+ if (typeof el.focus === "function") {
3463
+ el.focus({ preventScroll: true });
3464
+ }
3465
+ if (typeof el.click === "function") {
3466
+ el.click();
3467
+ return { activated: true, method: "dom-click", tag: el.tagName || "" };
3468
+ }
3469
+ if (typeof el.dispatchEvent === "function") {
3470
+ el.dispatchEvent(new MouseEvent("click", {
3471
+ bubbles: true,
3472
+ cancelable: true,
3473
+ view: window
3474
+ }));
3475
+ return { activated: true, method: "dispatch-click", tag: el.tagName || "" };
3476
+ }
3477
+ return {
3478
+ activated: Boolean(editable),
3479
+ method: editable ? "focus" : "none",
3480
+ tag: el?.tagName || ""
3481
+ };
3482
+ }, {
3483
+ innerOptions: options || {}
3484
+ });
3485
+ };
3486
+ var getScrollableRect = async (element) => {
3487
+ return element.evaluate((el) => {
3488
+ const isScrollable = (node) => {
3489
+ const style = window.getComputedStyle(node);
3490
+ if (!style) return false;
3491
+ const overflowY = style.overflowY;
3492
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3493
+ return node.scrollHeight > node.clientHeight + 1;
3494
+ };
3495
+ let current = el;
3496
+ while (current && current !== document.body) {
3497
+ if (isScrollable(current)) {
3498
+ const rect = current.getBoundingClientRect();
3499
+ if (rect && rect.width > 0 && rect.height > 0) {
3500
+ return {
3501
+ x: rect.x,
3502
+ y: rect.y,
3503
+ width: rect.width,
3504
+ height: rect.height
3505
+ };
3567
3506
  }
3568
3507
  }
3569
- logger5.success("humanType");
3570
- } catch (error) {
3571
- logger5.fail("humanType", error);
3572
- throw error;
3508
+ current = current.parentElement;
3573
3509
  }
3574
- },
3575
- /**
3576
- * 人类化按键 - 模拟用户在当前焦点或指定目标上按下一次键。
3577
- * @param {import('playwright').Page} page
3578
- * @param {string|import('playwright').ElementHandle|import('playwright').Locator} targetOrKey - 目标或按键
3579
- * @param {string|Object} [maybeKey] - 按键或选项
3580
- * @param {Object} [options]
3581
- */
3582
- async humanPress(page, targetOrKey, maybeKey, options = {}) {
3583
- const hasTarget = typeof maybeKey === "string";
3584
- const key = hasTarget ? maybeKey : targetOrKey;
3585
- const pressOptions = hasTarget ? options : maybeKey || options;
3586
- const {
3587
- reactionDelay = 180,
3588
- holdDelay = 45,
3589
- focusDelay = 180,
3590
- scrollIfNeeded = true,
3591
- throwOnMissing = true,
3592
- keyboardOptions = {}
3593
- } = pressOptions || {};
3594
- const targetDesc = hasTarget ? typeof targetOrKey === "string" ? targetOrKey : "ElementHandle" : "current focus";
3595
- logger5.start("humanPress", `key=${key}, target=${targetDesc}`);
3596
- try {
3597
- if (hasTarget) {
3598
- await this.humanClick(page, targetOrKey, { reactionDelay: focusDelay, scrollIfNeeded, throwOnMissing });
3510
+ return null;
3511
+ });
3512
+ };
3513
+ var scrollScrollableAncestor = async (element, deltaY) => {
3514
+ return element.evaluate((el, amount) => {
3515
+ const isScrollable = (node) => {
3516
+ const style = window.getComputedStyle(node);
3517
+ if (!style) return false;
3518
+ const overflowY = style.overflowY;
3519
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3520
+ return node.scrollHeight > node.clientHeight + 1;
3521
+ };
3522
+ let current = el;
3523
+ while (current && current !== document.body) {
3524
+ if (isScrollable(current)) {
3525
+ const beforeTop2 = current.scrollTop;
3526
+ current.scrollTop = beforeTop2 + amount;
3527
+ return {
3528
+ scroller: true,
3529
+ moved: current.scrollTop !== beforeTop2,
3530
+ scrollTop: current.scrollTop
3531
+ };
3599
3532
  }
3600
- await delay2(this.jitterMs(reactionDelay, 0.45));
3601
- await page.keyboard.press(key, {
3602
- ...keyboardOptions,
3603
- delay: this.jitterMs(holdDelay, 0.5)
3533
+ current = current.parentElement;
3534
+ }
3535
+ const beforeTop = window.scrollY;
3536
+ window.scrollBy(0, amount);
3537
+ return {
3538
+ scroller: null,
3539
+ moved: window.scrollY !== beforeTop,
3540
+ scrollTop: window.scrollY
3541
+ };
3542
+ }, deltaY);
3543
+ };
3544
+ var scrollAwayFromObstruction = async (element, status) => {
3545
+ return element.evaluate((el, innerStatus) => {
3546
+ const isScrollable = (node) => {
3547
+ const style = window.getComputedStyle(node);
3548
+ if (!style) return false;
3549
+ const overflowY = style.overflowY;
3550
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3551
+ return node.scrollHeight > node.clientHeight + 1;
3552
+ };
3553
+ let scroller = el;
3554
+ while (scroller && scroller !== document.body) {
3555
+ if (isScrollable(scroller)) break;
3556
+ scroller = scroller.parentElement;
3557
+ }
3558
+ if (!scroller || scroller === document.body) {
3559
+ return { moved: false, scrollTop: 0, deltaY: 0 };
3560
+ }
3561
+ const rect = el.getBoundingClientRect();
3562
+ const scrollerRect = scroller.getBoundingClientRect();
3563
+ const obstruction = innerStatus?.obstruction || {};
3564
+ const obstructionTop = Number(obstruction.top);
3565
+ const obstructionBottom = Number(obstruction.bottom);
3566
+ const obstructionMiddle = Number.isFinite(obstructionTop) && Number.isFinite(obstructionBottom) ? (obstructionTop + obstructionBottom) / 2 : scrollerRect.top + scrollerRect.height / 2;
3567
+ const scrollerMiddle = scrollerRect.top + scrollerRect.height / 2;
3568
+ const padding = 18;
3569
+ let deltaY = 0;
3570
+ if (Number.isFinite(obstructionTop) && rect.bottom > obstructionTop && obstructionTop >= scrollerRect.top && obstructionTop <= scrollerRect.bottom && obstructionMiddle >= scrollerMiddle) {
3571
+ deltaY = rect.bottom - obstructionTop + padding;
3572
+ } else if (Number.isFinite(obstructionBottom) && rect.top < obstructionBottom && obstructionBottom >= scrollerRect.top && obstructionBottom <= scrollerRect.bottom && obstructionMiddle < scrollerMiddle) {
3573
+ deltaY = rect.top - obstructionBottom - padding;
3574
+ } else {
3575
+ const fallbackDistance = Math.max(48, Math.min(180, scrollerRect.height * 0.45));
3576
+ deltaY = obstructionMiddle >= scrollerMiddle ? fallbackDistance : -fallbackDistance;
3577
+ }
3578
+ const beforeTop = scroller.scrollTop;
3579
+ scroller.scrollTop = beforeTop + deltaY;
3580
+ return {
3581
+ moved: Math.abs(scroller.scrollTop - beforeTop) > 2,
3582
+ scrollTop: scroller.scrollTop,
3583
+ deltaY
3584
+ };
3585
+ }, status);
3586
+ };
3587
+ var getElementViewportSnapshot = async (element) => {
3588
+ return element.evaluate((el) => {
3589
+ const rect = el.getBoundingClientRect();
3590
+ return {
3591
+ top: rect.top,
3592
+ bottom: rect.bottom,
3593
+ left: rect.left,
3594
+ right: rect.right,
3595
+ width: rect.width,
3596
+ height: rect.height,
3597
+ scrollX: window.scrollX,
3598
+ scrollY: window.scrollY
3599
+ };
3600
+ });
3601
+ };
3602
+ var isTargetImmobileAfterScroll = (before, after) => {
3603
+ if (!before || !after) return false;
3604
+ const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
3605
+ const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
3606
+ const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
3607
+ const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
3608
+ const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
3609
+ const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
3610
+ if (!rectMoved && !pageMoved) return true;
3611
+ if (pageMoved && !rectMoved) return true;
3612
+ if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
3613
+ return true;
3614
+ }
3615
+ return false;
3616
+ };
3617
+ var restoreWindowFromSnapshot = async (page, before, after) => {
3618
+ if (!before || !after) return;
3619
+ if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
3620
+ return;
3621
+ }
3622
+ await page.evaluate(
3623
+ (state2) => window.scrollTo(state2.x, state2.y),
3624
+ { x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
3625
+ ).catch(() => {
3626
+ });
3627
+ };
3628
+ var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
3629
+ const viewport = resolveViewport(page);
3630
+ const rawRect = options.rect || null;
3631
+ const rect = rawRect ? {
3632
+ x: clamp(rawRect.x, 0, viewport.width),
3633
+ y: clamp(rawRect.y, 0, viewport.height),
3634
+ width: clamp(rawRect.width, 0, viewport.width),
3635
+ height: clamp(rawRect.height, 0, viewport.height)
3636
+ } : null;
3637
+ const area = rect && rect.width > 24 && rect.height > 48 ? {
3638
+ left: rect.x,
3639
+ right: Math.min(viewport.width, rect.x + rect.width),
3640
+ top: rect.y,
3641
+ bottom: Math.min(viewport.height, rect.y + rect.height)
3642
+ } : {
3643
+ left: 0,
3644
+ right: viewport.width,
3645
+ top: 0,
3646
+ bottom: viewport.height
3647
+ };
3648
+ const areaWidth = Math.max(1, area.right - area.left);
3649
+ const areaHeight = Math.max(1, area.bottom - area.top);
3650
+ const maxDistance = rect ? Math.max(60, areaHeight * 0.72) : Math.max(120, viewport.height * 0.72);
3651
+ const distance = clamp(Math.abs(deltaY), Math.min(80, maxDistance), maxDistance);
3652
+ const direction = deltaY >= 0 ? 1 : -1;
3653
+ const startX = clamp(
3654
+ area.left + areaWidth * (0.45 + Math.random() * 0.1),
3655
+ 24,
3656
+ viewport.width - 24
3657
+ );
3658
+ const startY = direction > 0 ? clamp(area.top + areaHeight * (0.72 + Math.random() * 0.1), area.top + 24, area.bottom - 16) : clamp(area.top + areaHeight * (0.28 + Math.random() * 0.1), area.top + 16, area.bottom - 24);
3659
+ const endY = clamp(startY - direction * distance, Math.max(16, area.top + 12), Math.min(viewport.height - 16, area.bottom - 12));
3660
+ const steps = Math.max(4, Math.round(Number(options.steps || 6)));
3661
+ const durationMs = jitterMs(options.durationMs || 320, 0.35);
3662
+ let client = null;
3663
+ const scrollBefore = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
3664
+ try {
3665
+ if (page.context && typeof page.context().newCDPSession === "function") {
3666
+ client = await page.context().newCDPSession(page);
3667
+ }
3668
+ if (!client) throw new Error("CDP session unavailable");
3669
+ await client.send("Input.synthesizeScrollGesture", {
3670
+ x: startX,
3671
+ y: startY,
3672
+ yDistance: -direction * distance,
3673
+ xOverscroll: (Math.random() - 0.5) * 4,
3674
+ yOverscroll: (Math.random() - 0.5) * 8,
3675
+ speed: 700 + Math.round(Math.random() * 350),
3676
+ gestureSourceType: "touch"
3677
+ });
3678
+ await waitJitter(160, 0.35);
3679
+ const scrollAfterSynth = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
3680
+ if (scrollBefore && scrollAfterSynth && (Math.abs(scrollAfterSynth.y - scrollBefore.y) > 2 || Math.abs(scrollAfterSynth.x - scrollBefore.x) > 2)) {
3681
+ return true;
3682
+ }
3683
+ await client.send("Input.dispatchTouchEvent", {
3684
+ type: "touchStart",
3685
+ touchPoints: [{ x: startX, y: startY, id: 1 }]
3686
+ });
3687
+ for (let i = 1; i <= steps; i += 1) {
3688
+ const progress = i / steps;
3689
+ const eased = 1 - Math.pow(1 - progress, 2);
3690
+ const x = startX + (Math.random() - 0.5) * 3;
3691
+ const y = startY + (endY - startY) * eased + (Math.random() - 0.5) * 5;
3692
+ await waitJitter(durationMs / steps, 0.25);
3693
+ await client.send("Input.dispatchTouchEvent", {
3694
+ type: "touchMove",
3695
+ touchPoints: [{ x, y, id: 1 }]
3604
3696
  });
3605
- logger5.success("humanPress");
3606
- return true;
3607
- } catch (error) {
3608
- logger5.fail("humanPress", error);
3609
- throw error;
3610
3697
  }
3611
- },
3612
- /**
3613
- * 人类化清空输入框 - 模拟人类删除文本的行为
3614
- * @param {import('playwright').Page} page
3615
- * @param {string} selector - 输入框选择器
3616
- */
3617
- async humanClear(page, selector) {
3618
- logger5.start("humanClear", `selector=${selector}`);
3619
- try {
3620
- const locator = page.locator(selector);
3621
- await locator.click();
3622
- await delay2(this.jitterMs(200, 0.4));
3623
- const currentValue = await locator.inputValue();
3624
- if (!currentValue || currentValue.length === 0) {
3625
- logger5.success("humanClear", "already empty");
3626
- return;
3627
- }
3628
- await page.keyboard.press("Meta+A");
3629
- await delay2(this.jitterMs(100, 0.4));
3630
- await page.keyboard.press("Backspace");
3631
- logger5.success("humanClear");
3632
- } catch (error) {
3633
- logger5.fail("humanClear", error);
3634
- throw error;
3698
+ await client.send("Input.dispatchTouchEvent", {
3699
+ type: "touchEnd",
3700
+ touchPoints: []
3701
+ });
3702
+ await waitJitter(120, 0.35);
3703
+ const scrollAfterTouch = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
3704
+ if (scrollBefore && scrollAfterTouch && Math.abs(scrollAfterTouch.y - scrollBefore.y) <= 2 && Math.abs(scrollAfterTouch.x - scrollBefore.x) <= 2) {
3705
+ await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
3706
+ await waitJitter(120, 0.35);
3635
3707
  }
3636
- },
3637
- /**
3638
- * 页面预热浏览 - 模拟人类进入页面后的探索行为
3639
- * @param {import('playwright').Page} page
3640
- * @param {number} [baseDuration=3500] - 预热时长基础值 (±40% 抖动)
3641
- */
3642
- async warmUpBrowsing(page, baseDuration = 3500) {
3643
- const cursor = $GetCursor(page);
3644
- const durationMs = this.jitterMs(baseDuration, 0.4);
3645
- logger5.start("warmUpBrowsing", `duration=${durationMs}ms`);
3646
- const startTime = Date.now();
3647
- const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
3708
+ return true;
3709
+ } catch (error) {
3710
+ logger5.debug(`touch swipe fallback: ${error?.message || error}`);
3648
3711
  try {
3649
- while (Date.now() - startTime < durationMs) {
3650
- const action = Math.random();
3651
- if (action < 0.4) {
3652
- const x = 100 + Math.random() * (viewportSize.width - 200);
3653
- const y = 100 + Math.random() * (viewportSize.height - 200);
3654
- await cursor.actions.move({ x, y });
3655
- await delay2(this.jitterMs(350, 0.4));
3656
- } else if (action < 0.7) {
3657
- const scrollY = (Math.random() - 0.5) * 200;
3658
- await page.mouse.wheel(0, scrollY);
3659
- await delay2(this.jitterMs(500, 0.4));
3660
- } else {
3661
- await delay2(this.jitterMs(800, 0.5));
3662
- }
3663
- }
3664
- logger5.success("warmUpBrowsing");
3665
- } catch (error) {
3666
- logger5.fail("warmUpBrowsing", error);
3667
- throw error;
3712
+ await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
3713
+ await waitJitter(120, 0.35);
3714
+ return true;
3715
+ } catch {
3716
+ await page.mouse.wheel(0, deltaY);
3717
+ await waitJitter(120, 0.35);
3718
+ return true;
3668
3719
  }
3669
- },
3670
- /**
3671
- * 自然滚动 - 带惯性、减速效果和随机抖动
3672
- * @param {import('playwright').Page} page
3673
- * @param {'up' | 'down'} [direction='down'] - 滚动方向
3674
- * @param {number} [distance=300] - 总滚动距离基础值 (px),±15% 抖动
3675
- * @param {number} [baseSteps=5] - 分几步完成基础值,±1 随机
3676
- */
3677
- async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
3678
- const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
3679
- const actualDistance = this.jitterMs(distance, 0.15);
3680
- logger5.start("naturalScroll", `dir=${direction}, dist=${actualDistance}, steps=${steps}`);
3681
- const sign = direction === "down" ? 1 : -1;
3682
- const stepDistance = actualDistance / steps;
3683
- try {
3684
- for (let i = 0; i < steps; i++) {
3685
- const factor = 1 - i / steps * 0.5;
3686
- const jitter = 0.9 + Math.random() * 0.2;
3687
- const scrollAmount = stepDistance * factor * sign * jitter;
3688
- await page.mouse.wheel(0, scrollAmount);
3689
- const baseDelay = 60 + i * 25;
3690
- await delay2(this.jitterMs(baseDelay, 0.3));
3720
+ } finally {
3721
+ if (client && typeof client.detach === "function") {
3722
+ try {
3723
+ await client.detach();
3724
+ } catch {
3691
3725
  }
3692
- logger5.success("naturalScroll");
3693
- } catch (error) {
3694
- logger5.fail("naturalScroll", error);
3695
- throw error;
3696
3726
  }
3697
3727
  }
3698
3728
  };
3699
-
3700
- // src/internals/humanize/shared.js
3701
- import delay3 from "delay";
3702
- var jitterMs = (base, jitterPercent = 0.3) => {
3703
- const jitter = Number(base || 0) * Number(jitterPercent || 0) * (Math.random() * 2 - 1);
3704
- return Math.max(10, Math.round(Number(base || 0) + jitter));
3705
- };
3706
- var resolveElement = async (page, target, { throwOnMissing = true } = {}) => {
3707
- if (target == null) return null;
3708
- let element = target;
3709
- if (typeof target === "string") {
3710
- element = await page.$(target);
3711
- }
3712
- if (!element) {
3713
- if (throwOnMissing) {
3714
- throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${String(target)}`);
3729
+ var tapPoint = async (page, point, options = {}) => {
3730
+ const {
3731
+ timeoutMs = DEFAULT_TAP_TIMEOUT_MS,
3732
+ mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
3733
+ allowMouseFallback = true
3734
+ } = options;
3735
+ if (page.touchscreen && typeof page.touchscreen.tap === "function") {
3736
+ try {
3737
+ await withTimeout(
3738
+ () => page.touchscreen.tap(point.x, point.y),
3739
+ timeoutMs,
3740
+ "touchscreen.tap"
3741
+ );
3742
+ return { method: "touchscreen" };
3743
+ } catch (error) {
3744
+ logger5.warn(`tapPoint | touchscreen.tap \u5931\u8D25\u6216\u8D85\u65F6\uFF0C\u5C1D\u8BD5\u9F20\u6807\u515C\u5E95: ${error?.message || error}`);
3745
+ if (!allowMouseFallback) throw error;
3715
3746
  }
3716
- return null;
3717
- }
3718
- return element;
3719
- };
3720
- var waitJitter = (base, jitterPercent = 0.3) => delay3(jitterMs(base, jitterPercent));
3721
-
3722
- // src/internals/humanize/mobile.js
3723
- var logger6 = createInternalLogger("Humanize.Mobile");
3724
- var initializedPages = /* @__PURE__ */ new WeakSet();
3725
- var DEFAULT_TAP_TIMEOUT_MS = 2500;
3726
- var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
3727
- var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
3728
- var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
3729
- var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
3730
- var describeTarget = (target) => {
3731
- if (target == null) return "Current Position";
3732
- return typeof target === "string" ? target : "ElementHandle";
3733
- };
3734
- var clipBoxToViewport = (box, viewport) => {
3735
- if (!box || !viewport) return box;
3736
- const left = clamp(box.x, 0, viewport.width);
3737
- const top = clamp(box.y, 0, viewport.height);
3738
- const right = clamp(box.x + box.width, 0, viewport.width);
3739
- const bottom = clamp(box.y + box.height, 0, viewport.height);
3740
- if (right - left > 1 && bottom - top > 1) {
3741
- return {
3742
- x: left,
3743
- y: top,
3744
- width: right - left,
3745
- height: bottom - top
3746
- };
3747
- }
3748
- return box;
3749
- };
3750
- var centerPointInBox = (box) => ({
3751
- x: box.x + box.width / 2,
3752
- y: box.y + box.height / 2
3753
- });
3754
- var withTimeout = async (operation, timeoutMs, label) => {
3755
- const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
3756
- let timeoutId = null;
3757
- try {
3758
- return await Promise.race([
3759
- Promise.resolve().then(operation),
3760
- new Promise((_, reject) => {
3761
- timeoutId = setTimeout(() => {
3762
- reject(new Error(`${label} timeout after ${safeTimeoutMs}ms`));
3763
- }, safeTimeoutMs);
3764
- })
3765
- ]);
3766
- } finally {
3767
- if (timeoutId) clearTimeout(timeoutId);
3768
3747
  }
3748
+ await withTimeout(
3749
+ () => page.mouse.click(point.x, point.y),
3750
+ mouseFallbackTimeoutMs,
3751
+ "mouse.click fallback"
3752
+ );
3753
+ return { method: "mouse" };
3769
3754
  };
3770
- var checkElementVisibility = async (element) => {
3771
- return element.evaluate((el) => {
3772
- const targetStyle = window.getComputedStyle(el);
3773
- if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
3774
- return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
3755
+ var MobileHumanize = {
3756
+ jitterMs,
3757
+ async initializeCursor(page) {
3758
+ if (initializedPages.has(page)) return;
3759
+ initializedPages.add(page);
3760
+ logger5.debug("initializeCursor: mobile mode uses touch gestures, cursor init skipped");
3761
+ },
3762
+ async humanMove(page, target) {
3763
+ logger5.debug(`humanMove: mobile no-op target=${typeof target === "string" ? target : "element/coords"}`);
3764
+ if (typeof target === "string" || target && typeof target.boundingBox === "function") {
3765
+ const element = await resolveElement(page, target, { throwOnMissing: false });
3766
+ if (!element) {
3767
+ return false;
3768
+ }
3775
3769
  }
3776
- const rect = el.getBoundingClientRect();
3777
- if (!rect || rect.width <= 0 || rect.height <= 0) {
3778
- return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6", direction: "down" };
3770
+ await waitJitter(80, 0.4);
3771
+ return true;
3772
+ },
3773
+ async humanScroll(page, target, options = {}) {
3774
+ const {
3775
+ maxSteps = 20,
3776
+ minStep = 180,
3777
+ maxStep = 520,
3778
+ maxDurationMs = maxSteps * 280 + 1200,
3779
+ throwOnMissing = false
3780
+ } = options;
3781
+ const targetDesc = describeTarget(target);
3782
+ logger5.start("humanScroll", `target=${targetDesc}`);
3783
+ const element = await resolveElement(page, target, { throwOnMissing });
3784
+ if (!element) {
3785
+ logger5.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${targetDesc}`);
3786
+ return { element: null, didScroll: false, restore: null };
3779
3787
  }
3780
- const viewW = window.innerWidth;
3781
- const viewH = window.innerHeight;
3782
- const centerY = rect.top + rect.height / 2;
3783
- let clipLeft = 0;
3784
- let clipRight = viewW;
3785
- let clipTop = 0;
3786
- let clipBottom = viewH;
3787
- let isFixed = false;
3788
- let positioning = null;
3789
- for (let node = el; node && node !== document.body; node = node.parentElement) {
3790
- const style = window.getComputedStyle(node);
3791
- if (style && (style.position === "fixed" || style.position === "sticky")) {
3792
- isFixed = true;
3793
- positioning = style.position;
3794
- break;
3788
+ const startTime = Date.now();
3789
+ let didScroll = false;
3790
+ for (let i = 0; i < maxSteps; i += 1) {
3791
+ const status = await checkElementVisibility(element);
3792
+ if (status.code === "VISIBLE") {
3793
+ if (status.isFixed) {
3794
+ logger5.info("humanScroll | fixed/sticky \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
3795
+ } else {
3796
+ logger5.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
3797
+ }
3798
+ logger5.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
3799
+ return { element, didScroll, restore: null };
3800
+ }
3801
+ if (status.code === "ZERO_DIMENSIONS" || status.code === "NOT_INTERACTABLE") {
3802
+ logger5.warn(`humanScroll | \u5143\u7D20\u4E0D\u53EF\u6EDA\u52A8\u81F3\u53EF\u70B9\u51FB\u72B6\u6001: ${status.reason || status.code}`);
3803
+ return { element, didScroll, restore: null };
3804
+ }
3805
+ const scrollRect = await getScrollableRect(element);
3806
+ if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
3807
+ logger5.warn(`humanScroll | fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (direction=${status.direction || "unknown"})`);
3808
+ return { element, didScroll, restore: null, unscrollable: true };
3809
+ }
3810
+ if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
3811
+ logger5.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
3812
+ return { element, didScroll, restore: null, unscrollable: true };
3813
+ }
3814
+ if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
3815
+ const moved = await scrollAwayFromObstruction(element, status);
3816
+ if (moved.moved) {
3817
+ logger5.debug(`humanScroll | sticky/fixed \u906E\u6321\u8865\u507F\u6EDA\u52A8 top=${Math.round(moved.scrollTop || 0)}`);
3818
+ await waitJitter(90, 0.3);
3819
+ didScroll = true;
3820
+ continue;
3821
+ }
3795
3822
  }
3796
- }
3797
- for (let node = el.parentElement; node && node !== document.body; node = node.parentElement) {
3798
- const style = window.getComputedStyle(node);
3799
- if (!style) continue;
3800
- const clipsY = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowY);
3801
- const clipsX = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowX);
3802
- if (!clipsX && !clipsY) continue;
3803
- const nodeRect = node.getBoundingClientRect();
3804
- if (!nodeRect || nodeRect.width <= 0 || nodeRect.height <= 0) continue;
3805
- if (clipsX) {
3806
- clipLeft = Math.max(clipLeft, nodeRect.left);
3807
- clipRight = Math.min(clipRight, nodeRect.right);
3823
+ if (Date.now() - startTime > maxDurationMs) {
3824
+ logger5.warn(`humanScroll | mobile timeout (${maxDurationMs}ms, status=${status.code}, direction=${status.direction || "unknown"}, fixed=${Boolean(status.isFixed)})`);
3825
+ return { element, didScroll, restore: null };
3808
3826
  }
3809
- if (clipsY) {
3810
- clipTop = Math.max(clipTop, nodeRect.top);
3811
- clipBottom = Math.min(clipBottom, nodeRect.bottom);
3827
+ const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
3828
+ const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
3829
+ logger5.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason || status.code} ${status.direction ? `(${status.direction})` : ""}`);
3830
+ const distance = stepMin + Math.random() * Math.max(1, stepMax - stepMin);
3831
+ let deltaY = status.direction === "up" ? -distance : distance;
3832
+ if (status.code === "OBSTRUCTED") {
3833
+ if (status.obstruction?.isFixed && status.obstruction.top != null) {
3834
+ const obstructionMiddle = (Number(status.obstruction.top || 0) + Number(status.obstruction.bottom || status.obstruction.top || 0)) / 2;
3835
+ const visibleMiddle = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
3836
+ deltaY = obstructionMiddle < visibleMiddle ? -distance : distance;
3837
+ } else {
3838
+ const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
3839
+ deltaY = status.cy > halfY ? distance : -distance;
3840
+ }
3812
3841
  }
3813
- }
3814
- const visibleLeft = Math.max(clipLeft, Math.min(clipRight, rect.left));
3815
- const visibleRight = Math.max(clipLeft, Math.min(clipRight, rect.right));
3816
- const visibleTop = Math.max(clipTop, Math.min(clipBottom, rect.top));
3817
- const visibleBottom = Math.max(clipTop, Math.min(clipBottom, rect.bottom));
3818
- const visibleWidth = visibleRight - visibleLeft;
3819
- const visibleHeight = visibleBottom - visibleTop;
3820
- const cx = visibleLeft + visibleWidth / 2;
3821
- const cy = visibleTop + visibleHeight / 2;
3822
- if (visibleWidth <= 1 || visibleHeight <= 1) {
3823
- return {
3824
- code: "OUT_OF_VIEWPORT",
3825
- reason: "\u4E0D\u5728\u89C6\u53E3\u5185",
3826
- direction: centerY < clipTop ? "up" : "down",
3827
- cy: centerY,
3828
- viewH,
3829
- isFixed,
3830
- positioning
3842
+ const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
3843
+ const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
3844
+ const beforeState = scrollRect ? await element.evaluate((el) => {
3845
+ const isScrollable = (node) => {
3846
+ const style = window.getComputedStyle(node);
3847
+ if (!style) return false;
3848
+ const overflowY = style.overflowY;
3849
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3850
+ return node.scrollHeight > node.clientHeight + 1;
3851
+ };
3852
+ let current = el;
3853
+ while (current && current !== document.body) {
3854
+ if (isScrollable(current)) {
3855
+ return {
3856
+ kind: "element",
3857
+ top: current.scrollTop,
3858
+ left: current.scrollLeft
3859
+ };
3860
+ }
3861
+ current = current.parentElement;
3862
+ }
3863
+ return { kind: "window", top: window.scrollY, left: window.scrollX };
3864
+ }) : null;
3865
+ await dispatchTouchSwipe(page, deltaY, { rect: scrollRect });
3866
+ if (scrollRect && beforeWindowState) {
3867
+ const afterWindowState = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY }));
3868
+ if (Math.abs(afterWindowState.x - beforeWindowState.x) > 2 || Math.abs(afterWindowState.y - beforeWindowState.y) > 2) {
3869
+ await page.evaluate((state2) => window.scrollTo(state2.x, state2.y), beforeWindowState);
3870
+ logger5.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
3871
+ }
3872
+ }
3873
+ let afterElementSnapshot = null;
3874
+ const readAfterElementSnapshot = async () => {
3875
+ if (!afterElementSnapshot) {
3876
+ afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
3877
+ }
3878
+ return afterElementSnapshot;
3831
3879
  };
3880
+ if (!scrollRect && beforeElementSnapshot) {
3881
+ const afterSnapshot = await readAfterElementSnapshot();
3882
+ if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
3883
+ await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
3884
+ logger5.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u9875\u9762\u6EDA\u52A8\u79FB\u52A8\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
3885
+ return { element, didScroll, restore: null, unscrollable: true };
3886
+ }
3887
+ }
3888
+ if (scrollRect && beforeState) {
3889
+ const afterState = await element.evaluate((el) => {
3890
+ const isScrollable = (node) => {
3891
+ const style = window.getComputedStyle(node);
3892
+ if (!style) return false;
3893
+ const overflowY = style.overflowY;
3894
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3895
+ return node.scrollHeight > node.clientHeight + 1;
3896
+ };
3897
+ let current = el;
3898
+ while (current && current !== document.body) {
3899
+ if (isScrollable(current)) {
3900
+ return {
3901
+ kind: "element",
3902
+ top: current.scrollTop,
3903
+ left: current.scrollLeft
3904
+ };
3905
+ }
3906
+ current = current.parentElement;
3907
+ }
3908
+ return { kind: "window", top: window.scrollY, left: window.scrollX };
3909
+ });
3910
+ const topDelta = Number(afterState.top || 0) - Number(beforeState.top || 0);
3911
+ const leftDelta = Number(afterState.left || 0) - Number(beforeState.left || 0);
3912
+ const expectedDelta = Number(deltaY || 0);
3913
+ const moved = beforeState.kind !== afterState.kind || Math.abs(topDelta) > 2 || Math.abs(leftDelta) > 2;
3914
+ if (!moved) {
3915
+ const fallback = await scrollScrollableAncestor(element, deltaY);
3916
+ logger5.debug(`humanScroll | \u5BB9\u5668\u89E6\u6478\u65E0\u6548\uFF0C\u76F4\u63A5\u6EDA\u52A8 fallback=${fallback.scroller ? "ancestor" : "window"} top=${Math.round(fallback.scrollTop || 0)}`);
3917
+ } else if (beforeState.kind === afterState.kind && Math.abs(expectedDelta) > 24 && Math.sign(topDelta || expectedDelta) === Math.sign(expectedDelta) && Math.abs(topDelta) < Math.min(Math.abs(expectedDelta) * 0.45, 96)) {
3918
+ const residualDelta = expectedDelta - topDelta;
3919
+ if (Math.sign(residualDelta) === Math.sign(expectedDelta) && Math.abs(residualDelta) > 24) {
3920
+ const fallback = await scrollScrollableAncestor(element, residualDelta);
3921
+ logger5.debug(`humanScroll | \u5BB9\u5668\u89E6\u6478\u8DDD\u79BB\u4E0D\u8DB3\uFF0C\u8865\u507F\u6EDA\u52A8 fallback=${fallback.scroller ? "ancestor" : "window"} top=${Math.round(fallback.scrollTop || 0)}`);
3922
+ }
3923
+ }
3924
+ }
3925
+ if (scrollRect && beforeElementSnapshot) {
3926
+ const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
3927
+ if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
3928
+ await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
3929
+ logger5.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u6EDA\u52A8\u5BB9\u5668\u79FB\u52A8\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
3930
+ return { element, didScroll, restore: null, unscrollable: true };
3931
+ }
3932
+ }
3933
+ didScroll = true;
3832
3934
  }
3833
- const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
3834
- const commonAncestor = (a, b) => {
3835
- for (let current = a; current && !isRootNode(current); current = current.parentElement) {
3836
- if (current.contains(b)) return current;
3935
+ try {
3936
+ await element.scrollIntoViewIfNeeded?.();
3937
+ await waitJitter(80, 0.3);
3938
+ const finalStatus = await checkElementVisibility(element);
3939
+ if (finalStatus.code === "VISIBLE") {
3940
+ logger5.info("humanScroll | \u539F\u751F scrollIntoViewIfNeeded \u515C\u5E95\u6210\u529F");
3941
+ logger5.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
3942
+ return { element, didScroll: true, restore: null };
3837
3943
  }
3838
- return null;
3839
- };
3840
- const sameTapTarget = (pointElement) => {
3841
- if (!pointElement) return false;
3842
- if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
3944
+ } catch (fallbackError) {
3945
+ logger5.debug(`humanScroll | native fallback failed: ${fallbackError?.message || fallbackError}`);
3946
+ }
3947
+ logger5.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
3948
+ return { element, didScroll, restore: null };
3949
+ },
3950
+ async humanClick(page, target, options = {}) {
3951
+ const {
3952
+ reactionDelay = 220,
3953
+ throwOnMissing = true,
3954
+ scrollIfNeeded = true,
3955
+ tapTimeoutMs = DEFAULT_TAP_TIMEOUT_MS,
3956
+ mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
3957
+ activateFallbackTimeoutMs = DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS,
3958
+ fallbackDomClick = true,
3959
+ fallbackDomClickOnTapError = true
3960
+ } = options;
3961
+ const targetDesc = describeTarget(target);
3962
+ logger5.start("humanClick", `target=${targetDesc}`);
3963
+ try {
3964
+ if (target == null) {
3965
+ const viewport = resolveViewport(page);
3966
+ await waitJitter(reactionDelay, 0.45);
3967
+ await tapPoint(page, {
3968
+ x: viewport.width * (0.45 + Math.random() * 0.1),
3969
+ y: viewport.height * (0.48 + Math.random() * 0.12)
3970
+ }, {
3971
+ timeoutMs: tapTimeoutMs,
3972
+ mouseFallbackTimeoutMs
3973
+ });
3974
+ logger5.success("humanClick", "Tapped current position");
3843
3975
  return true;
3844
3976
  }
3845
- const common = commonAncestor(el, pointElement);
3846
- if (!common) return false;
3847
- const commonRect = common.getBoundingClientRect?.();
3848
- if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
3849
- const commonArea = commonRect.width * commonRect.height;
3850
- const targetArea = Math.max(1, rect.width * rect.height);
3851
- const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
3852
- if (commonArea > maxSharedRegionArea) return false;
3853
- if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
3977
+ const element = await resolveElement(page, target, { throwOnMissing });
3978
+ if (!element) {
3979
+ logger5.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
3854
3980
  return false;
3855
3981
  }
3856
- return common.contains(el) && common.contains(pointElement);
3857
- };
3858
- const describeElement = (node) => {
3859
- if (!node) return null;
3860
- const className = typeof node.className === "string" ? node.className : node.className && typeof node.className.baseVal === "string" ? node.className.baseVal : "";
3861
- const rect2 = node.getBoundingClientRect?.();
3862
- const style = window.getComputedStyle(node);
3863
- return {
3864
- tag: node.tagName,
3865
- id: node.id || "",
3866
- className,
3867
- isFixed: Boolean(style && (style.position === "fixed" || style.position === "sticky")),
3868
- positioning: style?.position || "",
3869
- top: rect2 ? rect2.top : null,
3870
- bottom: rect2 ? rect2.bottom : null,
3871
- left: rect2 ? rect2.left : null,
3872
- right: rect2 ? rect2.right : null
3873
- };
3874
- };
3875
- const samplePoints = [
3876
- { x: cx, y: cy },
3877
- { x: visibleLeft + Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3878
- { x: visibleRight - Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3879
- { x: cx, y: visibleTop + Math.min(8, Math.max(1, visibleHeight * 0.25)) },
3880
- { x: cx, y: visibleBottom - Math.min(8, Math.max(1, visibleHeight * 0.25)) }
3881
- ];
3882
- let obstruction = null;
3883
- for (const point of samplePoints) {
3884
- const pointElement = document.elementFromPoint(point.x, point.y);
3885
- if (sameTapTarget(pointElement)) {
3886
- return { code: "VISIBLE", isFixed, positioning };
3887
- }
3888
- obstruction = obstruction || describeElement(pointElement);
3889
- }
3890
- if (obstruction) {
3891
- return {
3892
- code: "OBSTRUCTED",
3893
- reason: "\u88AB\u906E\u6321",
3894
- direction: cy > viewH / 2 ? "down" : "up",
3895
- obstruction,
3896
- cy,
3897
- viewH,
3898
- isFixed,
3899
- positioning
3900
- };
3901
- }
3902
- return { code: "VISIBLE", isFixed, positioning };
3903
- });
3904
- };
3905
- var activateElementFallback = async (element, point = null, options = {}) => {
3906
- return element.evaluate((el, { innerOptions }) => {
3907
- const isEditable = (node) => {
3908
- if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
3909
- if (node.isContentEditable) return true;
3910
- if (node instanceof HTMLTextAreaElement) return !node.disabled && !node.readOnly;
3911
- if (node instanceof HTMLInputElement) {
3912
- return !node.disabled && !node.readOnly && typeof node.select === "function";
3982
+ const scrollResult = scrollIfNeeded ? await MobileHumanize.humanScroll(page, element, { throwOnMissing }) : null;
3983
+ const status = await checkElementVisibility(element).catch(() => null);
3984
+ if (status && status.code !== "VISIBLE") {
3985
+ if (fallbackDomClick && (status.code === "OUT_OF_VIEWPORT" || status.code === "OBSTRUCTED") && (status.isFixed || scrollResult?.unscrollable)) {
3986
+ let fallback = await withTimeout(
3987
+ () => activateElementFallback(element, null, {
3988
+ editableOnly: true
3989
+ }),
3990
+ activateFallbackTimeoutMs,
3991
+ "focus fallback"
3992
+ ).catch(() => null);
3993
+ if (!fallback?.activated) {
3994
+ fallback = await withTimeout(
3995
+ () => activateElementFallback(element, null, {
3996
+ editableOnly: false
3997
+ }),
3998
+ activateFallbackTimeoutMs,
3999
+ "activation fallback"
4000
+ ).catch(() => null);
4001
+ }
4002
+ if (fallback?.activated) {
4003
+ logger5.warn(`humanClick: \u4E0D\u53EF\u6EDA\u52A8\u76EE\u6807\u4E0D\u53EF\u7269\u7406\u70B9\u51FB\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
4004
+ return true;
4005
+ }
4006
+ }
4007
+ const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
4008
+ if (throwOnMissing) throw new Error(message);
4009
+ logger5.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
4010
+ return false;
3913
4011
  }
3914
- return false;
3915
- };
3916
- const findEditable = (node) => {
3917
- for (let current = node; current && current !== document.body; current = current.parentElement) {
3918
- if (isEditable(current)) return current;
4012
+ const box = await element.boundingBox();
4013
+ if (!box) {
4014
+ if (throwOnMissing) throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
4015
+ logger5.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
4016
+ return false;
3919
4017
  }
3920
- return null;
3921
- };
3922
- const editable = findEditable(el);
3923
- if (editable && typeof editable.focus === "function") {
3924
- editable.focus({ preventScroll: true });
3925
- if (innerOptions.editableOnly) {
3926
- return { activated: true, method: "focus", tag: editable.tagName || "" };
4018
+ await waitJitter(reactionDelay, 0.45);
4019
+ const visibleBox = clipBoxToViewport(box, resolveViewport(page));
4020
+ const tapTarget = centerPointInBox(visibleBox);
4021
+ try {
4022
+ await tapPoint(page, tapTarget, {
4023
+ timeoutMs: tapTimeoutMs,
4024
+ mouseFallbackTimeoutMs
4025
+ });
4026
+ } catch (tapError) {
4027
+ if (!fallbackDomClickOnTapError) throw tapError;
4028
+ const fallback = await withTimeout(
4029
+ () => activateElementFallback(element, tapTarget, {
4030
+ editableOnly: false
4031
+ }),
4032
+ activateFallbackTimeoutMs,
4033
+ "activation fallback"
4034
+ ).catch(() => null);
4035
+ if (!fallback?.activated) throw tapError;
4036
+ logger5.warn(`humanClick: tap \u5931\u8D25\u540E\u5DF2\u7528 ${fallback.method} \u515C\u5E95: ${tapError?.message || tapError}`);
3927
4037
  }
4038
+ await waitJitter(120, 0.35);
4039
+ logger5.success("humanClick");
4040
+ return true;
4041
+ } catch (error) {
4042
+ logger5.fail("humanClick", error);
4043
+ throw error;
3928
4044
  }
3929
- if (innerOptions.editableOnly) {
3930
- return { activated: false, method: "none", tag: el?.tagName || "" };
3931
- }
3932
- if (typeof el.focus === "function") {
3933
- el.focus({ preventScroll: true });
3934
- }
3935
- if (typeof el.click === "function") {
3936
- el.click();
3937
- return { activated: true, method: "dom-click", tag: el.tagName || "" };
3938
- }
3939
- if (typeof el.dispatchEvent === "function") {
3940
- el.dispatchEvent(new MouseEvent("click", {
3941
- bubbles: true,
3942
- cancelable: true,
3943
- view: window
3944
- }));
3945
- return { activated: true, method: "dispatch-click", tag: el.tagName || "" };
4045
+ },
4046
+ async randomSleep(baseMs, jitterPercent = 0.3) {
4047
+ await waitJitter(baseMs, jitterPercent);
4048
+ },
4049
+ async simulateGaze(page, baseDurationMs = 2500) {
4050
+ const durationMs = jitterMs(baseDurationMs, 0.4);
4051
+ const startTime = Date.now();
4052
+ while (Date.now() - startTime < durationMs) {
4053
+ if (Math.random() < 0.28) {
4054
+ const distance = 70 + Math.random() * 120;
4055
+ await dispatchTouchSwipe(page, Math.random() < 0.7 ? distance : -distance, {
4056
+ durationMs: 180,
4057
+ steps: 4
4058
+ });
4059
+ } else {
4060
+ await waitJitter(420, 0.55);
4061
+ }
3946
4062
  }
3947
- return {
3948
- activated: Boolean(editable),
3949
- method: editable ? "focus" : "none",
3950
- tag: el?.tagName || ""
3951
- };
3952
- }, {
3953
- innerOptions: options || {}
3954
- });
3955
- };
3956
- var getScrollableRect = async (element) => {
3957
- return element.evaluate((el) => {
3958
- const isScrollable = (node) => {
3959
- const style = window.getComputedStyle(node);
3960
- if (!style) return false;
3961
- const overflowY = style.overflowY;
3962
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3963
- return node.scrollHeight > node.clientHeight + 1;
3964
- };
3965
- let current = el;
3966
- while (current && current !== document.body) {
3967
- if (isScrollable(current)) {
3968
- const rect = current.getBoundingClientRect();
3969
- if (rect && rect.width > 0 && rect.height > 0) {
3970
- return {
3971
- x: rect.x,
3972
- y: rect.y,
3973
- width: rect.width,
3974
- height: rect.height
3975
- };
3976
- }
4063
+ },
4064
+ async humanType(page, selector, text, options = {}) {
4065
+ const {
4066
+ baseDelay = 160,
4067
+ pauseProbability = 0.08,
4068
+ pauseBase = 700
4069
+ } = options;
4070
+ await MobileHumanize.humanClick(page, selector, { scrollIfNeeded: true });
4071
+ await waitJitter(220, 0.45);
4072
+ for (let i = 0; i < String(text).length; i += 1) {
4073
+ const char = String(text)[i];
4074
+ const charDelay = /[,.!?;:,。!?;:]/.test(char) ? jitterMs(baseDelay * 1.45, 0.4) : jitterMs(char === " " ? baseDelay * 0.65 : baseDelay, 0.4);
4075
+ await page.keyboard.type(char);
4076
+ await waitJitter(charDelay, 0.15);
4077
+ if (Math.random() < pauseProbability && i < String(text).length - 1) {
4078
+ await waitJitter(pauseBase, 0.5);
3977
4079
  }
3978
- current = current.parentElement;
3979
4080
  }
3980
- return null;
3981
- });
3982
- };
3983
- var scrollScrollableAncestor = async (element, deltaY) => {
3984
- return element.evaluate((el, amount) => {
3985
- const isScrollable = (node) => {
3986
- const style = window.getComputedStyle(node);
3987
- if (!style) return false;
3988
- const overflowY = style.overflowY;
3989
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3990
- return node.scrollHeight > node.clientHeight + 1;
3991
- };
3992
- let current = el;
3993
- while (current && current !== document.body) {
3994
- if (isScrollable(current)) {
3995
- const beforeTop2 = current.scrollTop;
3996
- current.scrollTop = beforeTop2 + amount;
3997
- return {
3998
- scroller: true,
3999
- moved: current.scrollTop !== beforeTop2,
4000
- scrollTop: current.scrollTop
4001
- };
4081
+ },
4082
+ async humanPress(page, targetOrKey, maybeKey, options = {}) {
4083
+ const hasTarget = typeof maybeKey === "string";
4084
+ const key = hasTarget ? maybeKey : targetOrKey;
4085
+ const pressOptions = hasTarget ? options : maybeKey || options;
4086
+ const {
4087
+ reactionDelay = 170,
4088
+ holdDelay = 42,
4089
+ focusDelay = 180,
4090
+ scrollIfNeeded = true,
4091
+ throwOnMissing = true,
4092
+ keyboardOptions = {}
4093
+ } = pressOptions || {};
4094
+ const targetDesc = hasTarget ? describeTarget(targetOrKey) : "current focus";
4095
+ logger5.start("humanPress", `key=${key}, target=${targetDesc}`);
4096
+ try {
4097
+ if (hasTarget) {
4098
+ await MobileHumanize.humanClick(page, targetOrKey, {
4099
+ reactionDelay: focusDelay,
4100
+ scrollIfNeeded,
4101
+ throwOnMissing
4102
+ });
4002
4103
  }
4003
- current = current.parentElement;
4104
+ await waitJitter(reactionDelay, 0.45);
4105
+ await page.keyboard.press(key, {
4106
+ ...keyboardOptions,
4107
+ delay: jitterMs(holdDelay, 0.5)
4108
+ });
4109
+ logger5.success("humanPress");
4110
+ return true;
4111
+ } catch (error) {
4112
+ logger5.fail("humanPress", error);
4113
+ throw error;
4004
4114
  }
4005
- const beforeTop = window.scrollY;
4006
- window.scrollBy(0, amount);
4007
- return {
4008
- scroller: null,
4009
- moved: window.scrollY !== beforeTop,
4010
- scrollTop: window.scrollY
4011
- };
4012
- }, deltaY);
4013
- };
4014
- var scrollAwayFromObstruction = async (element, status) => {
4015
- return element.evaluate((el, innerStatus) => {
4016
- const isScrollable = (node) => {
4017
- const style = window.getComputedStyle(node);
4018
- if (!style) return false;
4019
- const overflowY = style.overflowY;
4020
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
4021
- return node.scrollHeight > node.clientHeight + 1;
4115
+ },
4116
+ async humanClear(page, selector) {
4117
+ const locator = page.locator(selector);
4118
+ await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
4119
+ await waitJitter(160, 0.4);
4120
+ const readValue = async () => {
4121
+ try {
4122
+ return await locator.inputValue({ timeout: 600 });
4123
+ } catch {
4124
+ return await locator.evaluate((el) => "value" in el ? String(el.value || "") : String(el.textContent || "")).catch(() => "");
4125
+ }
4022
4126
  };
4023
- let scroller = el;
4024
- while (scroller && scroller !== document.body) {
4025
- if (isScrollable(scroller)) break;
4026
- scroller = scroller.parentElement;
4027
- }
4028
- if (!scroller || scroller === document.body) {
4029
- return { moved: false, scrollTop: 0, deltaY: 0 };
4127
+ const currentValue = await readValue();
4128
+ if (!currentValue) return;
4129
+ await page.keyboard.press("ControlOrMeta+A");
4130
+ await waitJitter(90, 0.35);
4131
+ await page.keyboard.press("Backspace");
4132
+ await waitJitter(120, 0.35);
4133
+ if (!await readValue()) return;
4134
+ await locator.evaluate((el) => {
4135
+ if ("value" in el) {
4136
+ el.value = "";
4137
+ el.dispatchEvent(new Event("input", { bubbles: true }));
4138
+ el.dispatchEvent(new Event("change", { bubbles: true }));
4139
+ return;
4140
+ }
4141
+ el.textContent = "";
4142
+ el.dispatchEvent(new Event("input", { bubbles: true }));
4143
+ });
4144
+ },
4145
+ async warmUpBrowsing(page, baseDuration = 3500) {
4146
+ const durationMs = jitterMs(baseDuration, 0.4);
4147
+ const startTime = Date.now();
4148
+ while (Date.now() - startTime < durationMs) {
4149
+ const action = Math.random();
4150
+ if (action < 0.5) {
4151
+ await dispatchTouchSwipe(page, 120 + Math.random() * 220, {
4152
+ durationMs: 240,
4153
+ steps: 5
4154
+ });
4155
+ } else if (action < 0.7) {
4156
+ await dispatchTouchSwipe(page, -(80 + Math.random() * 160), {
4157
+ durationMs: 220,
4158
+ steps: 4
4159
+ });
4160
+ } else {
4161
+ await waitJitter(560, 0.55);
4162
+ }
4030
4163
  }
4031
- const rect = el.getBoundingClientRect();
4032
- const scrollerRect = scroller.getBoundingClientRect();
4033
- const obstruction = innerStatus?.obstruction || {};
4034
- const obstructionTop = Number(obstruction.top);
4035
- const obstructionBottom = Number(obstruction.bottom);
4036
- const obstructionMiddle = Number.isFinite(obstructionTop) && Number.isFinite(obstructionBottom) ? (obstructionTop + obstructionBottom) / 2 : scrollerRect.top + scrollerRect.height / 2;
4037
- const scrollerMiddle = scrollerRect.top + scrollerRect.height / 2;
4038
- const padding = 18;
4039
- let deltaY = 0;
4040
- if (Number.isFinite(obstructionTop) && rect.bottom > obstructionTop && obstructionTop >= scrollerRect.top && obstructionTop <= scrollerRect.bottom && obstructionMiddle >= scrollerMiddle) {
4041
- deltaY = rect.bottom - obstructionTop + padding;
4042
- } else if (Number.isFinite(obstructionBottom) && rect.top < obstructionBottom && obstructionBottom >= scrollerRect.top && obstructionBottom <= scrollerRect.bottom && obstructionMiddle < scrollerMiddle) {
4043
- deltaY = rect.top - obstructionBottom - padding;
4044
- } else {
4045
- const fallbackDistance = Math.max(48, Math.min(180, scrollerRect.height * 0.45));
4046
- deltaY = obstructionMiddle >= scrollerMiddle ? fallbackDistance : -fallbackDistance;
4164
+ },
4165
+ async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4166
+ const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
4167
+ const actualDistance = jitterMs(distance, 0.15);
4168
+ const sign = direction === "down" ? 1 : -1;
4169
+ for (let i = 0; i < steps; i += 1) {
4170
+ const factor = 1 - i / steps * 0.45;
4171
+ await dispatchTouchSwipe(page, actualDistance / steps * factor * sign, {
4172
+ durationMs: 150 + i * 20,
4173
+ steps: 4
4174
+ });
4047
4175
  }
4048
- const beforeTop = scroller.scrollTop;
4049
- scroller.scrollTop = beforeTop + deltaY;
4050
- return {
4051
- moved: Math.abs(scroller.scrollTop - beforeTop) > 2,
4052
- scrollTop: scroller.scrollTop,
4053
- deltaY
4054
- };
4055
- }, status);
4056
- };
4057
- var getElementViewportSnapshot = async (element) => {
4058
- return element.evaluate((el) => {
4059
- const rect = el.getBoundingClientRect();
4060
- return {
4061
- top: rect.top,
4062
- bottom: rect.bottom,
4063
- left: rect.left,
4064
- right: rect.right,
4065
- width: rect.width,
4066
- height: rect.height,
4067
- scrollX: window.scrollX,
4068
- scrollY: window.scrollY
4069
- };
4070
- });
4071
- };
4072
- var isTargetImmobileAfterScroll = (before, after) => {
4073
- if (!before || !after) return false;
4074
- const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
4075
- const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
4076
- const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
4077
- const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
4078
- const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
4079
- const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
4080
- if (!rectMoved && !pageMoved) return true;
4081
- if (pageMoved && !rectMoved) return true;
4082
- if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
4083
- return true;
4084
4176
  }
4085
- return false;
4086
4177
  };
4087
- var restoreWindowFromSnapshot = async (page, before, after) => {
4088
- if (!before || !after) return;
4089
- if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
4090
- return;
4178
+
4179
+ // src/internals/humanize/cloakbrowser.js
4180
+ var FORCE_CLICK_OPTIONS = Object.freeze({ force: true });
4181
+ var resolveDeviceFromPage2 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
4182
+ var isPoint2 = (value) => value && typeof value === "object" && Number.isFinite(Number(value.x)) && Number.isFinite(Number(value.y));
4183
+ var resolveTarget = (page, target) => typeof target === "string" ? page.locator(target).first() : target;
4184
+ var resolveTargetPoint = async (page, target) => {
4185
+ if (typeof target === "string") {
4186
+ return await DeviceInput.selectorCenterPoint(page, target).catch(() => null);
4091
4187
  }
4092
- await page.evaluate(
4093
- (state2) => window.scrollTo(state2.x, state2.y),
4094
- { x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
4095
- ).catch(() => {
4096
- });
4097
- };
4098
- var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
4099
- const viewport = resolveViewport(page);
4100
- const rawRect = options.rect || null;
4101
- const rect = rawRect ? {
4102
- x: clamp(rawRect.x, 0, viewport.width),
4103
- y: clamp(rawRect.y, 0, viewport.height),
4104
- width: clamp(rawRect.width, 0, viewport.width),
4105
- height: clamp(rawRect.height, 0, viewport.height)
4106
- } : null;
4107
- const area = rect && rect.width > 24 && rect.height > 48 ? {
4108
- left: rect.x,
4109
- right: Math.min(viewport.width, rect.x + rect.width),
4110
- top: rect.y,
4111
- bottom: Math.min(viewport.height, rect.y + rect.height)
4112
- } : {
4113
- left: 0,
4114
- right: viewport.width,
4115
- top: 0,
4116
- bottom: viewport.height
4188
+ const box = await target?.boundingBox?.().catch(() => null);
4189
+ if (!box) {
4190
+ return null;
4191
+ }
4192
+ return {
4193
+ x: box.x + box.width / 2,
4194
+ y: box.y + box.height / 2
4117
4195
  };
4118
- const areaWidth = Math.max(1, area.right - area.left);
4119
- const areaHeight = Math.max(1, area.bottom - area.top);
4120
- const maxDistance = rect ? Math.max(60, areaHeight * 0.72) : Math.max(120, viewport.height * 0.72);
4121
- const distance = clamp(Math.abs(deltaY), Math.min(80, maxDistance), maxDistance);
4122
- const direction = deltaY >= 0 ? 1 : -1;
4123
- const startX = clamp(
4124
- area.left + areaWidth * (0.45 + Math.random() * 0.1),
4125
- 24,
4126
- viewport.width - 24
4127
- );
4128
- const startY = direction > 0 ? clamp(area.top + areaHeight * (0.72 + Math.random() * 0.1), area.top + 24, area.bottom - 16) : clamp(area.top + areaHeight * (0.28 + Math.random() * 0.1), area.top + 16, area.bottom - 24);
4129
- const endY = clamp(startY - direction * distance, Math.max(16, area.top + 12), Math.min(viewport.height - 16, area.bottom - 12));
4130
- const steps = Math.max(4, Math.round(Number(options.steps || 6)));
4131
- const durationMs = jitterMs(options.durationMs || 320, 0.35);
4132
- let client = null;
4133
- const scrollBefore = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
4134
- try {
4135
- if (page.context && typeof page.context().newCDPSession === "function") {
4136
- client = await page.context().newCDPSession(page);
4196
+ };
4197
+ var sleep = (baseMs, jitterPercent = 0.3) => delay3(jitterMs(baseMs, jitterPercent));
4198
+ var buildKeyboardOptions = (options = {}) => ({
4199
+ ...options.holdDelay != null ? { delay: Math.max(0, Number(options.holdDelay) || 0) } : {},
4200
+ ...options.keyboardOptions || {}
4201
+ });
4202
+ var CloakBrowserDeviceHumanize = {
4203
+ jitterMs(base, jitterPercent = 0.3) {
4204
+ return jitterMs(base, jitterPercent);
4205
+ },
4206
+ async initializeCursor(page) {
4207
+ return Boolean(page);
4208
+ },
4209
+ async humanMove(page, target) {
4210
+ if (target == null) {
4211
+ return false;
4137
4212
  }
4138
- if (!client) throw new Error("CDP session unavailable");
4139
- await client.send("Input.synthesizeScrollGesture", {
4140
- x: startX,
4141
- y: startY,
4142
- yDistance: -direction * distance,
4143
- xOverscroll: (Math.random() - 0.5) * 4,
4144
- yOverscroll: (Math.random() - 0.5) * 8,
4145
- speed: 700 + Math.round(Math.random() * 350),
4146
- gestureSourceType: "touch"
4147
- });
4148
- await waitJitter(160, 0.35);
4149
- const scrollAfterSynth = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
4150
- if (scrollBefore && scrollAfterSynth && (Math.abs(scrollAfterSynth.y - scrollBefore.y) > 2 || Math.abs(scrollAfterSynth.x - scrollBefore.x) > 2)) {
4213
+ if (isPoint2(target)) {
4214
+ await DeviceInput.move(page, target, void 0, { forceMouse: true });
4151
4215
  return true;
4152
4216
  }
4153
- await client.send("Input.dispatchTouchEvent", {
4154
- type: "touchStart",
4155
- touchPoints: [{ x: startX, y: startY, id: 1 }]
4217
+ const point = await resolveTargetPoint(page, target);
4218
+ if (!point) {
4219
+ return false;
4220
+ }
4221
+ await DeviceInput.move(page, point, void 0, { forceMouse: true });
4222
+ return true;
4223
+ },
4224
+ async humanScroll(page, target) {
4225
+ const element = resolveTarget(page, target);
4226
+ if (!element) {
4227
+ return { element: null, didScroll: false, restore: null };
4228
+ }
4229
+ await element.scrollIntoViewIfNeeded?.().catch(() => {
4156
4230
  });
4157
- for (let i = 1; i <= steps; i += 1) {
4158
- const progress = i / steps;
4159
- const eased = 1 - Math.pow(1 - progress, 2);
4160
- const x = startX + (Math.random() - 0.5) * 3;
4161
- const y = startY + (endY - startY) * eased + (Math.random() - 0.5) * 5;
4162
- await waitJitter(durationMs / steps, 0.25);
4163
- await client.send("Input.dispatchTouchEvent", {
4164
- type: "touchMove",
4165
- touchPoints: [{ x, y, id: 1 }]
4166
- });
4231
+ return { element, didScroll: true, restore: null };
4232
+ },
4233
+ async humanClick(page, target) {
4234
+ if (target == null) {
4235
+ await DeviceInput.clickPoint(page, { x: 0, y: 0 }, void 0, { forceMouse: true });
4236
+ return true;
4167
4237
  }
4168
- await client.send("Input.dispatchTouchEvent", {
4169
- type: "touchEnd",
4170
- touchPoints: []
4238
+ if (isPoint2(target)) {
4239
+ await DeviceInput.clickPoint(page, target, void 0, { forceMouse: true });
4240
+ return true;
4241
+ }
4242
+ await DeviceInput.click(page, target, {
4243
+ clickOptions: FORCE_CLICK_OPTIONS
4171
4244
  });
4172
- await waitJitter(120, 0.35);
4173
- const scrollAfterTouch = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
4174
- if (scrollBefore && scrollAfterTouch && Math.abs(scrollAfterTouch.y - scrollBefore.y) <= 2 && Math.abs(scrollAfterTouch.x - scrollBefore.x) <= 2) {
4175
- await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
4176
- await waitJitter(120, 0.35);
4245
+ return true;
4246
+ },
4247
+ async randomSleep(baseMs, jitterPercent = 0.3) {
4248
+ await sleep(baseMs, jitterPercent);
4249
+ },
4250
+ async simulateGaze(page, baseDurationMs = 2500) {
4251
+ const durationMs = jitterMs(baseDurationMs, 0.4);
4252
+ const startedAt = Date.now();
4253
+ const viewport = page.viewportSize() || { width: 1365, height: 900 };
4254
+ while (Date.now() - startedAt < durationMs) {
4255
+ await DeviceInput.move(page, {
4256
+ x: 100 + Math.random() * Math.max(120, viewport.width - 200),
4257
+ y: 100 + Math.random() * Math.max(120, viewport.height - 200)
4258
+ }, void 0, { forceMouse: true });
4259
+ await sleep(300, 0.5);
4177
4260
  }
4261
+ },
4262
+ async humanType(page, target, text, options = {}) {
4263
+ await DeviceInput.click(page, target, {
4264
+ clickOptions: FORCE_CLICK_OPTIONS
4265
+ });
4266
+ await DeviceInput.keyboardType(page, text, {
4267
+ ...options.baseDelay != null ? { delay: Math.max(0, Number(options.baseDelay) || 0) } : {}
4268
+ });
4178
4269
  return true;
4179
- } catch (error) {
4180
- logger6.debug(`touch swipe fallback: ${error?.message || error}`);
4181
- try {
4182
- await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
4183
- await waitJitter(120, 0.35);
4184
- return true;
4185
- } catch {
4186
- await page.mouse.wheel(0, deltaY);
4187
- await waitJitter(120, 0.35);
4270
+ },
4271
+ async humanPress(page, targetOrKey, maybeKey, options = {}) {
4272
+ if (typeof maybeKey !== "string") {
4273
+ await DeviceInput.press(page, targetOrKey, {
4274
+ keyboardOptions: buildKeyboardOptions(maybeKey || options)
4275
+ });
4188
4276
  return true;
4189
4277
  }
4190
- } finally {
4191
- if (client && typeof client.detach === "function") {
4192
- try {
4193
- await client.detach();
4194
- } catch {
4195
- }
4196
- }
4197
- }
4198
- };
4199
- var tapPoint = async (page, point, options = {}) => {
4200
- const {
4201
- timeoutMs = DEFAULT_TAP_TIMEOUT_MS,
4202
- mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
4203
- allowMouseFallback = true
4204
- } = options;
4205
- if (page.touchscreen && typeof page.touchscreen.tap === "function") {
4206
- try {
4207
- await withTimeout(
4208
- () => page.touchscreen.tap(point.x, point.y),
4209
- timeoutMs,
4210
- "touchscreen.tap"
4211
- );
4212
- return { method: "touchscreen" };
4213
- } catch (error) {
4214
- logger6.warn(`tapPoint | touchscreen.tap \u5931\u8D25\u6216\u8D85\u65F6\uFF0C\u5C1D\u8BD5\u9F20\u6807\u515C\u5E95: ${error?.message || error}`);
4215
- if (!allowMouseFallback) throw error;
4278
+ await DeviceInput.press(page, targetOrKey, maybeKey, {
4279
+ clickOptions: FORCE_CLICK_OPTIONS,
4280
+ keyboardOptions: buildKeyboardOptions(options)
4281
+ });
4282
+ return true;
4283
+ },
4284
+ async humanClear(page, target) {
4285
+ await DeviceInput.fill(page, target, "", { force: true });
4286
+ },
4287
+ async warmUpBrowsing(page, baseDuration = 3500) {
4288
+ await this.simulateGaze(page, Math.min(baseDuration, 900));
4289
+ },
4290
+ async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4291
+ const steps = Math.max(1, Number(baseSteps) || 1);
4292
+ const delta = (Number(distance) || 0) / steps * (direction === "down" ? 1 : -1);
4293
+ for (let index = 0; index < steps; index += 1) {
4294
+ await page.mouse.wheel(0, delta);
4295
+ await sleep(60 + index * 20, 0.3);
4216
4296
  }
4217
4297
  }
4218
- await withTimeout(
4219
- () => page.mouse.click(point.x, point.y),
4220
- mouseFallbackTimeoutMs,
4221
- "mouse.click fallback"
4222
- );
4223
- return { method: "mouse" };
4224
4298
  };
4225
- var MobileHumanize = {
4226
- jitterMs,
4299
+ var resolveCloakBrowserHumanizeDelegate = (page) => resolveDeviceFromPage2(page) === Device.Mobile ? MobileHumanize : CloakBrowserDeviceHumanize;
4300
+ var CloakBrowserHumanizeContext = Object.freeze({
4301
+ desktopDelegate: CloakBrowserDeviceHumanize,
4302
+ resolveDelegate: resolveCloakBrowserHumanizeDelegate
4303
+ });
4304
+
4305
+ // src/internals/humanize/desktop.js
4306
+ import delay4 from "delay";
4307
+ import { createCursor } from "ghost-cursor-playwright";
4308
+ var logger6 = createInternalLogger("Humanize");
4309
+ var $CursorWeakMap = /* @__PURE__ */ new WeakMap();
4310
+ function $GetCursor(page) {
4311
+ const cursor = $CursorWeakMap.get(page);
4312
+ if (!cursor) {
4313
+ throw new Error("Cursor \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 Humanize.initializeCursor(page)");
4314
+ }
4315
+ return cursor;
4316
+ }
4317
+ var Humanize = {
4318
+ /**
4319
+ * 生成带抖动的毫秒数 - 基于基础值添加随机浮动 (±30% 默认)
4320
+ * @param {number} base - 基础延迟 (ms)
4321
+ * @param {number} [jitterPercent=0.3] - 抖动百分比 (0.3 = ±30%)
4322
+ * @returns {number} 抖动后的毫秒数
4323
+ */
4324
+ jitterMs(base, jitterPercent = 0.3) {
4325
+ const jitter = base * jitterPercent * (Math.random() * 2 - 1);
4326
+ return Math.max(10, Math.round(base + jitter));
4327
+ },
4328
+ /**
4329
+ * 初始化页面的 Ghost Cursor(必须在使用其他 cursor 相关方法前调用)
4330
+ *
4331
+ * @param {import('playwright').Page} page
4332
+ * @returns {Promise<void>}
4333
+ */
4227
4334
  async initializeCursor(page) {
4228
- if (initializedPages.has(page)) return;
4229
- initializedPages.add(page);
4230
- logger6.debug("initializeCursor: mobile mode uses touch gestures, cursor init skipped");
4335
+ if ($CursorWeakMap.has(page)) {
4336
+ logger6.debug("initializeCursor: cursor already exists, skipping");
4337
+ return;
4338
+ }
4339
+ logger6.start("initializeCursor", "creating cursor");
4340
+ const cursor = await createCursor(page);
4341
+ $CursorWeakMap.set(page, cursor);
4342
+ logger6.success("initializeCursor", "cursor initialized");
4231
4343
  },
4344
+ /**
4345
+ * 人类化鼠标移动 - 使用 ghost-cursor 移动到指定位置或元素
4346
+ *
4347
+ * @param {import('playwright').Page} page
4348
+ * @param {string|{x: number, y: number}|import('playwright').ElementHandle} target - CSS选择器、坐标对象或元素句柄
4349
+ */
4232
4350
  async humanMove(page, target) {
4233
- logger6.debug(`humanMove: mobile no-op target=${typeof target === "string" ? target : "element/coords"}`);
4234
- if (typeof target === "string" || target && typeof target.boundingBox === "function") {
4235
- const element = await resolveElement(page, target, { throwOnMissing: false });
4236
- if (!element) {
4237
- return false;
4351
+ const cursor = $GetCursor(page);
4352
+ logger6.start("humanMove", `target=${typeof target === "string" ? target : "element/coords"}`);
4353
+ try {
4354
+ if (typeof target === "string") {
4355
+ const element = await page.$(target);
4356
+ if (!element) {
4357
+ logger6.warn(`humanMove: \u5143\u7D20\u4E0D\u5B58\u5728 ${target}`);
4358
+ return false;
4359
+ }
4360
+ const box = await element.boundingBox();
4361
+ if (!box) {
4362
+ logger6.warn(`humanMove: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E ${target}`);
4363
+ return false;
4364
+ }
4365
+ const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
4366
+ const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
4367
+ await cursor.actions.move({ x, y });
4368
+ } else if (target && typeof target.x === "number" && typeof target.y === "number") {
4369
+ await cursor.actions.move(target);
4370
+ } else if (target && typeof target.boundingBox === "function") {
4371
+ const box = await target.boundingBox();
4372
+ if (box) {
4373
+ const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
4374
+ const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
4375
+ await cursor.actions.move({ x, y });
4376
+ }
4238
4377
  }
4378
+ logger6.success("humanMove");
4379
+ return true;
4380
+ } catch (error) {
4381
+ logger6.fail("humanMove", error);
4382
+ throw error;
4239
4383
  }
4240
- await waitJitter(80, 0.4);
4241
- return true;
4242
4384
  },
4385
+ /**
4386
+ * 渐进式滚动到元素可见(仅处理 Y 轴滚动)
4387
+ * 返回 restore 方法,用于将滚动容器恢复到原位置
4388
+ *
4389
+ * @param {import('playwright').Page} page
4390
+ * @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
4391
+ * @param {Object} [options]
4392
+ * @param {number} [options.maxSteps=20] - 最大滚动步数
4393
+ * @param {number} [options.minStep=260] - 单次滚动最小步长
4394
+ * @param {number} [options.maxStep=800] - 单次滚动最大步长
4395
+ * @param {number} [options.maxDurationMs] - 最长耗时上限 (默认随 maxSteps 估算)
4396
+ */
4243
4397
  async humanScroll(page, target, options = {}) {
4244
4398
  const {
4245
4399
  maxSteps = 20,
4246
- minStep = 180,
4247
- maxStep = 520,
4248
- maxDurationMs = maxSteps * 280 + 1200,
4249
- throwOnMissing = false
4400
+ minStep = 260,
4401
+ maxStep = 800,
4402
+ maxDurationMs = maxSteps * 220 + 800
4250
4403
  } = options;
4251
- const targetDesc = describeTarget(target);
4404
+ const targetDesc = typeof target === "string" ? target : "ElementHandle";
4252
4405
  logger6.start("humanScroll", `target=${targetDesc}`);
4253
- const element = await resolveElement(page, target, { throwOnMissing });
4254
- if (!element) {
4255
- logger6.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${targetDesc}`);
4256
- return { element: null, didScroll: false, restore: null };
4406
+ let element;
4407
+ if (typeof target === "string") {
4408
+ element = await page.$(target);
4409
+ if (!element) {
4410
+ logger6.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${target}`);
4411
+ return { element: null, didScroll: false };
4412
+ }
4413
+ } else {
4414
+ element = target;
4257
4415
  }
4258
- const startTime = Date.now();
4416
+ const cursor = $GetCursor(page);
4259
4417
  let didScroll = false;
4260
- for (let i = 0; i < maxSteps; i += 1) {
4261
- const status = await checkElementVisibility(element);
4262
- if (status.code === "VISIBLE") {
4263
- if (status.isFixed) {
4264
- logger6.info("humanScroll | fixed/sticky \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
4265
- } else {
4266
- logger6.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
4418
+ const checkVisibility = async () => {
4419
+ return await element.evaluate((el) => {
4420
+ const rect = el.getBoundingClientRect();
4421
+ if (!rect || rect.width === 0 || rect.height === 0) {
4422
+ return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6" };
4267
4423
  }
4268
- logger6.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
4269
- return { element, didScroll, restore: null };
4270
- }
4271
- if (status.code === "ZERO_DIMENSIONS" || status.code === "NOT_INTERACTABLE") {
4272
- logger6.warn(`humanScroll | \u5143\u7D20\u4E0D\u53EF\u6EDA\u52A8\u81F3\u53EF\u70B9\u51FB\u72B6\u6001: ${status.reason || status.code}`);
4273
- return { element, didScroll, restore: null };
4274
- }
4275
- const scrollRect = await getScrollableRect(element);
4276
- if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
4277
- logger6.warn(`humanScroll | fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (direction=${status.direction || "unknown"})`);
4278
- return { element, didScroll, restore: null, unscrollable: true };
4279
- }
4280
- if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
4281
- logger6.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
4282
- return { element, didScroll, restore: null, unscrollable: true };
4283
- }
4284
- if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
4285
- const moved = await scrollAwayFromObstruction(element, status);
4286
- if (moved.moved) {
4287
- logger6.debug(`humanScroll | sticky/fixed \u906E\u6321\u8865\u507F\u6EDA\u52A8 top=${Math.round(moved.scrollTop || 0)}`);
4288
- await waitJitter(90, 0.3);
4289
- didScroll = true;
4290
- continue;
4424
+ const cx = rect.left + rect.width / 2;
4425
+ const cy = rect.top + rect.height / 2;
4426
+ const viewH = window.innerHeight;
4427
+ const viewW = window.innerWidth;
4428
+ let isFixed = false;
4429
+ for (let node = el; node && node !== document.body; node = node.parentElement) {
4430
+ const style = window.getComputedStyle(node);
4431
+ if (style && style.position === "fixed") {
4432
+ isFixed = true;
4433
+ break;
4434
+ }
4291
4435
  }
4292
- }
4293
- if (Date.now() - startTime > maxDurationMs) {
4294
- logger6.warn(`humanScroll | mobile timeout (${maxDurationMs}ms, status=${status.code}, direction=${status.direction || "unknown"}, fixed=${Boolean(status.isFixed)})`);
4295
- return { element, didScroll, restore: null };
4296
- }
4297
- const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
4298
- const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
4299
- logger6.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason || status.code} ${status.direction ? `(${status.direction})` : ""}`);
4300
- const distance = stepMin + Math.random() * Math.max(1, stepMax - stepMin);
4301
- let deltaY = status.direction === "up" ? -distance : distance;
4302
- if (status.code === "OBSTRUCTED") {
4303
- if (status.obstruction?.isFixed && status.obstruction.top != null) {
4304
- const obstructionMiddle = (Number(status.obstruction.top || 0) + Number(status.obstruction.bottom || status.obstruction.top || 0)) / 2;
4305
- const visibleMiddle = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
4306
- deltaY = obstructionMiddle < visibleMiddle ? -distance : distance;
4307
- } else {
4308
- const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
4309
- deltaY = status.cy > halfY ? distance : -distance;
4436
+ if (cy < 0 || cy > viewH || cx < 0 || cx > viewW) {
4437
+ const direction = cy < 0 ? "up" : cy > viewH ? "down" : "unknown";
4438
+ return { code: "OUT_OF_VIEWPORT", reason: "\u4E0D\u5728\u89C6\u53E3\u5185", direction, cy, viewH, isFixed };
4310
4439
  }
4311
- }
4312
- const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
4313
- const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4314
- const beforeState = scrollRect ? await element.evaluate((el) => {
4440
+ const pointElement = document.elementFromPoint(cx, cy);
4441
+ if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
4442
+ return {
4443
+ code: "OBSTRUCTED",
4444
+ reason: "\u88AB\u906E\u6321",
4445
+ obstruction: {
4446
+ tag: pointElement.tagName,
4447
+ id: pointElement.id,
4448
+ className: pointElement.className
4449
+ },
4450
+ cy,
4451
+ // Return Center Y for smart direction calculation
4452
+ viewH,
4453
+ isFixed
4454
+ };
4455
+ }
4456
+ return { code: "VISIBLE", isFixed };
4457
+ });
4458
+ };
4459
+ const getScrollableRect2 = async () => {
4460
+ return await element.evaluate((el) => {
4315
4461
  const isScrollable = (node) => {
4316
4462
  const style = window.getComputedStyle(node);
4317
4463
  if (!style) return false;
@@ -4321,260 +4467,260 @@ var MobileHumanize = {
4321
4467
  };
4322
4468
  let current = el;
4323
4469
  while (current && current !== document.body) {
4324
- if (isScrollable(current)) {
4325
- return {
4326
- kind: "element",
4327
- top: current.scrollTop,
4328
- left: current.scrollLeft
4329
- };
4330
- }
4331
- current = current.parentElement;
4332
- }
4333
- return { kind: "window", top: window.scrollY, left: window.scrollX };
4334
- }) : null;
4335
- await dispatchTouchSwipe(page, deltaY, { rect: scrollRect });
4336
- if (scrollRect && beforeWindowState) {
4337
- const afterWindowState = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY }));
4338
- if (Math.abs(afterWindowState.x - beforeWindowState.x) > 2 || Math.abs(afterWindowState.y - beforeWindowState.y) > 2) {
4339
- await page.evaluate((state2) => window.scrollTo(state2.x, state2.y), beforeWindowState);
4340
- logger6.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
4341
- }
4342
- }
4343
- let afterElementSnapshot = null;
4344
- const readAfterElementSnapshot = async () => {
4345
- if (!afterElementSnapshot) {
4346
- afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4347
- }
4348
- return afterElementSnapshot;
4349
- };
4350
- if (!scrollRect && beforeElementSnapshot) {
4351
- const afterSnapshot = await readAfterElementSnapshot();
4352
- if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
4353
- await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
4354
- logger6.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u9875\u9762\u6EDA\u52A8\u79FB\u52A8\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
4355
- return { element, didScroll, restore: null, unscrollable: true };
4356
- }
4357
- }
4358
- if (scrollRect && beforeState) {
4359
- const afterState = await element.evaluate((el) => {
4360
- const isScrollable = (node) => {
4361
- const style = window.getComputedStyle(node);
4362
- if (!style) return false;
4363
- const overflowY = style.overflowY;
4364
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
4365
- return node.scrollHeight > node.clientHeight + 1;
4366
- };
4367
- let current = el;
4368
- while (current && current !== document.body) {
4369
- if (isScrollable(current)) {
4370
- return {
4371
- kind: "element",
4372
- top: current.scrollTop,
4373
- left: current.scrollLeft
4374
- };
4470
+ if (isScrollable(current)) {
4471
+ const rect = current.getBoundingClientRect();
4472
+ if (rect && rect.width > 0 && rect.height > 0) {
4473
+ return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
4375
4474
  }
4376
- current = current.parentElement;
4377
4475
  }
4378
- return { kind: "window", top: window.scrollY, left: window.scrollX };
4379
- });
4380
- const topDelta = Number(afterState.top || 0) - Number(beforeState.top || 0);
4381
- const leftDelta = Number(afterState.left || 0) - Number(beforeState.left || 0);
4382
- const expectedDelta = Number(deltaY || 0);
4383
- const moved = beforeState.kind !== afterState.kind || Math.abs(topDelta) > 2 || Math.abs(leftDelta) > 2;
4384
- if (!moved) {
4385
- const fallback = await scrollScrollableAncestor(element, deltaY);
4386
- logger6.debug(`humanScroll | \u5BB9\u5668\u89E6\u6478\u65E0\u6548\uFF0C\u76F4\u63A5\u6EDA\u52A8 fallback=${fallback.scroller ? "ancestor" : "window"} top=${Math.round(fallback.scrollTop || 0)}`);
4387
- } else if (beforeState.kind === afterState.kind && Math.abs(expectedDelta) > 24 && Math.sign(topDelta || expectedDelta) === Math.sign(expectedDelta) && Math.abs(topDelta) < Math.min(Math.abs(expectedDelta) * 0.45, 96)) {
4388
- const residualDelta = expectedDelta - topDelta;
4389
- if (Math.sign(residualDelta) === Math.sign(expectedDelta) && Math.abs(residualDelta) > 24) {
4390
- const fallback = await scrollScrollableAncestor(element, residualDelta);
4391
- logger6.debug(`humanScroll | \u5BB9\u5668\u89E6\u6478\u8DDD\u79BB\u4E0D\u8DB3\uFF0C\u8865\u507F\u6EDA\u52A8 fallback=${fallback.scroller ? "ancestor" : "window"} top=${Math.round(fallback.scrollTop || 0)}`);
4476
+ current = current.parentElement;
4477
+ }
4478
+ return null;
4479
+ });
4480
+ };
4481
+ const startTime = Date.now();
4482
+ try {
4483
+ for (let i = 0; i < maxSteps; i++) {
4484
+ if (Date.now() - startTime > maxDurationMs) {
4485
+ logger6.warn(`humanScroll | \u8D85\u65F6\u4FDD\u62A4\u89E6\u53D1 (${maxDurationMs}ms)`);
4486
+ return { element, didScroll };
4487
+ }
4488
+ const status = await checkVisibility();
4489
+ if (status.code === "VISIBLE") {
4490
+ if (status.isFixed) {
4491
+ logger6.info("humanScroll | fixed \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
4492
+ } else {
4493
+ logger6.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
4392
4494
  }
4495
+ logger6.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
4496
+ return { element, didScroll };
4393
4497
  }
4394
- }
4395
- if (scrollRect && beforeElementSnapshot) {
4396
- const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4397
- if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
4398
- await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
4399
- logger6.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u6EDA\u52A8\u5BB9\u5668\u79FB\u52A8\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
4400
- return { element, didScroll, restore: null, unscrollable: true };
4498
+ logger6.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
4499
+ if (status.code === "OBSTRUCTED" && status.obstruction) {
4500
+ logger6.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
4401
4501
  }
4502
+ const scrollRect = await getScrollableRect2();
4503
+ if (!scrollRect && status.isFixed) {
4504
+ logger6.warn("humanScroll | fixed \u5BB9\u5668\u5185\u4E14\u65E0\u53EF\u6EDA\u52A8\u7956\u5148\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
4505
+ return { element, didScroll };
4506
+ }
4507
+ const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
4508
+ const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
4509
+ let deltaY = 0;
4510
+ if (status.code === "OUT_OF_VIEWPORT") {
4511
+ if (status.direction === "down") {
4512
+ deltaY = stepMin + Math.random() * (stepMax - stepMin);
4513
+ } else if (status.direction === "up") {
4514
+ deltaY = -(stepMin + Math.random() * (stepMax - stepMin));
4515
+ } else {
4516
+ deltaY = 100;
4517
+ }
4518
+ } else if (status.code === "OBSTRUCTED") {
4519
+ const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
4520
+ const isBottomHalf = status.cy > halfY;
4521
+ const direction = isBottomHalf ? 1 : -1;
4522
+ deltaY = direction * (stepMin + Math.random() * 50);
4523
+ }
4524
+ if (i === 0) {
4525
+ const viewSize = page.viewportSize();
4526
+ if (scrollRect) {
4527
+ const safeX = scrollRect.x + scrollRect.width * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.width * 0.4);
4528
+ const safeY = scrollRect.y + scrollRect.height * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.height * 0.4);
4529
+ await cursor.actions.move({ x: safeX, y: safeY });
4530
+ } else if (viewSize) {
4531
+ const safeX = viewSize.width * 0.5 + (Math.random() - 0.5) * 80;
4532
+ const safeY = viewSize.height * 0.5 + (Math.random() - 0.5) * 80;
4533
+ await cursor.actions.move({ x: safeX, y: safeY });
4534
+ }
4535
+ }
4536
+ await page.mouse.wheel(0, deltaY);
4537
+ didScroll = true;
4538
+ await delay4(this.jitterMs(20 + Math.random() * 40, 0.2));
4402
4539
  }
4403
- didScroll = true;
4404
- }
4405
- try {
4406
- await element.scrollIntoViewIfNeeded?.();
4407
- await waitJitter(80, 0.3);
4408
- const finalStatus = await checkElementVisibility(element);
4409
- if (finalStatus.code === "VISIBLE") {
4410
- logger6.info("humanScroll | \u539F\u751F scrollIntoViewIfNeeded \u515C\u5E95\u6210\u529F");
4411
- logger6.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
4412
- return { element, didScroll: true, restore: null };
4413
- }
4414
- } catch (fallbackError) {
4415
- logger6.debug(`humanScroll | native fallback failed: ${fallbackError?.message || fallbackError}`);
4540
+ logger6.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
4541
+ return { element, didScroll };
4542
+ } catch (error) {
4543
+ logger6.fail("humanScroll", error);
4544
+ throw error;
4416
4545
  }
4417
- logger6.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
4418
- return { element, didScroll, restore: null };
4419
4546
  },
4547
+ /**
4548
+ * 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
4549
+ *
4550
+ * @param {import('playwright').Page} page
4551
+ * @param {string|import('playwright').ElementHandle} [target] - CSS 选择器或元素句柄。如果为空,则点击当前鼠标位置
4552
+ * @param {Object} [options]
4553
+ * @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
4554
+ * @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
4555
+ * @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
4556
+ */
4420
4557
  async humanClick(page, target, options = {}) {
4421
- const {
4422
- reactionDelay = 220,
4423
- throwOnMissing = true,
4424
- scrollIfNeeded = true,
4425
- tapTimeoutMs = DEFAULT_TAP_TIMEOUT_MS,
4426
- mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
4427
- activateFallbackTimeoutMs = DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS,
4428
- fallbackDomClick = true,
4429
- fallbackDomClickOnTapError = true
4430
- } = options;
4431
- const targetDesc = describeTarget(target);
4558
+ const cursor = $GetCursor(page);
4559
+ const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true, restore = false } = options;
4560
+ const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
4432
4561
  logger6.start("humanClick", `target=${targetDesc}`);
4562
+ const restoreOnce = async () => {
4563
+ if (restoreOnce.restored) return;
4564
+ restoreOnce.restored = true;
4565
+ if (typeof restoreOnce.do !== "function") return;
4566
+ try {
4567
+ await delay4(this.jitterMs(1e3));
4568
+ await restoreOnce.do();
4569
+ } catch (restoreError) {
4570
+ logger6.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
4571
+ }
4572
+ };
4433
4573
  try {
4434
4574
  if (target == null) {
4435
- const viewport = resolveViewport(page);
4436
- await waitJitter(reactionDelay, 0.45);
4437
- await tapPoint(page, {
4438
- x: viewport.width * (0.45 + Math.random() * 0.1),
4439
- y: viewport.height * (0.48 + Math.random() * 0.12)
4440
- }, {
4441
- timeoutMs: tapTimeoutMs,
4442
- mouseFallbackTimeoutMs
4443
- });
4444
- logger6.success("humanClick", "Tapped current position");
4575
+ await delay4(this.jitterMs(reactionDelay, 0.4));
4576
+ await cursor.actions.click();
4577
+ logger6.success("humanClick", "Clicked current position");
4445
4578
  return true;
4446
4579
  }
4447
- const element = await resolveElement(page, target, { throwOnMissing });
4448
- if (!element) {
4449
- logger6.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
4450
- return false;
4451
- }
4452
- const scrollResult = scrollIfNeeded ? await MobileHumanize.humanScroll(page, element, { throwOnMissing }) : null;
4453
- const status = await checkElementVisibility(element).catch(() => null);
4454
- if (status && status.code !== "VISIBLE") {
4455
- if (fallbackDomClick && (status.code === "OUT_OF_VIEWPORT" || status.code === "OBSTRUCTED") && (status.isFixed || scrollResult?.unscrollable)) {
4456
- let fallback = await withTimeout(
4457
- () => activateElementFallback(element, null, {
4458
- editableOnly: true
4459
- }),
4460
- activateFallbackTimeoutMs,
4461
- "focus fallback"
4462
- ).catch(() => null);
4463
- if (!fallback?.activated) {
4464
- fallback = await withTimeout(
4465
- () => activateElementFallback(element, null, {
4466
- editableOnly: false
4467
- }),
4468
- activateFallbackTimeoutMs,
4469
- "activation fallback"
4470
- ).catch(() => null);
4471
- }
4472
- if (fallback?.activated) {
4473
- logger6.warn(`humanClick: \u4E0D\u53EF\u6EDA\u52A8\u76EE\u6807\u4E0D\u53EF\u7269\u7406\u70B9\u51FB\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
4474
- return true;
4580
+ let element;
4581
+ if (typeof target === "string") {
4582
+ element = await page.$(target);
4583
+ if (!element) {
4584
+ if (throwOnMissing) {
4585
+ throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${target}`);
4475
4586
  }
4587
+ logger6.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${target}`);
4588
+ return false;
4476
4589
  }
4477
- const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
4478
- if (throwOnMissing) throw new Error(message);
4479
- logger6.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
4480
- return false;
4590
+ } else {
4591
+ element = target;
4592
+ }
4593
+ if (scrollIfNeeded) {
4594
+ const { restore: restoreFn, didScroll } = await this.humanScroll(page, element);
4595
+ restoreOnce.do = didScroll && restore ? restoreFn : null;
4481
4596
  }
4482
4597
  const box = await element.boundingBox();
4483
4598
  if (!box) {
4484
- if (throwOnMissing) throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
4599
+ await restoreOnce();
4600
+ if (throwOnMissing) {
4601
+ throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
4602
+ }
4485
4603
  logger6.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
4486
4604
  return false;
4487
4605
  }
4488
- await waitJitter(reactionDelay, 0.45);
4489
- const visibleBox = clipBoxToViewport(box, resolveViewport(page));
4490
- const tapTarget = centerPointInBox(visibleBox);
4491
- try {
4492
- await tapPoint(page, tapTarget, {
4493
- timeoutMs: tapTimeoutMs,
4494
- mouseFallbackTimeoutMs
4495
- });
4496
- } catch (tapError) {
4497
- if (!fallbackDomClickOnTapError) throw tapError;
4498
- const fallback = await withTimeout(
4499
- () => activateElementFallback(element, tapTarget, {
4500
- editableOnly: false
4501
- }),
4502
- activateFallbackTimeoutMs,
4503
- "activation fallback"
4504
- ).catch(() => null);
4505
- if (!fallback?.activated) throw tapError;
4506
- logger6.warn(`humanClick: tap \u5931\u8D25\u540E\u5DF2\u7528 ${fallback.method} \u515C\u5E95: ${tapError?.message || tapError}`);
4507
- }
4508
- await waitJitter(120, 0.35);
4606
+ const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
4607
+ const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
4608
+ await cursor.actions.move({ x, y });
4609
+ await delay4(this.jitterMs(reactionDelay, 0.4));
4610
+ await cursor.actions.click();
4611
+ await restoreOnce();
4509
4612
  logger6.success("humanClick");
4510
4613
  return true;
4511
4614
  } catch (error) {
4615
+ await restoreOnce();
4512
4616
  logger6.fail("humanClick", error);
4513
4617
  throw error;
4514
4618
  }
4515
4619
  },
4620
+ /**
4621
+ * 随机延迟一段毫秒数(带 ±30% 抖动)
4622
+ * @param {number} baseMs - 基础延迟毫秒数
4623
+ * @param {number} [jitterPercent=0.3] - 抖动百分比
4624
+ */
4516
4625
  async randomSleep(baseMs, jitterPercent = 0.3) {
4517
- await waitJitter(baseMs, jitterPercent);
4626
+ const ms = this.jitterMs(baseMs, jitterPercent);
4627
+ logger6.start("randomSleep", `base=${baseMs}, actual=${ms}ms`);
4628
+ await delay4(ms);
4629
+ logger6.success("randomSleep");
4518
4630
  },
4631
+ /**
4632
+ * 模拟人类"注视"或"阅读"行为:鼠标在页面上随机微动
4633
+ * @param {import('playwright').Page} page
4634
+ * @param {number} [baseDurationMs=2500] - 基础持续时间 (±40% 抖动)
4635
+ */
4519
4636
  async simulateGaze(page, baseDurationMs = 2500) {
4520
- const durationMs = jitterMs(baseDurationMs, 0.4);
4637
+ const cursor = $GetCursor(page);
4638
+ const durationMs = this.jitterMs(baseDurationMs, 0.4);
4639
+ logger6.start("simulateGaze", `duration=${durationMs}ms`);
4521
4640
  const startTime = Date.now();
4641
+ const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
4522
4642
  while (Date.now() - startTime < durationMs) {
4523
- if (Math.random() < 0.28) {
4524
- const distance = 70 + Math.random() * 120;
4525
- await dispatchTouchSwipe(page, Math.random() < 0.7 ? distance : -distance, {
4526
- durationMs: 180,
4527
- steps: 4
4528
- });
4529
- } else {
4530
- await waitJitter(420, 0.55);
4531
- }
4643
+ const x = 100 + Math.random() * (viewportSize.width - 200);
4644
+ const y = 100 + Math.random() * (viewportSize.height - 200);
4645
+ await cursor.actions.move({ x, y });
4646
+ await delay4(this.jitterMs(600, 0.5));
4532
4647
  }
4648
+ logger6.success("simulateGaze");
4533
4649
  },
4650
+ /**
4651
+ * 人类化输入 - 带节奏变化(快-慢-停顿-偶尔加速)
4652
+ * @param {import('playwright').Page} page
4653
+ * @param {string} selector - 输入框选择器
4654
+ * @param {string} text - 要输入的文本
4655
+ * @param {Object} [options]
4656
+ * @param {number} [options.baseDelay=180] - 基础按键延迟 (ms),实际 ±40% 抖动
4657
+ * @param {number} [options.pauseProbability=0.08] - 停顿概率 (0-1)
4658
+ * @param {number} [options.pauseBase=800] - 停顿时长基础值 (ms),实际 ±50% 抖动
4659
+ */
4534
4660
  async humanType(page, selector, text, options = {}) {
4661
+ logger6.start("humanType", `selector=${selector}, textLen=${text.length}`);
4535
4662
  const {
4536
- baseDelay = 160,
4663
+ baseDelay = 180,
4537
4664
  pauseProbability = 0.08,
4538
- pauseBase = 700
4539
- } = options;
4540
- await MobileHumanize.humanClick(page, selector, { scrollIfNeeded: true });
4541
- await waitJitter(220, 0.45);
4542
- for (let i = 0; i < String(text).length; i += 1) {
4543
- const char = String(text)[i];
4544
- const charDelay = /[,.!?;:,。!?;:]/.test(char) ? jitterMs(baseDelay * 1.45, 0.4) : jitterMs(char === " " ? baseDelay * 0.65 : baseDelay, 0.4);
4545
- await page.keyboard.type(char);
4546
- await waitJitter(charDelay, 0.15);
4547
- if (Math.random() < pauseProbability && i < String(text).length - 1) {
4548
- await waitJitter(pauseBase, 0.5);
4665
+ pauseBase = 800
4666
+ } = options;
4667
+ try {
4668
+ const locator = page.locator(selector);
4669
+ await Humanize.humanClick(page, locator);
4670
+ await delay4(this.jitterMs(200, 0.4));
4671
+ for (let i = 0; i < text.length; i++) {
4672
+ const char = text[i];
4673
+ let charDelay;
4674
+ if (char === " ") {
4675
+ charDelay = this.jitterMs(baseDelay * 0.6, 0.3);
4676
+ } else if (/[,.!?;:,。!?;:]/.test(char)) {
4677
+ charDelay = this.jitterMs(baseDelay * 1.5, 0.4);
4678
+ } else {
4679
+ charDelay = this.jitterMs(baseDelay, 0.4);
4680
+ }
4681
+ await page.keyboard.type(char);
4682
+ await delay4(charDelay);
4683
+ if (Math.random() < pauseProbability && i < text.length - 1) {
4684
+ const pauseTime = this.jitterMs(pauseBase, 0.5);
4685
+ logger6.debug(`\u505C\u987F ${pauseTime}ms...`);
4686
+ await delay4(pauseTime);
4687
+ }
4549
4688
  }
4689
+ logger6.success("humanType");
4690
+ } catch (error) {
4691
+ logger6.fail("humanType", error);
4692
+ throw error;
4550
4693
  }
4551
4694
  },
4695
+ /**
4696
+ * 人类化按键 - 模拟用户在当前焦点或指定目标上按下一次键。
4697
+ * @param {import('playwright').Page} page
4698
+ * @param {string|import('playwright').ElementHandle|import('playwright').Locator} targetOrKey - 目标或按键
4699
+ * @param {string|Object} [maybeKey] - 按键或选项
4700
+ * @param {Object} [options]
4701
+ */
4552
4702
  async humanPress(page, targetOrKey, maybeKey, options = {}) {
4553
4703
  const hasTarget = typeof maybeKey === "string";
4554
4704
  const key = hasTarget ? maybeKey : targetOrKey;
4555
4705
  const pressOptions = hasTarget ? options : maybeKey || options;
4556
4706
  const {
4557
- reactionDelay = 170,
4558
- holdDelay = 42,
4707
+ reactionDelay = 180,
4708
+ holdDelay = 45,
4559
4709
  focusDelay = 180,
4560
4710
  scrollIfNeeded = true,
4561
4711
  throwOnMissing = true,
4562
4712
  keyboardOptions = {}
4563
4713
  } = pressOptions || {};
4564
- const targetDesc = hasTarget ? describeTarget(targetOrKey) : "current focus";
4714
+ const targetDesc = hasTarget ? typeof targetOrKey === "string" ? targetOrKey : "ElementHandle" : "current focus";
4565
4715
  logger6.start("humanPress", `key=${key}, target=${targetDesc}`);
4566
4716
  try {
4567
4717
  if (hasTarget) {
4568
- await MobileHumanize.humanClick(page, targetOrKey, {
4569
- reactionDelay: focusDelay,
4570
- scrollIfNeeded,
4571
- throwOnMissing
4572
- });
4718
+ await this.humanClick(page, targetOrKey, { reactionDelay: focusDelay, scrollIfNeeded, throwOnMissing });
4573
4719
  }
4574
- await waitJitter(reactionDelay, 0.45);
4720
+ await delay4(this.jitterMs(reactionDelay, 0.45));
4575
4721
  await page.keyboard.press(key, {
4576
4722
  ...keyboardOptions,
4577
- delay: jitterMs(holdDelay, 0.5)
4723
+ delay: this.jitterMs(holdDelay, 0.5)
4578
4724
  });
4579
4725
  logger6.success("humanPress");
4580
4726
  return true;
@@ -4583,285 +4729,164 @@ var MobileHumanize = {
4583
4729
  throw error;
4584
4730
  }
4585
4731
  },
4732
+ /**
4733
+ * 人类化清空输入框 - 模拟人类删除文本的行为
4734
+ * @param {import('playwright').Page} page
4735
+ * @param {string} selector - 输入框选择器
4736
+ */
4586
4737
  async humanClear(page, selector) {
4587
- const locator = page.locator(selector);
4588
- await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
4589
- await waitJitter(160, 0.4);
4590
- const readValue = async () => {
4591
- try {
4592
- return await locator.inputValue({ timeout: 600 });
4593
- } catch {
4594
- return await locator.evaluate((el) => "value" in el ? String(el.value || "") : String(el.textContent || "")).catch(() => "");
4595
- }
4596
- };
4597
- const currentValue = await readValue();
4598
- if (!currentValue) return;
4599
- await page.keyboard.press("ControlOrMeta+A");
4600
- await waitJitter(90, 0.35);
4601
- await page.keyboard.press("Backspace");
4602
- await waitJitter(120, 0.35);
4603
- if (!await readValue()) return;
4604
- await locator.evaluate((el) => {
4605
- if ("value" in el) {
4606
- el.value = "";
4607
- el.dispatchEvent(new Event("input", { bubbles: true }));
4608
- el.dispatchEvent(new Event("change", { bubbles: true }));
4738
+ logger6.start("humanClear", `selector=${selector}`);
4739
+ try {
4740
+ const locator = page.locator(selector);
4741
+ await locator.click();
4742
+ await delay4(this.jitterMs(200, 0.4));
4743
+ const currentValue = await locator.inputValue();
4744
+ if (!currentValue || currentValue.length === 0) {
4745
+ logger6.success("humanClear", "already empty");
4609
4746
  return;
4610
4747
  }
4611
- el.textContent = "";
4612
- el.dispatchEvent(new Event("input", { bubbles: true }));
4613
- });
4748
+ await page.keyboard.press("Meta+A");
4749
+ await delay4(this.jitterMs(100, 0.4));
4750
+ await page.keyboard.press("Backspace");
4751
+ logger6.success("humanClear");
4752
+ } catch (error) {
4753
+ logger6.fail("humanClear", error);
4754
+ throw error;
4755
+ }
4614
4756
  },
4757
+ /**
4758
+ * 页面预热浏览 - 模拟人类进入页面后的探索行为
4759
+ * @param {import('playwright').Page} page
4760
+ * @param {number} [baseDuration=3500] - 预热时长基础值 (±40% 抖动)
4761
+ */
4615
4762
  async warmUpBrowsing(page, baseDuration = 3500) {
4616
- const durationMs = jitterMs(baseDuration, 0.4);
4763
+ const cursor = $GetCursor(page);
4764
+ const durationMs = this.jitterMs(baseDuration, 0.4);
4765
+ logger6.start("warmUpBrowsing", `duration=${durationMs}ms`);
4617
4766
  const startTime = Date.now();
4618
- while (Date.now() - startTime < durationMs) {
4619
- const action = Math.random();
4620
- if (action < 0.5) {
4621
- await dispatchTouchSwipe(page, 120 + Math.random() * 220, {
4622
- durationMs: 240,
4623
- steps: 5
4624
- });
4625
- } else if (action < 0.7) {
4626
- await dispatchTouchSwipe(page, -(80 + Math.random() * 160), {
4627
- durationMs: 220,
4628
- steps: 4
4629
- });
4630
- } else {
4631
- await waitJitter(560, 0.55);
4767
+ const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
4768
+ try {
4769
+ while (Date.now() - startTime < durationMs) {
4770
+ const action = Math.random();
4771
+ if (action < 0.4) {
4772
+ const x = 100 + Math.random() * (viewportSize.width - 200);
4773
+ const y = 100 + Math.random() * (viewportSize.height - 200);
4774
+ await cursor.actions.move({ x, y });
4775
+ await delay4(this.jitterMs(350, 0.4));
4776
+ } else if (action < 0.7) {
4777
+ const scrollY = (Math.random() - 0.5) * 200;
4778
+ await page.mouse.wheel(0, scrollY);
4779
+ await delay4(this.jitterMs(500, 0.4));
4780
+ } else {
4781
+ await delay4(this.jitterMs(800, 0.5));
4782
+ }
4632
4783
  }
4784
+ logger6.success("warmUpBrowsing");
4785
+ } catch (error) {
4786
+ logger6.fail("warmUpBrowsing", error);
4787
+ throw error;
4633
4788
  }
4634
4789
  },
4790
+ /**
4791
+ * 自然滚动 - 带惯性、减速效果和随机抖动
4792
+ * @param {import('playwright').Page} page
4793
+ * @param {'up' | 'down'} [direction='down'] - 滚动方向
4794
+ * @param {number} [distance=300] - 总滚动距离基础值 (px),±15% 抖动
4795
+ * @param {number} [baseSteps=5] - 分几步完成基础值,±1 随机
4796
+ */
4635
4797
  async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4636
4798
  const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
4637
- const actualDistance = jitterMs(distance, 0.15);
4799
+ const actualDistance = this.jitterMs(distance, 0.15);
4800
+ logger6.start("naturalScroll", `dir=${direction}, dist=${actualDistance}, steps=${steps}`);
4638
4801
  const sign = direction === "down" ? 1 : -1;
4639
- for (let i = 0; i < steps; i += 1) {
4640
- const factor = 1 - i / steps * 0.45;
4641
- await dispatchTouchSwipe(page, actualDistance / steps * factor * sign, {
4642
- durationMs: 150 + i * 20,
4643
- steps: 4
4644
- });
4802
+ const stepDistance = actualDistance / steps;
4803
+ try {
4804
+ for (let i = 0; i < steps; i++) {
4805
+ const factor = 1 - i / steps * 0.5;
4806
+ const jitter = 0.9 + Math.random() * 0.2;
4807
+ const scrollAmount = stepDistance * factor * sign * jitter;
4808
+ await page.mouse.wheel(0, scrollAmount);
4809
+ const baseDelay = 60 + i * 25;
4810
+ await delay4(this.jitterMs(baseDelay, 0.3));
4811
+ }
4812
+ logger6.success("naturalScroll");
4813
+ } catch (error) {
4814
+ logger6.fail("naturalScroll", error);
4815
+ throw error;
4645
4816
  }
4646
4817
  }
4647
4818
  };
4648
4819
 
4649
4820
  // src/internals/humanize/default.js
4650
- var resolveDeviceFromPage2 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
4651
- var resolveDelegate = (page) => {
4652
- return resolveDeviceFromPage2(page) === Device.Mobile ? MobileHumanize : Humanize;
4653
- };
4654
- var callDelegate = (method, page, args) => {
4655
- const delegate = resolveDelegate(page);
4656
- return delegate[method](page, ...args);
4657
- };
4658
- var DefaultHumanize = {
4659
- jitterMs(base, jitterPercent = 0.3) {
4660
- return Humanize.jitterMs(base, jitterPercent);
4661
- },
4662
- initializeCursor(page) {
4663
- return callDelegate("initializeCursor", page, []);
4664
- },
4665
- humanMove(page, target) {
4666
- return callDelegate("humanMove", page, [target]);
4667
- },
4668
- humanScroll(page, target, options = {}) {
4669
- return callDelegate("humanScroll", page, [target, options]);
4670
- },
4671
- humanClick(page, target, options = {}) {
4672
- return callDelegate("humanClick", page, [target, options]);
4673
- },
4674
- randomSleep(pageOrBaseMs, maybeBaseMs, maybeJitterPercent) {
4675
- if (pageOrBaseMs && typeof pageOrBaseMs === "object" && typeof pageOrBaseMs.evaluate === "function") {
4676
- const delegate = resolveDelegate(pageOrBaseMs);
4677
- return delegate.randomSleep(maybeBaseMs, maybeJitterPercent);
4678
- }
4679
- return Humanize.randomSleep(pageOrBaseMs, maybeBaseMs);
4680
- },
4681
- simulateGaze(page, baseDurationMs = 2500) {
4682
- return callDelegate("simulateGaze", page, [baseDurationMs]);
4683
- },
4684
- humanType(page, selector, text, options = {}) {
4685
- return callDelegate("humanType", page, [selector, text, options]);
4686
- },
4687
- humanPress(page, targetOrKey, maybeKey, options = {}) {
4688
- if (typeof maybeKey === "string") {
4689
- return callDelegate("humanPress", page, [targetOrKey, maybeKey, options]);
4690
- }
4691
- return callDelegate("humanPress", page, [targetOrKey, maybeKey || options]);
4692
- },
4693
- humanClear(page, selector) {
4694
- return callDelegate("humanClear", page, [selector]);
4695
- },
4696
- warmUpBrowsing(page, baseDuration = 3500) {
4697
- return callDelegate("warmUpBrowsing", page, [baseDuration]);
4698
- },
4699
- naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4700
- return callDelegate("naturalScroll", page, [direction, distance, baseSteps]);
4701
- }
4702
- };
4703
-
4704
- // src/internals/humanize/cloakbrowser.js
4705
- import delay4 from "delay";
4706
4821
  var resolveDeviceFromPage3 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
4707
- var isMobilePage = (page) => resolveDeviceFromPage3(page) === Device.Mobile;
4822
+ var resolveDefaultHumanizeDelegate = (page) => resolveDeviceFromPage3(page) === Device.Mobile ? MobileHumanize : Humanize;
4823
+ var DefaultHumanizeContext = Object.freeze({
4824
+ desktopDelegate: Humanize,
4825
+ resolveDelegate: resolveDefaultHumanizeDelegate
4826
+ });
4827
+
4828
+ // src/internals/humanize/index.js
4829
+ var HUMANIZE_DELEGATED_METHODS = Object.freeze({
4830
+ initializeCursor: { enumerable: true },
4831
+ humanMove: { enumerable: true },
4832
+ humanScroll: { enumerable: true },
4833
+ humanClick: { enumerable: true },
4834
+ simulateGaze: { enumerable: true },
4835
+ humanType: { enumerable: true },
4836
+ humanPress: { enumerable: true },
4837
+ humanClear: { enumerable: true },
4838
+ warmUpBrowsing: { enumerable: true },
4839
+ naturalScroll: { enumerable: true }
4840
+ });
4708
4841
  var isPageLike2 = (value) => value && typeof value === "object" && typeof value.evaluate === "function";
4709
- var isPoint2 = (value) => value && typeof value === "object" && Number.isFinite(Number(value.x)) && Number.isFinite(Number(value.y));
4710
- var resolveTarget = (page, target) => typeof target === "string" ? page.locator(target).first() : target;
4711
- var sleep = (base, jitter = 0.3) => delay4(jitterMs(base, jitter));
4712
- var FORCE_OPTIONS = Object.freeze({ force: true });
4713
- var DesktopCloakHumanize = {
4714
- jitterMs(base, jitterPercent = 0.3) {
4715
- return jitterMs(base, jitterPercent);
4716
- },
4717
- async initializeCursor(page) {
4718
- return Boolean(page);
4719
- },
4720
- async humanMove(page, target) {
4721
- if (target == null) return false;
4722
- if (isPoint2(target)) {
4723
- await page.mouse.move(Number(target.x), Number(target.y));
4724
- return true;
4725
- }
4726
- await resolveTarget(page, target).hover(FORCE_OPTIONS);
4727
- return true;
4728
- },
4729
- async humanScroll(page, target) {
4730
- const element = resolveTarget(page, target);
4731
- if (!element) return { element: null, didScroll: false, restore: null };
4732
- await element.scrollIntoViewIfNeeded?.();
4733
- return { element, didScroll: true, restore: null };
4734
- },
4735
- async humanClick(page, target) {
4736
- if (target == null) {
4737
- await page.mouse.click(0, 0);
4738
- return true;
4739
- }
4740
- if (isPoint2(target)) {
4741
- await page.mouse.click(Number(target.x), Number(target.y));
4742
- return true;
4743
- }
4744
- if (typeof target === "string") {
4745
- await page.click(target, FORCE_OPTIONS);
4746
- return true;
4747
- }
4748
- await target.click(FORCE_OPTIONS);
4749
- return true;
4750
- },
4751
- async randomSleep(baseMs, jitterPercent = 0.3) {
4752
- await sleep(baseMs, jitterPercent);
4753
- },
4754
- async simulateGaze(page, baseDurationMs = 2500) {
4755
- const durationMs = jitterMs(baseDurationMs, 0.4);
4756
- const startedAt = Date.now();
4757
- const viewport = page.viewportSize() || { width: 1365, height: 900 };
4758
- while (Date.now() - startedAt < durationMs) {
4759
- await page.mouse.move(
4760
- 100 + Math.random() * Math.max(120, viewport.width - 200),
4761
- 100 + Math.random() * Math.max(120, viewport.height - 200)
4762
- );
4763
- await sleep(300, 0.5);
4764
- }
4765
- },
4766
- async humanType(page, selector, text) {
4767
- const target = resolveTarget(page, selector);
4768
- await target.click(FORCE_OPTIONS);
4769
- await page.keyboard.type(text);
4770
- },
4771
- async humanPress(page, targetOrKey, maybeKey) {
4772
- if (typeof maybeKey !== "string") {
4773
- await page.keyboard.press(targetOrKey);
4774
- return true;
4775
- }
4776
- await resolveTarget(page, targetOrKey).click(FORCE_OPTIONS);
4777
- await page.keyboard.press(maybeKey);
4778
- return true;
4779
- },
4780
- async humanClear(page, selector) {
4781
- if (typeof selector === "string") {
4782
- await page.fill(selector, "", FORCE_OPTIONS);
4783
- return;
4784
- }
4785
- await selector.fill("", FORCE_OPTIONS);
4786
- },
4787
- async warmUpBrowsing(page, baseDuration = 3500) {
4788
- await this.simulateGaze(page, Math.min(baseDuration, 900));
4789
- },
4790
- async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4791
- const steps = Math.max(1, Number(baseSteps) || 1);
4792
- const delta = (Number(distance) || 0) / steps * (direction === "down" ? 1 : -1);
4793
- for (let index = 0; index < steps; index += 1) {
4794
- await page.mouse.wheel(0, delta);
4795
- await sleep(60 + index * 20, 0.3);
4796
- }
4797
- }
4798
- };
4799
- var resolveDelegate2 = (page) => isMobilePage(page) ? MobileHumanize : DesktopCloakHumanize;
4800
- var callDelegate2 = (method, page, args) => resolveDelegate2(page)[method](page, ...args);
4801
- var CloakBrowserHumanize = {
4842
+ var createSharedHumanizeMethods = ({ desktopDelegate, resolveDelegate }) => ({
4802
4843
  jitterMs(base, jitterPercent = 0.3) {
4803
- return jitterMs(base, jitterPercent);
4804
- },
4805
- initializeCursor(page) {
4806
- return callDelegate2("initializeCursor", page, []);
4807
- },
4808
- humanMove(page, target) {
4809
- return callDelegate2("humanMove", page, [target]);
4810
- },
4811
- humanScroll(page, target, options = {}) {
4812
- return callDelegate2("humanScroll", page, [target, options]);
4813
- },
4814
- humanClick(page, target, options = {}) {
4815
- return callDelegate2("humanClick", page, [target, options]);
4844
+ return desktopDelegate.jitterMs(base, jitterPercent);
4816
4845
  },
4817
4846
  randomSleep(pageOrBaseMs, maybeBaseMs, maybeJitterPercent) {
4818
4847
  if (isPageLike2(pageOrBaseMs)) {
4819
- return resolveDelegate2(pageOrBaseMs).randomSleep(maybeBaseMs, maybeJitterPercent);
4820
- }
4821
- return DesktopCloakHumanize.randomSleep(pageOrBaseMs, maybeBaseMs);
4822
- },
4823
- simulateGaze(page, baseDurationMs = 2500) {
4824
- return callDelegate2("simulateGaze", page, [baseDurationMs]);
4825
- },
4826
- humanType(page, selector, text, options = {}) {
4827
- return callDelegate2("humanType", page, [selector, text, options]);
4828
- },
4829
- humanPress(page, targetOrKey, maybeKey, options = {}) {
4830
- if (typeof maybeKey === "string") {
4831
- return callDelegate2("humanPress", page, [targetOrKey, maybeKey, options]);
4848
+ return resolveDelegate(pageOrBaseMs).randomSleep(maybeBaseMs, maybeJitterPercent);
4832
4849
  }
4833
- return callDelegate2("humanPress", page, [targetOrKey, maybeKey || options]);
4834
- },
4835
- humanClear(page, selector) {
4836
- return callDelegate2("humanClear", page, [selector]);
4837
- },
4838
- warmUpBrowsing(page, baseDuration = 3500) {
4839
- return callDelegate2("warmUpBrowsing", page, [baseDuration]);
4840
- },
4841
- naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4842
- return callDelegate2("naturalScroll", page, [direction, distance, baseSteps]);
4850
+ return desktopDelegate.randomSleep(pageOrBaseMs, maybeBaseMs);
4843
4851
  }
4852
+ });
4853
+ var withHumanizeDelegates = (target, resolveDelegate) => withDelegatedProperties(target, {
4854
+ namespace: "Humanize",
4855
+ methods: HUMANIZE_DELEGATED_METHODS,
4856
+ resolveDelegate: (_method, args) => ({
4857
+ delegate: resolveDelegate(args[0]),
4858
+ label: "resolved humanize delegate"
4859
+ })
4860
+ });
4861
+ var createHumanizeExport = (context = {}) => {
4862
+ return Object.assign(
4863
+ createSharedHumanizeMethods(context),
4864
+ withHumanizeDelegates({}, context.resolveDelegate)
4865
+ );
4844
4866
  };
4867
+ var DefaultHumanize = createHumanizeExport(DefaultHumanizeContext);
4868
+ var CloakBrowserHumanize = createHumanizeExport(CloakBrowserHumanizeContext);
4845
4869
 
4846
4870
  // src/humanize.js
4847
4871
  var humanizeStrategies = {
4848
4872
  [Mode.Default]: DefaultHumanize,
4849
4873
  [Mode.CloakBrowser]: CloakBrowserHumanize
4850
4874
  };
4851
- var Humanize2 = createDelegatedFacade("Humanize", humanizeStrategies, [
4852
- "jitterMs",
4853
- "initializeCursor",
4854
- "humanMove",
4855
- "humanScroll",
4856
- "humanClick",
4857
- "randomSleep",
4858
- "simulateGaze",
4859
- "humanType",
4860
- "humanPress",
4861
- "humanClear",
4862
- "warmUpBrowsing",
4863
- "naturalScroll"
4864
- ]);
4875
+ var humanizeFacadeMethods = Object.freeze({
4876
+ jitterMs: { enumerable: true },
4877
+ initializeCursor: { enumerable: true },
4878
+ humanMove: { enumerable: true },
4879
+ humanScroll: { enumerable: true },
4880
+ humanClick: { enumerable: true },
4881
+ randomSleep: { enumerable: true },
4882
+ simulateGaze: { enumerable: true },
4883
+ humanType: { enumerable: true },
4884
+ humanPress: { enumerable: true },
4885
+ humanClear: { enumerable: true },
4886
+ warmUpBrowsing: { enumerable: true },
4887
+ naturalScroll: { enumerable: true }
4888
+ });
4889
+ var Humanize2 = createDelegatedFacade("Humanize", humanizeStrategies, humanizeFacadeMethods);
4865
4890
 
4866
4891
  // src/internals/launch/default.js
4867
4892
  import { execFileSync } from "node:child_process";
@@ -5551,9 +5576,10 @@ var launchStrategies = {
5551
5576
  [Mode.Default]: DefaultLaunch,
5552
5577
  [Mode.CloakBrowser]: CloakBrowserLaunch
5553
5578
  };
5554
- var Launch = createDelegatedFacade("Launch", launchStrategies, [
5555
- "getPlaywrightCrawlerOptions"
5556
- ]);
5579
+ var launchFacadeMethods = Object.freeze({
5580
+ getPlaywrightCrawlerOptions: { enumerable: true }
5581
+ });
5582
+ var Launch = createDelegatedFacade("Launch", launchStrategies, launchFacadeMethods);
5557
5583
 
5558
5584
  // src/live-view.js
5559
5585
  import express from "express";