@skrillex1224/playwright-toolkit 2.1.245 → 2.1.246
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 +477 -330
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +477 -330
- package/dist/index.js.map +4 -4
- package/index.d.ts +7 -0
- package/package.json +1 -1
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:
|
|
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(
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
2838
|
-
y:
|
|
2861
|
+
x: sourceCenterX,
|
|
2862
|
+
y: sourceCenterY
|
|
2839
2863
|
},
|
|
2840
2864
|
lift: {
|
|
2841
|
-
x:
|
|
2842
|
-
y:
|
|
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
|
};
|
|
@@ -3411,11 +3435,6 @@ var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
|
|
|
3411
3435
|
var INTERACTIVE_SELECTOR = [
|
|
3412
3436
|
"button",
|
|
3413
3437
|
'[role="button"]',
|
|
3414
|
-
'[role="link"]',
|
|
3415
|
-
'[role="menuitem"]',
|
|
3416
|
-
'[role="tab"]',
|
|
3417
|
-
'[role="switch"]',
|
|
3418
|
-
'[role="checkbox"]',
|
|
3419
3438
|
"a[href]",
|
|
3420
3439
|
"label",
|
|
3421
3440
|
"input",
|
|
@@ -3430,34 +3449,6 @@ var EDITABLE_SELECTOR = [
|
|
|
3430
3449
|
"textarea",
|
|
3431
3450
|
'[contenteditable="true"]'
|
|
3432
3451
|
].join(",");
|
|
3433
|
-
var INTERACTIVE_HINT_PATTERN = [
|
|
3434
|
-
"btn",
|
|
3435
|
-
"button",
|
|
3436
|
-
"send",
|
|
3437
|
-
"submit",
|
|
3438
|
-
"confirm",
|
|
3439
|
-
"cancel",
|
|
3440
|
-
"retry",
|
|
3441
|
-
"reload",
|
|
3442
|
-
"search",
|
|
3443
|
-
"copy",
|
|
3444
|
-
"share",
|
|
3445
|
-
"close",
|
|
3446
|
-
"more",
|
|
3447
|
-
"\u53D1\u9001",
|
|
3448
|
-
"\u63D0\u4EA4",
|
|
3449
|
-
"\u786E\u5B9A",
|
|
3450
|
-
"\u786E\u8BA4",
|
|
3451
|
-
"\u53D6\u6D88",
|
|
3452
|
-
"\u91CD\u8BD5",
|
|
3453
|
-
"\u641C\u7D22",
|
|
3454
|
-
"\u590D\u5236",
|
|
3455
|
-
"\u5206\u4EAB",
|
|
3456
|
-
"\u5173\u95ED",
|
|
3457
|
-
"\u66F4\u591A",
|
|
3458
|
-
"\u5C55\u5F00",
|
|
3459
|
-
"\u6536\u8D77"
|
|
3460
|
-
].join("|");
|
|
3461
3452
|
var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
3462
3453
|
var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
|
|
3463
3454
|
var describeTarget = (target) => {
|
|
@@ -3497,47 +3488,7 @@ var withTimeout = async (operation, timeoutMs, label) => {
|
|
|
3497
3488
|
}
|
|
3498
3489
|
};
|
|
3499
3490
|
var checkElementVisibility = async (element) => {
|
|
3500
|
-
return element.evaluate((el,
|
|
3501
|
-
const interactiveHintRe = new RegExp(interactiveHintPattern, "i");
|
|
3502
|
-
const nodeClassName = (node) => {
|
|
3503
|
-
if (!node) return "";
|
|
3504
|
-
if (typeof node.className === "string") return node.className;
|
|
3505
|
-
if (node.className && typeof node.className.baseVal === "string") return node.className.baseVal;
|
|
3506
|
-
return "";
|
|
3507
|
-
};
|
|
3508
|
-
const interactiveScore = (node) => {
|
|
3509
|
-
if (!node || node.nodeType !== Node.ELEMENT_NODE) return 0;
|
|
3510
|
-
let score = 0;
|
|
3511
|
-
if (typeof node.matches === "function" && node.matches(interactiveSelector)) score += 8;
|
|
3512
|
-
const hints = [
|
|
3513
|
-
node.id || "",
|
|
3514
|
-
nodeClassName(node),
|
|
3515
|
-
node.getAttribute?.("aria-label") || "",
|
|
3516
|
-
node.getAttribute?.("title") || "",
|
|
3517
|
-
node.getAttribute?.("data-testid") || "",
|
|
3518
|
-
node.getAttribute?.("data-test") || "",
|
|
3519
|
-
node.getAttribute?.("data-click") || "",
|
|
3520
|
-
node.getAttribute?.("data-action") || "",
|
|
3521
|
-
node.getAttribute?.("onclick") || "",
|
|
3522
|
-
String(node.textContent || "").trim().slice(0, 24)
|
|
3523
|
-
].join(" ");
|
|
3524
|
-
if (interactiveHintRe.test(hints)) score += 4;
|
|
3525
|
-
const style = window.getComputedStyle(node);
|
|
3526
|
-
if (style?.cursor === "pointer") score += 2;
|
|
3527
|
-
return score;
|
|
3528
|
-
};
|
|
3529
|
-
const closestInteractive = (node) => {
|
|
3530
|
-
let best = null;
|
|
3531
|
-
let bestScore = 0;
|
|
3532
|
-
for (let current = node; current && current !== document.body; current = current.parentElement) {
|
|
3533
|
-
const score = interactiveScore(current);
|
|
3534
|
-
if (score > bestScore || score > 0 && score === bestScore) {
|
|
3535
|
-
best = current;
|
|
3536
|
-
bestScore = score;
|
|
3537
|
-
}
|
|
3538
|
-
}
|
|
3539
|
-
return best;
|
|
3540
|
-
};
|
|
3491
|
+
return element.evaluate((el, interactiveSelector) => {
|
|
3541
3492
|
const targetStyle = window.getComputedStyle(el);
|
|
3542
3493
|
if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
|
|
3543
3494
|
return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
|
|
@@ -3599,13 +3550,16 @@ var checkElementVisibility = async (element) => {
|
|
|
3599
3550
|
positioning
|
|
3600
3551
|
};
|
|
3601
3552
|
}
|
|
3602
|
-
const targetInteractive =
|
|
3553
|
+
const targetInteractive = typeof el.closest === "function" ? el.closest(interactiveSelector) : null;
|
|
3603
3554
|
const sameInteractiveTarget = (pointElement) => {
|
|
3604
3555
|
if (!pointElement) return false;
|
|
3605
3556
|
if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
|
|
3606
3557
|
return true;
|
|
3607
3558
|
}
|
|
3608
|
-
|
|
3559
|
+
if (!targetInteractive || typeof pointElement.closest !== "function") {
|
|
3560
|
+
return false;
|
|
3561
|
+
}
|
|
3562
|
+
return pointElement.closest(interactiveSelector) === targetInteractive;
|
|
3609
3563
|
};
|
|
3610
3564
|
const describeElement = (node) => {
|
|
3611
3565
|
if (!node) return null;
|
|
@@ -3652,53 +3606,10 @@ var checkElementVisibility = async (element) => {
|
|
|
3652
3606
|
};
|
|
3653
3607
|
}
|
|
3654
3608
|
return { code: "VISIBLE", isFixed, positioning };
|
|
3655
|
-
},
|
|
3656
|
-
interactiveSelector: INTERACTIVE_SELECTOR,
|
|
3657
|
-
interactiveHintPattern: INTERACTIVE_HINT_PATTERN
|
|
3658
|
-
});
|
|
3609
|
+
}, INTERACTIVE_SELECTOR);
|
|
3659
3610
|
};
|
|
3660
3611
|
var resolveSafeTapPoint = async (element) => {
|
|
3661
|
-
return element.evaluate((el,
|
|
3662
|
-
const interactiveHintRe = new RegExp(interactiveHintPattern, "i");
|
|
3663
|
-
const nodeClassName = (node) => {
|
|
3664
|
-
if (!node) return "";
|
|
3665
|
-
if (typeof node.className === "string") return node.className;
|
|
3666
|
-
if (node.className && typeof node.className.baseVal === "string") return node.className.baseVal;
|
|
3667
|
-
return "";
|
|
3668
|
-
};
|
|
3669
|
-
const interactiveScore = (node) => {
|
|
3670
|
-
if (!node || node.nodeType !== Node.ELEMENT_NODE) return 0;
|
|
3671
|
-
let score = 0;
|
|
3672
|
-
if (typeof node.matches === "function" && node.matches(interactiveSelector)) score += 8;
|
|
3673
|
-
const hints = [
|
|
3674
|
-
node.id || "",
|
|
3675
|
-
nodeClassName(node),
|
|
3676
|
-
node.getAttribute?.("aria-label") || "",
|
|
3677
|
-
node.getAttribute?.("title") || "",
|
|
3678
|
-
node.getAttribute?.("data-testid") || "",
|
|
3679
|
-
node.getAttribute?.("data-test") || "",
|
|
3680
|
-
node.getAttribute?.("data-click") || "",
|
|
3681
|
-
node.getAttribute?.("data-action") || "",
|
|
3682
|
-
node.getAttribute?.("onclick") || "",
|
|
3683
|
-
String(node.textContent || "").trim().slice(0, 24)
|
|
3684
|
-
].join(" ");
|
|
3685
|
-
if (interactiveHintRe.test(hints)) score += 4;
|
|
3686
|
-
const style = window.getComputedStyle(node);
|
|
3687
|
-
if (style?.cursor === "pointer") score += 2;
|
|
3688
|
-
return score;
|
|
3689
|
-
};
|
|
3690
|
-
const closestInteractive = (node) => {
|
|
3691
|
-
let best = null;
|
|
3692
|
-
let bestScore = 0;
|
|
3693
|
-
for (let current = node; current && current !== document.body; current = current.parentElement) {
|
|
3694
|
-
const score = interactiveScore(current);
|
|
3695
|
-
if (score > bestScore || score > 0 && score === bestScore) {
|
|
3696
|
-
best = current;
|
|
3697
|
-
bestScore = score;
|
|
3698
|
-
}
|
|
3699
|
-
}
|
|
3700
|
-
return best;
|
|
3701
|
-
};
|
|
3612
|
+
return element.evaluate((el, interactiveSelector) => {
|
|
3702
3613
|
const rect = el.getBoundingClientRect();
|
|
3703
3614
|
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
3704
3615
|
return null;
|
|
@@ -3735,13 +3646,16 @@ var resolveSafeTapPoint = async (element) => {
|
|
|
3735
3646
|
if (visibleWidth <= 1 || visibleHeight <= 1) {
|
|
3736
3647
|
return null;
|
|
3737
3648
|
}
|
|
3738
|
-
const targetInteractive =
|
|
3649
|
+
const targetInteractive = typeof el.closest === "function" ? el.closest(interactiveSelector) : null;
|
|
3739
3650
|
const sameInteractiveTarget = (pointElement) => {
|
|
3740
3651
|
if (!pointElement) return false;
|
|
3741
3652
|
if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
|
|
3742
3653
|
return true;
|
|
3743
3654
|
}
|
|
3744
|
-
|
|
3655
|
+
if (!targetInteractive || typeof pointElement.closest !== "function") {
|
|
3656
|
+
return false;
|
|
3657
|
+
}
|
|
3658
|
+
return pointElement.closest(interactiveSelector) === targetInteractive;
|
|
3745
3659
|
};
|
|
3746
3660
|
const cx = visibleLeft + visibleWidth / 2;
|
|
3747
3661
|
const cy = visibleTop + visibleHeight / 2;
|
|
@@ -3769,53 +3683,14 @@ var resolveSafeTapPoint = async (element) => {
|
|
|
3769
3683
|
x: chosen.x,
|
|
3770
3684
|
y: chosen.y
|
|
3771
3685
|
};
|
|
3772
|
-
},
|
|
3773
|
-
interactiveSelector: INTERACTIVE_SELECTOR,
|
|
3774
|
-
interactiveHintPattern: INTERACTIVE_HINT_PATTERN
|
|
3775
|
-
});
|
|
3686
|
+
}, INTERACTIVE_SELECTOR);
|
|
3776
3687
|
};
|
|
3777
3688
|
var activateElementFallback = async (element, point = null, options = {}) => {
|
|
3778
|
-
return element.evaluate((el, { innerPoint, innerOptions, interactiveSelector, editableSelector
|
|
3779
|
-
const interactiveHintRe = new RegExp(interactiveHintPattern, "i");
|
|
3780
|
-
const nodeClassName = (node) => {
|
|
3781
|
-
if (!node) return "";
|
|
3782
|
-
if (typeof node.className === "string") return node.className;
|
|
3783
|
-
if (node.className && typeof node.className.baseVal === "string") return node.className.baseVal;
|
|
3784
|
-
return "";
|
|
3785
|
-
};
|
|
3786
|
-
const interactiveScore = (node) => {
|
|
3787
|
-
if (!node || node.nodeType !== Node.ELEMENT_NODE) return 0;
|
|
3788
|
-
let score = 0;
|
|
3789
|
-
if (typeof node.matches === "function" && node.matches(interactiveSelector)) score += 8;
|
|
3790
|
-
const hints = [
|
|
3791
|
-
node.id || "",
|
|
3792
|
-
nodeClassName(node),
|
|
3793
|
-
node.getAttribute?.("aria-label") || "",
|
|
3794
|
-
node.getAttribute?.("title") || "",
|
|
3795
|
-
node.getAttribute?.("data-testid") || "",
|
|
3796
|
-
node.getAttribute?.("data-test") || "",
|
|
3797
|
-
node.getAttribute?.("data-click") || "",
|
|
3798
|
-
node.getAttribute?.("data-action") || "",
|
|
3799
|
-
node.getAttribute?.("onclick") || "",
|
|
3800
|
-
String(node.textContent || "").trim().slice(0, 24)
|
|
3801
|
-
].join(" ");
|
|
3802
|
-
if (interactiveHintRe.test(hints)) score += 4;
|
|
3803
|
-
const style = window.getComputedStyle(node);
|
|
3804
|
-
if (style?.cursor === "pointer") score += 2;
|
|
3805
|
-
return score;
|
|
3806
|
-
};
|
|
3689
|
+
return element.evaluate((el, { innerPoint, innerOptions, interactiveSelector, editableSelector }) => {
|
|
3807
3690
|
const pointElement = innerPoint ? document.elementFromPoint(innerPoint.x, innerPoint.y) : null;
|
|
3808
3691
|
const nearestInteractive = (node) => {
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
for (let current = node; current && current !== document.body; current = current.parentElement) {
|
|
3812
|
-
const score = interactiveScore(current);
|
|
3813
|
-
if (score > bestScore || score > 0 && score === bestScore) {
|
|
3814
|
-
best = current;
|
|
3815
|
-
bestScore = score;
|
|
3816
|
-
}
|
|
3817
|
-
}
|
|
3818
|
-
return best;
|
|
3692
|
+
if (!node || typeof node.closest !== "function") return null;
|
|
3693
|
+
return node.closest(interactiveSelector);
|
|
3819
3694
|
};
|
|
3820
3695
|
const targetInteractive = nearestInteractive(el);
|
|
3821
3696
|
const pointInteractive = nearestInteractive(pointElement);
|
|
@@ -3847,8 +3722,7 @@ var activateElementFallback = async (element, point = null, options = {}) => {
|
|
|
3847
3722
|
innerPoint: point,
|
|
3848
3723
|
innerOptions: options || {},
|
|
3849
3724
|
interactiveSelector: INTERACTIVE_SELECTOR,
|
|
3850
|
-
editableSelector: EDITABLE_SELECTOR
|
|
3851
|
-
interactiveHintPattern: INTERACTIVE_HINT_PATTERN
|
|
3725
|
+
editableSelector: EDITABLE_SELECTOR
|
|
3852
3726
|
});
|
|
3853
3727
|
};
|
|
3854
3728
|
var getScrollableRect = async (element) => {
|
|
@@ -3952,47 +3826,6 @@ var scrollAwayFromObstruction = async (element, status) => {
|
|
|
3952
3826
|
};
|
|
3953
3827
|
}, status);
|
|
3954
3828
|
};
|
|
3955
|
-
var getElementViewportSnapshot = async (element) => {
|
|
3956
|
-
return element.evaluate((el) => {
|
|
3957
|
-
const rect = el.getBoundingClientRect();
|
|
3958
|
-
return {
|
|
3959
|
-
top: rect.top,
|
|
3960
|
-
bottom: rect.bottom,
|
|
3961
|
-
left: rect.left,
|
|
3962
|
-
right: rect.right,
|
|
3963
|
-
width: rect.width,
|
|
3964
|
-
height: rect.height,
|
|
3965
|
-
scrollX: window.scrollX,
|
|
3966
|
-
scrollY: window.scrollY
|
|
3967
|
-
};
|
|
3968
|
-
});
|
|
3969
|
-
};
|
|
3970
|
-
var isTargetImmobileAfterScroll = (before, after) => {
|
|
3971
|
-
if (!before || !after) return false;
|
|
3972
|
-
const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
|
|
3973
|
-
const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
|
|
3974
|
-
const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
|
|
3975
|
-
const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
|
|
3976
|
-
const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
|
|
3977
|
-
const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
|
|
3978
|
-
if (!rectMoved && !pageMoved) return true;
|
|
3979
|
-
if (pageMoved && !rectMoved) return true;
|
|
3980
|
-
if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
|
|
3981
|
-
return true;
|
|
3982
|
-
}
|
|
3983
|
-
return false;
|
|
3984
|
-
};
|
|
3985
|
-
var restoreWindowFromSnapshot = async (page, before, after) => {
|
|
3986
|
-
if (!before || !after) return;
|
|
3987
|
-
if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
|
|
3988
|
-
return;
|
|
3989
|
-
}
|
|
3990
|
-
await page.evaluate(
|
|
3991
|
-
(state) => window.scrollTo(state.x, state.y),
|
|
3992
|
-
{ x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
|
|
3993
|
-
).catch(() => {
|
|
3994
|
-
});
|
|
3995
|
-
};
|
|
3996
3829
|
var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
|
|
3997
3830
|
const viewport = resolveViewport(page);
|
|
3998
3831
|
const rawRect = options.rect || null;
|
|
@@ -4173,11 +4006,11 @@ var MobileHumanize = {
|
|
|
4173
4006
|
const scrollRect = await getScrollableRect(element);
|
|
4174
4007
|
if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
|
|
4175
4008
|
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"})`);
|
|
4176
|
-
return { element, didScroll, restore: null
|
|
4009
|
+
return { element, didScroll, restore: null };
|
|
4177
4010
|
}
|
|
4178
4011
|
if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
|
|
4179
4012
|
logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
|
|
4180
|
-
return { element, didScroll, restore: null
|
|
4013
|
+
return { element, didScroll, restore: null };
|
|
4181
4014
|
}
|
|
4182
4015
|
if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
|
|
4183
4016
|
const moved = await scrollAwayFromObstruction(element, status);
|
|
@@ -4208,7 +4041,6 @@ var MobileHumanize = {
|
|
|
4208
4041
|
}
|
|
4209
4042
|
}
|
|
4210
4043
|
const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
|
|
4211
|
-
const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
|
|
4212
4044
|
const beforeState = scrollRect ? await element.evaluate((el) => {
|
|
4213
4045
|
const isScrollable = (node) => {
|
|
4214
4046
|
const style = window.getComputedStyle(node);
|
|
@@ -4238,21 +4070,6 @@ var MobileHumanize = {
|
|
|
4238
4070
|
logger7.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
|
|
4239
4071
|
}
|
|
4240
4072
|
}
|
|
4241
|
-
let afterElementSnapshot = null;
|
|
4242
|
-
const readAfterElementSnapshot = async () => {
|
|
4243
|
-
if (!afterElementSnapshot) {
|
|
4244
|
-
afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
|
|
4245
|
-
}
|
|
4246
|
-
return afterElementSnapshot;
|
|
4247
|
-
};
|
|
4248
|
-
if (!scrollRect && beforeElementSnapshot) {
|
|
4249
|
-
const afterSnapshot = await readAfterElementSnapshot();
|
|
4250
|
-
if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
|
|
4251
|
-
await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
|
|
4252
|
-
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"})`);
|
|
4253
|
-
return { element, didScroll, restore: null, unscrollable: true };
|
|
4254
|
-
}
|
|
4255
|
-
}
|
|
4256
4073
|
if (scrollRect && beforeState) {
|
|
4257
4074
|
const afterState = await element.evaluate((el) => {
|
|
4258
4075
|
const isScrollable = (node) => {
|
|
@@ -4290,14 +4107,6 @@ var MobileHumanize = {
|
|
|
4290
4107
|
}
|
|
4291
4108
|
}
|
|
4292
4109
|
}
|
|
4293
|
-
if (scrollRect && beforeElementSnapshot) {
|
|
4294
|
-
const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
|
|
4295
|
-
if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
|
|
4296
|
-
await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
|
|
4297
|
-
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"})`);
|
|
4298
|
-
return { element, didScroll, restore: null, unscrollable: true };
|
|
4299
|
-
}
|
|
4300
|
-
}
|
|
4301
4110
|
didScroll = true;
|
|
4302
4111
|
}
|
|
4303
4112
|
try {
|
|
@@ -4347,28 +4156,21 @@ var MobileHumanize = {
|
|
|
4347
4156
|
logger7.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
|
|
4348
4157
|
return false;
|
|
4349
4158
|
}
|
|
4350
|
-
|
|
4159
|
+
if (scrollIfNeeded) {
|
|
4160
|
+
await MobileHumanize.humanScroll(page, element, { throwOnMissing });
|
|
4161
|
+
}
|
|
4351
4162
|
const status = await checkElementVisibility(element).catch(() => null);
|
|
4352
4163
|
if (status && status.code !== "VISIBLE") {
|
|
4353
|
-
if (fallbackDomClick &&
|
|
4354
|
-
|
|
4164
|
+
if (fallbackDomClick && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
|
|
4165
|
+
const fallback = await withTimeout(
|
|
4355
4166
|
() => activateElementFallback(element, null, {
|
|
4356
4167
|
editableOnly: true
|
|
4357
4168
|
}),
|
|
4358
4169
|
activateFallbackTimeoutMs,
|
|
4359
4170
|
"focus fallback"
|
|
4360
4171
|
).catch(() => null);
|
|
4361
|
-
if (!fallback?.activated) {
|
|
4362
|
-
fallback = await withTimeout(
|
|
4363
|
-
() => activateElementFallback(element, null, {
|
|
4364
|
-
editableOnly: false
|
|
4365
|
-
}),
|
|
4366
|
-
activateFallbackTimeoutMs,
|
|
4367
|
-
"activation fallback"
|
|
4368
|
-
).catch(() => null);
|
|
4369
|
-
}
|
|
4370
4172
|
if (fallback?.activated) {
|
|
4371
|
-
logger7.warn(`humanClick: \
|
|
4173
|
+
logger7.warn(`humanClick: fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
|
|
4372
4174
|
return true;
|
|
4373
4175
|
}
|
|
4374
4176
|
}
|
|
@@ -4485,20 +4287,6 @@ var MobileHumanize = {
|
|
|
4485
4287
|
const locator = page.locator(selector);
|
|
4486
4288
|
await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
|
|
4487
4289
|
await waitJitter(160, 0.4);
|
|
4488
|
-
const readValue = async () => {
|
|
4489
|
-
try {
|
|
4490
|
-
return await locator.inputValue({ timeout: 600 });
|
|
4491
|
-
} catch {
|
|
4492
|
-
return await locator.evaluate((el) => "value" in el ? String(el.value || "") : String(el.textContent || "")).catch(() => "");
|
|
4493
|
-
}
|
|
4494
|
-
};
|
|
4495
|
-
const currentValue = await readValue();
|
|
4496
|
-
if (!currentValue) return;
|
|
4497
|
-
await page.keyboard.press("ControlOrMeta+A");
|
|
4498
|
-
await waitJitter(90, 0.35);
|
|
4499
|
-
await page.keyboard.press("Backspace");
|
|
4500
|
-
await waitJitter(120, 0.35);
|
|
4501
|
-
if (!await readValue()) return;
|
|
4502
4290
|
await locator.evaluate((el) => {
|
|
4503
4291
|
if ("value" in el) {
|
|
4504
4292
|
el.value = "";
|
|
@@ -5088,6 +4876,10 @@ var LiveView = {
|
|
|
5088
4876
|
// src/chaptcha.js
|
|
5089
4877
|
import { v4 as uuidv4 } from "uuid";
|
|
5090
4878
|
|
|
4879
|
+
// src/internals/captcha/bytedance.js
|
|
4880
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
4881
|
+
import path2 from "path";
|
|
4882
|
+
|
|
5091
4883
|
// src/internals/captcha/shared.js
|
|
5092
4884
|
var waitForVisible = async (locator, timeout) => {
|
|
5093
4885
|
try {
|
|
@@ -5105,38 +4897,71 @@ var isAnyCaptchaTextVisible = async (frame, texts, timeout) => {
|
|
|
5105
4897
|
if (!text) {
|
|
5106
4898
|
continue;
|
|
5107
4899
|
}
|
|
5108
|
-
const
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
4900
|
+
const textLocator = frame.getByText(text, { exact: false });
|
|
4901
|
+
const locatorText = frame.locator(`text=${text}`);
|
|
4902
|
+
const candidateGroups = [textLocator, locatorText];
|
|
4903
|
+
for (const candidateGroup of candidateGroups) {
|
|
4904
|
+
const candidateCount = await candidateGroup.count().catch(() => 0);
|
|
4905
|
+
for (let index = 0; index < candidateCount; index += 1) {
|
|
4906
|
+
const candidate = candidateGroup.nth(index);
|
|
4907
|
+
const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
|
|
4908
|
+
if (isVisible) {
|
|
4909
|
+
return true;
|
|
4910
|
+
}
|
|
5116
4911
|
}
|
|
5117
4912
|
}
|
|
5118
4913
|
}
|
|
5119
4914
|
return false;
|
|
5120
4915
|
};
|
|
4916
|
+
var collectVisibleCandidateIndexes = async (candidateGroup, count, timeout) => {
|
|
4917
|
+
const visibleIndexes = [];
|
|
4918
|
+
for (let index = 0; index < count; index += 1) {
|
|
4919
|
+
const candidate = candidateGroup.nth(index);
|
|
4920
|
+
const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
|
|
4921
|
+
if (isVisible) {
|
|
4922
|
+
visibleIndexes.push(index);
|
|
4923
|
+
}
|
|
4924
|
+
}
|
|
4925
|
+
return visibleIndexes;
|
|
4926
|
+
};
|
|
5121
4927
|
var clickCaptchaAction = async (frame, texts, options) => {
|
|
5122
4928
|
for (const text of texts || []) {
|
|
5123
|
-
const
|
|
5124
|
-
|
|
5125
|
-
|
|
4929
|
+
const textLocator = frame.getByText(text, { exact: false });
|
|
4930
|
+
const locatorText = frame.locator(`text=${text}`);
|
|
4931
|
+
const [getByTextCount, locatorTextCount] = await Promise.all([
|
|
4932
|
+
textLocator.count().catch(() => 0),
|
|
4933
|
+
locatorText.count().catch(() => 0)
|
|
4934
|
+
]);
|
|
4935
|
+
const [getByTextVisibleIndexes, locatorTextVisibleIndexes] = await Promise.all([
|
|
4936
|
+
collectVisibleCandidateIndexes(textLocator, getByTextCount, options.actionVisibleTimeoutMs),
|
|
4937
|
+
collectVisibleCandidateIndexes(locatorText, locatorTextCount, options.actionVisibleTimeoutMs)
|
|
4938
|
+
]);
|
|
4939
|
+
options.logger?.info(
|
|
4940
|
+
`[CaptchaAction] \u6587\u672C "${text}" \u547D\u4E2D\u6570\u91CF\uFF1AgetByText=${getByTextCount} (visible=${getByTextVisibleIndexes.length}), locator=${locatorTextCount} (visible=${locatorTextVisibleIndexes.length})`
|
|
4941
|
+
);
|
|
4942
|
+
const candidateGroups = [
|
|
4943
|
+
{ label: "getByText", locator: textLocator, count: getByTextCount },
|
|
4944
|
+
{ label: "locator", locator: locatorText, count: locatorTextCount }
|
|
5126
4945
|
];
|
|
5127
|
-
for (const
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
4946
|
+
for (const candidateGroup of candidateGroups) {
|
|
4947
|
+
for (let index = 0; index < candidateGroup.count; index += 1) {
|
|
4948
|
+
const candidate = candidateGroup.locator.nth(index);
|
|
4949
|
+
const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
|
|
4950
|
+
if (!isVisible) {
|
|
4951
|
+
continue;
|
|
4952
|
+
}
|
|
4953
|
+
options.logger?.info(
|
|
4954
|
+
`[CaptchaAction] \u6587\u672C "${text}" \u9009\u62E9 ${candidateGroup.label}[${index}] \u4F5C\u4E3A\u7B2C\u4E00\u4E2A\u53EF\u89C1\u8282\u70B9\u6267\u884C\u70B9\u51FB\u3002`
|
|
4955
|
+
);
|
|
4956
|
+
await DeviceInput.click(options.page, candidate, options);
|
|
4957
|
+
return true;
|
|
5131
4958
|
}
|
|
5132
|
-
await DeviceInput.click(options.page, candidate);
|
|
5133
|
-
return true;
|
|
5134
4959
|
}
|
|
5135
4960
|
}
|
|
5136
4961
|
return false;
|
|
5137
4962
|
};
|
|
5138
|
-
var dragCaptchaAction = async (page, sourceLocator, targetLocator) => {
|
|
5139
|
-
await DeviceInput.drag(page, sourceLocator, targetLocator);
|
|
4963
|
+
var dragCaptchaAction = async (page, sourceLocator, targetLocator, options = {}) => {
|
|
4964
|
+
await DeviceInput.drag(page, sourceLocator, targetLocator, options);
|
|
5140
4965
|
};
|
|
5141
4966
|
|
|
5142
4967
|
// src/internals/captcha/bytedance.js
|
|
@@ -5147,11 +4972,12 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
|
|
|
5147
4972
|
containerSelector: "#captcha_container",
|
|
5148
4973
|
iframeSelector: 'iframe[src*="verifycenter"]',
|
|
5149
4974
|
iframeFallbackSelector: "iframe",
|
|
5150
|
-
sourceImageSelector: "
|
|
5151
|
-
dropTargetContainerSelector: "
|
|
4975
|
+
sourceImageSelector: ".img-container .canvas-container",
|
|
4976
|
+
dropTargetContainerSelector: ".drag-area",
|
|
5152
4977
|
dropTargetTexts: ["\u62D6\u62FD\u5230\u8FD9\u91CC"],
|
|
5153
4978
|
refreshTexts: ["\u5237\u65B0"],
|
|
5154
4979
|
submitTexts: ["\u63D0\u4EA4"],
|
|
4980
|
+
guideMaskSelector: ".play-guide-mask",
|
|
5155
4981
|
recognitionSuccessCode: 1e4,
|
|
5156
4982
|
containerVisibleTimeoutMs: 2e3,
|
|
5157
4983
|
iframeVisibleTimeoutMs: 12e3,
|
|
@@ -5174,10 +5000,111 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
|
|
|
5174
5000
|
],
|
|
5175
5001
|
recognitionDelayMs: 2e3,
|
|
5176
5002
|
refreshWaitMs: 3e3,
|
|
5177
|
-
submitWaitMs:
|
|
5003
|
+
submitWaitMs: 5e3,
|
|
5004
|
+
submitReadyTimeoutMs: 2500,
|
|
5178
5005
|
retryDelayBaseMs: 2e3,
|
|
5179
|
-
retryDelayStepMs: 1e3
|
|
5006
|
+
retryDelayStepMs: 1e3,
|
|
5007
|
+
sourceImageRowTolerancePx: 24,
|
|
5008
|
+
dragBetweenWaitMs: 250,
|
|
5009
|
+
promptBadgeCountSelector: ".drag-area .photo-badge .badge span",
|
|
5010
|
+
promptSubmitButtonSelector: ".vc-captcha-verify-mobile-button",
|
|
5011
|
+
promptSelectedSourceSelector: ".img-container .canvas-container.selected",
|
|
5012
|
+
promptActiveSourceSelector: ".img-container .canvas-container.active",
|
|
5013
|
+
promptDragMoveSteps: 16,
|
|
5014
|
+
promptDragStepDelayMs: 55,
|
|
5015
|
+
promptDragHoldDelayMs: 240,
|
|
5016
|
+
promptDragBeforeReleaseDelayMs: 180,
|
|
5017
|
+
promptDragAfterReleaseDelayMs: 240,
|
|
5018
|
+
promptDragFinalMoveRepeats: 3,
|
|
5019
|
+
promptDragRetryDelayMs: 250,
|
|
5020
|
+
debugArtifacts: false
|
|
5180
5021
|
});
|
|
5022
|
+
var PROMPT_CAPTCHA_DRAG_PLANS = Object.freeze([
|
|
5023
|
+
{ name: "lower-middle", endXRatio: 0.5, endYRatio: 0.72 },
|
|
5024
|
+
{ name: "center-middle", endXRatio: 0.5, endYRatio: 0.56 },
|
|
5025
|
+
{ name: "upper-middle", endXRatio: 0.5, endYRatio: 0.4 }
|
|
5026
|
+
]);
|
|
5027
|
+
var resolveCaptchaDebugDir = () => path2.resolve(process.cwd(), "storage", "captcha-debug");
|
|
5028
|
+
var rectOf = (rect) => {
|
|
5029
|
+
if (!rect) {
|
|
5030
|
+
return null;
|
|
5031
|
+
}
|
|
5032
|
+
return {
|
|
5033
|
+
x: Number(rect.x.toFixed(2)),
|
|
5034
|
+
y: Number(rect.y.toFixed(2)),
|
|
5035
|
+
width: Number(rect.width.toFixed(2)),
|
|
5036
|
+
height: Number(rect.height.toFixed(2))
|
|
5037
|
+
};
|
|
5038
|
+
};
|
|
5039
|
+
var collectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, extra = null) => {
|
|
5040
|
+
const timestamp = Date.now();
|
|
5041
|
+
const debugDir = resolveCaptchaDebugDir();
|
|
5042
|
+
await mkdir(debugDir, { recursive: true });
|
|
5043
|
+
const baseName = `bytedance-${timestamp}-attempt${attempt}-${phase}`;
|
|
5044
|
+
const iframeScreenshotPath = path2.join(debugDir, `${baseName}-iframe.png`);
|
|
5045
|
+
const pageScreenshotPath = path2.join(debugDir, `${baseName}-page.png`);
|
|
5046
|
+
const htmlPath = path2.join(debugDir, `${baseName}-iframe.html`);
|
|
5047
|
+
const infoPath = path2.join(debugDir, `${baseName}-info.json`);
|
|
5048
|
+
await iframeLocator.screenshot({ path: iframeScreenshotPath }).catch(() => {
|
|
5049
|
+
});
|
|
5050
|
+
await page.screenshot({ path: pageScreenshotPath, fullPage: true }).catch(() => {
|
|
5051
|
+
});
|
|
5052
|
+
const html = await frame.evaluate(() => document.documentElement.outerHTML).catch(() => "");
|
|
5053
|
+
if (html) {
|
|
5054
|
+
await writeFile(htmlPath, html, "utf8");
|
|
5055
|
+
}
|
|
5056
|
+
const info = await frame.evaluate(() => {
|
|
5057
|
+
const toRect = (element) => {
|
|
5058
|
+
const rect = element.getBoundingClientRect();
|
|
5059
|
+
return {
|
|
5060
|
+
x: Number(rect.x.toFixed(2)),
|
|
5061
|
+
y: Number(rect.y.toFixed(2)),
|
|
5062
|
+
width: Number(rect.width.toFixed(2)),
|
|
5063
|
+
height: Number(rect.height.toFixed(2))
|
|
5064
|
+
};
|
|
5065
|
+
};
|
|
5066
|
+
const toItem = (element, index) => ({
|
|
5067
|
+
index,
|
|
5068
|
+
tag: element.tagName,
|
|
5069
|
+
id: element.id || "",
|
|
5070
|
+
className: typeof element.className === "string" ? element.className : "",
|
|
5071
|
+
text: String(element.textContent || "").trim(),
|
|
5072
|
+
rect: toRect(element)
|
|
5073
|
+
});
|
|
5074
|
+
const visibleNodes = Array.from(document.querySelectorAll("body *")).map(toItem).filter((item) => item.rect.width > 0 && item.rect.height > 0);
|
|
5075
|
+
return {
|
|
5076
|
+
title: document.title,
|
|
5077
|
+
bodyText: String(document.body?.innerText || "").trim(),
|
|
5078
|
+
canvasContainers: visibleNodes.filter((item) => item.className.includes("canvas-container")),
|
|
5079
|
+
canvasNodes: visibleNodes.filter((item) => item.tag === "CANVAS"),
|
|
5080
|
+
captchaNodes: visibleNodes.filter((item) => item.id.startsWith("captcha_") || item.className.includes("captcha") || item.className.includes("verify") || /拖拽到这里|刷新|提交/.test(item.text)),
|
|
5081
|
+
visibleTextNodes: visibleNodes.filter((item) => item.text).slice(0, 300)
|
|
5082
|
+
};
|
|
5083
|
+
}).catch(() => null);
|
|
5084
|
+
if (info) {
|
|
5085
|
+
const payload = {
|
|
5086
|
+
capturedAt: new Date(timestamp).toISOString(),
|
|
5087
|
+
pageUrl: page.url(),
|
|
5088
|
+
attempt,
|
|
5089
|
+
phase,
|
|
5090
|
+
iframeScreenshotPath,
|
|
5091
|
+
pageScreenshotPath,
|
|
5092
|
+
htmlPath,
|
|
5093
|
+
info
|
|
5094
|
+
};
|
|
5095
|
+
if (extra != null) {
|
|
5096
|
+
payload.extra = extra;
|
|
5097
|
+
}
|
|
5098
|
+
await writeFile(infoPath, JSON.stringify(payload, null, 2), "utf8");
|
|
5099
|
+
}
|
|
5100
|
+
logger10.info(`\u5DF2\u5199\u51FA\u9A8C\u8BC1\u7801\u8C03\u8BD5\u4EA7\u7269\uFF1A${debugDir}`);
|
|
5101
|
+
};
|
|
5102
|
+
var maybeCollectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, options, extra = null) => {
|
|
5103
|
+
if (!options.debugArtifacts) {
|
|
5104
|
+
return;
|
|
5105
|
+
}
|
|
5106
|
+
await collectCaptchaDebugInfo(page, frame, iframeLocator, attempt, phase, extra);
|
|
5107
|
+
};
|
|
5181
5108
|
var extractCaptchaSerialNumbers = (apiResponse) => {
|
|
5182
5109
|
const serialNumbers = apiResponse?.data?.data?.serial_number;
|
|
5183
5110
|
if (!Array.isArray(serialNumbers)) {
|
|
@@ -5186,7 +5113,7 @@ var extractCaptchaSerialNumbers = (apiResponse) => {
|
|
|
5186
5113
|
return serialNumbers.map((value) => Number(value)).filter((value) => Number.isInteger(value) && value >= 0);
|
|
5187
5114
|
};
|
|
5188
5115
|
var resolveContentFrame = async (page, iframeLocator, options) => {
|
|
5189
|
-
for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt
|
|
5116
|
+
for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt += 1) {
|
|
5190
5117
|
const iframeHandle = await iframeLocator.elementHandle();
|
|
5191
5118
|
const frame = await iframeHandle?.contentFrame();
|
|
5192
5119
|
if (frame) {
|
|
@@ -5231,20 +5158,15 @@ var getVerifycenterCaptchaContext = async (page, options) => {
|
|
|
5231
5158
|
}
|
|
5232
5159
|
return { iframeLocator, frame };
|
|
5233
5160
|
};
|
|
5234
|
-
var refreshCaptcha = async (page, frame, options) => {
|
|
5235
|
-
const clicked = await clickCaptchaAction(frame, options.refreshTexts, { ...options, page }).catch(() => false);
|
|
5236
|
-
if (!clicked) {
|
|
5237
|
-
logger10.warn("Refresh button not found.");
|
|
5238
|
-
return false;
|
|
5239
|
-
}
|
|
5240
|
-
await page.waitForTimeout(options.refreshWaitMs);
|
|
5241
|
-
return true;
|
|
5242
|
-
};
|
|
5243
5161
|
var findCaptchaDropTarget = async (frame, options) => {
|
|
5162
|
+
const directTarget = frame.locator(options.dropTargetContainerSelector).first();
|
|
5163
|
+
if (await waitForVisible(directTarget, options.actionVisibleTimeoutMs)) {
|
|
5164
|
+
return directTarget;
|
|
5165
|
+
}
|
|
5244
5166
|
for (const text of options.dropTargetTexts) {
|
|
5245
5167
|
const candidates = [
|
|
5246
|
-
frame.
|
|
5247
|
-
frame.
|
|
5168
|
+
frame.getByText(text, { exact: false }).first(),
|
|
5169
|
+
frame.locator(`text=${text}`).first()
|
|
5248
5170
|
];
|
|
5249
5171
|
for (const candidate of candidates) {
|
|
5250
5172
|
const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
|
|
@@ -5255,10 +5177,112 @@ var findCaptchaDropTarget = async (frame, options) => {
|
|
|
5255
5177
|
}
|
|
5256
5178
|
return null;
|
|
5257
5179
|
};
|
|
5180
|
+
var readPromptCaptchaState = async (frame, options) => frame.evaluate((selectors) => {
|
|
5181
|
+
const toRect = (element) => {
|
|
5182
|
+
if (!element) {
|
|
5183
|
+
return null;
|
|
5184
|
+
}
|
|
5185
|
+
const rect = element.getBoundingClientRect();
|
|
5186
|
+
return {
|
|
5187
|
+
x: Number(rect.x.toFixed(2)),
|
|
5188
|
+
y: Number(rect.y.toFixed(2)),
|
|
5189
|
+
width: Number(rect.width.toFixed(2)),
|
|
5190
|
+
height: Number(rect.height.toFixed(2))
|
|
5191
|
+
};
|
|
5192
|
+
};
|
|
5193
|
+
const badgeNode = document.querySelector(selectors.badgeCountSelector);
|
|
5194
|
+
const submitButton = document.querySelector(selectors.submitButtonSelector);
|
|
5195
|
+
const dragArea = document.querySelector(selectors.dragAreaSelector);
|
|
5196
|
+
const badgeCount = badgeNode ? Number.parseInt(String(badgeNode.textContent || "").trim(), 10) : 0;
|
|
5197
|
+
return {
|
|
5198
|
+
badgeCount: Number.isFinite(badgeCount) ? badgeCount : 0,
|
|
5199
|
+
selectedCount: document.querySelectorAll(selectors.selectedSourceSelector).length,
|
|
5200
|
+
activeCount: document.querySelectorAll(selectors.activeSourceSelector).length,
|
|
5201
|
+
submitDisabled: submitButton ? submitButton.classList.contains("disable") : null,
|
|
5202
|
+
dragAreaActive: dragArea ? dragArea.classList.contains("active") : false,
|
|
5203
|
+
dragAreaRect: toRect(dragArea)
|
|
5204
|
+
};
|
|
5205
|
+
}, {
|
|
5206
|
+
badgeCountSelector: options.promptBadgeCountSelector,
|
|
5207
|
+
submitButtonSelector: options.promptSubmitButtonSelector,
|
|
5208
|
+
selectedSourceSelector: options.promptSelectedSourceSelector,
|
|
5209
|
+
activeSourceSelector: options.promptActiveSourceSelector,
|
|
5210
|
+
dragAreaSelector: options.dropTargetContainerSelector
|
|
5211
|
+
}).catch(() => ({
|
|
5212
|
+
badgeCount: 0,
|
|
5213
|
+
selectedCount: 0,
|
|
5214
|
+
activeCount: 0,
|
|
5215
|
+
submitDisabled: null,
|
|
5216
|
+
dragAreaActive: false,
|
|
5217
|
+
dragAreaRect: null
|
|
5218
|
+
}));
|
|
5219
|
+
var normalizeCaptchaImageIndexes = (serialNumbers, imageCount) => {
|
|
5220
|
+
if (!Array.isArray(serialNumbers) || imageCount <= 0) {
|
|
5221
|
+
return [];
|
|
5222
|
+
}
|
|
5223
|
+
const areAllOneBased = serialNumbers.every((value) => value >= 1 && value <= imageCount);
|
|
5224
|
+
if (areAllOneBased) {
|
|
5225
|
+
return serialNumbers.map((value) => value - 1);
|
|
5226
|
+
}
|
|
5227
|
+
const areAllZeroBased = serialNumbers.every((value) => value >= 0 && value < imageCount);
|
|
5228
|
+
if (areAllZeroBased) {
|
|
5229
|
+
return [...serialNumbers];
|
|
5230
|
+
}
|
|
5231
|
+
return serialNumbers.map((value) => {
|
|
5232
|
+
if (value >= 1 && value <= imageCount) {
|
|
5233
|
+
return value - 1;
|
|
5234
|
+
}
|
|
5235
|
+
if (value >= 0 && value < imageCount) {
|
|
5236
|
+
return value;
|
|
5237
|
+
}
|
|
5238
|
+
return null;
|
|
5239
|
+
}).filter((value) => Number.isInteger(value));
|
|
5240
|
+
};
|
|
5241
|
+
var resolveCaptchaSourceImagesInVisualOrder = async (frame, options) => {
|
|
5242
|
+
const sourceImages = frame.locator(options.sourceImageSelector);
|
|
5243
|
+
const imageCount = await sourceImages.count().catch(() => 0);
|
|
5244
|
+
const sources = [];
|
|
5245
|
+
for (let domIndex = 0; domIndex < imageCount; domIndex += 1) {
|
|
5246
|
+
const locator = sourceImages.nth(domIndex);
|
|
5247
|
+
const box = await locator.boundingBox().catch(() => null);
|
|
5248
|
+
if (!box || box.width <= 0 || box.height <= 0) {
|
|
5249
|
+
continue;
|
|
5250
|
+
}
|
|
5251
|
+
sources.push({
|
|
5252
|
+
domIndex,
|
|
5253
|
+
locator,
|
|
5254
|
+
box
|
|
5255
|
+
});
|
|
5256
|
+
}
|
|
5257
|
+
sources.sort((left, right) => {
|
|
5258
|
+
const deltaY = left.box.y - right.box.y;
|
|
5259
|
+
if (Math.abs(deltaY) > options.sourceImageRowTolerancePx) {
|
|
5260
|
+
return deltaY;
|
|
5261
|
+
}
|
|
5262
|
+
return left.box.x - right.box.x;
|
|
5263
|
+
});
|
|
5264
|
+
return sources;
|
|
5265
|
+
};
|
|
5266
|
+
var refreshCaptcha = async (page, frame, options) => {
|
|
5267
|
+
const clicked = await clickCaptchaAction(frame, options.refreshTexts, {
|
|
5268
|
+
...options,
|
|
5269
|
+
page,
|
|
5270
|
+
logger: logger10,
|
|
5271
|
+
forceMouse: true
|
|
5272
|
+
}).catch(() => false);
|
|
5273
|
+
if (!clicked) {
|
|
5274
|
+
logger10.warn("Refresh button not found.");
|
|
5275
|
+
return false;
|
|
5276
|
+
}
|
|
5277
|
+
await page.waitForTimeout(options.refreshWaitMs);
|
|
5278
|
+
return true;
|
|
5279
|
+
};
|
|
5258
5280
|
var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
5259
5281
|
const deadline = Date.now() + options.challengeReadyTimeoutMs;
|
|
5260
5282
|
let refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
|
|
5261
5283
|
let hasSeenLoading = false;
|
|
5284
|
+
let hasSeenGuideMask = false;
|
|
5285
|
+
let hasLoggedGuideMask = false;
|
|
5262
5286
|
while (Date.now() < deadline) {
|
|
5263
5287
|
const isLoadingVisible = await isAnyCaptchaTextVisible(
|
|
5264
5288
|
frame,
|
|
@@ -5274,8 +5298,17 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
|
5274
5298
|
const sourceImages = frame.locator(options.sourceImageSelector);
|
|
5275
5299
|
const imageCount = await sourceImages.count().catch(() => 0);
|
|
5276
5300
|
const hasVisibleSourceImage = imageCount > 0 ? await sourceImages.first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
|
|
5277
|
-
|
|
5278
|
-
|
|
5301
|
+
const hasVisibleDropTarget = await frame.locator(options.dropTargetContainerSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false);
|
|
5302
|
+
const hasGuideMaskVisible = options.guideMaskSelector ? await frame.locator(options.guideMaskSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
|
|
5303
|
+
hasSeenGuideMask = hasSeenGuideMask || hasGuideMaskVisible;
|
|
5304
|
+
if (hasGuideMaskVisible && !hasLoggedGuideMask) {
|
|
5305
|
+
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");
|
|
5306
|
+
hasLoggedGuideMask = true;
|
|
5307
|
+
}
|
|
5308
|
+
if (!isLoadingVisible && hasVisibleSourceImage && hasVisibleDropTarget && !hasGuideMaskVisible) {
|
|
5309
|
+
logger10.info(
|
|
5310
|
+
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"
|
|
5311
|
+
);
|
|
5279
5312
|
return;
|
|
5280
5313
|
}
|
|
5281
5314
|
if (hasErrorTextVisible) {
|
|
@@ -5285,8 +5318,8 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
|
5285
5318
|
hasSeenLoading = false;
|
|
5286
5319
|
continue;
|
|
5287
5320
|
}
|
|
5288
|
-
if (!hasVisibleSourceImage && Date.now() >= refreshDeadline) {
|
|
5289
|
-
logger10.warn(`\u9A8C\u8BC1\u7801\
|
|
5321
|
+
if ((!hasVisibleSourceImage || !hasVisibleDropTarget) && Date.now() >= refreshDeadline) {
|
|
5322
|
+
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`);
|
|
5290
5323
|
await refreshCaptcha(page, frame, options);
|
|
5291
5324
|
refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
|
|
5292
5325
|
hasSeenLoading = false;
|
|
@@ -5296,6 +5329,69 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
|
5296
5329
|
}
|
|
5297
5330
|
throw new Error("Captcha challenge is still loading and did not become ready in time.");
|
|
5298
5331
|
};
|
|
5332
|
+
var dragPromptCaptchaImage = async (page, frame, iframeLocator, sourceLocator, dropTarget, options, {
|
|
5333
|
+
attempt,
|
|
5334
|
+
visualIndex
|
|
5335
|
+
}) => {
|
|
5336
|
+
const baselineState = await readPromptCaptchaState(frame, options);
|
|
5337
|
+
const dragAttempts = [];
|
|
5338
|
+
for (const plan of PROMPT_CAPTCHA_DRAG_PLANS) {
|
|
5339
|
+
const sourceBox = await sourceLocator.boundingBox().catch(() => null);
|
|
5340
|
+
const targetBox = await dropTarget.boundingBox().catch(() => null);
|
|
5341
|
+
if (!sourceBox || !targetBox) {
|
|
5342
|
+
throw new Error("Unable to resolve prompt captcha drag coordinates.");
|
|
5343
|
+
}
|
|
5344
|
+
const targetOffsetX = (plan.endXRatio - 0.5) * targetBox.width;
|
|
5345
|
+
const targetOffsetY = (plan.endYRatio - 0.5) * targetBox.height;
|
|
5346
|
+
await dragCaptchaAction(page, sourceLocator, dropTarget, {
|
|
5347
|
+
forceMouse: true,
|
|
5348
|
+
targetOffsetX,
|
|
5349
|
+
targetOffsetY,
|
|
5350
|
+
steps: options.promptDragMoveSteps,
|
|
5351
|
+
holdDelayMs: options.promptDragHoldDelayMs,
|
|
5352
|
+
stepDelayMs: options.promptDragStepDelayMs,
|
|
5353
|
+
beforeReleaseDelayMs: options.promptDragBeforeReleaseDelayMs,
|
|
5354
|
+
afterReleaseDelayMs: options.promptDragAfterReleaseDelayMs,
|
|
5355
|
+
finalMoveRepeats: options.promptDragFinalMoveRepeats
|
|
5356
|
+
});
|
|
5357
|
+
const afterState = await readPromptCaptchaState(frame, options);
|
|
5358
|
+
const accepted = afterState.badgeCount > baselineState.badgeCount || afterState.selectedCount > baselineState.selectedCount;
|
|
5359
|
+
const attemptInfo = {
|
|
5360
|
+
planName: plan.name,
|
|
5361
|
+
sourceRect: rectOf(sourceBox),
|
|
5362
|
+
targetRect: rectOf(targetBox),
|
|
5363
|
+
targetOffsetX: Number(targetOffsetX.toFixed(2)),
|
|
5364
|
+
targetOffsetY: Number(targetOffsetY.toFixed(2)),
|
|
5365
|
+
beforeState: baselineState,
|
|
5366
|
+
afterState,
|
|
5367
|
+
accepted
|
|
5368
|
+
};
|
|
5369
|
+
dragAttempts.push(attemptInfo);
|
|
5370
|
+
logger10.info(
|
|
5371
|
+
`\u9A8C\u8BC1\u7801\u62D6\u62FD\u7B2C ${visualIndex + 1} \u5F20\uFF0C\u65B9\u6848 ${plan.name}\uFF0Cbadge ${baselineState.badgeCount} -> ${afterState.badgeCount}\uFF0Cselected ${baselineState.selectedCount} -> ${afterState.selectedCount}`
|
|
5372
|
+
);
|
|
5373
|
+
if (accepted) {
|
|
5374
|
+
return {
|
|
5375
|
+
accepted: true,
|
|
5376
|
+
dragAttempts
|
|
5377
|
+
};
|
|
5378
|
+
}
|
|
5379
|
+
if (options.promptDragRetryDelayMs > 0) {
|
|
5380
|
+
await page.waitForTimeout(options.promptDragRetryDelayMs);
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5383
|
+
await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, `drag-${visualIndex + 1}-failed`, options, {
|
|
5384
|
+
visualIndex,
|
|
5385
|
+
dragAttempts,
|
|
5386
|
+
finalState: await readPromptCaptchaState(frame, options)
|
|
5387
|
+
}).catch((error) => {
|
|
5388
|
+
logger10.warn(`\u9A8C\u8BC1\u7801\u62D6\u62FD\u5931\u8D25\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
5389
|
+
});
|
|
5390
|
+
return {
|
|
5391
|
+
accepted: false,
|
|
5392
|
+
dragAttempts
|
|
5393
|
+
};
|
|
5394
|
+
};
|
|
5299
5395
|
async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
5300
5396
|
const { callCaptchaRecognitionApi: callCaptchaRecognitionApi2 } = dependencies;
|
|
5301
5397
|
if (typeof callCaptchaRecognitionApi2 !== "function") {
|
|
@@ -5310,7 +5406,7 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
|
5310
5406
|
return false;
|
|
5311
5407
|
}
|
|
5312
5408
|
logger10.info("\u5F53\u524D\u4F7F\u7528\u672Ctool\u2014\u2014\u6D4B\u8BD5\u7248\u672C");
|
|
5313
|
-
for (let attempt = 1; attempt <= config.maxRetries; attempt
|
|
5409
|
+
for (let attempt = 1; attempt <= config.maxRetries; attempt += 1) {
|
|
5314
5410
|
logger10.info(`\u5F00\u59CB\u7B2C ${attempt}/${config.maxRetries} \u6B21 verifycenter \u9A8C\u8BC1\u7801\u8BC6\u522B\u3002`);
|
|
5315
5411
|
try {
|
|
5316
5412
|
const captchaContext = await getVerifycenterCaptchaContext(page, config);
|
|
@@ -5320,6 +5416,16 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
|
5320
5416
|
}
|
|
5321
5417
|
const { iframeLocator, frame } = captchaContext;
|
|
5322
5418
|
await waitForCaptchaChallengeReady(page, frame, config);
|
|
5419
|
+
await maybeCollectCaptchaDebugInfo(
|
|
5420
|
+
page,
|
|
5421
|
+
frame,
|
|
5422
|
+
iframeLocator,
|
|
5423
|
+
attempt,
|
|
5424
|
+
"ready",
|
|
5425
|
+
config
|
|
5426
|
+
).catch((error) => {
|
|
5427
|
+
logger10.warn(`\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
5428
|
+
});
|
|
5323
5429
|
await page.waitForTimeout(config.recognitionDelayMs);
|
|
5324
5430
|
const screenshotBuffer = await iframeLocator.screenshot();
|
|
5325
5431
|
const apiResponse = await callCaptchaRecognitionApi2({
|
|
@@ -5343,33 +5449,74 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
|
5343
5449
|
await refreshCaptcha(page, frame, config);
|
|
5344
5450
|
continue;
|
|
5345
5451
|
}
|
|
5346
|
-
const
|
|
5347
|
-
const
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5452
|
+
const orderedSourceImages = await resolveCaptchaSourceImagesInVisualOrder(frame, config);
|
|
5453
|
+
const normalizedIndexes = normalizeCaptchaImageIndexes(serialNumbers, orderedSourceImages.length);
|
|
5454
|
+
if (normalizedIndexes.length !== serialNumbers.length) {
|
|
5455
|
+
throw new Error(
|
|
5456
|
+
`Captcha image indexes could not be normalized. raw=${serialNumbers.join(", ")}, count=${orderedSourceImages.length}`
|
|
5457
|
+
);
|
|
5458
|
+
}
|
|
5459
|
+
logger10.info(`\u9A8C\u8BC1\u7801\u89C6\u89C9\u4F4D\u5E8F\u6620\u5C04\uFF1A${normalizedIndexes.map((index) => index + 1).join(", ")}`);
|
|
5460
|
+
for (const imageIndex of normalizedIndexes) {
|
|
5461
|
+
if (imageIndex < 0 || imageIndex >= orderedSourceImages.length) {
|
|
5462
|
+
throw new Error(
|
|
5463
|
+
`Captcha image index ${imageIndex} is out of range. count=${orderedSourceImages.length}`
|
|
5464
|
+
);
|
|
5355
5465
|
}
|
|
5356
|
-
const sourceImage =
|
|
5466
|
+
const sourceImage = orderedSourceImages[imageIndex].locator;
|
|
5357
5467
|
await sourceImage.waitFor({
|
|
5358
5468
|
state: "visible",
|
|
5359
5469
|
timeout: config.sourceImageVisibleTimeoutMs
|
|
5360
5470
|
});
|
|
5361
|
-
await
|
|
5471
|
+
const dragResult = await dragPromptCaptchaImage(
|
|
5472
|
+
page,
|
|
5473
|
+
frame,
|
|
5474
|
+
iframeLocator,
|
|
5475
|
+
sourceImage,
|
|
5476
|
+
dropTarget,
|
|
5477
|
+
config,
|
|
5478
|
+
{
|
|
5479
|
+
attempt,
|
|
5480
|
+
visualIndex: imageIndex
|
|
5481
|
+
}
|
|
5482
|
+
);
|
|
5483
|
+
if (!dragResult.accepted) {
|
|
5484
|
+
throw new Error(`Captcha prompt drag was not accepted for visual index ${imageIndex + 1}.`);
|
|
5485
|
+
}
|
|
5486
|
+
if (config.dragBetweenWaitMs > 0) {
|
|
5487
|
+
await page.waitForTimeout(config.dragBetweenWaitMs);
|
|
5488
|
+
}
|
|
5362
5489
|
}
|
|
5363
|
-
const
|
|
5490
|
+
const beforeSubmitState = await readPromptCaptchaState(frame, config);
|
|
5491
|
+
logger10.info(
|
|
5492
|
+
`\u63D0\u4EA4\u524D\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${beforeSubmitState.badgeCount}, selected=${beforeSubmitState.selectedCount}, submitDisabled=${beforeSubmitState.submitDisabled}`
|
|
5493
|
+
);
|
|
5494
|
+
const submitted = await clickCaptchaAction(frame, config.submitTexts, {
|
|
5495
|
+
...config,
|
|
5496
|
+
page,
|
|
5497
|
+
logger: logger10,
|
|
5498
|
+
forceMouse: true,
|
|
5499
|
+
actionVisibleTimeoutMs: config.submitReadyTimeoutMs
|
|
5500
|
+
}).catch(() => false);
|
|
5364
5501
|
if (!submitted) {
|
|
5365
5502
|
logger10.warn("\u672A\u627E\u5230\u63D0\u4EA4\u6309\u94AE\uFF0C\u53EF\u80FD\u4F1A\u81EA\u52A8\u63D0\u4EA4\u3002");
|
|
5366
5503
|
}
|
|
5367
5504
|
await page.waitForTimeout(config.submitWaitMs);
|
|
5505
|
+
const afterSubmitState = await readPromptCaptchaState(frame, config);
|
|
5506
|
+
logger10.info(
|
|
5507
|
+
`\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${afterSubmitState.badgeCount}, selected=${afterSubmitState.selectedCount}, submitDisabled=${afterSubmitState.submitDisabled}`
|
|
5508
|
+
);
|
|
5368
5509
|
const stillVisible = await iframeLocator.isVisible({ timeout: config.containerVisibleTimeoutMs }).catch(() => false);
|
|
5369
5510
|
if (!stillVisible) {
|
|
5370
5511
|
logger10.info("\u9A8C\u8BC1\u7801\u8BC6\u522B\u5E76\u63D0\u4EA4\u6210\u529F\u3002");
|
|
5371
5512
|
return true;
|
|
5372
5513
|
}
|
|
5514
|
+
await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, "submit-still-visible", config, {
|
|
5515
|
+
beforeSubmitState,
|
|
5516
|
+
afterSubmitState
|
|
5517
|
+
}).catch((error) => {
|
|
5518
|
+
logger10.warn(`\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
5519
|
+
});
|
|
5373
5520
|
logger10.warn("\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801 iframe \u4ECD\u7136\u53EF\u89C1\uFF0C\u51C6\u5907\u5237\u65B0\u540E\u91CD\u8BD5\u3002");
|
|
5374
5521
|
await page.waitForTimeout(2e3);
|
|
5375
5522
|
await refreshCaptcha(page, frame, config);
|
|
@@ -5812,14 +5959,14 @@ var Mutation = {
|
|
|
5812
5959
|
const isFrameElement = tagName === "IFRAME" || tagName === "FRAME";
|
|
5813
5960
|
const nodeName = descriptor?.id || descriptor?.name || "no-id";
|
|
5814
5961
|
let source = "main";
|
|
5815
|
-
let
|
|
5962
|
+
let path3 = `${selector}[${index}]`;
|
|
5816
5963
|
let text = "";
|
|
5817
5964
|
let html = "";
|
|
5818
5965
|
let frameUrl = "";
|
|
5819
5966
|
let readyState = "";
|
|
5820
5967
|
if (isFrameElement) {
|
|
5821
5968
|
source = "iframe";
|
|
5822
|
-
|
|
5969
|
+
path3 = `${selector}[${index}]::iframe(${nodeName})`;
|
|
5823
5970
|
const frame = await handle.contentFrame();
|
|
5824
5971
|
if (frame) {
|
|
5825
5972
|
try {
|
|
@@ -5849,7 +5996,7 @@ var Mutation = {
|
|
|
5849
5996
|
items.push({
|
|
5850
5997
|
selector,
|
|
5851
5998
|
source,
|
|
5852
|
-
path:
|
|
5999
|
+
path: path3,
|
|
5853
6000
|
text,
|
|
5854
6001
|
html,
|
|
5855
6002
|
frameUrl,
|