@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/dist/index.js CHANGED
@@ -2485,6 +2485,43 @@ var Humanize = {
2485
2485
  throw error;
2486
2486
  }
2487
2487
  },
2488
+ /**
2489
+ * 人类化按键 - 模拟用户在当前焦点或指定目标上按下一次键。
2490
+ * @param {import('playwright').Page} page
2491
+ * @param {string|import('playwright').ElementHandle|import('playwright').Locator} targetOrKey - 目标或按键
2492
+ * @param {string|Object} [maybeKey] - 按键或选项
2493
+ * @param {Object} [options]
2494
+ */
2495
+ async humanPress(page, targetOrKey, maybeKey, options = {}) {
2496
+ const hasTarget = typeof maybeKey === "string";
2497
+ const key = hasTarget ? maybeKey : targetOrKey;
2498
+ const pressOptions = hasTarget ? options : maybeKey || options;
2499
+ const {
2500
+ reactionDelay = 180,
2501
+ holdDelay = 45,
2502
+ focusDelay = 180,
2503
+ scrollIfNeeded = true,
2504
+ throwOnMissing = true,
2505
+ keyboardOptions = {}
2506
+ } = pressOptions || {};
2507
+ const targetDesc = hasTarget ? typeof targetOrKey === "string" ? targetOrKey : "ElementHandle" : "current focus";
2508
+ logger6.start("humanPress", `key=${key}, target=${targetDesc}`);
2509
+ try {
2510
+ if (hasTarget) {
2511
+ await this.humanClick(page, targetOrKey, { reactionDelay: focusDelay, scrollIfNeeded, throwOnMissing });
2512
+ }
2513
+ await delay(this.jitterMs(reactionDelay, 0.45));
2514
+ await page.keyboard.press(key, {
2515
+ ...keyboardOptions,
2516
+ delay: this.jitterMs(holdDelay, 0.5)
2517
+ });
2518
+ logger6.success("humanPress");
2519
+ return true;
2520
+ } catch (error) {
2521
+ logger6.fail("humanPress", error);
2522
+ throw error;
2523
+ }
2524
+ },
2488
2525
  /**
2489
2526
  * 人类化清空输入框 - 模拟人类删除文本的行为
2490
2527
  * @param {import('playwright').Page} page
@@ -2573,6 +2610,141 @@ var Humanize = {
2573
2610
  }
2574
2611
  };
2575
2612
 
2613
+ // src/internals/humanize/machine.js
2614
+ var resolveDeviceFromPage = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
2615
+ var isPoint = (value) => value && typeof value === "object" && Number.isFinite(Number(value.x)) && Number.isFinite(Number(value.y));
2616
+ var resolveNativeTarget = (page, target) => {
2617
+ if (!target) return null;
2618
+ if (typeof target === "string") {
2619
+ return page.locator(target);
2620
+ }
2621
+ return target;
2622
+ };
2623
+ var normalizeSelectorOptions = (options = {}) => ({
2624
+ targetParent: Boolean(options.targetParent ?? options.parent ?? false),
2625
+ pick: options.pick === "last" || options.last ? "last" : "first",
2626
+ visibleOnly: options.visibleOnly !== false,
2627
+ fallbackDomClick: Boolean(options.fallbackDomClick),
2628
+ forceMouse: Boolean(options.forceMouse)
2629
+ });
2630
+ var MachineHumanize = {
2631
+ async clickPoint(page, pointOrX, maybeY, options = {}) {
2632
+ const clickOptions = isPoint(pointOrX) && maybeY && typeof maybeY === "object" ? maybeY : options;
2633
+ const point = isPoint(pointOrX) ? pointOrX : { x: pointOrX, y: maybeY };
2634
+ const x = Number(point.x);
2635
+ const y = Number(point.y);
2636
+ if (!Number.isFinite(x) || !Number.isFinite(y)) {
2637
+ throw new Error(`Invalid click point: ${JSON.stringify(point)}`);
2638
+ }
2639
+ const resolvedDevice = resolveDeviceFromPage(page);
2640
+ if (resolvedDevice === Device.Mobile && !clickOptions.forceMouse && page.touchscreen && typeof page.touchscreen.tap === "function") {
2641
+ await page.touchscreen.tap(x, y);
2642
+ return true;
2643
+ }
2644
+ await page.mouse.click(x, y, clickOptions.mouseOptions || {});
2645
+ return true;
2646
+ },
2647
+ async click(page, target, options = {}) {
2648
+ if (isPoint(target)) {
2649
+ return MachineHumanize.clickPoint(page, target, void 0, options);
2650
+ }
2651
+ const nativeTarget = resolveNativeTarget(page, target);
2652
+ if (!nativeTarget || typeof nativeTarget.click !== "function") {
2653
+ throw new Error("Machine click target does not expose click()");
2654
+ }
2655
+ await nativeTarget.click(options.clickOptions || options);
2656
+ return true;
2657
+ },
2658
+ async focus(page, target, options = {}) {
2659
+ const nativeTarget = resolveNativeTarget(page, target);
2660
+ if (!nativeTarget || typeof nativeTarget.focus !== "function") {
2661
+ throw new Error("Machine focus target does not expose focus()");
2662
+ }
2663
+ await nativeTarget.focus(options);
2664
+ return true;
2665
+ },
2666
+ async fill(page, target, value, options = {}) {
2667
+ const nativeTarget = resolveNativeTarget(page, target);
2668
+ if (!nativeTarget || typeof nativeTarget.fill !== "function") {
2669
+ throw new Error("Machine fill target does not expose fill()");
2670
+ }
2671
+ await nativeTarget.fill(value, options);
2672
+ return true;
2673
+ },
2674
+ async keyboardType(page, text, options = {}) {
2675
+ await page.keyboard.type(text, options);
2676
+ return true;
2677
+ },
2678
+ async type(page, targetOrText, maybeText, options = {}) {
2679
+ if (maybeText == null || typeof maybeText === "object" && !Array.isArray(maybeText)) {
2680
+ return MachineHumanize.keyboardType(page, targetOrText, maybeText || options);
2681
+ }
2682
+ const nativeTarget = resolveNativeTarget(page, targetOrText);
2683
+ if (!nativeTarget || typeof nativeTarget.type !== "function") {
2684
+ throw new Error("Machine type target does not expose type()");
2685
+ }
2686
+ await nativeTarget.type(maybeText, options);
2687
+ return true;
2688
+ },
2689
+ async selectorCenterPoint(page, selector, options = {}) {
2690
+ const normalizedOptions = normalizeSelectorOptions(options);
2691
+ return page.evaluate(({ innerSelector, innerOptions }) => {
2692
+ const nodes = Array.from(document.querySelectorAll(innerSelector));
2693
+ const ordered = innerOptions.pick === "last" ? nodes.reverse() : nodes;
2694
+ let rect = null;
2695
+ for (const node of ordered) {
2696
+ if (!node) continue;
2697
+ const target = innerOptions.targetParent ? node.parentElement || node : node;
2698
+ rect = target.getBoundingClientRect?.() || null;
2699
+ const isVisible = Boolean(rect && rect.width > 0 && rect.height > 0);
2700
+ if (innerOptions.visibleOnly && !isVisible) continue;
2701
+ break;
2702
+ }
2703
+ if (!rect || rect.width <= 0 || rect.height <= 0) return null;
2704
+ return {
2705
+ x: rect.left + rect.width / 2,
2706
+ y: rect.top + rect.height / 2
2707
+ };
2708
+ }, { innerSelector: selector, innerOptions: normalizedOptions });
2709
+ },
2710
+ async domClick(page, selector, options = {}) {
2711
+ const normalizedOptions = normalizeSelectorOptions(options);
2712
+ return page.evaluate(({ innerSelector, innerOptions }) => {
2713
+ const nodes = Array.from(document.querySelectorAll(innerSelector));
2714
+ const ordered = innerOptions.pick === "last" ? nodes.reverse() : nodes;
2715
+ let target = null;
2716
+ for (const node of ordered) {
2717
+ if (!node) continue;
2718
+ const candidate = innerOptions.targetParent ? node.parentElement || node : node;
2719
+ const rect = candidate.getBoundingClientRect?.() || null;
2720
+ const isVisible = Boolean(rect && rect.width > 0 && rect.height > 0);
2721
+ if (innerOptions.visibleOnly && !isVisible) continue;
2722
+ target = candidate;
2723
+ break;
2724
+ }
2725
+ if (!target || typeof target.click !== "function") return false;
2726
+ target.click();
2727
+ return true;
2728
+ }, { innerSelector: selector, innerOptions: normalizedOptions });
2729
+ },
2730
+ async clickSelectorCenter(page, selector, options = {}) {
2731
+ const normalizedOptions = normalizeSelectorOptions(options);
2732
+ try {
2733
+ const point = await MachineHumanize.selectorCenterPoint(page, selector, normalizedOptions);
2734
+ if (point) {
2735
+ await MachineHumanize.clickPoint(page, point, void 0, normalizedOptions);
2736
+ return true;
2737
+ }
2738
+ } catch (error) {
2739
+ if (!normalizedOptions.fallbackDomClick) throw error;
2740
+ }
2741
+ if (normalizedOptions.fallbackDomClick) {
2742
+ return MachineHumanize.domClick(page, selector, normalizedOptions);
2743
+ }
2744
+ return false;
2745
+ }
2746
+ };
2747
+
2576
2748
  // src/internals/humanize/shared.js
2577
2749
  import delay2 from "delay";
2578
2750
  var jitterMs = (base, jitterPercent = 0.3) => {
@@ -2902,6 +3074,49 @@ var scrollScrollableAncestor = async (element, deltaY) => {
2902
3074
  };
2903
3075
  }, deltaY);
2904
3076
  };
3077
+ var scrollAwayFromObstruction = async (element, status) => {
3078
+ return element.evaluate((el, innerStatus) => {
3079
+ const isScrollable = (node) => {
3080
+ const style = window.getComputedStyle(node);
3081
+ if (!style) return false;
3082
+ const overflowY = style.overflowY;
3083
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3084
+ return node.scrollHeight > node.clientHeight + 1;
3085
+ };
3086
+ let scroller = el;
3087
+ while (scroller && scroller !== document.body) {
3088
+ if (isScrollable(scroller)) break;
3089
+ scroller = scroller.parentElement;
3090
+ }
3091
+ if (!scroller || scroller === document.body) {
3092
+ return { moved: false, scrollTop: 0, deltaY: 0 };
3093
+ }
3094
+ const rect = el.getBoundingClientRect();
3095
+ const scrollerRect = scroller.getBoundingClientRect();
3096
+ const obstruction = innerStatus?.obstruction || {};
3097
+ const obstructionTop = Number(obstruction.top);
3098
+ const obstructionBottom = Number(obstruction.bottom);
3099
+ const obstructionMiddle = Number.isFinite(obstructionTop) && Number.isFinite(obstructionBottom) ? (obstructionTop + obstructionBottom) / 2 : scrollerRect.top + scrollerRect.height / 2;
3100
+ const scrollerMiddle = scrollerRect.top + scrollerRect.height / 2;
3101
+ const padding = 18;
3102
+ let deltaY = 0;
3103
+ if (Number.isFinite(obstructionTop) && rect.bottom > obstructionTop && obstructionTop >= scrollerRect.top && obstructionTop <= scrollerRect.bottom && obstructionMiddle >= scrollerMiddle) {
3104
+ deltaY = rect.bottom - obstructionTop + padding;
3105
+ } else if (Number.isFinite(obstructionBottom) && rect.top < obstructionBottom && obstructionBottom >= scrollerRect.top && obstructionBottom <= scrollerRect.bottom && obstructionMiddle < scrollerMiddle) {
3106
+ deltaY = rect.top - obstructionBottom - padding;
3107
+ } else {
3108
+ const fallbackDistance = Math.max(48, Math.min(180, scrollerRect.height * 0.45));
3109
+ deltaY = obstructionMiddle >= scrollerMiddle ? fallbackDistance : -fallbackDistance;
3110
+ }
3111
+ const beforeTop = scroller.scrollTop;
3112
+ scroller.scrollTop = beforeTop + deltaY;
3113
+ return {
3114
+ moved: Math.abs(scroller.scrollTop - beforeTop) > 2,
3115
+ scrollTop: scroller.scrollTop,
3116
+ deltaY
3117
+ };
3118
+ }, status);
3119
+ };
2905
3120
  var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
2906
3121
  const viewport = resolveViewport(page);
2907
3122
  const rawRect = options.rect || null;
@@ -3065,6 +3280,15 @@ var MobileHumanize = {
3065
3280
  logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
3066
3281
  return { element, didScroll, restore: null };
3067
3282
  }
3283
+ if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
3284
+ const moved = await scrollAwayFromObstruction(element, status);
3285
+ if (moved.moved) {
3286
+ logger7.debug(`humanScroll | sticky/fixed \u906E\u6321\u8865\u507F\u6EDA\u52A8 top=${Math.round(moved.scrollTop || 0)}`);
3287
+ await waitJitter(90, 0.3);
3288
+ didScroll = true;
3289
+ continue;
3290
+ }
3291
+ }
3068
3292
  if (Date.now() - startTime > maxDurationMs) {
3069
3293
  logger7.warn(`humanScroll | mobile timeout (${maxDurationMs}ms, status=${status.code}, direction=${status.direction || "unknown"}, fixed=${Boolean(status.isFixed)})`);
3070
3294
  return { element, didScroll, restore: null };
@@ -3196,7 +3420,7 @@ var MobileHumanize = {
3196
3420
  await MobileHumanize.humanScroll(page, element, { throwOnMissing });
3197
3421
  }
3198
3422
  const status = await checkElementVisibility(element).catch(() => null);
3199
- if (status && (status.code === "ZERO_DIMENSIONS" || status.code === "NOT_INTERACTABLE")) {
3423
+ if (status && status.code !== "VISIBLE") {
3200
3424
  const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
3201
3425
  if (throwOnMissing) throw new Error(message);
3202
3426
  logger7.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
@@ -3256,6 +3480,40 @@ var MobileHumanize = {
3256
3480
  }
3257
3481
  }
3258
3482
  },
3483
+ async humanPress(page, targetOrKey, maybeKey, options = {}) {
3484
+ const hasTarget = typeof maybeKey === "string";
3485
+ const key = hasTarget ? maybeKey : targetOrKey;
3486
+ const pressOptions = hasTarget ? options : maybeKey || options;
3487
+ const {
3488
+ reactionDelay = 170,
3489
+ holdDelay = 42,
3490
+ focusDelay = 180,
3491
+ scrollIfNeeded = true,
3492
+ throwOnMissing = true,
3493
+ keyboardOptions = {}
3494
+ } = pressOptions || {};
3495
+ const targetDesc = hasTarget ? describeTarget(targetOrKey) : "current focus";
3496
+ logger7.start("humanPress", `key=${key}, target=${targetDesc}`);
3497
+ try {
3498
+ if (hasTarget) {
3499
+ await MobileHumanize.humanClick(page, targetOrKey, {
3500
+ reactionDelay: focusDelay,
3501
+ scrollIfNeeded,
3502
+ throwOnMissing
3503
+ });
3504
+ }
3505
+ await waitJitter(reactionDelay, 0.45);
3506
+ await page.keyboard.press(key, {
3507
+ ...keyboardOptions,
3508
+ delay: jitterMs(holdDelay, 0.5)
3509
+ });
3510
+ logger7.success("humanPress");
3511
+ return true;
3512
+ } catch (error) {
3513
+ logger7.fail("humanPress", error);
3514
+ throw error;
3515
+ }
3516
+ },
3259
3517
  async humanClear(page, selector) {
3260
3518
  const locator = page.locator(selector);
3261
3519
  await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
@@ -3306,15 +3564,18 @@ var MobileHumanize = {
3306
3564
  };
3307
3565
 
3308
3566
  // src/humanize.js
3309
- var resolveDeviceFromPage = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
3567
+ var resolveDeviceFromPage2 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
3310
3568
  var resolveDelegate = (page) => {
3311
- return resolveDeviceFromPage(page) === Device.Mobile ? MobileHumanize : Humanize;
3569
+ return resolveDeviceFromPage2(page) === Device.Mobile ? MobileHumanize : Humanize;
3312
3570
  };
3313
3571
  var callDelegate = (method, page, args) => {
3314
3572
  const delegate = resolveDelegate(page);
3315
3573
  return delegate[method](page, ...args);
3316
3574
  };
3317
3575
  var Humanize2 = {
3576
+ // M = Machine: native/mechanical operations for compatibility paths.
3577
+ // These APIs intentionally do not humanize timing, motion, or intent.
3578
+ M: MachineHumanize,
3318
3579
  jitterMs(base, jitterPercent = 0.3) {
3319
3580
  return Humanize.jitterMs(base, jitterPercent);
3320
3581
  },
@@ -3343,6 +3604,12 @@ var Humanize2 = {
3343
3604
  humanType(page, selector, text, options = {}) {
3344
3605
  return callDelegate("humanType", page, [selector, text, options]);
3345
3606
  },
3607
+ humanPress(page, targetOrKey, maybeKey, options = {}) {
3608
+ if (typeof maybeKey === "string") {
3609
+ return callDelegate("humanPress", page, [targetOrKey, maybeKey, options]);
3610
+ }
3611
+ return callDelegate("humanPress", page, [targetOrKey, maybeKey || options]);
3612
+ },
3346
3613
  humanClear(page, selector) {
3347
3614
  return callDelegate("humanClear", page, [selector]);
3348
3615
  },