@skrillex1224/playwright-toolkit 2.1.239 → 2.1.241

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.cjs CHANGED
@@ -83,7 +83,7 @@ var createActorInfo = (info) => {
83
83
  const normalizeShare2 = (value) => {
84
84
  const raw = value && typeof value === "object" ? value : {};
85
85
  const modeRaw = String(raw.mode || "dom").trim().toLowerCase();
86
- const mode = modeRaw === "response" ? "response" : "dom";
86
+ const mode = ["dom", "response", "custom"].includes(modeRaw) ? modeRaw : "dom";
87
87
  const prefix = String(raw.prefix || "").trim();
88
88
  const rawXurl = Array.isArray(raw.xurl) ? raw.xurl : [];
89
89
  const normalizeMatcherList = (input) => {
@@ -241,7 +241,7 @@ var ActorInfo = {
241
241
  path: "/",
242
242
  device: Device.Mobile,
243
243
  share: {
244
- mode: "dom",
244
+ mode: "custom",
245
245
  prefix: "",
246
246
  xurl: []
247
247
  }
@@ -750,14 +750,16 @@ var expandScrollableContent = async (page, options = {}) => {
750
750
  expandDocumentElements: options.expandDocumentElements === true
751
751
  });
752
752
  };
753
- var pinBottomAffixedElements = async (page, options = {}) => {
753
+ var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
754
754
  return await page.evaluate(({ className, targetHeight }) => {
755
+ const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || document.body?.clientWidth || 0;
755
756
  const viewportHeight = window.innerHeight || document.documentElement?.clientHeight || document.body?.clientHeight || 0;
757
+ const scrollY = window.scrollY || window.pageYOffset || 0;
756
758
  const safeTargetHeight = Math.ceil(Number(targetHeight) || 0);
757
- const deltaY = safeTargetHeight - viewportHeight;
758
- if (deltaY <= 1) {
759
+ if (safeTargetHeight <= viewportHeight + 1) {
759
760
  return 0;
760
761
  }
762
+ const hasOwn = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
761
763
  const isVisible = (el, style, rect) => {
762
764
  if (!el || !style || !rect) return false;
763
765
  if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") {
@@ -766,7 +768,7 @@ var pinBottomAffixedElements = async (page, options = {}) => {
766
768
  if (Number(style.opacity) === 0) return false;
767
769
  return rect.width > 0 && rect.height > 0;
768
770
  };
769
- const bottomThreshold = Math.max(24, Math.min(96, viewportHeight * 0.12));
771
+ const edgeThreshold = Math.max(24, Math.min(96, viewportHeight * 0.12));
770
772
  const maxAffixedHeight = Math.max(56, viewportHeight * 0.65);
771
773
  const candidates = [];
772
774
  document.querySelectorAll("*").forEach((el) => {
@@ -776,28 +778,49 @@ var pinBottomAffixedElements = async (page, options = {}) => {
776
778
  const rect = el.getBoundingClientRect();
777
779
  if (!isVisible(el, style, rect)) return;
778
780
  if (rect.height > maxAffixedHeight) return;
779
- if (rect.bottom < viewportHeight - bottomThreshold) return;
781
+ if (rect.width < viewportWidth * 0.25 && rect.height < 80) return;
782
+ const top = Number.parseFloat(style.top);
780
783
  const bottom = Number.parseFloat(style.bottom);
784
+ const hasTopRule = Number.isFinite(top) && top <= Math.max(160, viewportHeight * 0.25);
781
785
  const hasBottomRule = Number.isFinite(bottom) && bottom <= Math.max(160, viewportHeight * 0.25);
782
- const isNearViewportBottom = rect.bottom >= viewportHeight - bottomThreshold;
783
- if (!hasBottomRule && !isNearViewportBottom) return;
784
- candidates.push(el);
786
+ const isNearViewportTop = rect.top <= edgeThreshold;
787
+ const isNearViewportBottom = rect.bottom >= viewportHeight - edgeThreshold;
788
+ const edge = (hasBottomRule || isNearViewportBottom) && !(hasTopRule || isNearViewportTop) ? "bottom" : "top";
789
+ if (position === "fixed" && edge === "top" && !hasTopRule && !isNearViewportTop) return;
790
+ if (position === "fixed" && edge === "bottom" && !hasBottomRule && !isNearViewportBottom) return;
791
+ if (position === "sticky" && !hasTopRule && !hasBottomRule && !isNearViewportTop && !isNearViewportBottom) return;
792
+ candidates.push({ el, position, edge });
785
793
  });
786
- const candidateSet = new Set(candidates);
787
- const topLevelCandidates = candidates.filter((el) => {
794
+ const candidateSet = new Set(candidates.map(({ el }) => el));
795
+ const topLevelCandidates = candidates.filter(({ el }) => {
788
796
  for (let parent = el.parentElement; parent; parent = parent.parentElement) {
789
797
  if (candidateSet.has(parent)) return false;
790
798
  }
791
799
  return true;
792
800
  });
793
- topLevelCandidates.forEach((el) => {
794
- if (!Object.prototype.hasOwnProperty.call(el.dataset, "pkBottomPinned")) {
795
- el.dataset.pkBottomPinned = "1";
801
+ topLevelCandidates.forEach(({ el, position, edge }) => {
802
+ if (!hasOwn(el.dataset, "pkAffixedAdjusted")) {
803
+ el.dataset.pkAffixedAdjusted = "1";
804
+ el.dataset.pkOrigPosition = el.style.getPropertyValue("position") || "";
805
+ el.dataset.pkOrigPositionPriority = el.style.getPropertyPriority("position") || "";
806
+ el.dataset.pkOrigTop = el.style.getPropertyValue("top") || "";
807
+ el.dataset.pkOrigTopPriority = el.style.getPropertyPriority("top") || "";
808
+ el.dataset.pkOrigBottom = el.style.getPropertyValue("bottom") || "";
809
+ el.dataset.pkOrigBottomPriority = el.style.getPropertyPriority("bottom") || "";
796
810
  el.dataset.pkOrigTranslate = el.style.getPropertyValue("translate") || "";
797
811
  el.dataset.pkOrigTranslatePriority = el.style.getPropertyPriority("translate") || "";
798
812
  }
799
813
  el.classList.add(className);
800
- el.style.setProperty("translate", `0 ${Math.round(deltaY)}px`, "important");
814
+ if (position === "fixed") {
815
+ const deltaY = edge === "bottom" ? safeTargetHeight - viewportHeight - scrollY : -scrollY;
816
+ if (Math.abs(deltaY) > 1) {
817
+ el.style.setProperty("translate", `0 ${Math.round(deltaY)}px`, "important");
818
+ }
819
+ return;
820
+ }
821
+ el.style.setProperty("position", "relative", "important");
822
+ el.style.setProperty("top", "auto", "important");
823
+ el.style.setProperty("bottom", "auto", "important");
801
824
  });
802
825
  return topLevelCandidates.length;
803
826
  }, {
@@ -805,7 +828,7 @@ var pinBottomAffixedElements = async (page, options = {}) => {
805
828
  targetHeight: options.targetHeight
806
829
  });
807
830
  };
808
- var restoreBottomAffixedElements = async (page) => {
831
+ var restoreAffixedElementsForExpandedScreenshot = async (page) => {
809
832
  await page.evaluate((className) => {
810
833
  const hasOwn = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
811
834
  const expansionKeys = [
@@ -814,13 +837,28 @@ var restoreBottomAffixedElements = async (page) => {
814
837
  "pkOrigMinHeight",
815
838
  "pkOrigMaxHeight"
816
839
  ];
817
- document.querySelectorAll('[data-pk-bottom-pinned="1"]').forEach((el) => {
840
+ document.querySelectorAll('[data-pk-affixed-adjusted="1"]').forEach((el) => {
841
+ if (hasOwn(el.dataset, "pkOrigPosition")) {
842
+ el.style.setProperty("position", el.dataset.pkOrigPosition || "", el.dataset.pkOrigPositionPriority || "");
843
+ delete el.dataset.pkOrigPosition;
844
+ delete el.dataset.pkOrigPositionPriority;
845
+ }
846
+ if (hasOwn(el.dataset, "pkOrigTop")) {
847
+ el.style.setProperty("top", el.dataset.pkOrigTop || "", el.dataset.pkOrigTopPriority || "");
848
+ delete el.dataset.pkOrigTop;
849
+ delete el.dataset.pkOrigTopPriority;
850
+ }
851
+ if (hasOwn(el.dataset, "pkOrigBottom")) {
852
+ el.style.setProperty("bottom", el.dataset.pkOrigBottom || "", el.dataset.pkOrigBottomPriority || "");
853
+ delete el.dataset.pkOrigBottom;
854
+ delete el.dataset.pkOrigBottomPriority;
855
+ }
818
856
  if (hasOwn(el.dataset, "pkOrigTranslate")) {
819
857
  el.style.setProperty("translate", el.dataset.pkOrigTranslate || "", el.dataset.pkOrigTranslatePriority || "");
820
858
  delete el.dataset.pkOrigTranslate;
821
859
  delete el.dataset.pkOrigTranslatePriority;
822
860
  }
823
- delete el.dataset.pkBottomPinned;
861
+ delete el.dataset.pkAffixedAdjusted;
824
862
  const stillExpanded = expansionKeys.some((key) => hasOwn(el.dataset, key));
825
863
  if (!stillExpanded) {
826
864
  el.classList.remove(className);
@@ -849,8 +887,8 @@ var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
849
887
  });
850
888
  viewportResized = true;
851
889
  } else {
852
- await pinBottomAffixedElements(page, { targetHeight }).catch((error) => {
853
- logger.warning(`\u79FB\u52A8\u7AEF\u5438\u5E95\u5143\u7D20\u91CD\u5B9A\u4F4D\u5931\u8D25: ${error?.message || error}`);
890
+ await adjustAffixedElementsForExpandedScreenshot(page, { targetHeight }).catch((error) => {
891
+ logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u91CD\u5B9A\u4F4D\u5931\u8D25: ${error?.message || error}`);
854
892
  });
855
893
  }
856
894
  if (settleMs > 0) {
@@ -956,8 +994,8 @@ var captureExpandedFullPageScreenshot = async (page, options = {}) => {
956
994
  maxClipHeight: state.targetHeight
957
995
  });
958
996
  } finally {
959
- await restoreBottomAffixedElements(page).catch((error) => {
960
- logger.warning(`\u79FB\u52A8\u7AEF\u5438\u5E95\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
997
+ await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
998
+ logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
961
999
  });
962
1000
  if (options.restore) {
963
1001
  await restoreExpandedFullPageScreenshot(page, state);
@@ -3398,6 +3436,26 @@ var waitJitter = (base, jitterPercent = 0.3) => (0, import_delay3.default)(jitte
3398
3436
  // src/internals/humanize/mobile.js
3399
3437
  var logger7 = createInternalLogger("Humanize.Mobile");
3400
3438
  var initializedPages = /* @__PURE__ */ new WeakSet();
3439
+ var DEFAULT_TAP_TIMEOUT_MS = 2500;
3440
+ var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
3441
+ var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
3442
+ var INTERACTIVE_SELECTOR = [
3443
+ "button",
3444
+ '[role="button"]',
3445
+ "a[href]",
3446
+ "label",
3447
+ "input",
3448
+ "textarea",
3449
+ "select",
3450
+ "summary",
3451
+ '[contenteditable="true"]',
3452
+ '[tabindex]:not([tabindex="-1"])'
3453
+ ].join(",");
3454
+ var EDITABLE_SELECTOR = [
3455
+ "input",
3456
+ "textarea",
3457
+ '[contenteditable="true"]'
3458
+ ].join(",");
3401
3459
  var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
3402
3460
  var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
3403
3461
  var describeTarget = (target) => {
@@ -3420,8 +3478,24 @@ var clipBoxToViewport = (box, viewport) => {
3420
3478
  }
3421
3479
  return box;
3422
3480
  };
3481
+ var withTimeout = async (operation, timeoutMs, label) => {
3482
+ const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
3483
+ let timeoutId = null;
3484
+ try {
3485
+ return await Promise.race([
3486
+ Promise.resolve().then(operation),
3487
+ new Promise((_, reject) => {
3488
+ timeoutId = setTimeout(() => {
3489
+ reject(new Error(`${label} timeout after ${safeTimeoutMs}ms`));
3490
+ }, safeTimeoutMs);
3491
+ })
3492
+ ]);
3493
+ } finally {
3494
+ if (timeoutId) clearTimeout(timeoutId);
3495
+ }
3496
+ };
3423
3497
  var checkElementVisibility = async (element) => {
3424
- return element.evaluate((el) => {
3498
+ return element.evaluate((el, interactiveSelector) => {
3425
3499
  const targetStyle = window.getComputedStyle(el);
3426
3500
  if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
3427
3501
  return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
@@ -3438,10 +3512,12 @@ var checkElementVisibility = async (element) => {
3438
3512
  let clipTop = 0;
3439
3513
  let clipBottom = viewH;
3440
3514
  let isFixed = false;
3515
+ let positioning = null;
3441
3516
  for (let node = el; node && node !== document.body; node = node.parentElement) {
3442
3517
  const style = window.getComputedStyle(node);
3443
3518
  if (style && (style.position === "fixed" || style.position === "sticky")) {
3444
3519
  isFixed = true;
3520
+ positioning = style.position;
3445
3521
  break;
3446
3522
  }
3447
3523
  }
@@ -3477,21 +3553,10 @@ var checkElementVisibility = async (element) => {
3477
3553
  direction: centerY < clipTop ? "up" : "down",
3478
3554
  cy: centerY,
3479
3555
  viewH,
3480
- isFixed
3556
+ isFixed,
3557
+ positioning
3481
3558
  };
3482
3559
  }
3483
- const interactiveSelector = [
3484
- "button",
3485
- '[role="button"]',
3486
- "a[href]",
3487
- "label",
3488
- "input",
3489
- "textarea",
3490
- "select",
3491
- "summary",
3492
- '[contenteditable="true"]',
3493
- '[tabindex]:not([tabindex="-1"])'
3494
- ].join(",");
3495
3560
  const targetInteractive = typeof el.closest === "function" ? el.closest(interactiveSelector) : null;
3496
3561
  const sameInteractiveTarget = (pointElement) => {
3497
3562
  if (!pointElement) return false;
@@ -3513,6 +3578,7 @@ var checkElementVisibility = async (element) => {
3513
3578
  id: node.id || "",
3514
3579
  className,
3515
3580
  isFixed: Boolean(style && (style.position === "fixed" || style.position === "sticky")),
3581
+ positioning: style?.position || "",
3516
3582
  top: rect2 ? rect2.top : null,
3517
3583
  bottom: rect2 ? rect2.bottom : null,
3518
3584
  left: rect2 ? rect2.left : null,
@@ -3530,7 +3596,7 @@ var checkElementVisibility = async (element) => {
3530
3596
  for (const point of samplePoints) {
3531
3597
  const pointElement = document.elementFromPoint(point.x, point.y);
3532
3598
  if (sameInteractiveTarget(pointElement)) {
3533
- return { code: "VISIBLE", isFixed };
3599
+ return { code: "VISIBLE", isFixed, positioning };
3534
3600
  }
3535
3601
  obstruction = obstruction || describeElement(pointElement);
3536
3602
  }
@@ -3542,14 +3608,15 @@ var checkElementVisibility = async (element) => {
3542
3608
  obstruction,
3543
3609
  cy,
3544
3610
  viewH,
3545
- isFixed
3611
+ isFixed,
3612
+ positioning
3546
3613
  };
3547
3614
  }
3548
- return { code: "VISIBLE", isFixed };
3549
- });
3615
+ return { code: "VISIBLE", isFixed, positioning };
3616
+ }, INTERACTIVE_SELECTOR);
3550
3617
  };
3551
3618
  var resolveSafeTapPoint = async (element) => {
3552
- return element.evaluate((el) => {
3619
+ return element.evaluate((el, interactiveSelector) => {
3553
3620
  const rect = el.getBoundingClientRect();
3554
3621
  if (!rect || rect.width <= 0 || rect.height <= 0) {
3555
3622
  return null;
@@ -3586,18 +3653,6 @@ var resolveSafeTapPoint = async (element) => {
3586
3653
  if (visibleWidth <= 1 || visibleHeight <= 1) {
3587
3654
  return null;
3588
3655
  }
3589
- const interactiveSelector = [
3590
- "button",
3591
- '[role="button"]',
3592
- "a[href]",
3593
- "label",
3594
- "input",
3595
- "textarea",
3596
- "select",
3597
- "summary",
3598
- '[contenteditable="true"]',
3599
- '[tabindex]:not([tabindex="-1"])'
3600
- ].join(",");
3601
3656
  const targetInteractive = typeof el.closest === "function" ? el.closest(interactiveSelector) : null;
3602
3657
  const sameInteractiveTarget = (pointElement) => {
3603
3658
  if (!pointElement) return false;
@@ -3635,6 +3690,46 @@ var resolveSafeTapPoint = async (element) => {
3635
3690
  x: chosen.x,
3636
3691
  y: chosen.y
3637
3692
  };
3693
+ }, INTERACTIVE_SELECTOR);
3694
+ };
3695
+ var activateElementFallback = async (element, point = null, options = {}) => {
3696
+ return element.evaluate((el, { innerPoint, innerOptions, interactiveSelector, editableSelector }) => {
3697
+ const pointElement = innerPoint ? document.elementFromPoint(innerPoint.x, innerPoint.y) : null;
3698
+ const nearestInteractive = (node) => {
3699
+ if (!node || typeof node.closest !== "function") return null;
3700
+ return node.closest(interactiveSelector);
3701
+ };
3702
+ const targetInteractive = nearestInteractive(el);
3703
+ const pointInteractive = nearestInteractive(pointElement);
3704
+ let target = null;
3705
+ if (pointInteractive && (pointInteractive === targetInteractive || pointInteractive === el || pointInteractive.contains(el) || el.contains(pointInteractive))) {
3706
+ target = pointInteractive;
3707
+ }
3708
+ target = target || targetInteractive || el;
3709
+ const editable = typeof target.closest === "function" ? target.closest(editableSelector) : null;
3710
+ if (editable && typeof editable.focus === "function") {
3711
+ editable.focus({ preventScroll: true });
3712
+ if (innerOptions.editableOnly) {
3713
+ return { activated: true, method: "focus", tag: editable.tagName || "" };
3714
+ }
3715
+ }
3716
+ if (typeof target.focus === "function") {
3717
+ target.focus({ preventScroll: true });
3718
+ }
3719
+ if (!innerOptions.editableOnly && typeof target.click === "function") {
3720
+ target.click();
3721
+ return { activated: true, method: "dom-click", tag: target.tagName || "" };
3722
+ }
3723
+ return {
3724
+ activated: Boolean(editable),
3725
+ method: editable ? "focus" : "none",
3726
+ tag: target?.tagName || ""
3727
+ };
3728
+ }, {
3729
+ innerPoint: point,
3730
+ innerOptions: options || {},
3731
+ interactiveSelector: INTERACTIVE_SELECTOR,
3732
+ editableSelector: EDITABLE_SELECTOR
3638
3733
  });
3639
3734
  };
3640
3735
  var getScrollableRect = async (element) => {
@@ -3839,12 +3934,31 @@ var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
3839
3934
  }
3840
3935
  }
3841
3936
  };
3842
- var tapPoint = async (page, point) => {
3937
+ var tapPoint = async (page, point, options = {}) => {
3938
+ const {
3939
+ timeoutMs = DEFAULT_TAP_TIMEOUT_MS,
3940
+ mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
3941
+ allowMouseFallback = true
3942
+ } = options;
3843
3943
  if (page.touchscreen && typeof page.touchscreen.tap === "function") {
3844
- await page.touchscreen.tap(point.x, point.y);
3845
- return;
3944
+ try {
3945
+ await withTimeout(
3946
+ () => page.touchscreen.tap(point.x, point.y),
3947
+ timeoutMs,
3948
+ "touchscreen.tap"
3949
+ );
3950
+ return { method: "touchscreen" };
3951
+ } catch (error) {
3952
+ logger7.warn(`tapPoint | touchscreen.tap \u5931\u8D25\u6216\u8D85\u65F6\uFF0C\u5C1D\u8BD5\u9F20\u6807\u515C\u5E95: ${error?.message || error}`);
3953
+ if (!allowMouseFallback) throw error;
3954
+ }
3846
3955
  }
3847
- await page.mouse.click(point.x, point.y);
3956
+ await withTimeout(
3957
+ () => page.mouse.click(point.x, point.y),
3958
+ mouseFallbackTimeoutMs,
3959
+ "mouse.click fallback"
3960
+ );
3961
+ return { method: "mouse" };
3848
3962
  };
3849
3963
  var MobileHumanize = {
3850
3964
  jitterMs,
@@ -3897,6 +4011,10 @@ var MobileHumanize = {
3897
4011
  return { element, didScroll, restore: null };
3898
4012
  }
3899
4013
  const scrollRect = await getScrollableRect(element);
4014
+ if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
4015
+ logger7.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"})`);
4016
+ return { element, didScroll, restore: null };
4017
+ }
3900
4018
  if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
3901
4019
  logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
3902
4020
  return { element, didScroll, restore: null };
@@ -4017,7 +4135,12 @@ var MobileHumanize = {
4017
4135
  const {
4018
4136
  reactionDelay = 220,
4019
4137
  throwOnMissing = true,
4020
- scrollIfNeeded = true
4138
+ scrollIfNeeded = true,
4139
+ tapTimeoutMs = DEFAULT_TAP_TIMEOUT_MS,
4140
+ mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
4141
+ activateFallbackTimeoutMs = DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS,
4142
+ fallbackDomClick = true,
4143
+ fallbackDomClickOnTapError = true
4021
4144
  } = options;
4022
4145
  const targetDesc = describeTarget(target);
4023
4146
  logger7.start("humanClick", `target=${targetDesc}`);
@@ -4028,6 +4151,9 @@ var MobileHumanize = {
4028
4151
  await tapPoint(page, {
4029
4152
  x: viewport.width * (0.45 + Math.random() * 0.1),
4030
4153
  y: viewport.height * (0.48 + Math.random() * 0.12)
4154
+ }, {
4155
+ timeoutMs: tapTimeoutMs,
4156
+ mouseFallbackTimeoutMs
4031
4157
  });
4032
4158
  logger7.success("humanClick", "Tapped current position");
4033
4159
  return true;
@@ -4042,6 +4168,19 @@ var MobileHumanize = {
4042
4168
  }
4043
4169
  const status = await checkElementVisibility(element).catch(() => null);
4044
4170
  if (status && status.code !== "VISIBLE") {
4171
+ if (fallbackDomClick && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
4172
+ const fallback = await withTimeout(
4173
+ () => activateElementFallback(element, null, {
4174
+ editableOnly: true
4175
+ }),
4176
+ activateFallbackTimeoutMs,
4177
+ "focus fallback"
4178
+ ).catch(() => null);
4179
+ if (fallback?.activated) {
4180
+ logger7.warn(`humanClick: fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
4181
+ return true;
4182
+ }
4183
+ }
4045
4184
  const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
4046
4185
  if (throwOnMissing) throw new Error(message);
4047
4186
  logger7.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
@@ -4056,7 +4195,23 @@ var MobileHumanize = {
4056
4195
  await waitJitter(reactionDelay, 0.45);
4057
4196
  const safePoint = await resolveSafeTapPoint(element).catch(() => null);
4058
4197
  const tapTarget = safePoint || randomPointInBox(clipBoxToViewport(box, resolveViewport(page)), 0.2);
4059
- await tapPoint(page, tapTarget);
4198
+ try {
4199
+ await tapPoint(page, tapTarget, {
4200
+ timeoutMs: tapTimeoutMs,
4201
+ mouseFallbackTimeoutMs
4202
+ });
4203
+ } catch (tapError) {
4204
+ if (!fallbackDomClickOnTapError) throw tapError;
4205
+ const fallback = await withTimeout(
4206
+ () => activateElementFallback(element, tapTarget, {
4207
+ editableOnly: false
4208
+ }),
4209
+ activateFallbackTimeoutMs,
4210
+ "activation fallback"
4211
+ ).catch(() => null);
4212
+ if (!fallback?.activated) throw tapError;
4213
+ logger7.warn(`humanClick: tap \u5931\u8D25\u540E\u5DF2\u7528 ${fallback.method} \u515C\u5E95: ${tapError?.message || tapError}`);
4214
+ }
4060
4215
  await waitJitter(120, 0.35);
4061
4216
  logger7.success("humanClick");
4062
4217
  return true;
@@ -6874,7 +7029,7 @@ var getHostname = (url) => {
6874
7029
  return "\u672C\u5730\u9875\u9762";
6875
7030
  }
6876
7031
  };
6877
- var withTimeout = async (promise, timeoutMs) => {
7032
+ var withTimeout2 = async (promise, timeoutMs) => {
6878
7033
  const safeTimeoutMs = Math.max(0, Number(timeoutMs) || 0);
6879
7034
  if (!safeTimeoutMs) {
6880
7035
  return promise;
@@ -6959,7 +7114,7 @@ var resolveWithCustomResolver = async (page, baseMeta, options = {}) => {
6959
7114
  const serverAddr = response && typeof response.serverAddr === "function" ? await response.serverAddr().catch(() => null) : null;
6960
7115
  const controller = typeof AbortController === "function" ? new AbortController() : null;
6961
7116
  try {
6962
- const resolved = await withTimeout(
7117
+ const resolved = await withTimeout2(
6963
7118
  Promise.resolve(resolver({
6964
7119
  page,
6965
7120
  response,
@@ -7259,13 +7414,13 @@ var resolveWithIpLookup = async (page, options = {}) => {
7259
7414
  const contentType = normalizeText(await pickHeaderValue(response, ["content-type"]));
7260
7415
  let rawText = "";
7261
7416
  if (response && typeof response.text === "function") {
7262
- rawText = String(await withTimeout(
7417
+ rawText = String(await withTimeout2(
7263
7418
  response.text().catch(() => ""),
7264
7419
  timeoutMs
7265
7420
  ) || "");
7266
7421
  }
7267
7422
  if (!rawText) {
7268
- rawText = String(await withTimeout(
7423
+ rawText = String(await withTimeout2(
7269
7424
  probePage.evaluate(() => {
7270
7425
  return document.body?.innerText || document.documentElement?.innerText || "";
7271
7426
  }).catch(() => ""),
@@ -8368,7 +8523,7 @@ var normalizeXurl = (value) => {
8368
8523
  var normalizeShare = (share) => {
8369
8524
  const source = share && typeof share === "object" ? share : {};
8370
8525
  const modeRaw = String(source.mode || "dom").trim().toLowerCase();
8371
- const mode = modeRaw === "response" ? "response" : "dom";
8526
+ const mode = ["dom", "response", "custom"].includes(modeRaw) ? modeRaw : "dom";
8372
8527
  return {
8373
8528
  mode,
8374
8529
  prefix: normalizePrefix(source.prefix),
@@ -8484,16 +8639,17 @@ var Share = {
8484
8639
  * 捕获分享链接(单模式)
8485
8640
  * - mode='dom': 仅 DOM 监听
8486
8641
  * - mode='response': 仅接口监听
8642
+ * - mode='custom': 使用 performActions 返回值作为链接
8487
8643
  *
8488
8644
  * @param {import('playwright').Page} page
8489
8645
  * @param {Object} [options]
8490
- * @param {{ mode?: 'dom' | 'response', prefix: string, xurl?: Array<string | string[]> }} options.share
8646
+ * @param {{ mode?: 'dom' | 'response' | 'custom', prefix: string, xurl?: Array<string | string[]> }} options.share
8491
8647
  * @param {number} [options.timeoutMs=50000]
8492
8648
  * @param {number} [options.payloadSnapshotMaxLen=500]
8493
8649
  * @param {string | string[]} [options.domSelectors='html']
8494
8650
  * @param {'added' | 'changed' | 'all'} [options.domMode='all']
8495
- * @param {() => Promise<void>} [options.performActions]
8496
- * @returns {Promise<{ link: string | null; payloadText: string; payloadSnapshot: string; source: 'dom' | 'response' | 'none' }>}
8651
+ * @param {() => void | string | { link?: string | null; payloadText?: string } | Promise<void | string | { link?: string | null; payloadText?: string }>} [options.performActions]
8652
+ * @returns {Promise<{ link: string | null; payloadText: string; payloadSnapshot: string; source: 'dom' | 'response' | 'custom' | 'none' }>}
8497
8653
  */
8498
8654
  async captureLink(page, options = {}) {
8499
8655
  const share = normalizeShare(options.share);
@@ -8506,7 +8662,7 @@ var Share = {
8506
8662
  if (share.prefix === void 0) {
8507
8663
  throw new Error("Share.captureLink requires options.share.prefix");
8508
8664
  }
8509
- if (share.mode !== "dom" && share.mode !== "response") {
8665
+ if (share.mode !== "dom" && share.mode !== "response" && share.mode !== "custom") {
8510
8666
  throw new Error(`Share.captureLink unsupported share.mode: ${share.mode}`);
8511
8667
  }
8512
8668
  const apiMatchers = share.mode === "response" ? share.xurl[0] || [] : [];
@@ -8528,7 +8684,8 @@ var Share = {
8528
8684
  };
8529
8685
  const candidates = {
8530
8686
  dom: null,
8531
- response: null
8687
+ response: null,
8688
+ custom: null
8532
8689
  };
8533
8690
  const setCandidate = (source, link, payloadText = "") => {
8534
8691
  const safe = String(link || "").trim();
@@ -8628,37 +8785,47 @@ var Share = {
8628
8785
  logger15.info(`\u5F53\u524D\u4E3A\u63A5\u53E3\u6A21\u5F0F\uFF0C\u6302\u8F7D response \u76D1\u542C: apiMatchers=${toJsonInline(apiMatchers, 160)}`);
8629
8786
  page.on("response", onResponse);
8630
8787
  }
8788
+ if (share.mode === "custom") {
8789
+ logger15.info("\u5F53\u524D\u4E3A custom \u6A21\u5F0F\uFF0C\u5C06\u4F7F\u7528 performActions \u8FD4\u56DE\u503C");
8790
+ }
8631
8791
  const deadline = Date.now() + timeoutMs;
8632
8792
  const getRemainingMs = () => Math.max(0, deadline - Date.now());
8633
8793
  try {
8634
8794
  const actionTimeout = getRemainingMs();
8635
8795
  logger15.start("captureLink.performActions", `\u6267\u884C\u52A8\u4F5C\u9884\u7B97=${actionTimeout}ms`);
8796
+ let actionValue;
8636
8797
  if (actionTimeout > 0) {
8637
8798
  let timer = null;
8638
- let actionError = null;
8639
- const actionPromise = Promise.resolve().then(() => performActions()).then(() => "__ACTION_DONE__").catch((error) => {
8640
- actionError = error;
8641
- return "__ACTION_ERROR__";
8642
- });
8799
+ const actionPromise = Promise.resolve().then(() => performActions()).then((result) => ({ type: "done", result })).catch((error) => ({ type: "error", error }));
8643
8800
  const timeoutPromise = new Promise((resolve) => {
8644
- timer = setTimeout(() => resolve("__ACTION_TIMEOUT__"), actionTimeout);
8801
+ timer = setTimeout(() => resolve({ type: "timeout" }), actionTimeout);
8645
8802
  });
8646
8803
  const actionResult = await Promise.race([actionPromise, timeoutPromise]);
8647
8804
  if (timer) clearTimeout(timer);
8648
- if (actionResult === "__ACTION_ERROR__") {
8649
- logger15.fail("captureLink.performActions", actionError);
8650
- throw actionError;
8805
+ if (actionResult.type === "error") {
8806
+ logger15.fail("captureLink.performActions", actionResult.error);
8807
+ throw actionResult.error;
8651
8808
  }
8652
- if (actionResult === "__ACTION_TIMEOUT__") {
8809
+ if (actionResult.type === "timeout") {
8653
8810
  stats.actionTimedOut = true;
8654
8811
  logger15.warning(`performActions \u5DF2\u8D85\u65F6 (${actionTimeout}ms)\uFF0C\u52A8\u4F5C\u53EF\u80FD\u4ECD\u5728\u5F02\u6B65\u6267\u884C`);
8655
8812
  } else {
8813
+ actionValue = actionResult.result;
8656
8814
  logger15.success("captureLink.performActions", "\u6267\u884C\u52A8\u4F5C\u5B8C\u6210");
8657
8815
  }
8658
8816
  }
8817
+ if (share.mode === "custom") {
8818
+ const customLink = typeof actionValue === "string" ? actionValue : actionValue?.link || actionValue?.payloadText;
8819
+ const customPayloadText = typeof actionValue === "string" ? actionValue : actionValue?.payloadText;
8820
+ if (setCandidate("custom", customLink, customPayloadText)) {
8821
+ logger15.success("captureLink.customResult", `custom \u547D\u4E2D\u5206\u4EAB\u94FE\u63A5: link=${candidates.custom.link}`);
8822
+ } else {
8823
+ logger15.warning("performActions \u6267\u884C\u5B8C\u6210\u4F46\u672A\u8FD4\u56DE\u6709\u6548\u5206\u4EAB\u94FE\u63A5");
8824
+ }
8825
+ }
8659
8826
  let nextProgressLogTs = Date.now() + 3e3;
8660
8827
  while (true) {
8661
- const selected = share.mode === "dom" ? candidates.dom : candidates.response;
8828
+ const selected = candidates[share.mode];
8662
8829
  if (selected?.link) {
8663
8830
  logger15.success("captureLink", `\u6355\u83B7\u6210\u529F: source=${share.mode}, link=${selected.link}`);
8664
8831
  return {
@@ -8668,6 +8835,7 @@ var Share = {
8668
8835
  source: share.mode
8669
8836
  };
8670
8837
  }
8838
+ if (share.mode === "custom") break;
8671
8839
  const remaining = getRemainingMs();
8672
8840
  if (remaining <= 0) break;
8673
8841
  const now = Date.now();