@skrillex1224/playwright-toolkit 2.1.246 → 2.1.247

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
@@ -133,10 +133,10 @@ var createActorInfo = (info) => {
133
133
  xurl
134
134
  };
135
135
  };
136
- const buildLandingUrl = ({ protocol: protocol2, domain: domain2, path: path4 }) => {
136
+ const buildLandingUrl = ({ protocol: protocol2, domain: domain2, path: path3 }) => {
137
137
  const safeProtocol = String(protocol2).trim();
138
138
  const safeDomain = normalizeDomain(domain2);
139
- const safePath = normalizePath(path4);
139
+ const safePath = normalizePath(path3);
140
140
  return `${safeProtocol}://${safeDomain}${safePath}`;
141
141
  };
142
142
  const buildIcon = ({ key }) => {
@@ -144,14 +144,14 @@ var createActorInfo = (info) => {
144
144
  };
145
145
  const protocol = info.protocol || "https";
146
146
  const domain = normalizeDomain(info.domain);
147
- const path3 = normalizePath(info.path);
147
+ const path2 = normalizePath(info.path);
148
148
  const share = normalizeShare2(info.share);
149
149
  const device = normalizeDevice(info.device);
150
150
  return {
151
151
  ...info,
152
152
  protocol,
153
153
  domain,
154
- path: path3,
154
+ path: path2,
155
155
  share,
156
156
  device,
157
157
  get icon() {
@@ -1512,7 +1512,7 @@ var normalizeCookies = (value) => {
1512
1512
  if (!name || !cookieValue || cookieValue === "<nil>") return null;
1513
1513
  const domain = String(raw.domain || "").trim();
1514
1514
  const url = normalizeHttpUrl(raw.url);
1515
- const path3 = String(raw.path || "").trim() || "/";
1515
+ const path2 = String(raw.path || "").trim() || "/";
1516
1516
  const sameSite = normalizeCookieSameSite(raw.sameSite);
1517
1517
  const expires = normalizeCookieExpires(raw);
1518
1518
  const secure = Boolean(raw.secure);
@@ -1521,7 +1521,7 @@ var normalizeCookies = (value) => {
1521
1521
  const normalized = {
1522
1522
  name,
1523
1523
  value: cookieValue,
1524
- path: path3,
1524
+ path: path2,
1525
1525
  ...domain ? { domain } : {},
1526
1526
  ...!domain && url ? { url } : {},
1527
1527
  ...sameSite ? { sameSite } : {},
@@ -2592,10 +2592,6 @@ var assertPoint = (point) => {
2592
2592
  throw new Error(`Invalid input point: ${JSON.stringify(point)}`);
2593
2593
  }
2594
2594
  };
2595
- var toFiniteNumber = (value, fallback = 0) => {
2596
- const number = Number(value);
2597
- return Number.isFinite(number) ? number : fallback;
2598
- };
2599
2595
  var dispatchMouseMove = (page, point, options = {}) => page.mouse.move(point.x, point.y, options);
2600
2596
  var dispatchMouseStart = (page, options = {}) => page.mouse.down(options);
2601
2597
  var dispatchMouseEnd = (page, options = {}) => page.mouse.up(options);
@@ -2615,11 +2611,6 @@ var dragWithMouse = async (page, points, options = {}) => {
2615
2611
  }, { steps: 2 });
2616
2612
  await waitFor(page, options.stepDelayMs ?? 90);
2617
2613
  }
2618
- const finalMoveRepeats = Math.max(0, Math.floor(toFiniteNumber(options.finalMoveRepeats)));
2619
- for (let repeat = 0; repeat < finalMoveRepeats; repeat += 1) {
2620
- await dispatchMouseMove(page, points.end, { steps: 1 });
2621
- await waitFor(page, options.finalMoveDelayMs ?? 35);
2622
- }
2623
2614
  await waitFor(page, options.beforeReleaseDelayMs ?? 100);
2624
2615
  await dispatchMouseEnd(page);
2625
2616
  await waitFor(page, options.afterReleaseDelayMs ?? 100);
@@ -2629,7 +2620,6 @@ var dragWithTouch = async (page, points, options = {}) => {
2629
2620
  let client = null;
2630
2621
  try {
2631
2622
  client = await page.context().newCDPSession(page);
2632
- await waitFor(page, options.initialDelayMs ?? 250);
2633
2623
  await client.send("Input.dispatchTouchEvent", {
2634
2624
  type: "touchStart",
2635
2625
  touchPoints: [{ x: points.start.x, y: points.start.y, id: 1 }]
@@ -2651,14 +2641,6 @@ var dragWithTouch = async (page, points, options = {}) => {
2651
2641
  });
2652
2642
  await waitFor(page, options.stepDelayMs ?? 90);
2653
2643
  }
2654
- const finalMoveRepeats = Math.max(0, Math.floor(toFiniteNumber(options.finalMoveRepeats)));
2655
- for (let repeat = 0; repeat < finalMoveRepeats; repeat += 1) {
2656
- await client.send("Input.dispatchTouchEvent", {
2657
- type: "touchMove",
2658
- touchPoints: [{ x: points.end.x, y: points.end.y, id: 1 }]
2659
- });
2660
- await waitFor(page, options.finalMoveDelayMs ?? 35);
2661
- }
2662
2644
  await waitFor(page, options.beforeReleaseDelayMs ?? 100);
2663
2645
  await client.send("Input.dispatchTouchEvent", {
2664
2646
  type: "touchEnd",
@@ -2676,7 +2658,7 @@ var dragWithTouch = async (page, points, options = {}) => {
2676
2658
  var clickTargetWithDevice = async (page, target, options = {}) => {
2677
2659
  const normalizedOptions = normalizeSelectorOptions(options);
2678
2660
  const resolvedDevice = resolveDeviceFromPage(page);
2679
- if (target && resolvedDevice === Device.Mobile && !normalizedOptions.forceClick && !normalizedOptions.forceMouse) {
2661
+ if (target && resolvedDevice === Device.Mobile && !normalizedOptions.forceClick) {
2680
2662
  if (typeof target.tap === "function") {
2681
2663
  await target.tap(normalizedOptions.tapOptions);
2682
2664
  return true;
@@ -2876,26 +2858,20 @@ var DeviceInput = {
2876
2858
  throw new Error("Unable to resolve drag coordinates.");
2877
2859
  }
2878
2860
  const steps = options.steps || 10;
2879
- const sourceOffsetX = toFiniteNumber(options.sourceOffsetX);
2880
- const sourceOffsetY = toFiniteNumber(options.sourceOffsetY);
2881
- const targetOffsetX = toFiniteNumber(options.targetOffsetX);
2882
- const targetOffsetY = toFiniteNumber(options.targetOffsetY);
2883
- const sourceCenterX = sourceBox.x + sourceBox.width / 2 + sourceOffsetX;
2884
- const sourceCenterY = sourceBox.y + sourceBox.height / 2 + sourceOffsetY;
2885
2861
  const liftOffsetX = Math.min(18, Math.max(8, sourceBox.width * 0.12));
2886
2862
  const liftOffsetY = Math.min(12, Math.max(4, sourceBox.height * 0.08));
2887
2863
  const points = {
2888
2864
  start: {
2889
- x: sourceCenterX,
2890
- y: sourceCenterY
2865
+ x: sourceBox.x + sourceBox.width / 2,
2866
+ y: sourceBox.y + sourceBox.height / 2
2891
2867
  },
2892
2868
  lift: {
2893
- x: sourceCenterX + liftOffsetX,
2894
- y: sourceCenterY + liftOffsetY
2869
+ x: sourceBox.x + sourceBox.width / 2 + liftOffsetX,
2870
+ y: sourceBox.y + sourceBox.height / 2 + liftOffsetY
2895
2871
  },
2896
2872
  end: {
2897
- x: targetBox.x + targetBox.width / 2 + targetOffsetX,
2898
- y: targetBox.y + targetBox.height / 2 + targetOffsetY
2873
+ x: targetBox.x + targetBox.width / 2,
2874
+ y: targetBox.y + targetBox.height / 2
2899
2875
  },
2900
2876
  steps
2901
2877
  };
@@ -3460,23 +3436,6 @@ var initializedPages = /* @__PURE__ */ new WeakSet();
3460
3436
  var DEFAULT_TAP_TIMEOUT_MS = 2500;
3461
3437
  var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
3462
3438
  var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
3463
- var INTERACTIVE_SELECTOR = [
3464
- "button",
3465
- '[role="button"]',
3466
- "a[href]",
3467
- "label",
3468
- "input",
3469
- "textarea",
3470
- "select",
3471
- "summary",
3472
- '[contenteditable="true"]',
3473
- '[tabindex]:not([tabindex="-1"])'
3474
- ].join(",");
3475
- var EDITABLE_SELECTOR = [
3476
- "input",
3477
- "textarea",
3478
- '[contenteditable="true"]'
3479
- ].join(",");
3480
3439
  var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
3481
3440
  var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
3482
3441
  var describeTarget = (target) => {
@@ -3516,7 +3475,7 @@ var withTimeout = async (operation, timeoutMs, label) => {
3516
3475
  }
3517
3476
  };
3518
3477
  var checkElementVisibility = async (element) => {
3519
- return element.evaluate((el, interactiveSelector) => {
3478
+ return element.evaluate((el) => {
3520
3479
  const targetStyle = window.getComputedStyle(el);
3521
3480
  if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
3522
3481
  return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
@@ -3578,16 +3537,30 @@ var checkElementVisibility = async (element) => {
3578
3537
  positioning
3579
3538
  };
3580
3539
  }
3581
- const targetInteractive = typeof el.closest === "function" ? el.closest(interactiveSelector) : null;
3582
- const sameInteractiveTarget = (pointElement) => {
3540
+ const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
3541
+ const commonAncestor = (a, b) => {
3542
+ for (let current = a; current && !isRootNode(current); current = current.parentElement) {
3543
+ if (current.contains(b)) return current;
3544
+ }
3545
+ return null;
3546
+ };
3547
+ const sameTapTarget = (pointElement) => {
3583
3548
  if (!pointElement) return false;
3584
3549
  if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
3585
3550
  return true;
3586
3551
  }
3587
- if (!targetInteractive || typeof pointElement.closest !== "function") {
3552
+ const common = commonAncestor(el, pointElement);
3553
+ if (!common) return false;
3554
+ const commonRect = common.getBoundingClientRect?.();
3555
+ if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
3556
+ const commonArea = commonRect.width * commonRect.height;
3557
+ const targetArea = Math.max(1, rect.width * rect.height);
3558
+ const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
3559
+ if (commonArea > maxSharedRegionArea) return false;
3560
+ if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
3588
3561
  return false;
3589
3562
  }
3590
- return pointElement.closest(interactiveSelector) === targetInteractive;
3563
+ return common.contains(el) && common.contains(pointElement);
3591
3564
  };
3592
3565
  const describeElement = (node) => {
3593
3566
  if (!node) return null;
@@ -3616,7 +3589,7 @@ var checkElementVisibility = async (element) => {
3616
3589
  let obstruction = null;
3617
3590
  for (const point of samplePoints) {
3618
3591
  const pointElement = document.elementFromPoint(point.x, point.y);
3619
- if (sameInteractiveTarget(pointElement)) {
3592
+ if (sameTapTarget(pointElement)) {
3620
3593
  return { code: "VISIBLE", isFixed, positioning };
3621
3594
  }
3622
3595
  obstruction = obstruction || describeElement(pointElement);
@@ -3634,10 +3607,10 @@ var checkElementVisibility = async (element) => {
3634
3607
  };
3635
3608
  }
3636
3609
  return { code: "VISIBLE", isFixed, positioning };
3637
- }, INTERACTIVE_SELECTOR);
3610
+ });
3638
3611
  };
3639
3612
  var resolveSafeTapPoint = async (element) => {
3640
- return element.evaluate((el, interactiveSelector) => {
3613
+ return element.evaluate((el) => {
3641
3614
  const rect = el.getBoundingClientRect();
3642
3615
  if (!rect || rect.width <= 0 || rect.height <= 0) {
3643
3616
  return null;
@@ -3674,16 +3647,30 @@ var resolveSafeTapPoint = async (element) => {
3674
3647
  if (visibleWidth <= 1 || visibleHeight <= 1) {
3675
3648
  return null;
3676
3649
  }
3677
- const targetInteractive = typeof el.closest === "function" ? el.closest(interactiveSelector) : null;
3678
- const sameInteractiveTarget = (pointElement) => {
3650
+ const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
3651
+ const commonAncestor = (a, b) => {
3652
+ for (let current = a; current && !isRootNode(current); current = current.parentElement) {
3653
+ if (current.contains(b)) return current;
3654
+ }
3655
+ return null;
3656
+ };
3657
+ const sameTapTarget = (pointElement) => {
3679
3658
  if (!pointElement) return false;
3680
3659
  if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
3681
3660
  return true;
3682
3661
  }
3683
- if (!targetInteractive || typeof pointElement.closest !== "function") {
3662
+ const common = commonAncestor(el, pointElement);
3663
+ if (!common) return false;
3664
+ const commonRect = common.getBoundingClientRect?.();
3665
+ if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
3666
+ const commonArea = commonRect.width * commonRect.height;
3667
+ const targetArea = Math.max(1, rect.width * rect.height);
3668
+ const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
3669
+ if (commonArea > maxSharedRegionArea) return false;
3670
+ if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
3684
3671
  return false;
3685
3672
  }
3686
- return pointElement.closest(interactiveSelector) === targetInteractive;
3673
+ return common.contains(el) && common.contains(pointElement);
3687
3674
  };
3688
3675
  const cx = visibleLeft + visibleWidth / 2;
3689
3676
  const cy = visibleTop + visibleHeight / 2;
@@ -3702,7 +3689,7 @@ var resolveSafeTapPoint = async (element) => {
3702
3689
  ];
3703
3690
  const safePoints = points.filter((point) => {
3704
3691
  const pointElement = document.elementFromPoint(point.x, point.y);
3705
- return sameInteractiveTarget(pointElement);
3692
+ return sameTapTarget(pointElement);
3706
3693
  });
3707
3694
  const candidates = safePoints.length ? safePoints : points;
3708
3695
  const chosen = candidates[Math.floor(Math.random() * candidates.length)];
@@ -3711,46 +3698,57 @@ var resolveSafeTapPoint = async (element) => {
3711
3698
  x: chosen.x,
3712
3699
  y: chosen.y
3713
3700
  };
3714
- }, INTERACTIVE_SELECTOR);
3701
+ });
3715
3702
  };
3716
3703
  var activateElementFallback = async (element, point = null, options = {}) => {
3717
- return element.evaluate((el, { innerPoint, innerOptions, interactiveSelector, editableSelector }) => {
3718
- const pointElement = innerPoint ? document.elementFromPoint(innerPoint.x, innerPoint.y) : null;
3719
- const nearestInteractive = (node) => {
3720
- if (!node || typeof node.closest !== "function") return null;
3721
- return node.closest(interactiveSelector);
3704
+ return element.evaluate((el, { innerOptions }) => {
3705
+ const isEditable = (node) => {
3706
+ if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
3707
+ if (node.isContentEditable) return true;
3708
+ if (node instanceof HTMLTextAreaElement) return !node.disabled && !node.readOnly;
3709
+ if (node instanceof HTMLInputElement) {
3710
+ return !node.disabled && !node.readOnly && typeof node.select === "function";
3711
+ }
3712
+ return false;
3722
3713
  };
3723
- const targetInteractive = nearestInteractive(el);
3724
- const pointInteractive = nearestInteractive(pointElement);
3725
- let target = null;
3726
- if (pointInteractive && (pointInteractive === targetInteractive || pointInteractive === el || pointInteractive.contains(el) || el.contains(pointInteractive))) {
3727
- target = pointInteractive;
3728
- }
3729
- target = target || targetInteractive || el;
3730
- const editable = typeof target.closest === "function" ? target.closest(editableSelector) : null;
3714
+ const findEditable = (node) => {
3715
+ for (let current = node; current && current !== document.body; current = current.parentElement) {
3716
+ if (isEditable(current)) return current;
3717
+ }
3718
+ return null;
3719
+ };
3720
+ const editable = findEditable(el);
3731
3721
  if (editable && typeof editable.focus === "function") {
3732
3722
  editable.focus({ preventScroll: true });
3733
3723
  if (innerOptions.editableOnly) {
3734
3724
  return { activated: true, method: "focus", tag: editable.tagName || "" };
3735
3725
  }
3736
3726
  }
3737
- if (typeof target.focus === "function") {
3738
- target.focus({ preventScroll: true });
3727
+ if (innerOptions.editableOnly) {
3728
+ return { activated: false, method: "none", tag: el?.tagName || "" };
3739
3729
  }
3740
- if (!innerOptions.editableOnly && typeof target.click === "function") {
3741
- target.click();
3742
- return { activated: true, method: "dom-click", tag: target.tagName || "" };
3730
+ if (typeof el.focus === "function") {
3731
+ el.focus({ preventScroll: true });
3732
+ }
3733
+ if (typeof el.click === "function") {
3734
+ el.click();
3735
+ return { activated: true, method: "dom-click", tag: el.tagName || "" };
3736
+ }
3737
+ if (typeof el.dispatchEvent === "function") {
3738
+ el.dispatchEvent(new MouseEvent("click", {
3739
+ bubbles: true,
3740
+ cancelable: true,
3741
+ view: window
3742
+ }));
3743
+ return { activated: true, method: "dispatch-click", tag: el.tagName || "" };
3743
3744
  }
3744
3745
  return {
3745
3746
  activated: Boolean(editable),
3746
3747
  method: editable ? "focus" : "none",
3747
- tag: target?.tagName || ""
3748
+ tag: el?.tagName || ""
3748
3749
  };
3749
3750
  }, {
3750
- innerPoint: point,
3751
- innerOptions: options || {},
3752
- interactiveSelector: INTERACTIVE_SELECTOR,
3753
- editableSelector: EDITABLE_SELECTOR
3751
+ innerOptions: options || {}
3754
3752
  });
3755
3753
  };
3756
3754
  var getScrollableRect = async (element) => {
@@ -3854,6 +3852,47 @@ var scrollAwayFromObstruction = async (element, status) => {
3854
3852
  };
3855
3853
  }, status);
3856
3854
  };
3855
+ var getElementViewportSnapshot = async (element) => {
3856
+ return element.evaluate((el) => {
3857
+ const rect = el.getBoundingClientRect();
3858
+ return {
3859
+ top: rect.top,
3860
+ bottom: rect.bottom,
3861
+ left: rect.left,
3862
+ right: rect.right,
3863
+ width: rect.width,
3864
+ height: rect.height,
3865
+ scrollX: window.scrollX,
3866
+ scrollY: window.scrollY
3867
+ };
3868
+ });
3869
+ };
3870
+ var isTargetImmobileAfterScroll = (before, after) => {
3871
+ if (!before || !after) return false;
3872
+ const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
3873
+ const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
3874
+ const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
3875
+ const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
3876
+ const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
3877
+ const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
3878
+ if (!rectMoved && !pageMoved) return true;
3879
+ if (pageMoved && !rectMoved) return true;
3880
+ if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
3881
+ return true;
3882
+ }
3883
+ return false;
3884
+ };
3885
+ var restoreWindowFromSnapshot = async (page, before, after) => {
3886
+ if (!before || !after) return;
3887
+ if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
3888
+ return;
3889
+ }
3890
+ await page.evaluate(
3891
+ (state) => window.scrollTo(state.x, state.y),
3892
+ { x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
3893
+ ).catch(() => {
3894
+ });
3895
+ };
3857
3896
  var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
3858
3897
  const viewport = resolveViewport(page);
3859
3898
  const rawRect = options.rect || null;
@@ -4034,11 +4073,11 @@ var MobileHumanize = {
4034
4073
  const scrollRect = await getScrollableRect(element);
4035
4074
  if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
4036
4075
  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"})`);
4037
- return { element, didScroll, restore: null };
4076
+ return { element, didScroll, restore: null, unscrollable: true };
4038
4077
  }
4039
4078
  if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
4040
4079
  logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
4041
- return { element, didScroll, restore: null };
4080
+ return { element, didScroll, restore: null, unscrollable: true };
4042
4081
  }
4043
4082
  if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
4044
4083
  const moved = await scrollAwayFromObstruction(element, status);
@@ -4069,6 +4108,7 @@ var MobileHumanize = {
4069
4108
  }
4070
4109
  }
4071
4110
  const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
4111
+ const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4072
4112
  const beforeState = scrollRect ? await element.evaluate((el) => {
4073
4113
  const isScrollable = (node) => {
4074
4114
  const style = window.getComputedStyle(node);
@@ -4098,6 +4138,21 @@ var MobileHumanize = {
4098
4138
  logger7.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
4099
4139
  }
4100
4140
  }
4141
+ let afterElementSnapshot = null;
4142
+ const readAfterElementSnapshot = async () => {
4143
+ if (!afterElementSnapshot) {
4144
+ afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4145
+ }
4146
+ return afterElementSnapshot;
4147
+ };
4148
+ if (!scrollRect && beforeElementSnapshot) {
4149
+ const afterSnapshot = await readAfterElementSnapshot();
4150
+ if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
4151
+ await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
4152
+ logger7.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"})`);
4153
+ return { element, didScroll, restore: null, unscrollable: true };
4154
+ }
4155
+ }
4101
4156
  if (scrollRect && beforeState) {
4102
4157
  const afterState = await element.evaluate((el) => {
4103
4158
  const isScrollable = (node) => {
@@ -4135,6 +4190,14 @@ var MobileHumanize = {
4135
4190
  }
4136
4191
  }
4137
4192
  }
4193
+ if (scrollRect && beforeElementSnapshot) {
4194
+ const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4195
+ if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
4196
+ await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
4197
+ logger7.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"})`);
4198
+ return { element, didScroll, restore: null, unscrollable: true };
4199
+ }
4200
+ }
4138
4201
  didScroll = true;
4139
4202
  }
4140
4203
  try {
@@ -4184,21 +4247,28 @@ var MobileHumanize = {
4184
4247
  logger7.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
4185
4248
  return false;
4186
4249
  }
4187
- if (scrollIfNeeded) {
4188
- await MobileHumanize.humanScroll(page, element, { throwOnMissing });
4189
- }
4250
+ const scrollResult = scrollIfNeeded ? await MobileHumanize.humanScroll(page, element, { throwOnMissing }) : null;
4190
4251
  const status = await checkElementVisibility(element).catch(() => null);
4191
4252
  if (status && status.code !== "VISIBLE") {
4192
- if (fallbackDomClick && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
4193
- const fallback = await withTimeout(
4253
+ if (fallbackDomClick && (status.code === "OUT_OF_VIEWPORT" || status.code === "OBSTRUCTED") && (status.isFixed || scrollResult?.unscrollable)) {
4254
+ let fallback = await withTimeout(
4194
4255
  () => activateElementFallback(element, null, {
4195
4256
  editableOnly: true
4196
4257
  }),
4197
4258
  activateFallbackTimeoutMs,
4198
4259
  "focus fallback"
4199
4260
  ).catch(() => null);
4261
+ if (!fallback?.activated) {
4262
+ fallback = await withTimeout(
4263
+ () => activateElementFallback(element, null, {
4264
+ editableOnly: false
4265
+ }),
4266
+ activateFallbackTimeoutMs,
4267
+ "activation fallback"
4268
+ ).catch(() => null);
4269
+ }
4200
4270
  if (fallback?.activated) {
4201
- logger7.warn(`humanClick: fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
4271
+ logger7.warn(`humanClick: \u4E0D\u53EF\u6EDA\u52A8\u76EE\u6807\u4E0D\u53EF\u7269\u7406\u70B9\u51FB\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
4202
4272
  return true;
4203
4273
  }
4204
4274
  }
@@ -4315,6 +4385,20 @@ var MobileHumanize = {
4315
4385
  const locator = page.locator(selector);
4316
4386
  await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
4317
4387
  await waitJitter(160, 0.4);
4388
+ const readValue = async () => {
4389
+ try {
4390
+ return await locator.inputValue({ timeout: 600 });
4391
+ } catch {
4392
+ return await locator.evaluate((el) => "value" in el ? String(el.value || "") : String(el.textContent || "")).catch(() => "");
4393
+ }
4394
+ };
4395
+ const currentValue = await readValue();
4396
+ if (!currentValue) return;
4397
+ await page.keyboard.press("ControlOrMeta+A");
4398
+ await waitJitter(90, 0.35);
4399
+ await page.keyboard.press("Backspace");
4400
+ await waitJitter(120, 0.35);
4401
+ if (!await readValue()) return;
4318
4402
  await locator.evaluate((el) => {
4319
4403
  if ("value" in el) {
4320
4404
  el.value = "";
@@ -4904,10 +4988,6 @@ var LiveView = {
4904
4988
  // src/chaptcha.js
4905
4989
  var import_uuid = require("uuid");
4906
4990
 
4907
- // src/internals/captcha/bytedance.js
4908
- var import_promises = require("fs/promises");
4909
- var import_path2 = __toESM(require("path"), 1);
4910
-
4911
4991
  // src/internals/captcha/shared.js
4912
4992
  var waitForVisible = async (locator, timeout) => {
4913
4993
  try {
@@ -4925,71 +5005,38 @@ var isAnyCaptchaTextVisible = async (frame, texts, timeout) => {
4925
5005
  if (!text) {
4926
5006
  continue;
4927
5007
  }
4928
- const textLocator = frame.getByText(text, { exact: false });
4929
- const locatorText = frame.locator(`text=${text}`);
4930
- const candidateGroups = [textLocator, locatorText];
4931
- for (const candidateGroup of candidateGroups) {
4932
- const candidateCount = await candidateGroup.count().catch(() => 0);
4933
- for (let index = 0; index < candidateCount; index += 1) {
4934
- const candidate = candidateGroup.nth(index);
4935
- const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
4936
- if (isVisible) {
4937
- return true;
4938
- }
5008
+ const candidates = [
5009
+ frame.getByText(text, { exact: false }).first(),
5010
+ frame.locator(`text=${text}`).first()
5011
+ ];
5012
+ for (const candidate of candidates) {
5013
+ const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
5014
+ if (isVisible) {
5015
+ return true;
4939
5016
  }
4940
5017
  }
4941
5018
  }
4942
5019
  return false;
4943
5020
  };
4944
- var collectVisibleCandidateIndexes = async (candidateGroup, count, timeout) => {
4945
- const visibleIndexes = [];
4946
- for (let index = 0; index < count; index += 1) {
4947
- const candidate = candidateGroup.nth(index);
4948
- const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
4949
- if (isVisible) {
4950
- visibleIndexes.push(index);
4951
- }
4952
- }
4953
- return visibleIndexes;
4954
- };
4955
5021
  var clickCaptchaAction = async (frame, texts, options) => {
4956
5022
  for (const text of texts || []) {
4957
- const textLocator = frame.getByText(text, { exact: false });
4958
- const locatorText = frame.locator(`text=${text}`);
4959
- const [getByTextCount, locatorTextCount] = await Promise.all([
4960
- textLocator.count().catch(() => 0),
4961
- locatorText.count().catch(() => 0)
4962
- ]);
4963
- const [getByTextVisibleIndexes, locatorTextVisibleIndexes] = await Promise.all([
4964
- collectVisibleCandidateIndexes(textLocator, getByTextCount, options.actionVisibleTimeoutMs),
4965
- collectVisibleCandidateIndexes(locatorText, locatorTextCount, options.actionVisibleTimeoutMs)
4966
- ]);
4967
- options.logger?.info(
4968
- `[CaptchaAction] \u6587\u672C "${text}" \u547D\u4E2D\u6570\u91CF\uFF1AgetByText=${getByTextCount} (visible=${getByTextVisibleIndexes.length}), locator=${locatorTextCount} (visible=${locatorTextVisibleIndexes.length})`
4969
- );
4970
- const candidateGroups = [
4971
- { label: "getByText", locator: textLocator, count: getByTextCount },
4972
- { label: "locator", locator: locatorText, count: locatorTextCount }
5023
+ const candidates = [
5024
+ frame.getByText(text, { exact: false }).first(),
5025
+ frame.locator(`text=${text}`).first()
4973
5026
  ];
4974
- for (const candidateGroup of candidateGroups) {
4975
- for (let index = 0; index < candidateGroup.count; index += 1) {
4976
- const candidate = candidateGroup.locator.nth(index);
4977
- const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
4978
- if (!isVisible) {
4979
- continue;
4980
- }
4981
- options.logger?.info(
4982
- `[CaptchaAction] \u6587\u672C "${text}" \u9009\u62E9 ${candidateGroup.label}[${index}] \u4F5C\u4E3A\u7B2C\u4E00\u4E2A\u53EF\u89C1\u8282\u70B9\u6267\u884C\u70B9\u51FB\u3002`
4983
- );
4984
- await DeviceInput.click(options.page, candidate, options);
4985
- return true;
5027
+ for (const candidate of candidates) {
5028
+ const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
5029
+ if (!isVisible) {
5030
+ continue;
4986
5031
  }
5032
+ await DeviceInput.click(options.page, candidate);
5033
+ return true;
4987
5034
  }
4988
5035
  }
4989
5036
  return false;
4990
5037
  };
4991
- var dragCaptchaAction = async (page, sourceLocator, targetLocator, options = {}) => {
4992
- await DeviceInput.drag(page, sourceLocator, targetLocator, options);
5038
+ var dragCaptchaAction = async (page, sourceLocator, targetLocator) => {
5039
+ await DeviceInput.drag(page, sourceLocator, targetLocator);
4993
5040
  };
4994
5041
 
4995
5042
  // src/internals/captcha/bytedance.js
@@ -5000,12 +5047,11 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
5000
5047
  containerSelector: "#captcha_container",
5001
5048
  iframeSelector: 'iframe[src*="verifycenter"]',
5002
5049
  iframeFallbackSelector: "iframe",
5003
- sourceImageSelector: ".img-container .canvas-container",
5004
- dropTargetContainerSelector: ".drag-area",
5050
+ sourceImageSelector: "div.canvas-container",
5051
+ dropTargetContainerSelector: "#captcha_verify_image div",
5005
5052
  dropTargetTexts: ["\u62D6\u62FD\u5230\u8FD9\u91CC"],
5006
5053
  refreshTexts: ["\u5237\u65B0"],
5007
5054
  submitTexts: ["\u63D0\u4EA4"],
5008
- guideMaskSelector: ".play-guide-mask",
5009
5055
  recognitionSuccessCode: 1e4,
5010
5056
  containerVisibleTimeoutMs: 2e3,
5011
5057
  iframeVisibleTimeoutMs: 12e3,
@@ -5028,111 +5074,10 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
5028
5074
  ],
5029
5075
  recognitionDelayMs: 2e3,
5030
5076
  refreshWaitMs: 3e3,
5031
- submitWaitMs: 5e3,
5032
- submitReadyTimeoutMs: 2500,
5077
+ submitWaitMs: 3e3,
5033
5078
  retryDelayBaseMs: 2e3,
5034
- retryDelayStepMs: 1e3,
5035
- sourceImageRowTolerancePx: 24,
5036
- dragBetweenWaitMs: 250,
5037
- promptBadgeCountSelector: ".drag-area .photo-badge .badge span",
5038
- promptSubmitButtonSelector: ".vc-captcha-verify-mobile-button",
5039
- promptSelectedSourceSelector: ".img-container .canvas-container.selected",
5040
- promptActiveSourceSelector: ".img-container .canvas-container.active",
5041
- promptDragMoveSteps: 16,
5042
- promptDragStepDelayMs: 55,
5043
- promptDragHoldDelayMs: 240,
5044
- promptDragBeforeReleaseDelayMs: 180,
5045
- promptDragAfterReleaseDelayMs: 240,
5046
- promptDragFinalMoveRepeats: 3,
5047
- promptDragRetryDelayMs: 250,
5048
- debugArtifacts: false
5079
+ retryDelayStepMs: 1e3
5049
5080
  });
5050
- var PROMPT_CAPTCHA_DRAG_PLANS = Object.freeze([
5051
- { name: "lower-middle", endXRatio: 0.5, endYRatio: 0.72 },
5052
- { name: "center-middle", endXRatio: 0.5, endYRatio: 0.56 },
5053
- { name: "upper-middle", endXRatio: 0.5, endYRatio: 0.4 }
5054
- ]);
5055
- var resolveCaptchaDebugDir = () => import_path2.default.resolve(process.cwd(), "storage", "captcha-debug");
5056
- var rectOf = (rect) => {
5057
- if (!rect) {
5058
- return null;
5059
- }
5060
- return {
5061
- x: Number(rect.x.toFixed(2)),
5062
- y: Number(rect.y.toFixed(2)),
5063
- width: Number(rect.width.toFixed(2)),
5064
- height: Number(rect.height.toFixed(2))
5065
- };
5066
- };
5067
- var collectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, extra = null) => {
5068
- const timestamp = Date.now();
5069
- const debugDir = resolveCaptchaDebugDir();
5070
- await (0, import_promises.mkdir)(debugDir, { recursive: true });
5071
- const baseName = `bytedance-${timestamp}-attempt${attempt}-${phase}`;
5072
- const iframeScreenshotPath = import_path2.default.join(debugDir, `${baseName}-iframe.png`);
5073
- const pageScreenshotPath = import_path2.default.join(debugDir, `${baseName}-page.png`);
5074
- const htmlPath = import_path2.default.join(debugDir, `${baseName}-iframe.html`);
5075
- const infoPath = import_path2.default.join(debugDir, `${baseName}-info.json`);
5076
- await iframeLocator.screenshot({ path: iframeScreenshotPath }).catch(() => {
5077
- });
5078
- await page.screenshot({ path: pageScreenshotPath, fullPage: true }).catch(() => {
5079
- });
5080
- const html = await frame.evaluate(() => document.documentElement.outerHTML).catch(() => "");
5081
- if (html) {
5082
- await (0, import_promises.writeFile)(htmlPath, html, "utf8");
5083
- }
5084
- const info = await frame.evaluate(() => {
5085
- const toRect = (element) => {
5086
- const rect = element.getBoundingClientRect();
5087
- return {
5088
- x: Number(rect.x.toFixed(2)),
5089
- y: Number(rect.y.toFixed(2)),
5090
- width: Number(rect.width.toFixed(2)),
5091
- height: Number(rect.height.toFixed(2))
5092
- };
5093
- };
5094
- const toItem = (element, index) => ({
5095
- index,
5096
- tag: element.tagName,
5097
- id: element.id || "",
5098
- className: typeof element.className === "string" ? element.className : "",
5099
- text: String(element.textContent || "").trim(),
5100
- rect: toRect(element)
5101
- });
5102
- const visibleNodes = Array.from(document.querySelectorAll("body *")).map(toItem).filter((item) => item.rect.width > 0 && item.rect.height > 0);
5103
- return {
5104
- title: document.title,
5105
- bodyText: String(document.body?.innerText || "").trim(),
5106
- canvasContainers: visibleNodes.filter((item) => item.className.includes("canvas-container")),
5107
- canvasNodes: visibleNodes.filter((item) => item.tag === "CANVAS"),
5108
- captchaNodes: visibleNodes.filter((item) => item.id.startsWith("captcha_") || item.className.includes("captcha") || item.className.includes("verify") || /拖拽到这里|刷新|提交/.test(item.text)),
5109
- visibleTextNodes: visibleNodes.filter((item) => item.text).slice(0, 300)
5110
- };
5111
- }).catch(() => null);
5112
- if (info) {
5113
- const payload = {
5114
- capturedAt: new Date(timestamp).toISOString(),
5115
- pageUrl: page.url(),
5116
- attempt,
5117
- phase,
5118
- iframeScreenshotPath,
5119
- pageScreenshotPath,
5120
- htmlPath,
5121
- info
5122
- };
5123
- if (extra != null) {
5124
- payload.extra = extra;
5125
- }
5126
- await (0, import_promises.writeFile)(infoPath, JSON.stringify(payload, null, 2), "utf8");
5127
- }
5128
- logger10.info(`\u5DF2\u5199\u51FA\u9A8C\u8BC1\u7801\u8C03\u8BD5\u4EA7\u7269\uFF1A${debugDir}`);
5129
- };
5130
- var maybeCollectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, options, extra = null) => {
5131
- if (!options.debugArtifacts) {
5132
- return;
5133
- }
5134
- await collectCaptchaDebugInfo(page, frame, iframeLocator, attempt, phase, extra);
5135
- };
5136
5081
  var extractCaptchaSerialNumbers = (apiResponse) => {
5137
5082
  const serialNumbers = apiResponse?.data?.data?.serial_number;
5138
5083
  if (!Array.isArray(serialNumbers)) {
@@ -5141,7 +5086,7 @@ var extractCaptchaSerialNumbers = (apiResponse) => {
5141
5086
  return serialNumbers.map((value) => Number(value)).filter((value) => Number.isInteger(value) && value >= 0);
5142
5087
  };
5143
5088
  var resolveContentFrame = async (page, iframeLocator, options) => {
5144
- for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt += 1) {
5089
+ for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt++) {
5145
5090
  const iframeHandle = await iframeLocator.elementHandle();
5146
5091
  const frame = await iframeHandle?.contentFrame();
5147
5092
  if (frame) {
@@ -5186,15 +5131,20 @@ var getVerifycenterCaptchaContext = async (page, options) => {
5186
5131
  }
5187
5132
  return { iframeLocator, frame };
5188
5133
  };
5189
- var findCaptchaDropTarget = async (frame, options) => {
5190
- const directTarget = frame.locator(options.dropTargetContainerSelector).first();
5191
- if (await waitForVisible(directTarget, options.actionVisibleTimeoutMs)) {
5192
- return directTarget;
5134
+ var refreshCaptcha = async (page, frame, options) => {
5135
+ const clicked = await clickCaptchaAction(frame, options.refreshTexts, { ...options, page }).catch(() => false);
5136
+ if (!clicked) {
5137
+ logger10.warn("Refresh button not found.");
5138
+ return false;
5193
5139
  }
5140
+ await page.waitForTimeout(options.refreshWaitMs);
5141
+ return true;
5142
+ };
5143
+ var findCaptchaDropTarget = async (frame, options) => {
5194
5144
  for (const text of options.dropTargetTexts) {
5195
5145
  const candidates = [
5196
- frame.getByText(text, { exact: false }).first(),
5197
- frame.locator(`text=${text}`).first()
5146
+ frame.locator(options.dropTargetContainerSelector).filter({ hasText: text }).first(),
5147
+ frame.getByText(text, { exact: false }).first()
5198
5148
  ];
5199
5149
  for (const candidate of candidates) {
5200
5150
  const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
@@ -5205,112 +5155,10 @@ var findCaptchaDropTarget = async (frame, options) => {
5205
5155
  }
5206
5156
  return null;
5207
5157
  };
5208
- var readPromptCaptchaState = async (frame, options) => frame.evaluate((selectors) => {
5209
- const toRect = (element) => {
5210
- if (!element) {
5211
- return null;
5212
- }
5213
- const rect = element.getBoundingClientRect();
5214
- return {
5215
- x: Number(rect.x.toFixed(2)),
5216
- y: Number(rect.y.toFixed(2)),
5217
- width: Number(rect.width.toFixed(2)),
5218
- height: Number(rect.height.toFixed(2))
5219
- };
5220
- };
5221
- const badgeNode = document.querySelector(selectors.badgeCountSelector);
5222
- const submitButton = document.querySelector(selectors.submitButtonSelector);
5223
- const dragArea = document.querySelector(selectors.dragAreaSelector);
5224
- const badgeCount = badgeNode ? Number.parseInt(String(badgeNode.textContent || "").trim(), 10) : 0;
5225
- return {
5226
- badgeCount: Number.isFinite(badgeCount) ? badgeCount : 0,
5227
- selectedCount: document.querySelectorAll(selectors.selectedSourceSelector).length,
5228
- activeCount: document.querySelectorAll(selectors.activeSourceSelector).length,
5229
- submitDisabled: submitButton ? submitButton.classList.contains("disable") : null,
5230
- dragAreaActive: dragArea ? dragArea.classList.contains("active") : false,
5231
- dragAreaRect: toRect(dragArea)
5232
- };
5233
- }, {
5234
- badgeCountSelector: options.promptBadgeCountSelector,
5235
- submitButtonSelector: options.promptSubmitButtonSelector,
5236
- selectedSourceSelector: options.promptSelectedSourceSelector,
5237
- activeSourceSelector: options.promptActiveSourceSelector,
5238
- dragAreaSelector: options.dropTargetContainerSelector
5239
- }).catch(() => ({
5240
- badgeCount: 0,
5241
- selectedCount: 0,
5242
- activeCount: 0,
5243
- submitDisabled: null,
5244
- dragAreaActive: false,
5245
- dragAreaRect: null
5246
- }));
5247
- var normalizeCaptchaImageIndexes = (serialNumbers, imageCount) => {
5248
- if (!Array.isArray(serialNumbers) || imageCount <= 0) {
5249
- return [];
5250
- }
5251
- const areAllOneBased = serialNumbers.every((value) => value >= 1 && value <= imageCount);
5252
- if (areAllOneBased) {
5253
- return serialNumbers.map((value) => value - 1);
5254
- }
5255
- const areAllZeroBased = serialNumbers.every((value) => value >= 0 && value < imageCount);
5256
- if (areAllZeroBased) {
5257
- return [...serialNumbers];
5258
- }
5259
- return serialNumbers.map((value) => {
5260
- if (value >= 1 && value <= imageCount) {
5261
- return value - 1;
5262
- }
5263
- if (value >= 0 && value < imageCount) {
5264
- return value;
5265
- }
5266
- return null;
5267
- }).filter((value) => Number.isInteger(value));
5268
- };
5269
- var resolveCaptchaSourceImagesInVisualOrder = async (frame, options) => {
5270
- const sourceImages = frame.locator(options.sourceImageSelector);
5271
- const imageCount = await sourceImages.count().catch(() => 0);
5272
- const sources = [];
5273
- for (let domIndex = 0; domIndex < imageCount; domIndex += 1) {
5274
- const locator = sourceImages.nth(domIndex);
5275
- const box = await locator.boundingBox().catch(() => null);
5276
- if (!box || box.width <= 0 || box.height <= 0) {
5277
- continue;
5278
- }
5279
- sources.push({
5280
- domIndex,
5281
- locator,
5282
- box
5283
- });
5284
- }
5285
- sources.sort((left, right) => {
5286
- const deltaY = left.box.y - right.box.y;
5287
- if (Math.abs(deltaY) > options.sourceImageRowTolerancePx) {
5288
- return deltaY;
5289
- }
5290
- return left.box.x - right.box.x;
5291
- });
5292
- return sources;
5293
- };
5294
- var refreshCaptcha = async (page, frame, options) => {
5295
- const clicked = await clickCaptchaAction(frame, options.refreshTexts, {
5296
- ...options,
5297
- page,
5298
- logger: logger10,
5299
- forceMouse: true
5300
- }).catch(() => false);
5301
- if (!clicked) {
5302
- logger10.warn("Refresh button not found.");
5303
- return false;
5304
- }
5305
- await page.waitForTimeout(options.refreshWaitMs);
5306
- return true;
5307
- };
5308
5158
  var waitForCaptchaChallengeReady = async (page, frame, options) => {
5309
5159
  const deadline = Date.now() + options.challengeReadyTimeoutMs;
5310
5160
  let refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
5311
5161
  let hasSeenLoading = false;
5312
- let hasSeenGuideMask = false;
5313
- let hasLoggedGuideMask = false;
5314
5162
  while (Date.now() < deadline) {
5315
5163
  const isLoadingVisible = await isAnyCaptchaTextVisible(
5316
5164
  frame,
@@ -5326,17 +5174,8 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
5326
5174
  const sourceImages = frame.locator(options.sourceImageSelector);
5327
5175
  const imageCount = await sourceImages.count().catch(() => 0);
5328
5176
  const hasVisibleSourceImage = imageCount > 0 ? await sourceImages.first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
5329
- const hasVisibleDropTarget = await frame.locator(options.dropTargetContainerSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false);
5330
- const hasGuideMaskVisible = options.guideMaskSelector ? await frame.locator(options.guideMaskSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
5331
- hasSeenGuideMask = hasSeenGuideMask || hasGuideMaskVisible;
5332
- if (hasGuideMaskVisible && !hasLoggedGuideMask) {
5333
- logger10.info("\u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801\u64CD\u4F5C\u5F15\u5BFC\u5C42\uFF0C\u7B49\u5F85\u5176\u6D88\u5931\u540E\u518D\u5F00\u59CB\u8BC6\u522B\u3002");
5334
- hasLoggedGuideMask = true;
5335
- }
5336
- if (!isLoadingVisible && hasVisibleSourceImage && hasVisibleDropTarget && !hasGuideMaskVisible) {
5337
- logger10.info(
5338
- hasSeenGuideMask ? "\u9A8C\u8BC1\u7801\u56FE\u7247\u548C\u62D6\u62FD\u533A\u57DF\u5DF2\u5C31\u7EEA\uFF0C\u5F15\u5BFC\u5C42\u5DF2\u6D88\u5931\u3002" : hasSeenLoading ? "\u9A8C\u8BC1\u7801\u56FE\u7247\u5DF2\u52A0\u8F7D\u5B8C\u6210\u3002" : "\u9A8C\u8BC1\u7801\u56FE\u7247\u5DF2\u5C31\u7EEA\u3002"
5339
- );
5177
+ if (!isLoadingVisible && hasVisibleSourceImage) {
5178
+ logger10.info(hasSeenLoading ? "\u9A8C\u8BC1\u7801\u56FE\u7247\u5DF2\u52A0\u8F7D\u5B8C\u6210\u3002" : "\u9A8C\u8BC1\u7801\u56FE\u7247\u5DF2\u5C31\u7EEA\u3002");
5340
5179
  return;
5341
5180
  }
5342
5181
  if (hasErrorTextVisible) {
@@ -5346,8 +5185,8 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
5346
5185
  hasSeenLoading = false;
5347
5186
  continue;
5348
5187
  }
5349
- if ((!hasVisibleSourceImage || !hasVisibleDropTarget) && Date.now() >= refreshDeadline) {
5350
- logger10.warn(`\u9A8C\u8BC1\u7801\u9898\u76EE\u8D85\u8FC7 ${options.challengeReadyRefreshTimeoutMs}ms \u4ECD\u672A\u51C6\u5907\u597D\uFF0C\u5C1D\u8BD5\u5237\u65B0\u9898\u76EE\u3002`);
5188
+ if (!hasVisibleSourceImage && Date.now() >= refreshDeadline) {
5189
+ logger10.warn(`\u9A8C\u8BC1\u7801\u56FE\u7247\u8D85\u8FC7 ${options.challengeReadyRefreshTimeoutMs}ms \u4ECD\u672A\u51FA\u73B0\uFF0C\u5C1D\u8BD5\u5237\u65B0\u9898\u76EE\u3002`);
5351
5190
  await refreshCaptcha(page, frame, options);
5352
5191
  refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
5353
5192
  hasSeenLoading = false;
@@ -5357,69 +5196,6 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
5357
5196
  }
5358
5197
  throw new Error("Captcha challenge is still loading and did not become ready in time.");
5359
5198
  };
5360
- var dragPromptCaptchaImage = async (page, frame, iframeLocator, sourceLocator, dropTarget, options, {
5361
- attempt,
5362
- visualIndex
5363
- }) => {
5364
- const baselineState = await readPromptCaptchaState(frame, options);
5365
- const dragAttempts = [];
5366
- for (const plan of PROMPT_CAPTCHA_DRAG_PLANS) {
5367
- const sourceBox = await sourceLocator.boundingBox().catch(() => null);
5368
- const targetBox = await dropTarget.boundingBox().catch(() => null);
5369
- if (!sourceBox || !targetBox) {
5370
- throw new Error("Unable to resolve prompt captcha drag coordinates.");
5371
- }
5372
- const targetOffsetX = (plan.endXRatio - 0.5) * targetBox.width;
5373
- const targetOffsetY = (plan.endYRatio - 0.5) * targetBox.height;
5374
- await dragCaptchaAction(page, sourceLocator, dropTarget, {
5375
- forceMouse: true,
5376
- targetOffsetX,
5377
- targetOffsetY,
5378
- steps: options.promptDragMoveSteps,
5379
- holdDelayMs: options.promptDragHoldDelayMs,
5380
- stepDelayMs: options.promptDragStepDelayMs,
5381
- beforeReleaseDelayMs: options.promptDragBeforeReleaseDelayMs,
5382
- afterReleaseDelayMs: options.promptDragAfterReleaseDelayMs,
5383
- finalMoveRepeats: options.promptDragFinalMoveRepeats
5384
- });
5385
- const afterState = await readPromptCaptchaState(frame, options);
5386
- const accepted = afterState.badgeCount > baselineState.badgeCount || afterState.selectedCount > baselineState.selectedCount;
5387
- const attemptInfo = {
5388
- planName: plan.name,
5389
- sourceRect: rectOf(sourceBox),
5390
- targetRect: rectOf(targetBox),
5391
- targetOffsetX: Number(targetOffsetX.toFixed(2)),
5392
- targetOffsetY: Number(targetOffsetY.toFixed(2)),
5393
- beforeState: baselineState,
5394
- afterState,
5395
- accepted
5396
- };
5397
- dragAttempts.push(attemptInfo);
5398
- logger10.info(
5399
- `\u9A8C\u8BC1\u7801\u62D6\u62FD\u7B2C ${visualIndex + 1} \u5F20\uFF0C\u65B9\u6848 ${plan.name}\uFF0Cbadge ${baselineState.badgeCount} -> ${afterState.badgeCount}\uFF0Cselected ${baselineState.selectedCount} -> ${afterState.selectedCount}`
5400
- );
5401
- if (accepted) {
5402
- return {
5403
- accepted: true,
5404
- dragAttempts
5405
- };
5406
- }
5407
- if (options.promptDragRetryDelayMs > 0) {
5408
- await page.waitForTimeout(options.promptDragRetryDelayMs);
5409
- }
5410
- }
5411
- await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, `drag-${visualIndex + 1}-failed`, options, {
5412
- visualIndex,
5413
- dragAttempts,
5414
- finalState: await readPromptCaptchaState(frame, options)
5415
- }).catch((error) => {
5416
- logger10.warn(`\u9A8C\u8BC1\u7801\u62D6\u62FD\u5931\u8D25\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
5417
- });
5418
- return {
5419
- accepted: false,
5420
- dragAttempts
5421
- };
5422
- };
5423
5199
  async function solveCaptcha(page, options = {}, dependencies = {}) {
5424
5200
  const { callCaptchaRecognitionApi: callCaptchaRecognitionApi2 } = dependencies;
5425
5201
  if (typeof callCaptchaRecognitionApi2 !== "function") {
@@ -5434,7 +5210,7 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
5434
5210
  return false;
5435
5211
  }
5436
5212
  logger10.info("\u5F53\u524D\u4F7F\u7528\u672Ctool\u2014\u2014\u6D4B\u8BD5\u7248\u672C");
5437
- for (let attempt = 1; attempt <= config.maxRetries; attempt += 1) {
5213
+ for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
5438
5214
  logger10.info(`\u5F00\u59CB\u7B2C ${attempt}/${config.maxRetries} \u6B21 verifycenter \u9A8C\u8BC1\u7801\u8BC6\u522B\u3002`);
5439
5215
  try {
5440
5216
  const captchaContext = await getVerifycenterCaptchaContext(page, config);
@@ -5444,16 +5220,6 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
5444
5220
  }
5445
5221
  const { iframeLocator, frame } = captchaContext;
5446
5222
  await waitForCaptchaChallengeReady(page, frame, config);
5447
- await maybeCollectCaptchaDebugInfo(
5448
- page,
5449
- frame,
5450
- iframeLocator,
5451
- attempt,
5452
- "ready",
5453
- config
5454
- ).catch((error) => {
5455
- logger10.warn(`\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
5456
- });
5457
5223
  await page.waitForTimeout(config.recognitionDelayMs);
5458
5224
  const screenshotBuffer = await iframeLocator.screenshot();
5459
5225
  const apiResponse = await callCaptchaRecognitionApi2({
@@ -5477,74 +5243,33 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
5477
5243
  await refreshCaptcha(page, frame, config);
5478
5244
  continue;
5479
5245
  }
5480
- const orderedSourceImages = await resolveCaptchaSourceImagesInVisualOrder(frame, config);
5481
- const normalizedIndexes = normalizeCaptchaImageIndexes(serialNumbers, orderedSourceImages.length);
5482
- if (normalizedIndexes.length !== serialNumbers.length) {
5483
- throw new Error(
5484
- `Captcha image indexes could not be normalized. raw=${serialNumbers.join(", ")}, count=${orderedSourceImages.length}`
5485
- );
5486
- }
5487
- logger10.info(`\u9A8C\u8BC1\u7801\u89C6\u89C9\u4F4D\u5E8F\u6620\u5C04\uFF1A${normalizedIndexes.map((index) => index + 1).join(", ")}`);
5488
- for (const imageIndex of normalizedIndexes) {
5489
- if (imageIndex < 0 || imageIndex >= orderedSourceImages.length) {
5490
- throw new Error(
5491
- `Captcha image index ${imageIndex} is out of range. count=${orderedSourceImages.length}`
5492
- );
5246
+ const sourceImages = frame.locator(config.sourceImageSelector);
5247
+ const imageCount = await sourceImages.count();
5248
+ for (const rawIndex of serialNumbers) {
5249
+ let imageIndex = rawIndex;
5250
+ if (imageIndex >= imageCount && imageIndex > 0 && imageIndex - 1 < imageCount) {
5251
+ imageIndex -= 1;
5493
5252
  }
5494
- const sourceImage = orderedSourceImages[imageIndex].locator;
5253
+ if (imageIndex < 0 || imageIndex >= imageCount) {
5254
+ throw new Error(`Captcha image index ${rawIndex} is out of range. count=${imageCount}`);
5255
+ }
5256
+ const sourceImage = sourceImages.nth(imageIndex);
5495
5257
  await sourceImage.waitFor({
5496
5258
  state: "visible",
5497
5259
  timeout: config.sourceImageVisibleTimeoutMs
5498
5260
  });
5499
- const dragResult = await dragPromptCaptchaImage(
5500
- page,
5501
- frame,
5502
- iframeLocator,
5503
- sourceImage,
5504
- dropTarget,
5505
- config,
5506
- {
5507
- attempt,
5508
- visualIndex: imageIndex
5509
- }
5510
- );
5511
- if (!dragResult.accepted) {
5512
- throw new Error(`Captcha prompt drag was not accepted for visual index ${imageIndex + 1}.`);
5513
- }
5514
- if (config.dragBetweenWaitMs > 0) {
5515
- await page.waitForTimeout(config.dragBetweenWaitMs);
5516
- }
5261
+ await dragCaptchaAction(page, sourceImage, dropTarget);
5517
5262
  }
5518
- const beforeSubmitState = await readPromptCaptchaState(frame, config);
5519
- logger10.info(
5520
- `\u63D0\u4EA4\u524D\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${beforeSubmitState.badgeCount}, selected=${beforeSubmitState.selectedCount}, submitDisabled=${beforeSubmitState.submitDisabled}`
5521
- );
5522
- const submitted = await clickCaptchaAction(frame, config.submitTexts, {
5523
- ...config,
5524
- page,
5525
- logger: logger10,
5526
- forceMouse: true,
5527
- actionVisibleTimeoutMs: config.submitReadyTimeoutMs
5528
- }).catch(() => false);
5263
+ const submitted = await clickCaptchaAction(frame, config.submitTexts, { ...config, page }).catch(() => false);
5529
5264
  if (!submitted) {
5530
5265
  logger10.warn("\u672A\u627E\u5230\u63D0\u4EA4\u6309\u94AE\uFF0C\u53EF\u80FD\u4F1A\u81EA\u52A8\u63D0\u4EA4\u3002");
5531
5266
  }
5532
5267
  await page.waitForTimeout(config.submitWaitMs);
5533
- const afterSubmitState = await readPromptCaptchaState(frame, config);
5534
- logger10.info(
5535
- `\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${afterSubmitState.badgeCount}, selected=${afterSubmitState.selectedCount}, submitDisabled=${afterSubmitState.submitDisabled}`
5536
- );
5537
5268
  const stillVisible = await iframeLocator.isVisible({ timeout: config.containerVisibleTimeoutMs }).catch(() => false);
5538
5269
  if (!stillVisible) {
5539
5270
  logger10.info("\u9A8C\u8BC1\u7801\u8BC6\u522B\u5E76\u63D0\u4EA4\u6210\u529F\u3002");
5540
5271
  return true;
5541
5272
  }
5542
- await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, "submit-still-visible", config, {
5543
- beforeSubmitState,
5544
- afterSubmitState
5545
- }).catch((error) => {
5546
- logger10.warn(`\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
5547
- });
5548
5273
  logger10.warn("\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801 iframe \u4ECD\u7136\u53EF\u89C1\uFF0C\u51C6\u5907\u5237\u65B0\u540E\u91CD\u8BD5\u3002");
5549
5274
  await page.waitForTimeout(2e3);
5550
5275
  await refreshCaptcha(page, frame, config);
@@ -5987,14 +5712,14 @@ var Mutation = {
5987
5712
  const isFrameElement = tagName === "IFRAME" || tagName === "FRAME";
5988
5713
  const nodeName = descriptor?.id || descriptor?.name || "no-id";
5989
5714
  let source = "main";
5990
- let path3 = `${selector}[${index}]`;
5715
+ let path2 = `${selector}[${index}]`;
5991
5716
  let text = "";
5992
5717
  let html = "";
5993
5718
  let frameUrl = "";
5994
5719
  let readyState = "";
5995
5720
  if (isFrameElement) {
5996
5721
  source = "iframe";
5997
- path3 = `${selector}[${index}]::iframe(${nodeName})`;
5722
+ path2 = `${selector}[${index}]::iframe(${nodeName})`;
5998
5723
  const frame = await handle.contentFrame();
5999
5724
  if (frame) {
6000
5725
  try {
@@ -6024,7 +5749,7 @@ var Mutation = {
6024
5749
  items.push({
6025
5750
  selector,
6026
5751
  source,
6027
- path: path3,
5752
+ path: path2,
6028
5753
  text,
6029
5754
  html,
6030
5755
  frameUrl,