@skrillex1224/playwright-toolkit 2.1.226 → 2.1.227

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -102,10 +102,12 @@ await Actor.exit();
102
102
  | `Humanize` | `initializeCursor(page)` | 初始化 Cursor (必须先调用) |
103
103
  | `Humanize` | `jitterMs(base, jitterPercent?)` | 生成带抖动的毫秒数 (同步,返回 number) |
104
104
  | `Humanize` | `humanType(page, selector, text, options?)` | 人类化输入 (baseDelay=180ms ±40%) |
105
+ | `Humanize` | `humanPress(page, target?, key, options?)` | 人类化按键,支持当前焦点或先聚焦目标后按键 |
105
106
  | `Humanize` | `humanClick(page, selector, options?)` | 人类化点击 (reactionDelay=250ms ±40%) |
106
107
  | `Humanize` | `warmUpBrowsing(page, baseDuration?)` | 页面预热 (3500ms ±40%) |
107
108
  | `Humanize` | `simulateGaze(page, baseDurationMs?)` | 模拟注视 (2500ms ±40%) |
108
109
  | `Humanize` | `randomSleep(baseMs, jitterPercent?)` | 随机延迟 (±30% 抖动) |
110
+ | `Humanize` | `M.click/focus/fill/type/clickPoint(...)` | Machine 机械化操作;用于必须保持非拟人化 click/focus/fill/type 的兼容路径 |
109
111
  | `Captcha` | `useCaptchaMonitor(page, options)` | 验证码监控 |
110
112
 
111
113
  ---
package/dist/index.cjs CHANGED
@@ -2513,6 +2513,43 @@ var Humanize = {
2513
2513
  throw error;
2514
2514
  }
2515
2515
  },
