@skrillex1224/playwright-toolkit 2.1.247 → 2.1.249

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: path3 }) => {
136
+ const buildLandingUrl = ({ protocol: protocol2, domain: domain2, path: path4 }) => {
137
137
  const safeProtocol = String(protocol2).trim();
138
138
  const safeDomain = normalizeDomain(domain2);
139
- const safePath = normalizePath(path3);
139
+ const safePath = normalizePath(path4);
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 path2 = normalizePath(info.path);
147
+ const path3 = 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: path2,
154
+ path: path3,
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 path2 = String(raw.path || "").trim() || "/";
1515
+ const path3 = 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: path2,
1524
+ path: path3,
1525
1525
  ...domain ? { domain } : {},
1526
1526
  ...!domain && url ? { url } : {},
1527
1527
  ...sameSite ? { sameSite } : {},
@@ -2592,6 +2592,10 @@ 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
+ };
2595
2599
  var dispatchMouseMove = (page, point, options = {}) => page.mouse.move(point.x, point.y, options);
2596
2600
  var dispatchMouseStart = (page, options = {}) => page.mouse.down(options);
2597
2601
  var dispatchMouseEnd = (page, options = {}) => page.mouse.up(options);
@@ -2611,6 +2615,11 @@ var dragWithMouse = async (page, points, options = {}) => {
2611
2615
  }, { steps: 2 });
2612
2616
  await waitFor(page, options.stepDelayMs ?? 90);
2613
2617
  }
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
+ }
2614
2623
  await waitFor(page, options.beforeReleaseDelayMs ?? 100);
2615
2624
  await dispatchMouseEnd(page);
2616
2625
  await waitFor(page, options.afterReleaseDelayMs ?? 100);
