@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.js CHANGED
@@ -106,10 +106,10 @@ var createActorInfo = (info) => {
106
106
  xurl
107
107
  };
108
108
  };
109
- const buildLandingUrl = ({ protocol: protocol2, domain: domain2, path: path3 }) => {
109
+ const buildLandingUrl = ({ protocol: protocol2, domain: domain2, path: path4 }) => {
110
110
  const safeProtocol = String(protocol2).trim();
111
111
  const safeDomain = normalizeDomain(domain2);
112
- const safePath = normalizePath(path3);
112
+ const safePath = normalizePath(path4);
113
113
  return `${safeProtocol}://${safeDomain}${safePath}`;
114
114
  };
115
115
  const buildIcon = ({ key }) => {
@@ -117,14 +117,14 @@ var createActorInfo = (info) => {
117
117
  };
118
118
  const protocol = info.protocol || "https";
119
119
  const domain = normalizeDomain(info.domain);
120
- const path2 = normalizePath(info.path);
120
+ const path3 = normalizePath(info.path);
121
121
  const share = normalizeShare2(info.share);
122
122
  const device = normalizeDevice(info.device);
123
123
  return {
124
124
  ...info,
125
125
  protocol,
126
126
  domain,
127
- path: path2,
127
+ path: path3,
128
128
  share,
129
129
  device,
130
130
  get icon() {
@@ -1484,7 +1484,7 @@ var normalizeCookies = (value) => {
1484
1484
  if (!name || !cookieValue || cookieValue === "<nil>") return null;
1485
1485
  const domain = String(raw.domain || "").trim();
1486
1486
  const url = normalizeHttpUrl(raw.url);
1487
- const path2 = String(raw.path || "").trim() || "/";
1487
+ const path3 = String(raw.path || "").trim() || "/";
1488
1488
  const sameSite = normalizeCookieSameSite(raw.sameSite);
1489
1489
  const expires = normalizeCookieExpires(raw);
1490
1490
  const secure = Boolean(raw.secure);
@@ -1493,7 +1493,7 @@ var normalizeCookies = (value) => {
1493
1493
  const normalized = {
1494
1494
  name,
1495
1495
  value: cookieValue,
1496
- path: path2,
1496
+ path: path3,
1497
1497
  ...domain ? { domain } : {},
1498
1498
  ...!domain && url ? { url } : {},
1499
1499
  ...sameSite ? { sameSite } : {},
@@ -2564,6 +2564,10 @@ var assertPoint = (point) => {
2564
2564
  throw new Error(`Invalid input point: ${JSON.stringify(point)}`);
2565
2565
  }
2566
2566
  };
2567
+ var toFiniteNumber = (value, fallback = 0) => {
2568
+ const number = Number(value);
2569
+ return Number.isFinite(number) ? number : fallback;
2570
+ };
2567
2571
  var dispatchMouseMove = (page, point, options = {}) => page.mouse.move(point.x, point.y, options);
2568
2572
  var dispatchMouseStart = (page, options = {}) => page.mouse.down(options);
2569
2573
  var dispatchMouseEnd = (page, options = {}) => page.mouse.up(options);
@@ -2583,6 +2587,11 @@ var dragWithMouse = async (page, points, options = {}) => {
2583
2587
  }, { steps: 2 });
2584
2588
  await waitFor(page, options.stepDelayMs ?? 90);
2585
2589
  }
2590
+ const finalMoveRepeats = Math.max(0, Math.floor(toFiniteNumber(options.finalMoveRepeats)));
2591
+ for (let repeat = 0; repeat < finalMoveRepeats; repeat += 1) {
2592
+ await dispatchMouseMove(page, points.end, { steps: 1 });
2593
+ await waitFor(page, options.finalMoveDelayMs ?? 35);
2594
+ }
2586
2595
  await waitFor(page, options.beforeReleaseDelayMs ?? 100);
2587
2596
  await dispatchMouseEnd(page);
2588
2597
  await waitFor(page, options.afterReleaseDelayMs ?? 100);
@@ -2592,6 +2601,7 @@ var dragWithTouch = async (page, points, options = {}) => {
2592
2601
  let client = null;
2593
2602
  try {
2594
2603
  client = await page.context().newCDPSession(page);
2604
+ await waitFor(page, options.initialDelayMs ?? 250);
2595
2605
  await client.send("Input.dispatchTouchEvent", {
2596
2606
  type: "touchStart",
2597
2607
  touchPoints: [{ x: points.start.x, y: points.start.y, id: 1 }]
@@ -2613,6 +2623,14 @@ var dragWithTouch = async (page, points, options = {}) => {
2613
2623
  });
2614
2624
  await waitFor(page, options.stepDelayMs ?? 90);
2615
2625
  }
2626
+ const finalMoveRepeats = Math.max(0, Math.floor(toFiniteNumber(options.finalMoveRepeats)));
2627
+ for (let repeat = 0; repeat < finalMoveRepeats; repeat += 1) {
2628
+ await client.send("Input.dispatchTouchEvent", {
2629
+ type: "touchMove",
2630
+ touchPoints: [{ x: points.end.x, y: points.end.y, id: 1 }]
2631
+ });
2632
+ await waitFor(page, options.finalMoveDelayMs ?? 35);
2633
+ }
2616
2634
  await waitFor(page, options.beforeReleaseDelayMs ?? 100);
2617
2635
  await client.send("Input.dispatchTouchEvent", {
2618
2636
  type: "touchEnd",
@@ -2630,7 +2648,7 @@ var dragWithTouch = async (page, points, options = {}) => {
2630
2648
  var clickTargetWithDevice = async (page, target, options = {}) => {
2631
2649
  const normalizedOptions = normalizeSelectorOptions(options);
2632
2650
  const resolvedDevice = resolveDeviceFromPage(page);
2633
- if (target && resolvedDevice === Device.Mobile && !normalizedOptions.forceClick) {
2651
+ if (target && resolvedDevice === Device.Mobile && !normalizedOptions.forceClick && !normalizedOptions.forceMouse) {
2634
2652
  if (typeof target.tap === "function") {
2635
2653
  await target.tap(normalizedOptions.tapOptions);
2636
2654
  return true;
@@ -2830,20 +2848,26 @@ var DeviceInput = {
2830
2848
  throw new Error("Unable to resolve drag coordinates.");
2831
2849
  }
2832
2850
  const steps = options.steps || 10;
2851
+ const sourceOffsetX = toFiniteNumber(options.sourceOffsetX);
2852
+ const sourceOffsetY = toFiniteNumber(options.sourceOffsetY);
2853
+ const targetOffsetX = toFiniteNumber(options.targetOffsetX);
2854
+ const targetOffsetY = toFiniteNumber(options.targetOffsetY);
2855
+ const sourceCenterX = sourceBox.x + sourceBox.width / 2 + sourceOffsetX;
2856
+ const sourceCenterY = sourceBox.y + sourceBox.height / 2 + sourceOffsetY;
2833
2857
  const liftOffsetX = Math.min(18, Math.max(8, sourceBox.width * 0.12));
2834
2858
  const liftOffsetY = Math.min(12, Math.max(4, sourceBox.height * 0.08));
2835
2859
  const points = {
2836
2860
  start: {
2837
- x: sourceBox.x + sourceBox.width / 2,
2838
- y: sourceBox.y + sourceBox.height / 2
2861
+ x: sourceCenterX,
2862
+ y: sourceCenterY
2839
2863
  },
2840
2864
  lift: {
2841
- x: sourceBox.x + sourceBox.width / 2 + liftOffsetX,
2842
- y: sourceBox.y + sourceBox.height / 2 + liftOffsetY
2865
+ x: sourceCenterX + liftOffsetX,
2866
+ y: sourceCenterY + liftOffsetY
2843
2867
  },
2844
2868
  end: {
2845
- x: targetBox.x + targetBox.width / 2,
2846
- y: targetBox.y + targetBox.height / 2
2869
+ x: targetBox.x + targetBox.width / 2 + targetOffsetX,
2870
+ y: targetBox.y + targetBox.height / 2 + targetOffsetY
2847
2871
  },
2848
2872
  steps
2849
2873
  };
@@ -3379,13 +3403,6 @@ var jitterMs = (base, jitterPercent = 0.3) => {
3379
3403
  const jitter = Number(base || 0) * Number(jitterPercent || 0) * (Math.random() * 2 - 1);
3380
3404
  return Math.max(10, Math.round(Number(base || 0) + jitter));
3381
3405
  };
3382
- var randomPointInBox = (box, paddingRatio = 0.24) => {
3383
- const safePaddingX = Math.max(2, Math.min(box.width * paddingRatio, box.width / 2));
3384
- const safePaddingY = Math.max(2, Math.min(box.height * paddingRatio, box.height / 2));
3385
- const x = box.x + safePaddingX + Math.random() * Math.max(1, box.width - safePaddingX * 2);
3386
- const y = box.y + safePaddingY + Math.random() * Math.max(1, box.height - safePaddingY * 2);
3387
- return { x, y };
3388
- };
3389
3406
  var resolveElement = async (page, target, { throwOnMissing = true } = {}) => {
3390
3407
  if (target == null) return null;
3391
3408
  let element = target;
@@ -3430,6 +3447,10 @@ var clipBoxToViewport = (box, viewport) => {
3430
3447
  }
3431
3448
  return box;
3432
3449
  };
3450
+ var centerPointInBox = (box) => ({
3451
+ x: box.x + box.width / 2,
3452
+ y: box.y + box.height / 2
3453
+ });
3433
3454
  var withTimeout = async (operation, timeoutMs, label) => {
3434
3455
  const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
3435
3456
  let timeoutId = null;
@@ -3581,97 +3602,6 @@ var checkElementVisibility = async (element) => {
3581
3602
  return { code: "VISIBLE", isFixed, positioning };
3582
3603
  });
3583
3604
  };
3584
- var resolveSafeTapPoint = async (element) => {
3585
- return element.evaluate((el) => {
3586
- const rect = el.getBoundingClientRect();
3587
- if (!rect || rect.width <= 0 || rect.height <= 0) {
3588
- return null;
3589
- }
3590
- const viewW = window.innerWidth;
3591
- const viewH = window.innerHeight;
3592
- let clipLeft = 0;
3593
- let clipRight = viewW;
3594
- let clipTop = 0;
3595
- let clipBottom = viewH;
3596
- for (let node = el.parentElement; node && node !== document.body; node = node.parentElement) {
3597
- const style = window.getComputedStyle(node);
3598
- if (!style) continue;
3599
- const clipsX = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowX);
3600
- const clipsY = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowY);
3601
- if (!clipsX && !clipsY) continue;
3602
- const nodeRect = node.getBoundingClientRect();
3603
- if (!nodeRect || nodeRect.width <= 0 || nodeRect.height <= 0) continue;
3604
- if (clipsX) {
3605
- clipLeft = Math.max(clipLeft, nodeRect.left);
3606
- clipRight = Math.min(clipRight, nodeRect.right);
3607
- }
3608
- if (clipsY) {
3609
- clipTop = Math.max(clipTop, nodeRect.top);
3610
- clipBottom = Math.min(clipBottom, nodeRect.bottom);
3611
- }
3612
- }
3613
- const visibleLeft = Math.max(clipLeft, Math.min(clipRight, rect.left));
3614
- const visibleRight = Math.max(clipLeft, Math.min(clipRight, rect.right));
3615
- const visibleTop = Math.max(clipTop, Math.min(clipBottom, rect.top));
3616
- const visibleBottom = Math.max(clipTop, Math.min(clipBottom, rect.bottom));
3617
- const visibleWidth = visibleRight - visibleLeft;
3618
- const visibleHeight = visibleBottom - visibleTop;
3619
- if (visibleWidth <= 1 || visibleHeight <= 1) {
3620
- return null;
3621
- }
3622
- const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
3623
- const commonAncestor = (a, b) => {
3624
- for (let current = a; current && !isRootNode(current); current = current.parentElement) {
3625
- if (current.contains(b)) return current;
3626
- }
3627
- return null;
3628
- };
3629
- const sameTapTarget = (pointElement) => {
3630
- if (!pointElement) return false;
3631
- if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
3632
- return true;
3633
- }
3634
- const common = commonAncestor(el, pointElement);
3635
- if (!common) return false;
3636
- const commonRect = common.getBoundingClientRect?.();
3637
- if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
3638
- const commonArea = commonRect.width * commonRect.height;
3639
- const targetArea = Math.max(1, rect.width * rect.height);
3640
- const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
3641
- if (commonArea > maxSharedRegionArea) return false;
3642
- if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
3643
- return false;
3644
- }
3645
- return common.contains(el) && common.contains(pointElement);
3646
- };
3647
- const cx = visibleLeft + visibleWidth / 2;
3648
- const cy = visibleTop + visibleHeight / 2;
3649
- const smallX = Math.min(12, Math.max(2, visibleWidth * 0.18));
3650
- const smallY = Math.min(12, Math.max(2, visibleHeight * 0.18));
3651
- const points = [
3652
- { x: cx, y: cy },
3653
- { x: visibleLeft + smallX, y: visibleTop + smallY },
3654
- { x: visibleRight - smallX, y: visibleTop + smallY },
3655
- { x: visibleLeft + smallX, y: visibleBottom - smallY },
3656
- { x: visibleRight - smallX, y: visibleBottom - smallY },
3657
- { x: cx, y: visibleTop + Math.min(10, Math.max(2, visibleHeight * 0.2)) },
3658
- { x: cx, y: visibleBottom - Math.min(10, Math.max(2, visibleHeight * 0.2)) },
3659
- { x: visibleLeft + Math.min(10, Math.max(2, visibleWidth * 0.2)), y: cy },
3660
- { x: visibleRight - Math.min(10, Math.max(2, visibleWidth * 0.2)), y: cy }
3661
- ];
3662
- const safePoints = points.filter((point) => {
3663
- const pointElement = document.elementFromPoint(point.x, point.y);
3664
- return sameTapTarget(pointElement);
3665
- });
3666
- const candidates = safePoints.length ? safePoints : points;
3667
- const chosen = candidates[Math.floor(Math.random() * candidates.length)];
3668
- if (!chosen) return null;
3669
- return {
3670
- x: chosen.x,
3671
- y: chosen.y
3672
- };
3673
- });
3674
- };
3675
3605
  var activateElementFallback = async (element, point = null, options = {}) => {
3676
3606
  return element.evaluate((el, { innerOptions }) => {
3677
3607
  const isEditable = (node) => {
@@ -4256,8 +4186,8 @@ var MobileHumanize = {
4256
4186
  return false;
4257
4187
  }
4258
4188
  await waitJitter(reactionDelay, 0.45);
4259
- const safePoint = await resolveSafeTapPoint(element).catch(() => null);
4260
- const tapTarget = safePoint || randomPointInBox(clipBoxToViewport(box, resolveViewport(page)), 0.2);
4189
+ const visibleBox = clipBoxToViewport(box, resolveViewport(page));
4190
+ const tapTarget = centerPointInBox(visibleBox);
4261
4191
  try {
4262
4192
  await tapPoint(page, tapTarget, {
4263
4193
  timeoutMs: tapTimeoutMs,
@@ -4960,6 +4890,10 @@ var LiveView = {
4960
4890
  // src/chaptcha.js
4961
4891
  import { v4 as uuidv4 } from "uuid";
4962
4892
 
4893
+ // src/internals/captcha/bytedance.js
4894
+ import { mkdir, writeFile } from "fs/promises";
4895
+ import path2 from "path";
4896
+
4963
4897
  // src/internals/captcha/shared.js
4964
4898
  var waitForVisible = async (locator, timeout) => {
4965
4899
  try {
@@ -4977,38 +4911,71 @@ var isAnyCaptchaTextVisible = async (frame, texts, timeout) => {
4977
4911
  if (!text) {
4978
4912
  continue;
4979
4913
  }
4980
- const candidates = [
4981
- frame.getByText(text, { exact: false }).first(),
4982
- frame.locator(`text=${text}`).first()
4983
- ];
4984
- for (const candidate of candidates) {
4985
- const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
4986
- if (isVisible) {
4987
- return true;
4914
+ const textLocator = frame.getByText(text, { exact: false });
4915
+ const locatorText = frame.locator(`text=${text}`);
4916
+ const candidateGroups = [textLocator, locatorText];
4917
+ for (const candidateGroup of candidateGroups) {
4918
+ const candidateCount = await candidateGroup.count().catch(() => 0);
4919
+ for (let index = 0; index < candidateCount; index += 1) {
4920
+ const candidate = candidateGroup.nth(index);
4921
+ const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
4922
+ if (isVisible) {
4923
+ return true;
4924
+ }
4988
4925
  }
4989
4926
  }
4990
4927
  }
4991
4928
  return false;
4992
4929
  };
4930
+ var collectVisibleCandidateIndexes = async (candidateGroup, count, timeout) => {
4931
+ const visibleIndexes = [];
4932
+ for (let index = 0; index < count; index += 1) {
4933
+ const candidate = candidateGroup.nth(index);
4934
+ const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
4935
+ if (isVisible) {
4936
+ visibleIndexes.push(index);
4937
+ }
4938
+ }
4939
+ return visibleIndexes;
4940
+ };
4993
4941
  var clickCaptchaAction = async (frame, texts, options) => {
4994
4942
  for (const text of texts || []) {
4995
- const candidates = [
4996
- frame.getByText(text, { exact: false }).first(),
4997
- frame.locator(`text=${text}`).first()
4943
+ const textLocator = frame.getByText(text, { exact: false });
4944
+ const locatorText = frame.locator(`text=${text}`);
4945
+ const [getByTextCount, locatorTextCount] = await Promise.all([
4946
+ textLocator.count().catch(() => 0),
4947
+ locatorText.count().catch(() => 0)
4948
+ ]);
4949
+ const [getByTextVisibleIndexes, locatorTextVisibleIndexes] = await Promise.all([
4950
+ collectVisibleCandidateIndexes(textLocator, getByTextCount, options.actionVisibleTimeoutMs),
4951
+ collectVisibleCandidateIndexes(locatorText, locatorTextCount, options.actionVisibleTimeoutMs)
4952
+ ]);
4953
+ options.logger?.info(
4954
+ `[CaptchaAction] \u6587\u672C "${text}" \u547D\u4E2D\u6570\u91CF\uFF1AgetByText=${getByTextCount} (visible=${getByTextVisibleIndexes.length}), locator=${locatorTextCount} (visible=${locatorTextVisibleIndexes.length})`
4955
+ );
4956
+ const candidateGroups = [
4957
+ { label: "getByText", locator: textLocator, count: getByTextCount },
4958
+ { label: "locator", locator: locatorText, count: locatorTextCount }
4998
4959
  ];
4999
- for (const candidate of candidates) {
5000
- const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
5001
- if (!isVisible) {
5002
- continue;
4960
+ for (const candidateGroup of candidateGroups) {
4961
+ for (let index = 0; index < candidateGroup.count; index += 1) {
4962
+ const candidate = candidateGroup.locator.nth(index);
4963
+ const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
4964
+ if (!isVisible) {
4965
+ continue;
4966
+ }
4967
+ options.logger?.info(
4968
+ `[CaptchaAction] \u6587\u672C "${text}" \u9009\u62E9 ${candidateGroup.label}[${index}] \u4F5C\u4E3A\u7B2C\u4E00\u4E2A\u53EF\u89C1\u8282\u70B9\u6267\u884C\u70B9\u51FB\u3002`
4969
+ );
4970
+ await DeviceInput.click(options.page, candidate, options);
4971
+ return true;
5003
4972
  }
5004
- await DeviceInput.click(options.page, candidate);
5005
- return true;
5006
4973
  }
5007
4974
  }
5008
4975
  return false;
5009
4976
  };
5010
- var dragCaptchaAction = async (page, sourceLocator, targetLocator) => {
5011
- await DeviceInput.drag(page, sourceLocator, targetLocator);
4977
+ var dragCaptchaAction = async (page, sourceLocator, targetLocator, options = {}) => {
4978
+ await DeviceInput.drag(page, sourceLocator, targetLocator, options);
5012
4979
  };
5013
4980
 
5014
4981
  // src/internals/captcha/bytedance.js
@@ -5019,11 +4986,12 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
5019
4986
  containerSelector: "#captcha_container",
5020
4987
  iframeSelector: 'iframe[src*="verifycenter"]',
5021
4988
  iframeFallbackSelector: "iframe",
5022
- sourceImageSelector: "div.canvas-container",
5023
- dropTargetContainerSelector: "#captcha_verify_image div",
4989
+ sourceImageSelector: ".img-container .canvas-container",
4990
+ dropTargetContainerSelector: ".drag-area",
5024
4991
  dropTargetTexts: ["\u62D6\u62FD\u5230\u8FD9\u91CC"],
5025
4992
  refreshTexts: ["\u5237\u65B0"],
5026
4993
  submitTexts: ["\u63D0\u4EA4"],
4994
+ guideMaskSelector: ".play-guide-mask",
5027
4995
  recognitionSuccessCode: 1e4,
5028
4996
  containerVisibleTimeoutMs: 2e3,
5029
4997
  iframeVisibleTimeoutMs: 12e3,
@@ -5046,10 +5014,111 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
5046
5014
  ],
5047
5015
  recognitionDelayMs: 2e3,
5048
5016
  refreshWaitMs: 3e3,
5049
- submitWaitMs: 3e3,
5017
+ submitWaitMs: 5e3,
5018
+ submitReadyTimeoutMs: 2500,
5050
5019
  retryDelayBaseMs: 2e3,
5051
- retryDelayStepMs: 1e3
5020
+ retryDelayStepMs: 1e3,
5021
+ sourceImageRowTolerancePx: 24,
5022
+ dragBetweenWaitMs: 250,
5023
+ promptBadgeCountSelector: ".drag-area .photo-badge .badge span",
5024
+ promptSubmitButtonSelector: ".vc-captcha-verify-mobile-button",
5025
+ promptSelectedSourceSelector: ".img-container .canvas-container.selected",
5026
+ promptActiveSourceSelector: ".img-container .canvas-container.active",
5027
+ promptDragMoveSteps: 16,
5028
+ promptDragStepDelayMs: 55,
5029
+ promptDragHoldDelayMs: 240,
5030
+ promptDragBeforeReleaseDelayMs: 180,
5031
+ promptDragAfterReleaseDelayMs: 240,
5032
+ promptDragFinalMoveRepeats: 3,
5033
+ promptDragRetryDelayMs: 250,
5034
+ debugArtifacts: false
5052
5035
  });
5036
+ var PROMPT_CAPTCHA_DRAG_PLANS = Object.freeze([
5037
+ { name: "lower-middle", endXRatio: 0.5, endYRatio: 0.72 },
5038
+ { name: "center-middle", endXRatio: 0.5, endYRatio: 0.56 },
5039
+ { name: "upper-middle", endXRatio: 0.5, endYRatio: 0.4 }
5040
+ ]);
5041
+ var resolveCaptchaDebugDir = () => path2.resolve(process.cwd(), "storage", "captcha-debug");
5042
+ var rectOf = (rect) => {
5043
+ if (!rect) {
5044
+ return null;
5045
+ }
5046
+ return {
5047
+ x: Number(rect.x.toFixed(2)),
5048
+ y: Number(rect.y.toFixed(2)),
5049
+ width: Number(rect.width.toFixed(2)),
5050
+ height: Number(rect.height.toFixed(2))
5051
+ };
5052
+ };
5053
+ var collectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, extra = null) => {
5054
+ const timestamp = Date.now();
5055
+ const debugDir = resolveCaptchaDebugDir();
5056
+ await mkdir(debugDir, { recursive: true });
5057
+ const baseName = `bytedance-${timestamp}-attempt${attempt}-${phase}`;
5058
+ const iframeScreenshotPath = path2.join(debugDir, `${baseName}-iframe.png`);
5059
+ const pageScreenshotPath = path2.join(debugDir, `${baseName}-page.png`);
5060
+ const htmlPath = path2.join(debugDir, `${baseName}-iframe.html`);
5061
+ const infoPath = path2.join(debugDir, `${baseName}-info.json`);
5062
+ await iframeLocator.screenshot({ path: iframeScreenshotPath }).catch(() => {
5063
+ });
5064
+ await page.screenshot({ path: pageScreenshotPath, fullPage: true }).catch(() => {
5065
+ });
5066
+ const html = await frame.evaluate(() => document.documentElement.outerHTML).catch(() => "");
5067
+ if (html) {
5068
+ await writeFile(htmlPath, html, "utf8");
5069
+ }
5070
+ const info = await frame.evaluate(() => {
5071
+ const toRect = (element) => {
5072
+ const rect = element.getBoundingClientRect();
5073
+ return {
5074
+ x: Number(rect.x.toFixed(2)),
5075
+ y: Number(rect.y.toFixed(2)),
5076
+ width: Number(rect.width.toFixed(2)),
5077
+ height: Number(rect.height.toFixed(2))
5078
+ };
5079
+ };
5080
+ const toItem = (element, index) => ({
5081
+ index,
5082
+ tag: element.tagName,
5083
+ id: element.id || "",
5084
+ className: typeof element.className === "string" ? element.className : "",
5085
+ text: String(element.textContent || "").trim(),
5086
+ rect: toRect(element)
5087
+ });
5088
+ const visibleNodes = Array.from(document.querySelectorAll("body *")).map(toItem).filter((item) => item.rect.width > 0 && item.rect.height > 0);
5089
+ return {
5090
+ title: document.title,
5091
+ bodyText: String(document.body?.innerText || "").trim(),
5092
+ canvasContainers: visibleNodes.filter((item) => item.className.includes("canvas-container")),
5093
+ canvasNodes: visibleNodes.filter((item) => item.tag === "CANVAS"),
5094
+ captchaNodes: visibleNodes.filter((item) => item.id.startsWith("captcha_") || item.className.includes("captcha") || item.className.includes("verify") || /拖拽到这里|刷新|提交/.test(item.text)),
5095
+ visibleTextNodes: visibleNodes.filter((item) => item.text).slice(0, 300)
5096
+ };
5097
+ }).catch(() => null);
5098
+ if (info) {
5099
+ const payload = {
5100
+ capturedAt: new Date(timestamp).toISOString(),
5101
+ pageUrl: page.url(),
5102
+ attempt,
5103
+ phase,
5104
+ iframeScreenshotPath,
5105
+ pageScreenshotPath,
5106
+ htmlPath,
5107
+ info
5108
+ };
5109
+ if (extra != null) {
5110
+ payload.extra = extra;
5111
+ }
5112
+ await writeFile(infoPath, JSON.stringify(payload, null, 2), "utf8");
5113
+ }
5114
+ logger10.info(`\u5DF2\u5199\u51FA\u9A8C\u8BC1\u7801\u8C03\u8BD5\u4EA7\u7269\uFF1A${debugDir}`);
5115
+ };
5116
+ var maybeCollectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, options, extra = null) => {
5117
+ if (!options.debugArtifacts) {
5118
+ return;
5119
+ }
5120
+ await collectCaptchaDebugInfo(page, frame, iframeLocator, attempt, phase, extra);
5121
+ };
5053
5122
  var extractCaptchaSerialNumbers = (apiResponse) => {
5054
5123
  const serialNumbers = apiResponse?.data?.data?.serial_number;
5055
5124
  if (!Array.isArray(serialNumbers)) {
@@ -5058,7 +5127,7 @@ var extractCaptchaSerialNumbers = (apiResponse) => {
5058
5127
  return serialNumbers.map((value) => Number(value)).filter((value) => Number.isInteger(value) && value >= 0);
5059
5128
  };
5060
5129
  var resolveContentFrame = async (page, iframeLocator, options) => {
5061
- for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt++) {
5130
+ for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt += 1) {
5062
5131
  const iframeHandle = await iframeLocator.elementHandle();
5063
5132
  const frame = await iframeHandle?.contentFrame();
5064
5133
  if (frame) {
@@ -5103,20 +5172,15 @@ var getVerifycenterCaptchaContext = async (page, options) => {
5103
5172
  }
5104
5173
  return { iframeLocator, frame };
5105
5174
  };
5106
- var refreshCaptcha = async (page, frame, options) => {
5107
- const clicked = await clickCaptchaAction(frame, options.refreshTexts, { ...options, page }).catch(() => false);
5108
- if (!clicked) {
5109
- logger10.warn("Refresh button not found.");
5110
- return false;
5111
- }
5112
- await page.waitForTimeout(options.refreshWaitMs);
5113
- return true;
5114
- };
5115
5175
  var findCaptchaDropTarget = async (frame, options) => {
5176
+ const directTarget = frame.locator(options.dropTargetContainerSelector).first();
5177
+ if (await waitForVisible(directTarget, options.actionVisibleTimeoutMs)) {
5178
+ return directTarget;
5179
+ }
5116
5180
  for (const text of options.dropTargetTexts) {
5117
5181
  const candidates = [
5118
- frame.locator(options.dropTargetContainerSelector).filter({ hasText: text }).first(),
5119
- frame.getByText(text, { exact: false }).first()
5182
+ frame.getByText(text, { exact: false }).first(),
5183
+ frame.locator(`text=${text}`).first()
5120
5184
  ];
5121
5185
  for (const candidate of candidates) {
5122
5186
  const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
@@ -5127,10 +5191,112 @@ var findCaptchaDropTarget = async (frame, options) => {
5127
5191
  }
5128
5192
  return null;
5129
5193
  };
5194
+ var readPromptCaptchaState = async (frame, options) => frame.evaluate((selectors) => {
5195
+ const toRect = (element) => {
5196
+ if (!element) {
5197
+ return null;
5198
+ }
5199
+ const rect = element.getBoundingClientRect();
5200
+ return {
5201
+ x: Number(rect.x.toFixed(2)),
5202
+ y: Number(rect.y.toFixed(2)),
5203
+ width: Number(rect.width.toFixed(2)),
5204
+ height: Number(rect.height.toFixed(2))
5205
+ };
5206
+ };
5207
+ const badgeNode = document.querySelector(selectors.badgeCountSelector);
5208
+ const submitButton = document.querySelector(selectors.submitButtonSelector);
5209
+ const dragArea = document.querySelector(selectors.dragAreaSelector);
5210
+ const badgeCount = badgeNode ? Number.parseInt(String(badgeNode.textContent || "").trim(), 10) : 0;
5211
+ return {
5212
+ badgeCount: Number.isFinite(badgeCount) ? badgeCount : 0,
5213
+ selectedCount: document.querySelectorAll(selectors.selectedSourceSelector).length,
5214
+ activeCount: document.querySelectorAll(selectors.activeSourceSelector).length,
5215
+ submitDisabled: submitButton ? submitButton.classList.contains("disable") : null,
5216
+ dragAreaActive: dragArea ? dragArea.classList.contains("active") : false,
5217
+ dragAreaRect: toRect(dragArea)
5218
+ };
5219
+ }, {
5220
+ badgeCountSelector: options.promptBadgeCountSelector,
5221
+ submitButtonSelector: options.promptSubmitButtonSelector,
5222
+ selectedSourceSelector: options.promptSelectedSourceSelector,
5223
+ activeSourceSelector: options.promptActiveSourceSelector,
5224
+ dragAreaSelector: options.dropTargetContainerSelector
5225
+ }).catch(() => ({
5226
+ badgeCount: 0,
5227
+ selectedCount: 0,
5228
+ activeCount: 0,
5229
+ submitDisabled: null,
5230
+ dragAreaActive: false,
5231
+ dragAreaRect: null
5232
+ }));
5233
+ var normalizeCaptchaImageIndexes = (serialNumbers, imageCount) => {
5234
+ if (!Array.isArray(serialNumbers) || imageCount <= 0) {
5235
+ return [];
5236
+ }
5237
+ const areAllOneBased = serialNumbers.every((value) => value >= 1 && value <= imageCount);
5238
+ if (areAllOneBased) {
5239
+ return serialNumbers.map((value) => value - 1);
5240
+ }
5241
+ const areAllZeroBased = serialNumbers.every((value) => value >= 0 && value < imageCount);
5242
+ if (areAllZeroBased) {
5243
+ return [...serialNumbers];
5244
+ }
5245
+ return serialNumbers.map((value) => {
5246
+ if (value >= 1 && value <= imageCount) {
5247
+ return value - 1;
5248
+ }
5249
+ if (value >= 0 && value < imageCount) {
5250
+ return value;
5251
+ }
5252
+ return null;
5253
+ }).filter((value) => Number.isInteger(value));
5254
+ };
5255
+ var resolveCaptchaSourceImagesInVisualOrder = async (frame, options) => {
5256
+ const sourceImages = frame.locator(options.sourceImageSelector);
5257
+ const imageCount = await sourceImages.count().catch(() => 0);
5258
+ const sources = [];
5259
+ for (let domIndex = 0; domIndex < imageCount; domIndex += 1) {
5260
+ const locator = sourceImages.nth(domIndex);
5261
+ const box = await locator.boundingBox().catch(() => null);
5262
+ if (!box || box.width <= 0 || box.height <= 0) {
5263
+ continue;
5264
+ }
5265
+ sources.push({
5266
+ domIndex,
5267
+ locator,
5268
+ box
5269
+ });
5270
+ }
5271
+ sources.sort((left, right) => {
5272
+ const deltaY = left.box.y - right.box.y;
5273
+ if (Math.abs(deltaY) > options.sourceImageRowTolerancePx) {
5274
+ return deltaY;
5275
+ }
5276
+ return left.box.x - right.box.x;
5277
+ });
5278
+ return sources;
5279
+ };
5280
+ var refreshCaptcha = async (page, frame, options) => {
5281
+ const clicked = await clickCaptchaAction(frame, options.refreshTexts, {
5282
+ ...options,
5283
+ page,
5284
+ logger: logger10,
5285
+ forceMouse: true
5286
+ }).catch(() => false);
5287
+ if (!clicked) {
5288
+ logger10.warn("Refresh button not found.");
5289
+ return false;
5290
+ }
5291
+ await page.waitForTimeout(options.refreshWaitMs);
5292
+ return true;
5293
+ };
5130
5294
  var waitForCaptchaChallengeReady = async (page, frame, options) => {
5131
5295
  const deadline = Date.now() + options.challengeReadyTimeoutMs;
5132
5296
  let refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
5133
5297
  let hasSeenLoading = false;
5298
+ let hasSeenGuideMask = false;
5299
+ let hasLoggedGuideMask = false;
5134
5300
  while (Date.now() < deadline) {
5135
5301
  const isLoadingVisible = await isAnyCaptchaTextVisible(
5136
5302
  frame,
@@ -5146,8 +5312,17 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
5146
5312
  const sourceImages = frame.locator(options.sourceImageSelector);
5147
5313
  const imageCount = await sourceImages.count().catch(() => 0);
5148
5314
  const hasVisibleSourceImage = imageCount > 0 ? await sourceImages.first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
5149
- if (!isLoadingVisible && hasVisibleSourceImage) {
5150
- logger10.info(hasSeenLoading ? "\u9A8C\u8BC1\u7801\u56FE\u7247\u5DF2\u52A0\u8F7D\u5B8C\u6210\u3002" : "\u9A8C\u8BC1\u7801\u56FE\u7247\u5DF2\u5C31\u7EEA\u3002");
5315
+ const hasVisibleDropTarget = await frame.locator(options.dropTargetContainerSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false);
5316
+ const hasGuideMaskVisible = options.guideMaskSelector ? await frame.locator(options.guideMaskSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
5317
+ hasSeenGuideMask = hasSeenGuideMask || hasGuideMaskVisible;
5318
+ if (hasGuideMaskVisible && !hasLoggedGuideMask) {
5319
+ 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");
5320
+ hasLoggedGuideMask = true;
5321
+ }
5322
+ if (!isLoadingVisible && hasVisibleSourceImage && hasVisibleDropTarget && !hasGuideMaskVisible) {
5323
+ logger10.info(
5324
+ 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"
5325
+ );
5151
5326
  return;
5152
5327
  }
5153
5328
  if (hasErrorTextVisible) {
@@ -5157,8 +5332,8 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
5157
5332
  hasSeenLoading = false;
5158
5333
  continue;
5159
5334
  }
5160
- if (!hasVisibleSourceImage && Date.now() >= refreshDeadline) {
5161
- logger10.warn(`\u9A8C\u8BC1\u7801\u56FE\u7247\u8D85\u8FC7 ${options.challengeReadyRefreshTimeoutMs}ms \u4ECD\u672A\u51FA\u73B0\uFF0C\u5C1D\u8BD5\u5237\u65B0\u9898\u76EE\u3002`);
5335
+ if ((!hasVisibleSourceImage || !hasVisibleDropTarget) && Date.now() >= refreshDeadline) {
5336
+ 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`);
5162
5337
  await refreshCaptcha(page, frame, options);
5163
5338
  refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
5164
5339
  hasSeenLoading = false;
@@ -5168,6 +5343,69 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
5168
5343
  }
5169
5344
  throw new Error("Captcha challenge is still loading and did not become ready in time.");
5170
5345
  };
5346
+ var dragPromptCaptchaImage = async (page, frame, iframeLocator, sourceLocator, dropTarget, options, {
5347
+ attempt,
5348
+ visualIndex
5349
+ }) => {
5350
+ const baselineState = await readPromptCaptchaState(frame, options);
5351
+ const dragAttempts = [];
5352
+ for (const plan of PROMPT_CAPTCHA_DRAG_PLANS) {
5353
+ const sourceBox = await sourceLocator.boundingBox().catch(() => null);
5354
+ const targetBox = await dropTarget.boundingBox().catch(() => null);
5355
+ if (!sourceBox || !targetBox) {
5356
+ throw new Error("Unable to resolve prompt captcha drag coordinates.");
5357
+ }
5358
+ const targetOffsetX = (plan.endXRatio - 0.5) * targetBox.width;
5359
+ const targetOffsetY = (plan.endYRatio - 0.5) * targetBox.height;
5360
+ await dragCaptchaAction(page, sourceLocator, dropTarget, {
5361
+ forceMouse: true,
5362
+ targetOffsetX,
5363
+ targetOffsetY,
5364
+ steps: options.promptDragMoveSteps,
5365
+ holdDelayMs: options.promptDragHoldDelayMs,
5366
+ stepDelayMs: options.promptDragStepDelayMs,
5367
+ beforeReleaseDelayMs: options.promptDragBeforeReleaseDelayMs,
5368
+ afterReleaseDelayMs: options.promptDragAfterReleaseDelayMs,
5369
+ finalMoveRepeats: options.promptDragFinalMoveRepeats
5370
+ });
5371
+ const afterState = await readPromptCaptchaState(frame, options);
5372
+ const accepted = afterState.badgeCount > baselineState.badgeCount || afterState.selectedCount > baselineState.selectedCount;
5373
+ const attemptInfo = {
5374
+ planName: plan.name,
5375
+ sourceRect: rectOf(sourceBox),
5376
+ targetRect: rectOf(targetBox),
5377
+ targetOffsetX: Number(targetOffsetX.toFixed(2)),
5378
+ targetOffsetY: Number(targetOffsetY.toFixed(2)),
5379
+ beforeState: baselineState,
5380
+ afterState,
5381
+ accepted
5382
+ };
5383
+ dragAttempts.push(attemptInfo);
5384
+ logger10.info(
5385
+ `\u9A8C\u8BC1\u7801\u62D6\u62FD\u7B2C ${visualIndex + 1} \u5F20\uFF0C\u65B9\u6848 ${plan.name}\uFF0Cbadge ${baselineState.badgeCount} -> ${afterState.badgeCount}\uFF0Cselected ${baselineState.selectedCount} -> ${afterState.selectedCount}`
5386
+ );
5387
+ if (accepted) {
5388
+ return {
5389
+ accepted: true,
5390
+ dragAttempts
5391
+ };
5392
+ }
5393
+ if (options.promptDragRetryDelayMs > 0) {
5394
+ await page.waitForTimeout(options.promptDragRetryDelayMs);
5395
+ }
5396
+ }
5397
+ await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, `drag-${visualIndex + 1}-failed`, options, {
5398
+ visualIndex,
5399
+ dragAttempts,
5400
+ finalState: await readPromptCaptchaState(frame, options)
5401
+ }).catch((error) => {
5402
+ logger10.warn(`\u9A8C\u8BC1\u7801\u62D6\u62FD\u5931\u8D25\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
5403
+ });
5404
+ return {
5405
+ accepted: false,
5406
+ dragAttempts
5407
+ };
5408
+ };
5171
5409
  async function solveCaptcha(page, options = {}, dependencies = {}) {
5172
5410
  const { callCaptchaRecognitionApi: callCaptchaRecognitionApi2 } = dependencies;
5173
5411
  if (typeof callCaptchaRecognitionApi2 !== "function") {
@@ -5182,7 +5420,7 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
5182
5420
  return false;
5183
5421
  }
5184
5422
  logger10.info("\u5F53\u524D\u4F7F\u7528\u672Ctool\u2014\u2014\u6D4B\u8BD5\u7248\u672C");
5185
- for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
5423
+ for (let attempt = 1; attempt <= config.maxRetries; attempt += 1) {
5186
5424
  logger10.info(`\u5F00\u59CB\u7B2C ${attempt}/${config.maxRetries} \u6B21 verifycenter \u9A8C\u8BC1\u7801\u8BC6\u522B\u3002`);
5187
5425
  try {
5188
5426
  const captchaContext = await getVerifycenterCaptchaContext(page, config);
@@ -5192,6 +5430,16 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
5192
5430
  }
5193
5431
  const { iframeLocator, frame } = captchaContext;
5194
5432
  await waitForCaptchaChallengeReady(page, frame, config);
5433
+ await maybeCollectCaptchaDebugInfo(
5434
+ page,
5435
+ frame,
5436
+ iframeLocator,
5437
+ attempt,
5438
+ "ready",
5439
+ config
5440
+ ).catch((error) => {
5441
+ logger10.warn(`\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
5442
+ });
5195
5443
  await page.waitForTimeout(config.recognitionDelayMs);
5196
5444
  const screenshotBuffer = await iframeLocator.screenshot();
5197
5445
  const apiResponse = await callCaptchaRecognitionApi2({
@@ -5215,33 +5463,74 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
5215
5463
  await refreshCaptcha(page, frame, config);
5216
5464
  continue;
5217
5465
  }
5218
- const sourceImages = frame.locator(config.sourceImageSelector);
5219
- const imageCount = await sourceImages.count();
5220
- for (const rawIndex of serialNumbers) {
5221
- let imageIndex = rawIndex;
5222
- if (imageIndex >= imageCount && imageIndex > 0 && imageIndex - 1 < imageCount) {
5223
- imageIndex -= 1;
5224
- }
5225
- if (imageIndex < 0 || imageIndex >= imageCount) {
5226
- throw new Error(`Captcha image index ${rawIndex} is out of range. count=${imageCount}`);
5466
+ const orderedSourceImages = await resolveCaptchaSourceImagesInVisualOrder(frame, config);
5467
+ const normalizedIndexes = normalizeCaptchaImageIndexes(serialNumbers, orderedSourceImages.length);
5468
+ if (normalizedIndexes.length !== serialNumbers.length) {
5469
+ throw new Error(
5470
+ `Captcha image indexes could not be normalized. raw=${serialNumbers.join(", ")}, count=${orderedSourceImages.length}`
5471
+ );
5472
+ }
5473
+ logger10.info(`\u9A8C\u8BC1\u7801\u89C6\u89C9\u4F4D\u5E8F\u6620\u5C04\uFF1A${normalizedIndexes.map((index) => index + 1).join(", ")}`);
5474
+ for (const imageIndex of normalizedIndexes) {
5475
+ if (imageIndex < 0 || imageIndex >= orderedSourceImages.length) {
5476
+ throw new Error(
5477
+ `Captcha image index ${imageIndex} is out of range. count=${orderedSourceImages.length}`
5478
+ );
5227
5479
  }
5228
- const sourceImage = sourceImages.nth(imageIndex);
5480
+ const sourceImage = orderedSourceImages[imageIndex].locator;
5229
5481
  await sourceImage.waitFor({
5230
5482
  state: "visible",
5231
5483
  timeout: config.sourceImageVisibleTimeoutMs
5232
5484
  });
5233
- await dragCaptchaAction(page, sourceImage, dropTarget);
5485
+ const dragResult = await dragPromptCaptchaImage(
5486
+ page,
5487
+ frame,
5488
+ iframeLocator,
5489
+ sourceImage,
5490
+ dropTarget,
5491
+ config,
5492
+ {
5493
+ attempt,
5494
+ visualIndex: imageIndex
5495
+ }
5496
+ );
5497
+ if (!dragResult.accepted) {
5498
+ throw new Error(`Captcha prompt drag was not accepted for visual index ${imageIndex + 1}.`);
5499
+ }
5500
+ if (config.dragBetweenWaitMs > 0) {
5501
+ await page.waitForTimeout(config.dragBetweenWaitMs);
5502
+ }
5234
5503
  }
5235
- const submitted = await clickCaptchaAction(frame, config.submitTexts, { ...config, page }).catch(() => false);
5504
+ const beforeSubmitState = await readPromptCaptchaState(frame, config);
5505
+ logger10.info(
5506
+ `\u63D0\u4EA4\u524D\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${beforeSubmitState.badgeCount}, selected=${beforeSubmitState.selectedCount}, submitDisabled=${beforeSubmitState.submitDisabled}`
5507
+ );
5508
+ const submitted = await clickCaptchaAction(frame, config.submitTexts, {
5509
+ ...config,
5510
+ page,
5511
+ logger: logger10,
5512
+ forceMouse: true,
5513
+ actionVisibleTimeoutMs: config.submitReadyTimeoutMs
5514
+ }).catch(() => false);
5236
5515
  if (!submitted) {
5237
5516
  logger10.warn("\u672A\u627E\u5230\u63D0\u4EA4\u6309\u94AE\uFF0C\u53EF\u80FD\u4F1A\u81EA\u52A8\u63D0\u4EA4\u3002");
5238
5517
  }
5239
5518
  await page.waitForTimeout(config.submitWaitMs);
5519
+ const afterSubmitState = await readPromptCaptchaState(frame, config);
5520
+ logger10.info(
5521
+ `\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${afterSubmitState.badgeCount}, selected=${afterSubmitState.selectedCount}, submitDisabled=${afterSubmitState.submitDisabled}`
5522
+ );
5240
5523
  const stillVisible = await iframeLocator.isVisible({ timeout: config.containerVisibleTimeoutMs }).catch(() => false);
5241
5524
  if (!stillVisible) {
5242
5525
  logger10.info("\u9A8C\u8BC1\u7801\u8BC6\u522B\u5E76\u63D0\u4EA4\u6210\u529F\u3002");
5243
5526
  return true;
5244
5527
  }
5528
+ await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, "submit-still-visible", config, {
5529
+ beforeSubmitState,
5530
+ afterSubmitState
5531
+ }).catch((error) => {
5532
+ logger10.warn(`\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
5533
+ });
5245
5534
  logger10.warn("\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801 iframe \u4ECD\u7136\u53EF\u89C1\uFF0C\u51C6\u5907\u5237\u65B0\u540E\u91CD\u8BD5\u3002");
5246
5535
  await page.waitForTimeout(2e3);
5247
5536
  await refreshCaptcha(page, frame, config);
@@ -5684,14 +5973,14 @@ var Mutation = {
5684
5973
  const isFrameElement = tagName === "IFRAME" || tagName === "FRAME";
5685
5974
  const nodeName = descriptor?.id || descriptor?.name || "no-id";
5686
5975
  let source = "main";
5687
- let path2 = `${selector}[${index}]`;
5976
+ let path3 = `${selector}[${index}]`;
5688
5977
  let text = "";
5689
5978
  let html = "";
5690
5979
  let frameUrl = "";
5691
5980
  let readyState = "";
5692
5981
  if (isFrameElement) {
5693
5982
  source = "iframe";
5694
- path2 = `${selector}[${index}]::iframe(${nodeName})`;
5983
+ path3 = `${selector}[${index}]::iframe(${nodeName})`;
5695
5984
  const frame = await handle.contentFrame();
5696
5985
  if (frame) {
5697
5986
  try {
@@ -5721,7 +6010,7 @@ var Mutation = {
5721
6010
  items.push({
5722
6011
  selector,
5723
6012
  source,
5724
- path: path2,
6013
+ path: path3,
5725
6014
  text,
5726
6015
  html,
5727
6016
  frameUrl,