2516
+ /**
2517
+ * 人类化按键 - 模拟用户在当前焦点或指定目标上按下一次键。
2518
+ * @param {import('playwright').Page} page
2519
+ * @param {string|import('playwright').ElementHandle|import('playwright').Locator} targetOrKey - 目标或按键
2520
+ * @param {string|Object} [maybeKey] - 按键或选项
2521
+ * @param {Object} [options]
2522
+ */
2523
+ async humanPress(page, targetOrKey, maybeKey, options = {}) {
2524
+ const hasTarget = typeof maybeKey === "string";
2525
+ const key = hasTarget ? maybeKey : targetOrKey;
2526
+ const pressOptions = hasTarget ? options : maybeKey || options;
2527
+ const {
2528
+ reactionDelay = 180,
2529
+ holdDelay = 45,
2530
+ focusDelay = 180,
2531
+ scrollIfNeeded = true,
2532
+ throwOnMissing = true,
2533
+ keyboardOptions = {}
2534
+ } = pressOptions || {};
2535
+ const targetDesc = hasTarget ? typeof targetOrKey === "string" ? targetOrKey : "ElementHandle" : "current focus";
2536
+ logger6.start("humanPress", `key=${key}, target=${targetDesc}`);
2537
+ try {
2538
+ if (hasTarget) {
2539
+ await this.humanClick(page, targetOrKey, { reactionDelay: focusDelay, scrollIfNeeded, throwOnMissing });
2540
+ }
2541
+ await (0, import_delay.default)(this.jitterMs(reactionDelay, 0.45));
2542
+ await page.keyboard.press(key, {
2543
+ ...keyboardOptions,
2544
+ delay: this.jitterMs(holdDelay, 0.5)
2545
+ });
2546
+ logger6.success("humanPress");
2547
+ return true;
2548
+ } catch (error) {
2549
+ logger6.fail("humanPress", error);
2550
+ throw error;
2551
+ }
2552
+ },
2516
2553
  /**
2517
2554
  * 人类化清空输入框 - 模拟人类删除文本的行为
2518
2555
  * @param {import('playwright').Page} page
@@ -2601,6 +2638,141 @@ var Humanize = {
2601
2638
  }
2602
2639
  };
2603
2640
 
2641
+ // src/internals/humanize/machine.js
2642
+ var resolveDeviceFromPage = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
2643
+ var isPoint = (value) => value && typeof value === "object" && Number.isFinite(Number(value.x)) && Number.isFinite(Number(value.y));
2644
+ var resolveNativeTarget = (page, target) => {
2645
+ if (!target) return null;
2646
+ if (typeof target === "string") {
2647
+ return page.locator(target);
2648
+ }
2649
+ return target;
2650
+ };
2651
+ var normalizeSelectorOptions = (options = {}) => ({
2652
+ targetParent: Boolean(options.targetParent ?? options.parent ?? false),
2653
+ pick: options.pick === "last" || options.last ? "last" : "first",
2654
+ visibleOnly: options.visibleOnly !== false,
2655
+ fallbackDomClick: Boolean(options.fallbackDomClick),
2656
+ forceMouse: Boolean(options.forceMouse)
2657
+ });
2658
+ var MachineHumanize = {
2659
+ async clickPoint(page, pointOrX, maybeY, options = {}) {
2660
+ const clickOptions = isPoint(pointOrX) && maybeY && typeof maybeY === "object" ? maybeY : options;
2661
+ const point = isPoint(pointOrX) ? pointOrX : { x: pointOrX, y: maybeY };
2662
+ const x = Number(point.x);
2663
+ const y = Number(point.y);
2664
+ if (!Number.isFinite(x) || !Number.isFinite(y)) {
2665
+ throw new Error(`Invalid click point: ${JSON.stringify(point)}`);
2666
+ }
2667
+ const resolvedDevice = resolveDeviceFromPage(page);
2668
+ if (resolvedDevice === Device.Mobile && !clickOptions.forceMouse && page.touchscreen && typeof page.touchscreen.tap === "function") {
2669
+ await page.touchscreen.tap(x, y);
2670
+ return true;
2671
+ }
2672
+ await page.mouse.click(x, y, clickOptions.mouseOptions || {});
2673
+ return true;
2674
+ },
2675
+ async click(page, target, options = {}) {
2676
+ if (isPoint(target)) {
2677
+ return MachineHumanize.clickPoint(page, target, void 0, options);
2678
+ }
2679
+ const nativeTarget = resolveNativeTarget(page, target);
2680
+ if (!nativeTarget || typeof nativeTarget.click !== "function") {
2681
+ throw new Error("Machine click target does not expose click()");
2682
+ }
2683
+ await nativeTarget.click(options.clickOptions || options);
2684
+ return true;
2685
+ },
2686
+ async focus(page, target, options = {}) {
2687
+ const nativeTarget = resolveNativeTarget(page, target);
2688
+ if (!nativeTarget || typeof nativeTarget.focus !== "function") {
2689
+ throw new Error("Machine focus target does not expose focus()");
2690
+ }
2691
+ await nativeTarget.focus(options);
2692
+ return true;
2693
+ },
2694
+ async fill(page, target, value, options = {}) {
2695
+ const nativeTarget = resolveNativeTarget(page, target);
2696
+ if (!nativeTarget || typeof nativeTarget.fill !== "function") {
2697
+ throw new Error("Machine fill target does not expose fill()");
2698
+ }
2699
+ await nativeTarget.fill(value, options);
2700
+ return true;
2701
+ },
2702
+ async keyboardType(page, text, options = {}) {
2703
+ await page.keyboard.type(text, options);
2704
+ return true;
2705
+ },
2706
+ async type(page, targetOrText, maybeText, options = {}) {
2707
+ if (maybeText == null || typeof maybeText === "object" && !Array.isArray(maybeText)) {
2708
+ return MachineHumanize.keyboardType(page, targetOrText, maybeText || options);
2709
+ }
2710
+ const nativeTarget = resolveNativeTarget(page, targetOrText);
2711
+ if (!nativeTarget || typeof nativeTarget.type !== "function") {
2712
+ throw new Error("Machine type target does not expose type()");
2713
+ }
2714
+ await nativeTarget.type(maybeText, options);
2715
+ return true;
2716
+ },
2717
+ async selectorCenterPoint(page, selector, options = {}) {
2718
+ const normalizedOptions = normalizeSelectorOptions(options);
2719
+ return page.evaluate(({ innerSelector, innerOptions }) => {
2720
+ const nodes = Array.from(document.querySelectorAll(innerSelector));
2721
+ const ordered = innerOptions.pick === "last" ? nodes.reverse() : nodes;
2722
+ let rect = null;
2723
+ for (const node of ordered) {
2724
+ if (!node) continue;
2725
+ const target = innerOptions.targetParent ? node.parentElement || node : node;
2726
+ rect = target.getBoundingClientRect?.() || null;
2727
+ const isVisible = Boolean(rect && rect.width > 0 && rect.height > 0);
2728
+ if (innerOptions.visibleOnly && !isVisible) continue;
2729
+ break;
2730
+ }
2731
+ if (!rect || rect.width <= 0 || rect.height <= 0) return null;
2732
+ return {
2733
+ x: rect.left + rect.width / 2,
2734
+ y: rect.top + rect.height / 2
2735
+ };
2736
+ }, { innerSelector: selector, innerOptions: normalizedOptions });
2737
+ },
2738
+ async domClick(page, selector, options = {}) {
2739
+ const normalizedOptions = normalizeSelectorOptions(options);
2740
+ return page.evaluate(({ innerSelector, innerOptions }) => {
2741
+ const nodes = Array.from(document.querySelectorAll(innerSelector));
2742
+ const ordered = innerOptions.pick === "last" ? nodes.reverse() : nodes;
2743
+ let target = null;
2744
+ for (const node of ordered) {
2745
+ if (!node) continue;
2746
+ const candidate = innerOptions.targetParent ? node.parentElement || node : node;
2747
+ const rect = candidate.getBoundingClientRect?.() || null;
2748
+ const isVisible = Boolean(rect && rect.width > 0 && rect.height > 0);
2749
+ if (innerOptions.visibleOnly && !isVisible) continue;
2750
+ target = candidate;
2751
+ break;
2752
+ }
2753
+ if (!target || typeof target.click !== "function") return false;
2754
+ target.click();
2755
+ return true;
2756
+ }, { innerSelector: selector, innerOptions: normalizedOptions });
2757
+ },
2758
+ async clickSelectorCenter(page, selector, options = {}) {
2759
+ const normalizedOptions = normalizeSelectorOptions(options);
2760
+ try {
2761
+ const point = await MachineHumanize.selectorCenterPoint(page, selector, normalizedOptions);
2762
+ if (point) {
2763
+ await MachineHumanize.clickPoint(page, point, void 0, normalizedOptions);
2764
+ return true;
2765
+ }
2766
+ } catch (error) {
2767
+ if (!normalizedOptions.fallbackDomClick) throw error;
2768
+ }
2769
+ if (normalizedOptions.fallbackDomClick) {
2770
+ return MachineHumanize.domClick(page, selector, normalizedOptions);
2771
+ }
2772
+ return false;
2773
+ }
2774
+ };
2775
+
2604
2776
  // src/internals/humanize/shared.js
2605
2777
  var import_delay2 = __toESM(require("delay"), 1);
2606
2778
  var jitterMs = (base, jitterPercent = 0.3) => {
@@ -2930,6 +3102,49 @@ var scrollScrollableAncestor = async (element, deltaY) => {
2930
3102
  };
2931
3103
  }, deltaY);
2932
3104
  };
3105
+ var scrollAwayFromObstruction = async (element, status) => {
3106
+ return element.evaluate((el, innerStatus) => {
3107
+ const isScrollable = (node) => {
3108
+ const style = window.getComputedStyle(node);
3109
+ if (!style) return false;
3110
+ const overflowY = style.overflowY;
3111
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3112
+ return node.scrollHeight > node.clientHeight + 1;
3113
+ };
3114
+ let scroller = el;
3115
+ while (scroller && scroller !== document.body) {
3116
+ if (isScrollable(scroller)) break;
3117
+ scroller = scroller.parentElement;
3118
+ }
3119
+ if (!scroller || scroller === document.body) {
3120
+ return { moved: false, scrollTop: 0, deltaY: 0 };
3121
+ }
3122
+ const rect = el.getBoundingClientRect();
3123
+ const scrollerRect = scroller.getBoundingClientRect();
3124
+ const obstruction = innerStatus?.obstruction || {};
3125
+ const obstructionTop = Number(obstruction.top);
3126
+ const obstructionBottom = Number(obstruction.bottom);
3127
+ const obstructionMiddle = Number.isFinite(obstructionTop) && Number.isFinite(obstructionBottom) ? (obstructionTop + obstructionBottom) / 2 : scrollerRect.top + scrollerRect.height / 2;
3128
+ const scrollerMiddle = scrollerRect.top + scrollerRect.height / 2;
3129
+ const padding = 18;
3130
+ let deltaY = 0;
3131
+ if (Number.isFinite(obstructionTop) && rect.bottom > obstructionTop && obstructionTop >= scrollerRect.top && obstructionTop <= scrollerRect.bottom && obstructionMiddle >= scrollerMiddle) {
3132
+ deltaY = rect.bottom - obstructionTop + padding;
3133
+ } else if (Number.isFinite(obstructionBottom) && rect.top < obstructionBottom && obstructionBottom >= scrollerRect.top && obstructionBottom <= scrollerRect.bottom && obstructionMiddle < scrollerMiddle) {
3134
+ deltaY = rect.top - obstructionBottom - padding;
3135
+ } else {
3136
+ const fallbackDistance = Math.max(48, Math.min(180, scrollerRect.height * 0.45));
3137
+ deltaY = obstructionMiddle >= scrollerMiddle ? fallbackDistance : -fallbackDistance;
3138
+ }
3139
+ const beforeTop = scroller.scrollTop;
3140
+ scroller.scrollTop = beforeTop + deltaY;
3141
+ return {
3142
+ moved: Math.abs(scroller.scrollTop - beforeTop) > 2,
3143
+ scrollTop: scroller.scrollTop,
3144
+ deltaY
3145
+ };
3146
+ }, status);
3147
+ };
2933
3148
  var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
2934
3149
  const viewport = resolveViewport(page);
2935
3150
  const rawRect = options.rect || null;
@@ -3093,6 +3308,15 @@ var MobileHumanize = {
3093
3308
  logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
3094
3309
  return { element, didScroll, restore: null };
3095
3310
  }
3311
+ if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
3312
+ const moved = await scrollAwayFromObstruction(element, status);
3313
+ if (moved.moved) {
3314
+ logger7.debug(`humanScroll | sticky/fixed \u906E\u6321\u8865\u507F\u6EDA\u52A8 top=${Math.round(moved.scrollTop || 0)}`);
3315
+ await waitJitter(90, 0.3);
3316
+ didScroll = true;
3317
+ continue;
3318
+ }
3319
+ }
3096
3320
  if (Date.now() - startTime > maxDurationMs) {
3097
3321
  logger7.warn(`humanScroll | mobile timeout (${maxDurationMs}ms, status=${status.code}, direction=${status.direction || "unknown"}, fixed=${Boolean(status.isFixed)})`);
3098
3322
  return { element, didScroll, restore: null };
@@ -3224,7 +3448,7 @@ var MobileHumanize = {
3224
3448
  await MobileHumanize.humanScroll(page, element, { throwOnMissing });
3225
3449
  }
3226
3450
  const status = await checkElementVisibility(element).catch(() => null);
3227
- if (status && (status.code === "ZERO_DIMENSIONS" || status.code === "NOT_INTERACTABLE")) {
3451
+ if (status && status.code !== "VISIBLE") {
3228
3452
  const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
3229
3453
  if (throwOnMissing) throw new Error(message);
3230
3454
  logger7.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
@@ -3284,6 +3508,40 @@ var MobileHumanize = {
3284
3508
  }
3285
3509
  }
3286
3510
  },
3511
+ async humanPress(page, targetOrKey, maybeKey, options = {}) {
3512
+ const hasTarget = typeof maybeKey === "string";
3513
+ const key = hasTarget ? maybeKey : targetOrKey;
3514
+ const pressOptions = hasTarget ? options : maybeKey || options;
3515
+ const {
3516
+ reactionDelay = 170,
3517
+ holdDelay = 42,
3518
+ focusDelay = 180,
3519
+ scrollIfNeeded = true,
3520
+ throwOnMissing = true,
3521
+ keyboardOptions = {}
3522
+ } = pressOptions || {};
3523
+ const targetDesc = hasTarget ? describeTarget(targetOrKey) : "current focus";
3524
+ logger7.start("humanPress", `key=${key}, target=${targetDesc}`);
3525
+ try {
3526
+ if (hasTarget) {
3527
+ await MobileHumanize.humanClick(page, targetOrKey, {
3528
+ reactionDelay: focusDelay,
3529
+ scrollIfNeeded,
3530
+ throwOnMissing
3531
+ });
3532
+ }
3533
+ await waitJitter(reactionDelay, 0.45);
3534
+ await page.keyboard.press(key, {
3535
+ ...keyboardOptions,
3536
+ delay: jitterMs(holdDelay, 0.5)
3537
+ });
3538
+ logger7.success("humanPress");
3539
+ return true;
3540
+ } catch (error) {
3541
+ logger7.fail("humanPress", error);
3542
+ throw error;
3543
+ }
3544
+ },
3287
3545
  async humanClear(page, selector) {
3288
3546
  const locator = page.locator(selector);
3289
3547
  await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
@@ -3334,15 +3592,18 @@ var MobileHumanize = {
3334
3592
  };
3335
3593
 
3336
3594
  // src/humanize.js
3337
- var resolveDeviceFromPage = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
3595
+ var resolveDeviceFromPage2 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
3338
3596
  var resolveDelegate = (page) => {
3339
- return resolveDeviceFromPage(page) === Device.Mobile ? MobileHumanize : Humanize;
3597
+ return resolveDeviceFromPage2(page) === Device.Mobile ? MobileHumanize : Humanize;
3340
3598
  };
3341
3599
  var callDelegate = (method, page, args) => {
3342
3600
  const delegate = resolveDelegate(page);
3343
3601
  return delegate[method](page, ...args);
3344
3602
  };
3345
3603
  var Humanize2 = {
3604
+ // M = Machine: native/mechanical operations for compatibility paths.
3605
+ // These APIs intentionally do not humanize timing, motion, or intent.
3606
+ M: MachineHumanize,
3346
3607
  jitterMs(base, jitterPercent = 0.3) {
3347
3608
  return Humanize.jitterMs(base, jitterPercent);
3348
3609
  },
@@ -3371,6 +3632,12 @@ var Humanize2 = {
3371
3632
  humanType(page, selector, text, options = {}) {
3372
3633
  return callDelegate("humanType", page, [selector, text, options]);
3373
3634
  },
3635
+ humanPress(page, targetOrKey, maybeKey, options = {}) {
3636
+ if (typeof maybeKey === "string") {
3637
+ return callDelegate("humanPress", page, [targetOrKey, maybeKey, options]);
3638
+ }
3639
+ return callDelegate("humanPress", page, [targetOrKey, maybeKey || options]);
3640
+ },
3374
3641
  humanClear(page, selector) {
3375
3642
  return callDelegate("humanClear", page, [selector]);
3376
3643
  },