@@ -2620,6 +2629,7 @@ var dragWithTouch = async (page, points, options = {}) => {
2620
2629
  let client = null;
2621
2630
  try {
2622
2631
  client = await page.context().newCDPSession(page);
2632
+ await waitFor(page, options.initialDelayMs ?? 250);
2623
2633
  await client.send("Input.dispatchTouchEvent", {
2624
2634
  type: "touchStart",
2625
2635
  touchPoints: [{ x: points.start.x, y: points.start.y, id: 1 }]
@@ -2641,6 +2651,14 @@ var dragWithTouch = async (page, points, options = {}) => {
2641
2651
  });
2642
2652
  await waitFor(page, options.stepDelayMs ?? 90);
2643
2653
  }
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
+ }
2644
2662
  await waitFor(page, options.beforeReleaseDelayMs ?? 100);
2645
2663
  await client.send("Input.dispatchTouchEvent", {
2646
2664
  type: "touchEnd",
@@ -2658,7 +2676,7 @@ var dragWithTouch = async (page, points, options = {}) => {
2658
2676
  var clickTargetWithDevice = async (page, target, options = {}) => {
2659
2677
  const normalizedOptions = normalizeSelectorOptions(options);
2660
2678
  const resolvedDevice = resolveDeviceFromPage(page);
2661
- if (target && resolvedDevice === Device.Mobile && !normalizedOptions.forceClick) {
2679
+ if (target && resolvedDevice === Device.Mobile && !normalizedOptions.forceClick && !normalizedOptions.forceMouse) {
2662
2680
  if (typeof target.tap === "function") {
2663
2681
  await target.tap(normalizedOptions.tapOptions);
2664
2682
  return true;
@@ -2858,20 +2876,26 @@ var DeviceInput = {
2858
2876
  throw new Error("Unable to resolve drag coordinates.");
2859
2877
  }
2860
2878
  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;
2861
2885
  const liftOffsetX = Math.min(18, Math.max(8, sourceBox.width * 0.12));
2862
2886
  const liftOffsetY = Math.min(12, Math.max(4, sourceBox.height * 0.08));
2863
2887
  const points = {
2864
2888
  start: {
2865
- x: sourceBox.x + sourceBox.width / 2,
2866
- y: sourceBox.y + sourceBox.height / 2
2889
+ x: sourceCenterX,
2890
+ y: sourceCenterY
2867
2891
  },
2868
2892
  lift: {
2869
- x: sourceBox.x + sourceBox.width / 2 + liftOffsetX,
2870
- y: sourceBox.y + sourceBox.height / 2 + liftOffsetY
2893
+ x: sourceCenterX + liftOffsetX,
2894
+ y: sourceCenterY + liftOffsetY
2871
2895
  },
2872
2896
  end: {
2873
- x: targetBox.x + targetBox.width / 2,
2874
- y: targetBox.y + targetBox.height / 2
2897
+ x: targetBox.x + targetBox.width / 2 + targetOffsetX,
2898
+ y: targetBox.y + targetBox.height / 2 + targetOffsetY
2875
2899
  },
2876
2900
  steps
2877
2901
  };
@@ -3407,13 +3431,6 @@ var jitterMs = (base, jitterPercent = 0.3) => {
3407
3431
  const jitter = Number(base || 0) * Number(jitterPercent || 0) * (Math.random() * 2 - 1);
3408
3432
  return Math.max(10, Math.round(Number(base || 0) + jitter));
3409
3433
  };
3410
- var randomPointInBox = (box, paddingRatio = 0.24) => {
3411
- const safePaddingX = Math.max(2, Math.min(box.width * paddingRatio, box.width / 2));
3412
- const safePaddingY = Math.max(2, Math.min(box.height * paddingRatio, box.height / 2));
3413
- const x = box.x + safePaddingX + Math.random() * Math.max(1, box.width - safePaddingX * 2);
3414
- const y = box.y + safePaddingY + Math.random() * Math.max(1, box.height - safePaddingY * 2);
3415
- return { x, y };
3416
- };
3417
3434
  var resolveElement = async (page, target, { throwOnMissing = true } = {}) => {
3418
3435
  if (target == null) return null;
3419
3436
  let element = target;
@@ -3458,6 +3475,10 @@ var clipBoxToViewport = (box, viewport) => {
3458
3475
  }
3459
3476
  return box;
3460
3477
  };
3478
+ var centerPointInBox = (box) => ({
3479
+ x: box.x + box.width / 2,
3480
+ y: box.y + box.height / 2
3481
+ });
3461
3482
  var withTimeout = async (operation, timeoutMs, label) => {
3462
3483
  const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
3463
3484
  let timeoutId = null;
@@ -3609,97 +3630,6 @@ var checkElementVisibility = async (element) => {
3609
3630
  return { code: "VISIBLE", isFixed, positioning };
3610
3631
  });
3611
3632
  };
3612
- var resolveSafeTapPoint = async (element) => {
3613
- return element.evaluate((el) => {
3614
- const rect = el.getBoundingClientRect();
3615
- if (!rect || rect.width <= 0 || rect.height <= 0) {
3616
- return null;
3617
- }
3618
- const viewW = window.innerWidth;
3619
- const viewH = window.innerHeight;
3620
- let clipLeft = 0;
3621
- let clipRight = viewW;
3622
- let clipTop = 0;
3623
- let clipBottom = viewH;
3624
- for (let node = el.parentElement; node && node !== document.body; node = node.parentElement) {
3625
- const style = window.getComputedStyle(node);
3626
- if (!style) continue;
3627
- const clipsX = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowX);
3628
- const clipsY = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowY);
3629
- if (!clipsX && !clipsY) continue;
3630
- const nodeRect = node.getBoundingClientRect();
3631
- if (!nodeRect || nodeRect.width <= 0 || nodeRect.height <= 0) continue;
3632
- if (clipsX) {
3633
- clipLeft = Math.max(clipLeft, nodeRect.left);
3634
- clipRight = Math.min(clipRight, nodeRect.right);
3635
- }
3636
- if (clipsY) {
3637
- clipTop = Math.max(clipTop, nodeRect.top);
3638
- clipBottom = Math.min(clipBottom, nodeRect.bottom);
3639
- }
3640
- }
3641
- const visibleLeft = Math.max(clipLeft, Math.min(clipRight, rect.left));
3642
- const visibleRight = Math.max(clipLeft, Math.min(clipRight, rect.right));
3643
- const visibleTop = Math.max(clipTop, Math.min(clipBottom, rect.top));
3644
- const visibleBottom = Math.max(clipTop, Math.min(clipBottom, rect.bottom));
3645
- const visibleWidth = visibleRight - visibleLeft;
3646
- const visibleHeight = visibleBottom - visibleTop;
3647
- if (visibleWidth <= 1 || visibleHeight <= 1) {
3648
- return null;
3649
- }
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) => {
3658
- if (!pointElement) return false;
3659
- if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
3660
- return true;
3661
- }
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)) {
3671
- return false;
3672
- }
3673
- return common.contains(el) && common.contains(pointElement);
3674
- };
3675
- const cx = visibleLeft + visibleWidth / 2;
3676
- const cy = visibleTop + visibleHeight / 2;
3677
- const smallX = Math.min(12, Math.max(2, visibleWidth * 0.18));
3678
- const smallY = Math.min(12, Math.max(2, visibleHeight * 0.18));
3679
- const points = [
3680
- { x: cx, y: cy },
3681
- { x: visibleLeft + smallX, y: visibleTop + smallY },
3682
- { x: visibleRight - smallX, y: visibleTop + smallY },
3683
- { x: visibleLeft + smallX, y: visibleBottom - smallY },
3684
- { x: visibleRight - smallX, y: visibleBottom - smallY },
3685
- { x: cx, y: visibleTop + Math.min(10, Math.max(2, visibleHeight * 0.2)) },
3686
- { x: cx, y: visibleBottom - Math.min(10, Math.max(2, visibleHeight * 0.2)) },
3687
- { x: visibleLeft + Math.min(10, Math.max(2, visibleWidth * 0.2)), y: cy },
3688
- { x: visibleRight - Math.min(10, Math.max(2, visibleWidth * 0.2)), y: cy }
3689
- ];
3690
- const safePoints = points.filter((point) => {
3691
- const pointElement = document.elementFromPoint(point.x, point.y);
3692
- return sameTapTarget(pointElement);
3693
- });
3694
- const candidates = safePoints.length ? safePoints : points;
3695
- const chosen = candidates[Math.floor(Math.random() * candidates.length)];
3696
- if (!chosen) return null;
3697
- return {
3698
- x: chosen.x,
3699
- y: chosen.y
3700
- };
3701
- });
3702
- };
3703
3633
  var activateElementFallback = async (element, point = null, options = {}) => {
3704
3634
  return element.evaluate((el, { innerOptions }) => {
3705
3635
  const isEditable = (node) => {
@@ -4284,8 +4214,8 @@ var MobileHumanize = {
4284
4214
  return false;
4285
4215
  }
4286
4216
  await waitJitter(reactionDelay, 0.45);
4287
- const safePoint = await resolveSafeTapPoint(element).catch(() => null);
4288
- const tapTarget = safePoint || randomPointInBox(clipBoxToViewport(box, resolveViewport(page)), 0.2);
4217
+ const visibleBox = clipBoxToViewport(box, resolveViewport(page));
4218
+ const tapTarget = centerPointInBox(visibleBox);
4289
4219
  try {
4290
4220
  await tapPoint(page, tapTarget, {
4291
4221
  timeoutMs: tapTimeoutMs,
@@ -4988,6 +4918,10 @@ var LiveView = {
4988
4918
  // src/chaptcha.js
4989
4919
  var import_uuid = require("uuid");
4990
4920
 
4921
+ // src/internals/captcha/bytedance.js
4922
+ var import_promises = require("fs/promises");
4923
+ var import_path2 = __toESM(require("path"), 1);
4924
+
4991
4925
  // src/internals/captcha/shared.js
4992
4926
  var waitForVisible = async (locator, timeout) => {
4993
4927
  try {
@@ -5005,38 +4939,71 @@ var isAnyCaptchaTextVisible = async (frame, texts, timeout) => {
5005
4939
  if (!text) {
5006
4940
  continue;
5007
4941
  }
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;
4942
+ const textLocator = frame.getByText(text, { exact: false });
4943
+ const locatorText = frame.locator(`text=${text}`);
4944
+ const candidateGroups = [textLocator, locatorText];
4945
+ for (const candidateGroup of candidateGroups) {
4946
+ const candidateCount = await candidateGroup.count().catch(() => 0);
4947
+ for (let index = 0; index < candidateCount; index += 1) {
4948
+ const candidate = candidateGroup.nth(index);
4949
+ const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
4950
+ if (isVisible) {
4951
+ return true;
4952
+ }
5016
4953
  }
5017
4954
  }
5018
4955
  }
5019
4956
  return false;
5020
4957
  };
4958
+ var collectVisibleCandidateIndexes = async (candidateGroup, count, timeout) => {
4959
+ const visibleIndexes = [];
4960
+ for (let index = 0; index < count; index += 1) {
4961
+ const candidate = candidateGroup.nth(index);
4962
+ const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
4963
+ if (isVisible) {
4964
+ visibleIndexes.push(index);
4965
+ }
4966
+ }
4967
+ return visibleIndexes;
4968
+ };
5021
4969
  var clickCaptchaAction = async (frame, texts, options) => {
5022
4970
  for (const text of texts || []) {
5023
- const candidates = [
5024
- frame.getByText(text, { exact: false }).first(),
5025
- frame.locator(`text=${text}`).first()
4971
+ const textLocator = frame.getByText(text, { exact: false });
4972
+ const locatorText = frame.locator(`text=${text}`);
4973
+ const [getByTextCount, locatorTextCount] = await Promise.all([
4974
+ textLocator.count().catch(() => 0),
4975
+ locatorText.count().catch(() => 0)
4976
+ ]);
4977
+ const [getByTextVisibleIndexes, locatorTextVisibleIndexes] = await Promise.all([
4978
+ collectVisibleCandidateIndexes(textLocator, getByTextCount, options.actionVisibleTimeoutMs),
4979
+ collectVisibleCandidateIndexes(locatorText, locatorTextCount, options.actionVisibleTimeoutMs)
4980
+ ]);
4981
+ options.logger?.info(
4982
+ `[CaptchaAction] \u6587\u672C "${text}" \u547D\u4E2D\u6570\u91CF\uFF1AgetByText=${getByTextCount} (visible=${getByTextVisibleIndexes.length}), locator=${locatorTextCount} (visible=${locatorTextVisibleIndexes.length})`
4983
+ );
4984
+ const candidateGroups = [
4985
+ { label: "getByText", locator: textLocator, count: getByTextCount },
4986
+ { label: "locator", locator: locatorText, count: locatorTextCount }
5026
4987
  ];
5027
- for (const candidate of candidates) {
5028
- const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
5029
- if (!isVisible) {
5030
- continue;
4988
+ for (const candidateGroup of candidateGroups) {
4989
+ for (let index = 0; index < candidateGroup.count; index += 1) {
4990
+ const candidate = candidateGroup.locator.nth(index);
4991
+ const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
4992
+ if (!isVisible) {
4993
+ continue;
4994
+ }
4995
+ options.logger?.info(
4996
+ `[CaptchaAction] \u6587\u672C "${text}" \u9009\u62E9 ${candidateGroup.label}[${index}] \u4F5C\u4E3A\u7B2C\u4E00\u4E2A\u53EF\u89C1\u8282\u70B9\u6267\u884C\u70B9\u51FB\u3002`
4997
+ );
4998
+ await DeviceInput.click(options.page, candidate, options);
4999
+ return true;
5031
5000
  }
5032
- await DeviceInput.click(options.page, candidate);
5033
- return true;
5034
5001
  }
5035
5002
  }
5036
5003
  return false;
5037
5004
  };
5038
- var dragCaptchaAction = async (page, sourceLocator, targetLocator) => {
5039
- await DeviceInput.drag(page, sourceLocator, targetLocator);
5005
+ var dragCaptchaAction = async (page, sourceLocator, targetLocator, options = {}) => {
5006
+ await DeviceInput.drag(page, sourceLocator, targetLocator, options);
5040
5007
  };
5041
5008
 
5042
5009
  // src/internals/captcha/bytedance.js
@@ -5047,11 +5014,12 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
5047
5014
  containerSelector: "#captcha_container",
5048
5015
  iframeSelector: 'iframe[src*="verifycenter"]',
5049
5016
  iframeFallbackSelector: "iframe",
5050
- sourceImageSelector: "div.canvas-container",
5051
- dropTargetContainerSelector: "#captcha_verify_image div",
5017
+ sourceImageSelector: ".img-container .canvas-container",
5018
+ dropTargetContainerSelector: ".drag-area",
5052
5019
  dropTargetTexts: ["\u62D6\u62FD\u5230\u8FD9\u91CC"],
5053
5020
  refreshTexts: ["\u5237\u65B0"],
5054
5021
  submitTexts: ["\u63D0\u4EA4"],
5022
+ guideMaskSelector: ".play-guide-mask",
5055
5023
  recognitionSuccessCode: 1e4,
5056
5024
  containerVisibleTimeoutMs: 2e3,
5057
5025
  iframeVisibleTimeoutMs: 12e3,
@@ -5074,10 +5042,111 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
5074
5042
  ],
5075
5043
  recognitionDelayMs: 2e3,
5076
5044
  refreshWaitMs: 3e3,
5077
- submitWaitMs: 3e3,
5045
+ submitWaitMs: 5e3,
5046
+ submitReadyTimeoutMs: 2500,
5078
5047
  retryDelayBaseMs: 2e3,
5079
- retryDelayStepMs: 1e3
5048
+ retryDelayStepMs: 1e3,
5049
+ sourceImageRowTolerancePx: 24,
5050
+ dragBetweenWaitMs: 250,
5051
+ promptBadgeCountSelector: ".drag-area .photo-badge .badge span",
5052
+ promptSubmitButtonSelector: ".vc-captcha-verify-mobile-button",
5053
+ promptSelectedSourceSelector: ".img-container .canvas-container.selected",
5054
+ promptActiveSourceSelector: ".img-container .canvas-container.active",
5055
+ promptDragMoveSteps: 16,
5056
+ promptDragStepDelayMs: 55,
5057
+ promptDragHoldDelayMs: 240,
5058
+ promptDragBeforeReleaseDelayMs: 180,
5059
+ promptDragAfterReleaseDelayMs: 240,
5060
+ promptDragFinalMoveRepeats: 3,
5061
+ promptDragRetryDelayMs: 250,
5062
+ debugArtifacts: false
5080
5063
  });
5064
+ var PROMPT_CAPTCHA_DRAG_PLANS = Object.freeze([
5065
+ { name: "lower-middle", endXRatio: 0.5, endYRatio: 0.72 },
5066
+ { name: "center-middle", endXRatio: 0.5, endYRatio: 0.56 },
5067
+ { name: "upper-middle", endXRatio: 0.5, endYRatio: 0.4 }
5068
+ ]);
5069
+ var resolveCaptchaDebugDir = () => import_path2.default.resolve(process.cwd(), "storage", "captcha-debug");
5070
+ var rectOf = (rect) => {
5071
+ if (!rect) {
5072
+ return null;
5073
+ }
5074
+ return {
5075
+ x: Number(rect.x.toFixed(2)),
5076
+ y: Number(rect.y.toFixed(2)),
5077
+ width: Number(rect.width.toFixed(2)),
5078
+ height: Number(rect.height.toFixed(2))
5079
+ };
5080
+ };
5081
+ var collectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, extra = null) => {
5082
+ const timestamp = Date.now();
5083
+ const debugDir = resolveCaptchaDebugDir();
5084
+ await (0, import_promises.mkdir)(debugDir, { recursive: true });
5085
+ const baseName = `bytedance-${timestamp}-attempt${attempt}-${phase}`;
5086
+ const iframeScreenshotPath = import_path2.default.join(debugDir, `${baseName}-iframe.png`);
5087
+ const pageScreenshotPath = import_path2.default.join(debugDir, `${baseName}-page.png`);
5088
+ const htmlPath = import_path2.default.join(debugDir, `${baseName}-iframe.html`);
5089
+ const infoPath = import_path2.default.join(debugDir, `${baseName}-info.json`);
5090
+ await iframeLocator.screenshot({ path: iframeScreenshotPath }).catch(() => {
5091
+ });
5092
+ await page.screenshot({ path: pageScreenshotPath, fullPage: true }).catch(() => {
5093
+ });
5094
+ const html = await frame.evaluate(() => document.documentElement.outerHTML).catch(() => "");
5095
+ if (html) {
5096
+ await (0, import_promises.writeFile)(htmlPath, html, "utf8");
5097
+ }
5098
+ const info = await frame.evaluate(() => {
5099
+ const toRect = (element) => {
5100
+ const rect = element.getBoundingClientRect();
5101
+ return {
5102
+ x: Number(rect.x.toFixed(2)),
5103
+ y: Number(rect.y.toFixed(2)),
5104
+ width: Number(rect.width.toFixed(2)),
5105
+ height: Number(rect.height.toFixed(2))
5106
+ };
5107
+ };
5108
+ const toItem = (element, index) => ({
5109
+ index,
5110
+ tag: element.tagName,
5111
+ id: element.id || "",
5112
+ className: typeof element.className === "string" ? element.className : "",
5113
+ text: String(element.textContent || "").trim(),
5114
+ rect: toRect(element)
5115
+ });
5116
+ const visibleNodes = Array.from(document.querySelectorAll("body *")).map(toItem).filter((item) => item.rect.width > 0 && item.rect.height > 0);
5117
+ return {
5118
+ title: document.title,
5119
+ bodyText: String(document.body?.innerText || "").trim(),
5120
+ canvasContainers: visibleNodes.filter((item) => item.className.includes("canvas-container")),
5121
+ canvasNodes: visibleNodes.filter((item) => item.tag === "CANVAS"),
5122
+ captchaNodes: visibleNodes.filter((item) => item.id.startsWith("captcha_") || item.className.includes("captcha") || item.className.includes("verify") || /拖拽到这里|刷新|提交/.test(item.text)),
5123
+ visibleTextNodes: visibleNodes.filter((item) => item.text).slice(0, 300)
5124
+ };
5125
+ }).catch(() => null);
5126
+ if (info) {
5127
+ const payload = {
5128
+ capturedAt: new Date(timestamp).toISOString(),
5129
+ pageUrl: page.url(),
5130
+ attempt,
5131
+ phase,
5132
+ iframeScreenshotPath,
5133
+ pageScreenshotPath,
5134
+ htmlPath,
5135
+ info
5136
+ };
5137
+ if (extra != null) {
5138
+ payload.extra = extra;
5139
+ }
5140
+ await (0, import_promises.writeFile)(infoPath, JSON.stringify(payload, null, 2), "utf8");
5141
+ }
5142
+ logger10.info(`\u5DF2\u5199\u51FA\u9A8C\u8BC1\u7801\u8C03\u8BD5\u4EA7\u7269\uFF1A${debugDir}`);
5143
+ };
5144
+ var maybeCollectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, options, extra = null) => {
5145
+ if (!options.debugArtifacts) {
5146
+ return;
5147
+ }
5148
+ await collectCaptchaDebugInfo(page, frame, iframeLocator, attempt, phase, extra);
5149
+ };
5081
5150
  var extractCaptchaSerialNumbers = (apiResponse) => {
5082
5151
  const serialNumbers = apiResponse?.data?.data?.serial_number;
5083
5152
  if (!Array.isArray(serialNumbers)) {
@@ -5086,7 +5155,7 @@ var extractCaptchaSerialNumbers = (apiResponse) => {
5086
5155
  return serialNumbers.map((value) => Number(value)).filter((value) => Number.isInteger(value) && value >= 0);
5087
5156
  };
5088
5157
  var resolveContentFrame = async (page, iframeLocator, options) => {
5089
- for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt++) {
5158
+ for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt += 1) {
5090
5159
  const iframeHandle = await iframeLocator.elementHandle();
5091
5160
  const frame = await iframeHandle?.contentFrame();
5092
5161
  if (frame) {
@@ -5131,20 +5200,15 @@ var getVerifycenterCaptchaContext = async (page, options) => {
5131
5200
  }
5132
5201
  return { iframeLocator, frame };
5133
5202
  };
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;
5139
- }
5140
- await page.waitForTimeout(options.refreshWaitMs);
5141
- return true;
5142
- };
5143
5203
  var findCaptchaDropTarget = async (frame, options) => {
5204
+ const directTarget = frame.locator(options.dropTargetContainerSelector).first();
5205
+ if (await waitForVisible(directTarget, options.actionVisibleTimeoutMs)) {
5206
+ return directTarget;
5207
+ }
5144
5208
  for (const text of options.dropTargetTexts) {
5145
5209
  const candidates = [
5146
- frame.locator(options.dropTargetContainerSelector).filter({ hasText: text }).first(),
5147
- frame.getByText(text, { exact: false }).first()
5210
+ frame.getByText(text, { exact: false }).first(),
5211
+ frame.locator(`text=${text}`).first()
5148
5212
  ];
5149
5213
  for (const candidate of candidates) {
5150
5214
  const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
@@ -5155,10 +5219,112 @@ var findCaptchaDropTarget = async (frame, options) => {
5155
5219
  }
5156
5220
  return null;
5157
5221
  };
5222
+ var readPromptCaptchaState = async (frame, options) => frame.evaluate((selectors) => {
5223
+ const toRect = (element) => {
5224
+ if (!element) {
5225
+ return null;
5226
+ }
5227
+ const rect = element.getBoundingClientRect();
5228
+ return {
5229
+ x: Number(rect.x.toFixed(2)),
5230
+ y: Number(rect.y.toFixed(2)),
5231
+ width: Number(rect.width.toFixed(2)),
5232
+ height: Number(rect.height.toFixed(2))
5233
+ };
5234
+ };
5235
+ const badgeNode = document.querySelector(selectors.badgeCountSelector);
5236
+ const submitButton = document.querySelector(selectors.submitButtonSelector);
5237
+ const dragArea = document.querySelector(selectors.dragAreaSelector);
5238
+ const badgeCount = badgeNode ? Number.parseInt(String(badgeNode.textContent || "").trim(), 10) : 0;
5239
+ return {
5240
+ badgeCount: Number.isFinite(badgeCount) ? badgeCount : 0,
5241
+ selectedCount: document.querySelectorAll(selectors.selectedSourceSelector).length,
5242
+ activeCount: document.querySelectorAll(selectors.activeSourceSelector).length,
5243
+ submitDisabled: submitButton ? submitButton.classList.contains("disable") : null,
5244
+ dragAreaActive: dragArea ? dragArea.classList.contains("active") : false,
5245
+ dragAreaRect: toRect(dragArea)
5246
+ };
5247
+ }, {
5248
+ badgeCountSelector: options.promptBadgeCountSelector,
5249
+ submitButtonSelector: options.promptSubmitButtonSelector,
5250
+ selectedSourceSelector: options.promptSelectedSourceSelector,
5251
+ activeSourceSelector: options.promptActiveSourceSelector,
5252
+ dragAreaSelector: options.dropTargetContainerSelector
5253
+ }).catch(() => ({
5254
+ badgeCount: 0,
5255
+ selectedCount: 0,
5256
+ activeCount: 0,
5257
+ submitDisabled: null,
5258
+ dragAreaActive: false,
5259
+ dragAreaRect: null
5260
+ }));
5261
+ var normalizeCaptchaImageIndexes = (serialNumbers, imageCount) => {
5262
+ if (!Array.isArray(serialNumbers) || imageCount <= 0) {
5263
+ return [];
5264
+ }
5265
+ const areAllOneBased = serialNumbers.every((value) => value >= 1 && value <= imageCount);
5266
+ if (areAllOneBased) {
5267
+ return serialNumbers.map((value) => value - 1);
5268
+ }
5269
+ const areAllZeroBased = serialNumbers.every((value) => value >= 0 && value < imageCount);
5270
+ if (areAllZeroBased) {
5271
+ return [...serialNumbers];
5272
+ }
5273
+ return serialNumbers.map((value) => {
5274
+ if (value >= 1 && value <= imageCount) {
5275
+ return value - 1;
5276
+ }
5277
+ if (value >= 0 && value < imageCount) {
5278
+ return value;
5279
+ }
5280
+ return null;
5281
+ }).filter((value) => Number.isInteger(value));
5282
+ };
5283
+ var resolveCaptchaSourceImagesInVisualOrder = async (frame, options) => {
5284
+ const sourceImages = frame.locator(options.sourceImageSelector);
5285
+ const imageCount = await sourceImages.count().catch(() => 0);
5286
+ const sources = [];
5287
+ for (let domIndex = 0; domIndex < imageCount; domIndex += 1) {
5288
+ const locator = sourceImages.nth(domIndex);
5289
+ const box = await locator.boundingBox().catch(() => null);
5290
+ if (!box || box.width <= 0 || box.height <= 0) {
5291
+ continue;
5292
+ }
5293
+ sources.push({
5294
+ domIndex,
5295
+ locator,
5296
+ box
5297
+ });
5298
+ }
5299
+ sources.sort((left, right) => {
5300
+ const deltaY = left.box.y - right.box.y;
5301
+ if (Math.abs(deltaY) > options.sourceImageRowTolerancePx) {
5302
+ return deltaY;
5303
+ }
5304
+ return left.box.x - right.box.x;
5305
+ });
5306
+ return sources;
5307
+ };
5308
+ var refreshCaptcha = async (page, frame, options) => {
5309
+ const clicked = await clickCaptchaAction(frame, options.refreshTexts, {
5310
+ ...options,
5311
+ page,
5312
+ logger: logger10,
5313
+ forceMouse: true
5314
+ }).catch(() => false);
5315
+ if (!clicked) {
5316
+ logger10.warn("Refresh button not found.");
5317
+ return false;
5318
+ }
5319
+ await page.waitForTimeout(options.refreshWaitMs);
5320
+ return true;
5321
+ };
5158
5322
  var waitForCaptchaChallengeReady = async (page, frame, options) => {
5159
5323
  const deadline = Date.now() + options.challengeReadyTimeoutMs;
5160
5324
  let refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
5161
5325
  let hasSeenLoading = false;
5326
+ let hasSeenGuideMask = false;
5327
+ let hasLoggedGuideMask = false;
5162
5328
  while (Date.now() < deadline) {
5163
5329
  const isLoadingVisible = await isAnyCaptchaTextVisible(
5164
5330
  frame,
@@ -5174,8 +5340,17 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
5174
5340
  const sourceImages = frame.locator(options.sourceImageSelector);
5175
5341
  const imageCount = await sourceImages.count().catch(() => 0);
5176
5342
  const hasVisibleSourceImage = imageCount > 0 ? await sourceImages.first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
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");
5343
+ const hasVisibleDropTarget = await frame.locator(options.dropTargetContainerSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false);
5344
+ const hasGuideMaskVisible = options.guideMaskSelector ? await frame.locator(options.guideMaskSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
5345
+ hasSeenGuideMask = hasSeenGuideMask || hasGuideMaskVisible;
5346
+ if (hasGuideMaskVisible && !hasLoggedGuideMask) {
5347
+ 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");
5348
+ hasLoggedGuideMask = true;
5349
+ }
5350
+ if (!isLoadingVisible && hasVisibleSourceImage && hasVisibleDropTarget && !hasGuideMaskVisible) {
5351
+ logger10.info(
5352
+ 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"
5353
+ );
5179
5354
  return;
5180
5355
  }
5181
5356
  if (hasErrorTextVisible) {
@@ -5185,8 +5360,8 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
5185
5360
  hasSeenLoading = false;
5186
5361
  continue;
5187
5362
  }
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`);
5363
+ if ((!hasVisibleSourceImage || !hasVisibleDropTarget) && Date.now() >= refreshDeadline) {
5364
+ 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`);
5190
5365
  await refreshCaptcha(page, frame, options);
5191
5366
  refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
5192
5367
  hasSeenLoading = false;
@@ -5196,6 +5371,69 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
5196
5371
  }
5197
5372
  throw new Error("Captcha challenge is still loading and did not become ready in time.");
5198
5373
  };
5374
+ var dragPromptCaptchaImage = async (page, frame, iframeLocator, sourceLocator, dropTarget, options, {
5375
+ attempt,
5376
+ visualIndex
5377
+ }) => {
5378
+ const baselineState = await readPromptCaptchaState(frame, options);
5379
+ const dragAttempts = [];
5380
+ for (const plan of PROMPT_CAPTCHA_DRAG_PLANS) {
5381
+ const sourceBox = await sourceLocator.boundingBox().catch(() => null);
5382
+ const targetBox = await dropTarget.boundingBox().catch(() => null);
5383
+ if (!sourceBox || !targetBox) {
5384
+ throw new Error("Unable to resolve prompt captcha drag coordinates.");
5385
+ }
5386
+ const targetOffsetX = (plan.endXRatio - 0.5) * targetBox.width;
5387
+ const targetOffsetY = (plan.endYRatio - 0.5) * targetBox.height;
5388
+ await dragCaptchaAction(page, sourceLocator, dropTarget, {
5389
+ forceMouse: true,
5390
+ targetOffsetX,
5391
+ targetOffsetY,
5392
+ steps: options.promptDragMoveSteps,
5393
+ holdDelayMs: options.promptDragHoldDelayMs,
5394
+ stepDelayMs: options.promptDragStepDelayMs,
5395
+ beforeReleaseDelayMs: options.promptDragBeforeReleaseDelayMs,
5396
+ afterReleaseDelayMs: options.promptDragAfterReleaseDelayMs,
5397
+ finalMoveRepeats: options.promptDragFinalMoveRepeats
5398
+ });
5399
+ const afterState = await readPromptCaptchaState(frame, options);
5400
+ const accepted = afterState.badgeCount > baselineState.badgeCount || afterState.selectedCount > baselineState.selectedCount;
5401
+ const attemptInfo = {
5402
+ planName: plan.name,
5403
+ sourceRect: rectOf(sourceBox),
5404
+ targetRect: rectOf(targetBox),
5405
+ targetOffsetX: Number(targetOffsetX.toFixed(2)),
5406
+ targetOffsetY: Number(targetOffsetY.toFixed(2)),
5407
+ beforeState: baselineState,
5408
+ afterState,
5409
+ accepted
5410
+ };
5411
+ dragAttempts.push(attemptInfo);
5412
+ logger10.info(
5413
+ `\u9A8C\u8BC1\u7801\u62D6\u62FD\u7B2C ${visualIndex + 1} \u5F20\uFF0C\u65B9\u6848 ${plan.name}\uFF0Cbadge ${baselineState.badgeCount} -> ${afterState.badgeCount}\uFF0Cselected ${baselineState.selectedCount} -> ${afterState.selectedCount}`
5414
+ );
5415
+ if (accepted) {
5416
+ return {
5417
+ accepted: true,
5418
+ dragAttempts
5419
+ };
5420
+ }
5421
+ if (options.promptDragRetryDelayMs > 0) {
5422
+ await page.waitForTimeout(options.promptDragRetryDelayMs);
5423
+ }
5424
+ }
5425
+ await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, `drag-${visualIndex + 1}-failed`, options, {
5426
+ visualIndex,
5427
+ dragAttempts,
5428
+ finalState: await readPromptCaptchaState(frame, options)
5429
+ }).catch((error) => {
5430
+ logger10.warn(`\u9A8C\u8BC1\u7801\u62D6\u62FD\u5931\u8D25\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
5431
+ });
5432
+ return {
5433
+ accepted: false,
5434
+ dragAttempts
5435
+ };
5436
+ };
5199
5437
  async function solveCaptcha(page, options = {}, dependencies = {}) {
5200
5438
  const { callCaptchaRecognitionApi: callCaptchaRecognitionApi2 } = dependencies;
5201
5439
  if (typeof callCaptchaRecognitionApi2 !== "function") {
@@ -5210,7 +5448,7 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
5210
5448
  return false;
5211
5449
  }
5212
5450
  logger10.info("\u5F53\u524D\u4F7F\u7528\u672Ctool\u2014\u2014\u6D4B\u8BD5\u7248\u672C");
5213
- for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
5451
+ for (let attempt = 1; attempt <= config.maxRetries; attempt += 1) {
5214
5452
  logger10.info(`\u5F00\u59CB\u7B2C ${attempt}/${config.maxRetries} \u6B21 verifycenter \u9A8C\u8BC1\u7801\u8BC6\u522B\u3002`);
5215
5453
  try {
5216
5454
  const captchaContext = await getVerifycenterCaptchaContext(page, config);
@@ -5220,6 +5458,16 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
5220
5458
  }
5221
5459
  const { iframeLocator, frame } = captchaContext;
5222
5460
  await waitForCaptchaChallengeReady(page, frame, config);
5461
+ await maybeCollectCaptchaDebugInfo(
5462
+ page,
5463
+ frame,
5464
+ iframeLocator,
5465
+ attempt,
5466
+ "ready",
5467
+ config
5468
+ ).catch((error) => {
5469
+ logger10.warn(`\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
5470
+ });
5223
5471
  await page.waitForTimeout(config.recognitionDelayMs);
5224
5472
  const screenshotBuffer = await iframeLocator.screenshot();
5225
5473
  const apiResponse = await callCaptchaRecognitionApi2({
@@ -5243,33 +5491,74 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
5243
5491
  await refreshCaptcha(page, frame, config);
5244
5492
  continue;
5245
5493
  }
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;
5252
- }
5253
- if (imageIndex < 0 || imageIndex >= imageCount) {
5254
- throw new Error(`Captcha image index ${rawIndex} is out of range. count=${imageCount}`);
5494
+ const orderedSourceImages = await resolveCaptchaSourceImagesInVisualOrder(frame, config);
5495
+ const normalizedIndexes = normalizeCaptchaImageIndexes(serialNumbers, orderedSourceImages.length);
5496
+ if (normalizedIndexes.length !== serialNumbers.length) {
5497
+ throw new Error(
5498
+ `Captcha image indexes could not be normalized. raw=${serialNumbers.join(", ")}, count=${orderedSourceImages.length}`
5499
+ );
5500
+ }
5501
+ logger10.info(`\u9A8C\u8BC1\u7801\u89C6\u89C9\u4F4D\u5E8F\u6620\u5C04\uFF1A${normalizedIndexes.map((index) => index + 1).join(", ")}`);
5502
+ for (const imageIndex of normalizedIndexes) {
5503
+ if (imageIndex < 0 || imageIndex >= orderedSourceImages.length) {
5504
+ throw new Error(
5505
+ `Captcha image index ${imageIndex} is out of range. count=${orderedSourceImages.length}`
5506
+ );
5255
5507
  }
5256
- const sourceImage = sourceImages.nth(imageIndex);
5508
+ const sourceImage = orderedSourceImages[imageIndex].locator;
5257
5509
  await sourceImage.waitFor({
5258
5510
  state: "visible",
5259
5511
  timeout: config.sourceImageVisibleTimeoutMs
5260
5512
  });
5261
- await dragCaptchaAction(page, sourceImage, dropTarget);
5513
+ const dragResult = await dragPromptCaptchaImage(
5514
+ page,
5515
+ frame,
5516
+ iframeLocator,
5517
+ sourceImage,
5518
+ dropTarget,
5519
+ config,
5520
+ {
5521
+ attempt,
5522
+ visualIndex: imageIndex
5523
+ }
5524
+ );
5525
+ if (!dragResult.accepted) {
5526
+ throw new Error(`Captcha prompt drag was not accepted for visual index ${imageIndex + 1}.`);
5527
+ }
5528
+ if (config.dragBetweenWaitMs > 0) {
5529
+ await page.waitForTimeout(config.dragBetweenWaitMs);
5530
+ }
5262
5531
  }
5263
- const submitted = await clickCaptchaAction(frame, config.submitTexts, { ...config, page }).catch(() => false);
5532
+ const beforeSubmitState = await readPromptCaptchaState(frame, config);
5533
+ logger10.info(
5534
+ `\u63D0\u4EA4\u524D\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${beforeSubmitState.badgeCount}, selected=${beforeSubmitState.selectedCount}, submitDisabled=${beforeSubmitState.submitDisabled}`
5535
+ );
5536
+ const submitted = await clickCaptchaAction(frame, config.submitTexts, {
5537
+ ...config,
5538
+ page,
5539
+ logger: logger10,
5540
+ forceMouse: true,
5541
+ actionVisibleTimeoutMs: config.submitReadyTimeoutMs
5542
+ }).catch(() => false);
5264
5543
  if (!submitted) {
5265
5544
  logger10.warn("\u672A\u627E\u5230\u63D0\u4EA4\u6309\u94AE\uFF0C\u53EF\u80FD\u4F1A\u81EA\u52A8\u63D0\u4EA4\u3002");
5266
5545
  }
5267
5546
  await page.waitForTimeout(config.submitWaitMs);
5547
+ const afterSubmitState = await readPromptCaptchaState(frame, config);
5548
+ logger10.info(
5549
+ `\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${afterSubmitState.badgeCount}, selected=${afterSubmitState.selectedCount}, submitDisabled=${afterSubmitState.submitDisabled}`
5550
+ );
5268
5551
  const stillVisible = await iframeLocator.isVisible({ timeout: config.containerVisibleTimeoutMs }).catch(() => false);
5269
5552
  if (!stillVisible) {
5270
5553
  logger10.info("\u9A8C\u8BC1\u7801\u8BC6\u522B\u5E76\u63D0\u4EA4\u6210\u529F\u3002");
5271
5554
  return true;
5272
5555
  }
5556
+ await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, "submit-still-visible", config, {
5557
+ beforeSubmitState,
5558
+ afterSubmitState
5559
+ }).catch((error) => {
5560
+ logger10.warn(`\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
5561
+ });
5273
5562
  logger10.warn("\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801 iframe \u4ECD\u7136\u53EF\u89C1\uFF0C\u51C6\u5907\u5237\u65B0\u540E\u91CD\u8BD5\u3002");
5274
5563
  await page.waitForTimeout(2e3);
5275
5564
  await refreshCaptcha(page, frame, config);
@@ -5712,14 +6001,14 @@ var Mutation = {
5712
6001
  const isFrameElement = tagName === "IFRAME" || tagName === "FRAME";
5713
6002
  const nodeName = descriptor?.id || descriptor?.name || "no-id";
5714
6003
  let source = "main";
5715
- let path2 = `${selector}[${index}]`;
6004
+ let path3 = `${selector}[${index}]`;
5716
6005
  let text = "";
5717
6006
  let html = "";
5718
6007
  let frameUrl = "";
5719
6008
  let readyState = "";
5720
6009
  if (isFrameElement) {
5721
6010
  source = "iframe";
5722
- path2 = `${selector}[${index}]::iframe(${nodeName})`;
6011
+ path3 = `${selector}[${index}]::iframe(${nodeName})`;
5723
6012
  const frame = await handle.contentFrame();
5724
6013
  if (frame) {
5725
6014
  try {
@@ -5749,7 +6038,7 @@ var Mutation = {
5749
6038
  items.push({
5750
6039
  selector,
5751
6040
  source,
5752
- path: path2,
6041
+ path: path3,
5753
6042
  text,
5754
6043
  html,
5755
6044
  frameUrl,