@skrillex1224/playwright-toolkit 2.1.239 → 2.1.241
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +2 -2
- package/dist/browser.js.map +2 -2
- package/dist/index.cjs +249 -81
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +249 -81
- package/dist/index.js.map +3 -3
- package/index.d.ts +8 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -56,7 +56,7 @@ var createActorInfo = (info) => {
|
|
|
56
56
|
const normalizeShare2 = (value) => {
|
|
57
57
|
const raw = value && typeof value === "object" ? value : {};
|
|
58
58
|
const modeRaw = String(raw.mode || "dom").trim().toLowerCase();
|
|
59
|
-
const mode =
|
|
59
|
+
const mode = ["dom", "response", "custom"].includes(modeRaw) ? modeRaw : "dom";
|
|
60
60
|
const prefix = String(raw.prefix || "").trim();
|
|
61
61
|
const rawXurl = Array.isArray(raw.xurl) ? raw.xurl : [];
|
|
62
62
|
const normalizeMatcherList = (input) => {
|
|
@@ -214,7 +214,7 @@ var ActorInfo = {
|
|
|
214
214
|
path: "/",
|
|
215
215
|
device: Device.Mobile,
|
|
216
216
|
share: {
|
|
217
|
-
mode: "
|
|
217
|
+
mode: "custom",
|
|
218
218
|
prefix: "",
|
|
219
219
|
xurl: []
|
|
220
220
|
}
|
|
@@ -723,14 +723,16 @@ var expandScrollableContent = async (page, options = {}) => {
|
|
|
723
723
|
expandDocumentElements: options.expandDocumentElements === true
|
|
724
724
|
});
|
|
725
725
|
};
|
|
726
|
-
var
|
|
726
|
+
var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
|
|
727
727
|
return await page.evaluate(({ className, targetHeight }) => {
|
|
728
|
+
const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || document.body?.clientWidth || 0;
|
|
728
729
|
const viewportHeight = window.innerHeight || document.documentElement?.clientHeight || document.body?.clientHeight || 0;
|
|
730
|
+
const scrollY = window.scrollY || window.pageYOffset || 0;
|
|
729
731
|
const safeTargetHeight = Math.ceil(Number(targetHeight) || 0);
|
|
730
|
-
|
|
731
|
-
if (deltaY <= 1) {
|
|
732
|
+
if (safeTargetHeight <= viewportHeight + 1) {
|
|
732
733
|
return 0;
|
|
733
734
|
}
|
|
735
|
+
const hasOwn = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
|
|
734
736
|
const isVisible = (el, style, rect) => {
|
|
735
737
|
if (!el || !style || !rect) return false;
|
|
736
738
|
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") {
|
|
@@ -739,7 +741,7 @@ var pinBottomAffixedElements = async (page, options = {}) => {
|
|
|
739
741
|
if (Number(style.opacity) === 0) return false;
|
|
740
742
|
return rect.width > 0 && rect.height > 0;
|
|
741
743
|
};
|
|
742
|
-
const
|
|
744
|
+
const edgeThreshold = Math.max(24, Math.min(96, viewportHeight * 0.12));
|
|
743
745
|
const maxAffixedHeight = Math.max(56, viewportHeight * 0.65);
|
|
744
746
|
const candidates = [];
|
|
745
747
|
document.querySelectorAll("*").forEach((el) => {
|
|
@@ -749,28 +751,49 @@ var pinBottomAffixedElements = async (page, options = {}) => {
|
|
|
749
751
|
const rect = el.getBoundingClientRect();
|
|
750
752
|
if (!isVisible(el, style, rect)) return;
|
|
751
753
|
if (rect.height > maxAffixedHeight) return;
|
|
752
|
-
if (rect.
|
|
754
|
+
if (rect.width < viewportWidth * 0.25 && rect.height < 80) return;
|
|
755
|
+
const top = Number.parseFloat(style.top);
|
|
753
756
|
const bottom = Number.parseFloat(style.bottom);
|
|
757
|
+
const hasTopRule = Number.isFinite(top) && top <= Math.max(160, viewportHeight * 0.25);
|
|
754
758
|
const hasBottomRule = Number.isFinite(bottom) && bottom <= Math.max(160, viewportHeight * 0.25);
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
759
|
+
const isNearViewportTop = rect.top <= edgeThreshold;
|
|
760
|
+
const isNearViewportBottom = rect.bottom >= viewportHeight - edgeThreshold;
|
|
761
|
+
const edge = (hasBottomRule || isNearViewportBottom) && !(hasTopRule || isNearViewportTop) ? "bottom" : "top";
|
|
762
|
+
if (position === "fixed" && edge === "top" && !hasTopRule && !isNearViewportTop) return;
|
|
763
|
+
if (position === "fixed" && edge === "bottom" && !hasBottomRule && !isNearViewportBottom) return;
|
|
764
|
+
if (position === "sticky" && !hasTopRule && !hasBottomRule && !isNearViewportTop && !isNearViewportBottom) return;
|
|
765
|
+
candidates.push({ el, position, edge });
|
|
758
766
|
});
|
|
759
|
-
const candidateSet = new Set(candidates);
|
|
760
|
-
const topLevelCandidates = candidates.filter((el) => {
|
|
767
|
+
const candidateSet = new Set(candidates.map(({ el }) => el));
|
|
768
|
+
const topLevelCandidates = candidates.filter(({ el }) => {
|
|
761
769
|
for (let parent = el.parentElement; parent; parent = parent.parentElement) {
|
|
762
770
|
if (candidateSet.has(parent)) return false;
|
|
763
771
|
}
|
|
764
772
|
return true;
|
|
765
773
|
});
|
|
766
|
-
topLevelCandidates.forEach((el) => {
|
|
767
|
-
if (!
|
|
768
|
-
el.dataset.
|
|
774
|
+
topLevelCandidates.forEach(({ el, position, edge }) => {
|
|
775
|
+
if (!hasOwn(el.dataset, "pkAffixedAdjusted")) {
|
|
776
|
+
el.dataset.pkAffixedAdjusted = "1";
|
|
777
|
+
el.dataset.pkOrigPosition = el.style.getPropertyValue("position") || "";
|
|
778
|
+
el.dataset.pkOrigPositionPriority = el.style.getPropertyPriority("position") || "";
|
|
779
|
+
el.dataset.pkOrigTop = el.style.getPropertyValue("top") || "";
|
|
780
|
+
el.dataset.pkOrigTopPriority = el.style.getPropertyPriority("top") || "";
|
|
781
|
+
el.dataset.pkOrigBottom = el.style.getPropertyValue("bottom") || "";
|
|
782
|
+
el.dataset.pkOrigBottomPriority = el.style.getPropertyPriority("bottom") || "";
|
|
769
783
|
el.dataset.pkOrigTranslate = el.style.getPropertyValue("translate") || "";
|
|
770
784
|
el.dataset.pkOrigTranslatePriority = el.style.getPropertyPriority("translate") || "";
|
|
771
785
|
}
|
|
772
786
|
el.classList.add(className);
|
|
773
|
-
|
|
787
|
+
if (position === "fixed") {
|
|
788
|
+
const deltaY = edge === "bottom" ? safeTargetHeight - viewportHeight - scrollY : -scrollY;
|
|
789
|
+
if (Math.abs(deltaY) > 1) {
|
|
790
|
+
el.style.setProperty("translate", `0 ${Math.round(deltaY)}px`, "important");
|
|
791
|
+
}
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
el.style.setProperty("position", "relative", "important");
|
|
795
|
+
el.style.setProperty("top", "auto", "important");
|
|
796
|
+
el.style.setProperty("bottom", "auto", "important");
|
|
774
797
|
});
|
|
775
798
|
return topLevelCandidates.length;
|
|
776
799
|
}, {
|
|
@@ -778,7 +801,7 @@ var pinBottomAffixedElements = async (page, options = {}) => {
|
|
|
778
801
|
targetHeight: options.targetHeight
|
|
779
802
|
});
|
|
780
803
|
};
|
|
781
|
-
var
|
|
804
|
+
var restoreAffixedElementsForExpandedScreenshot = async (page) => {
|
|
782
805
|
await page.evaluate((className) => {
|
|
783
806
|
const hasOwn = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
|
|
784
807
|
const expansionKeys = [
|
|
@@ -787,13 +810,28 @@ var restoreBottomAffixedElements = async (page) => {
|
|
|
787
810
|
"pkOrigMinHeight",
|
|
788
811
|
"pkOrigMaxHeight"
|
|
789
812
|
];
|
|
790
|
-
document.querySelectorAll('[data-pk-
|
|
813
|
+
document.querySelectorAll('[data-pk-affixed-adjusted="1"]').forEach((el) => {
|
|
814
|
+
if (hasOwn(el.dataset, "pkOrigPosition")) {
|
|
815
|
+
el.style.setProperty("position", el.dataset.pkOrigPosition || "", el.dataset.pkOrigPositionPriority || "");
|
|
816
|
+
delete el.dataset.pkOrigPosition;
|
|
817
|
+
delete el.dataset.pkOrigPositionPriority;
|
|
818
|
+
}
|
|
819
|
+
if (hasOwn(el.dataset, "pkOrigTop")) {
|
|
820
|
+
el.style.setProperty("top", el.dataset.pkOrigTop || "", el.dataset.pkOrigTopPriority || "");
|
|
821
|
+
delete el.dataset.pkOrigTop;
|
|
822
|
+
delete el.dataset.pkOrigTopPriority;
|
|
823
|
+
}
|
|
824
|
+
if (hasOwn(el.dataset, "pkOrigBottom")) {
|
|
825
|
+
el.style.setProperty("bottom", el.dataset.pkOrigBottom || "", el.dataset.pkOrigBottomPriority || "");
|
|
826
|
+
delete el.dataset.pkOrigBottom;
|
|
827
|
+
delete el.dataset.pkOrigBottomPriority;
|
|
828
|
+
}
|
|
791
829
|
if (hasOwn(el.dataset, "pkOrigTranslate")) {
|
|
792
830
|
el.style.setProperty("translate", el.dataset.pkOrigTranslate || "", el.dataset.pkOrigTranslatePriority || "");
|
|
793
831
|
delete el.dataset.pkOrigTranslate;
|
|
794
832
|
delete el.dataset.pkOrigTranslatePriority;
|
|
795
833
|
}
|
|
796
|
-
delete el.dataset.
|
|
834
|
+
delete el.dataset.pkAffixedAdjusted;
|
|
797
835
|
const stillExpanded = expansionKeys.some((key) => hasOwn(el.dataset, key));
|
|
798
836
|
if (!stillExpanded) {
|
|
799
837
|
el.classList.remove(className);
|
|
@@ -822,8 +860,8 @@ var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
822
860
|
});
|
|
823
861
|
viewportResized = true;
|
|
824
862
|
} else {
|
|
825
|
-
await
|
|
826
|
-
logger.warning(`\u79FB\u52A8\u7AEF\u5438\
|
|
863
|
+
await adjustAffixedElementsForExpandedScreenshot(page, { targetHeight }).catch((error) => {
|
|
864
|
+
logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u91CD\u5B9A\u4F4D\u5931\u8D25: ${error?.message || error}`);
|
|
827
865
|
});
|
|
828
866
|
}
|
|
829
867
|
if (settleMs > 0) {
|
|
@@ -929,8 +967,8 @@ var captureExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
929
967
|
maxClipHeight: state.targetHeight
|
|
930
968
|
});
|
|
931
969
|
} finally {
|
|
932
|
-
await
|
|
933
|
-
logger.warning(`\u79FB\u52A8\u7AEF\u5438\
|
|
970
|
+
await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
|
|
971
|
+
logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
|
|
934
972
|
});
|
|
935
973
|
if (options.restore) {
|
|
936
974
|
await restoreExpandedFullPageScreenshot(page, state);
|
|
@@ -3370,6 +3408,26 @@ var waitJitter = (base, jitterPercent = 0.3) => delay3(jitterMs(base, jitterPerc
|
|
|
3370
3408
|
// src/internals/humanize/mobile.js
|
|
3371
3409
|
var logger7 = createInternalLogger("Humanize.Mobile");
|
|
3372
3410
|
var initializedPages = /* @__PURE__ */ new WeakSet();
|
|
3411
|
+
var DEFAULT_TAP_TIMEOUT_MS = 2500;
|
|
3412
|
+
var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
|
|
3413
|
+
var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
|
|
3414
|
+
var INTERACTIVE_SELECTOR = [
|
|
3415
|
+
"button",
|
|
3416
|
+
'[role="button"]',
|
|
3417
|
+
"a[href]",
|
|
3418
|
+
"label",
|
|
3419
|
+
"input",
|
|
3420
|
+
"textarea",
|
|
3421
|
+
"select",
|
|
3422
|
+
"summary",
|
|
3423
|
+
'[contenteditable="true"]',
|
|
3424
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
3425
|
+
].join(",");
|
|
3426
|
+
var EDITABLE_SELECTOR = [
|
|
3427
|
+
"input",
|
|
3428
|
+
"textarea",
|
|
3429
|
+
'[contenteditable="true"]'
|
|
3430
|
+
].join(",");
|
|
3373
3431
|
var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
3374
3432
|
var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
|
|
3375
3433
|
var describeTarget = (target) => {
|
|
@@ -3392,8 +3450,24 @@ var clipBoxToViewport = (box, viewport) => {
|
|
|
3392
3450
|
}
|
|
3393
3451
|
return box;
|
|
3394
3452
|
};
|
|
3453
|
+
var withTimeout = async (operation, timeoutMs, label) => {
|
|
3454
|
+
const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
|
|
3455
|
+
let timeoutId = null;
|
|
3456
|
+
try {
|
|
3457
|
+
return await Promise.race([
|
|
3458
|
+
Promise.resolve().then(operation),
|
|
3459
|
+
new Promise((_, reject) => {
|
|
3460
|
+
timeoutId = setTimeout(() => {
|
|
3461
|
+
reject(new Error(`${label} timeout after ${safeTimeoutMs}ms`));
|
|
3462
|
+
}, safeTimeoutMs);
|
|
3463
|
+
})
|
|
3464
|
+
]);
|
|
3465
|
+
} finally {
|
|
3466
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
3467
|
+
}
|
|
3468
|
+
};
|
|
3395
3469
|
var checkElementVisibility = async (element) => {
|
|
3396
|
-
return element.evaluate((el) => {
|
|
3470
|
+
return element.evaluate((el, interactiveSelector) => {
|
|
3397
3471
|
const targetStyle = window.getComputedStyle(el);
|
|
3398
3472
|
if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
|
|
3399
3473
|
return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
|
|
@@ -3410,10 +3484,12 @@ var checkElementVisibility = async (element) => {
|
|
|
3410
3484
|
let clipTop = 0;
|
|
3411
3485
|
let clipBottom = viewH;
|
|
3412
3486
|
let isFixed = false;
|
|
3487
|
+
let positioning = null;
|
|
3413
3488
|
for (let node = el; node && node !== document.body; node = node.parentElement) {
|
|
3414
3489
|
const style = window.getComputedStyle(node);
|
|
3415
3490
|
if (style && (style.position === "fixed" || style.position === "sticky")) {
|
|
3416
3491
|
isFixed = true;
|
|
3492
|
+
positioning = style.position;
|
|
3417
3493
|
break;
|
|
3418
3494
|
}
|
|
3419
3495
|
}
|
|
@@ -3449,21 +3525,10 @@ var checkElementVisibility = async (element) => {
|
|
|
3449
3525
|
direction: centerY < clipTop ? "up" : "down",
|
|
3450
3526
|
cy: centerY,
|
|
3451
3527
|
viewH,
|
|
3452
|
-
isFixed
|
|
3528
|
+
isFixed,
|
|
3529
|
+
positioning
|
|
3453
3530
|
};
|
|
3454
3531
|
}
|
|
3455
|
-
const interactiveSelector = [
|
|
3456
|
-
"button",
|
|
3457
|
-
'[role="button"]',
|
|
3458
|
-
"a[href]",
|
|
3459
|
-
"label",
|
|
3460
|
-
"input",
|
|
3461
|
-
"textarea",
|
|
3462
|
-
"select",
|
|
3463
|
-
"summary",
|
|
3464
|
-
'[contenteditable="true"]',
|
|
3465
|
-
'[tabindex]:not([tabindex="-1"])'
|
|
3466
|
-
].join(",");
|
|
3467
3532
|
const targetInteractive = typeof el.closest === "function" ? el.closest(interactiveSelector) : null;
|
|
3468
3533
|
const sameInteractiveTarget = (pointElement) => {
|
|
3469
3534
|
if (!pointElement) return false;
|
|
@@ -3485,6 +3550,7 @@ var checkElementVisibility = async (element) => {
|
|
|
3485
3550
|
id: node.id || "",
|
|
3486
3551
|
className,
|
|
3487
3552
|
isFixed: Boolean(style && (style.position === "fixed" || style.position === "sticky")),
|
|
3553
|
+
positioning: style?.position || "",
|
|
3488
3554
|
top: rect2 ? rect2.top : null,
|
|
3489
3555
|
bottom: rect2 ? rect2.bottom : null,
|
|
3490
3556
|
left: rect2 ? rect2.left : null,
|
|
@@ -3502,7 +3568,7 @@ var checkElementVisibility = async (element) => {
|
|
|
3502
3568
|
for (const point of samplePoints) {
|
|
3503
3569
|
const pointElement = document.elementFromPoint(point.x, point.y);
|
|
3504
3570
|
if (sameInteractiveTarget(pointElement)) {
|
|
3505
|
-
return { code: "VISIBLE", isFixed };
|
|
3571
|
+
return { code: "VISIBLE", isFixed, positioning };
|
|
3506
3572
|
}
|
|
3507
3573
|
obstruction = obstruction || describeElement(pointElement);
|
|
3508
3574
|
}
|
|
@@ -3514,14 +3580,15 @@ var checkElementVisibility = async (element) => {
|
|
|
3514
3580
|
obstruction,
|
|
3515
3581
|
cy,
|
|
3516
3582
|
viewH,
|
|
3517
|
-
isFixed
|
|
3583
|
+
isFixed,
|
|
3584
|
+
positioning
|
|
3518
3585
|
};
|
|
3519
3586
|
}
|
|
3520
|
-
return { code: "VISIBLE", isFixed };
|
|
3521
|
-
});
|
|
3587
|
+
return { code: "VISIBLE", isFixed, positioning };
|
|
3588
|
+
}, INTERACTIVE_SELECTOR);
|
|
3522
3589
|
};
|
|
3523
3590
|
var resolveSafeTapPoint = async (element) => {
|
|
3524
|
-
return element.evaluate((el) => {
|
|
3591
|
+
return element.evaluate((el, interactiveSelector) => {
|
|
3525
3592
|
const rect = el.getBoundingClientRect();
|
|
3526
3593
|
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
3527
3594
|
return null;
|
|
@@ -3558,18 +3625,6 @@ var resolveSafeTapPoint = async (element) => {
|
|
|
3558
3625
|
if (visibleWidth <= 1 || visibleHeight <= 1) {
|
|
3559
3626
|
return null;
|
|
3560
3627
|
}
|
|
3561
|
-
const interactiveSelector = [
|
|
3562
|
-
"button",
|
|
3563
|
-
'[role="button"]',
|
|
3564
|
-
"a[href]",
|
|
3565
|
-
"label",
|
|
3566
|
-
"input",
|
|
3567
|
-
"textarea",
|
|
3568
|
-
"select",
|
|
3569
|
-
"summary",
|
|
3570
|
-
'[contenteditable="true"]',
|
|
3571
|
-
'[tabindex]:not([tabindex="-1"])'
|
|
3572
|
-
].join(",");
|
|
3573
3628
|
const targetInteractive = typeof el.closest === "function" ? el.closest(interactiveSelector) : null;
|
|
3574
3629
|
const sameInteractiveTarget = (pointElement) => {
|
|
3575
3630
|
if (!pointElement) return false;
|
|
@@ -3607,6 +3662,46 @@ var resolveSafeTapPoint = async (element) => {
|
|
|
3607
3662
|
x: chosen.x,
|
|
3608
3663
|
y: chosen.y
|
|
3609
3664
|
};
|
|
3665
|
+
}, INTERACTIVE_SELECTOR);
|
|
3666
|
+
};
|
|
3667
|
+
var activateElementFallback = async (element, point = null, options = {}) => {
|
|
3668
|
+
return element.evaluate((el, { innerPoint, innerOptions, interactiveSelector, editableSelector }) => {
|
|
3669
|
+
const pointElement = innerPoint ? document.elementFromPoint(innerPoint.x, innerPoint.y) : null;
|
|
3670
|
+
const nearestInteractive = (node) => {
|
|
3671
|
+
if (!node || typeof node.closest !== "function") return null;
|
|
3672
|
+
return node.closest(interactiveSelector);
|
|
3673
|
+
};
|
|
3674
|
+
const targetInteractive = nearestInteractive(el);
|
|
3675
|
+
const pointInteractive = nearestInteractive(pointElement);
|
|
3676
|
+
let target = null;
|
|
3677
|
+
if (pointInteractive && (pointInteractive === targetInteractive || pointInteractive === el || pointInteractive.contains(el) || el.contains(pointInteractive))) {
|
|
3678
|
+
target = pointInteractive;
|
|
3679
|
+
}
|
|
3680
|
+
target = target || targetInteractive || el;
|
|
3681
|
+
const editable = typeof target.closest === "function" ? target.closest(editableSelector) : null;
|
|
3682
|
+
if (editable && typeof editable.focus === "function") {
|
|
3683
|
+
editable.focus({ preventScroll: true });
|
|
3684
|
+
if (innerOptions.editableOnly) {
|
|
3685
|
+
return { activated: true, method: "focus", tag: editable.tagName || "" };
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
if (typeof target.focus === "function") {
|
|
3689
|
+
target.focus({ preventScroll: true });
|
|
3690
|
+
}
|
|
3691
|
+
if (!innerOptions.editableOnly && typeof target.click === "function") {
|
|
3692
|
+
target.click();
|
|
3693
|
+
return { activated: true, method: "dom-click", tag: target.tagName || "" };
|
|
3694
|
+
}
|
|
3695
|
+
return {
|
|
3696
|
+
activated: Boolean(editable),
|
|
3697
|
+
method: editable ? "focus" : "none",
|
|
3698
|
+
tag: target?.tagName || ""
|
|
3699
|
+
};
|
|
3700
|
+
}, {
|
|
3701
|
+
innerPoint: point,
|
|
3702
|
+
innerOptions: options || {},
|
|
3703
|
+
interactiveSelector: INTERACTIVE_SELECTOR,
|
|
3704
|
+
editableSelector: EDITABLE_SELECTOR
|
|
3610
3705
|
});
|
|
3611
3706
|
};
|
|
3612
3707
|
var getScrollableRect = async (element) => {
|
|
@@ -3811,12 +3906,31 @@ var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
|
|
|
3811
3906
|
}
|
|
3812
3907
|
}
|
|
3813
3908
|
};
|
|
3814
|
-
var tapPoint = async (page, point) => {
|
|
3909
|
+
var tapPoint = async (page, point, options = {}) => {
|
|
3910
|
+
const {
|
|
3911
|
+
timeoutMs = DEFAULT_TAP_TIMEOUT_MS,
|
|
3912
|
+
mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
|
|
3913
|
+
allowMouseFallback = true
|
|
3914
|
+
} = options;
|
|
3815
3915
|
if (page.touchscreen && typeof page.touchscreen.tap === "function") {
|
|
3816
|
-
|
|
3817
|
-
|
|
3916
|
+
try {
|
|
3917
|
+
await withTimeout(
|
|
3918
|
+
() => page.touchscreen.tap(point.x, point.y),
|
|
3919
|
+
timeoutMs,
|
|
3920
|
+
"touchscreen.tap"
|
|
3921
|
+
);
|
|
3922
|
+
return { method: "touchscreen" };
|
|
3923
|
+
} catch (error) {
|
|
3924
|
+
logger7.warn(`tapPoint | touchscreen.tap \u5931\u8D25\u6216\u8D85\u65F6\uFF0C\u5C1D\u8BD5\u9F20\u6807\u515C\u5E95: ${error?.message || error}`);
|
|
3925
|
+
if (!allowMouseFallback) throw error;
|
|
3926
|
+
}
|
|
3818
3927
|
}
|
|
3819
|
-
await
|
|
3928
|
+
await withTimeout(
|
|
3929
|
+
() => page.mouse.click(point.x, point.y),
|
|
3930
|
+
mouseFallbackTimeoutMs,
|
|
3931
|
+
"mouse.click fallback"
|
|
3932
|
+
);
|
|
3933
|
+
return { method: "mouse" };
|
|
3820
3934
|
};
|
|
3821
3935
|
var MobileHumanize = {
|
|
3822
3936
|
jitterMs,
|
|
@@ -3869,6 +3983,10 @@ var MobileHumanize = {
|
|
|
3869
3983
|
return { element, didScroll, restore: null };
|
|
3870
3984
|
}
|
|
3871
3985
|
const scrollRect = await getScrollableRect(element);
|
|
3986
|
+
if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
|
|
3987
|
+
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"})`);
|
|
3988
|
+
return { element, didScroll, restore: null };
|
|
3989
|
+
}
|
|
3872
3990
|
if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
|
|
3873
3991
|
logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
|
|
3874
3992
|
return { element, didScroll, restore: null };
|
|
@@ -3989,7 +4107,12 @@ var MobileHumanize = {
|
|
|
3989
4107
|
const {
|
|
3990
4108
|
reactionDelay = 220,
|
|
3991
4109
|
throwOnMissing = true,
|
|
3992
|
-
scrollIfNeeded = true
|
|
4110
|
+
scrollIfNeeded = true,
|
|
4111
|
+
tapTimeoutMs = DEFAULT_TAP_TIMEOUT_MS,
|
|
4112
|
+
mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
|
|
4113
|
+
activateFallbackTimeoutMs = DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS,
|
|
4114
|
+
fallbackDomClick = true,
|
|
4115
|
+
fallbackDomClickOnTapError = true
|
|
3993
4116
|
} = options;
|
|
3994
4117
|
const targetDesc = describeTarget(target);
|
|
3995
4118
|
logger7.start("humanClick", `target=${targetDesc}`);
|
|
@@ -4000,6 +4123,9 @@ var MobileHumanize = {
|
|
|
4000
4123
|
await tapPoint(page, {
|
|
4001
4124
|
x: viewport.width * (0.45 + Math.random() * 0.1),
|
|
4002
4125
|
y: viewport.height * (0.48 + Math.random() * 0.12)
|
|
4126
|
+
}, {
|
|
4127
|
+
timeoutMs: tapTimeoutMs,
|
|
4128
|
+
mouseFallbackTimeoutMs
|
|
4003
4129
|
});
|
|
4004
4130
|
logger7.success("humanClick", "Tapped current position");
|
|
4005
4131
|
return true;
|
|
@@ -4014,6 +4140,19 @@ var MobileHumanize = {
|
|
|
4014
4140
|
}
|
|
4015
4141
|
const status = await checkElementVisibility(element).catch(() => null);
|
|
4016
4142
|
if (status && status.code !== "VISIBLE") {
|
|
4143
|
+
if (fallbackDomClick && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
|
|
4144
|
+
const fallback = await withTimeout(
|
|
4145
|
+
() => activateElementFallback(element, null, {
|
|
4146
|
+
editableOnly: true
|
|
4147
|
+
}),
|
|
4148
|
+
activateFallbackTimeoutMs,
|
|
4149
|
+
"focus fallback"
|
|
4150
|
+
).catch(() => null);
|
|
4151
|
+
if (fallback?.activated) {
|
|
4152
|
+
logger7.warn(`humanClick: fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
|
|
4153
|
+
return true;
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4017
4156
|
const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
|
|
4018
4157
|
if (throwOnMissing) throw new Error(message);
|
|
4019
4158
|
logger7.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
|
|
@@ -4028,7 +4167,23 @@ var MobileHumanize = {
|
|
|
4028
4167
|
await waitJitter(reactionDelay, 0.45);
|
|
4029
4168
|
const safePoint = await resolveSafeTapPoint(element).catch(() => null);
|
|
4030
4169
|
const tapTarget = safePoint || randomPointInBox(clipBoxToViewport(box, resolveViewport(page)), 0.2);
|
|
4031
|
-
|
|
4170
|
+
try {
|
|
4171
|
+
await tapPoint(page, tapTarget, {
|
|
4172
|
+
timeoutMs: tapTimeoutMs,
|
|
4173
|
+
mouseFallbackTimeoutMs
|
|
4174
|
+
});
|
|
4175
|
+
} catch (tapError) {
|
|
4176
|
+
if (!fallbackDomClickOnTapError) throw tapError;
|
|
4177
|
+
const fallback = await withTimeout(
|
|
4178
|
+
() => activateElementFallback(element, tapTarget, {
|
|
4179
|
+
editableOnly: false
|
|
4180
|
+
}),
|
|
4181
|
+
activateFallbackTimeoutMs,
|
|
4182
|
+
"activation fallback"
|
|
4183
|
+
).catch(() => null);
|
|
4184
|
+
if (!fallback?.activated) throw tapError;
|
|
4185
|
+
logger7.warn(`humanClick: tap \u5931\u8D25\u540E\u5DF2\u7528 ${fallback.method} \u515C\u5E95: ${tapError?.message || tapError}`);
|
|
4186
|
+
}
|
|
4032
4187
|
await waitJitter(120, 0.35);
|
|
4033
4188
|
logger7.success("humanClick");
|
|
4034
4189
|
return true;
|
|
@@ -6846,7 +7001,7 @@ var getHostname = (url) => {
|
|
|
6846
7001
|
return "\u672C\u5730\u9875\u9762";
|
|
6847
7002
|
}
|
|
6848
7003
|
};
|
|
6849
|
-
var
|
|
7004
|
+
var withTimeout2 = async (promise, timeoutMs) => {
|
|
6850
7005
|
const safeTimeoutMs = Math.max(0, Number(timeoutMs) || 0);
|
|
6851
7006
|
if (!safeTimeoutMs) {
|
|
6852
7007
|
return promise;
|
|
@@ -6931,7 +7086,7 @@ var resolveWithCustomResolver = async (page, baseMeta, options = {}) => {
|
|
|
6931
7086
|
const serverAddr = response && typeof response.serverAddr === "function" ? await response.serverAddr().catch(() => null) : null;
|
|
6932
7087
|
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
6933
7088
|
try {
|
|
6934
|
-
const resolved = await
|
|
7089
|
+
const resolved = await withTimeout2(
|
|
6935
7090
|
Promise.resolve(resolver({
|
|
6936
7091
|
page,
|
|
6937
7092
|
response,
|
|
@@ -7231,13 +7386,13 @@ var resolveWithIpLookup = async (page, options = {}) => {
|
|
|
7231
7386
|
const contentType = normalizeText(await pickHeaderValue(response, ["content-type"]));
|
|
7232
7387
|
let rawText = "";
|
|
7233
7388
|
if (response && typeof response.text === "function") {
|
|
7234
|
-
rawText = String(await
|
|
7389
|
+
rawText = String(await withTimeout2(
|
|
7235
7390
|
response.text().catch(() => ""),
|
|
7236
7391
|
timeoutMs
|
|
7237
7392
|
) || "");
|
|
7238
7393
|
}
|
|
7239
7394
|
if (!rawText) {
|
|
7240
|
-
rawText = String(await
|
|
7395
|
+
rawText = String(await withTimeout2(
|
|
7241
7396
|
probePage.evaluate(() => {
|
|
7242
7397
|
return document.body?.innerText || document.documentElement?.innerText || "";
|
|
7243
7398
|
}).catch(() => ""),
|
|
@@ -8340,7 +8495,7 @@ var normalizeXurl = (value) => {
|
|
|
8340
8495
|
var normalizeShare = (share) => {
|
|
8341
8496
|
const source = share && typeof share === "object" ? share : {};
|
|
8342
8497
|
const modeRaw = String(source.mode || "dom").trim().toLowerCase();
|
|
8343
|
-
const mode =
|
|
8498
|
+
const mode = ["dom", "response", "custom"].includes(modeRaw) ? modeRaw : "dom";
|
|
8344
8499
|
return {
|
|
8345
8500
|
mode,
|
|
8346
8501
|
prefix: normalizePrefix(source.prefix),
|
|
@@ -8456,16 +8611,17 @@ var Share = {
|
|
|
8456
8611
|
* 捕获分享链接(单模式)
|
|
8457
8612
|
* - mode='dom': 仅 DOM 监听
|
|
8458
8613
|
* - mode='response': 仅接口监听
|
|
8614
|
+
* - mode='custom': 使用 performActions 返回值作为链接
|
|
8459
8615
|
*
|
|
8460
8616
|
* @param {import('playwright').Page} page
|
|
8461
8617
|
* @param {Object} [options]
|
|
8462
|
-
* @param {{ mode?: 'dom' | 'response', prefix: string, xurl?: Array<string | string[]> }} options.share
|
|
8618
|
+
* @param {{ mode?: 'dom' | 'response' | 'custom', prefix: string, xurl?: Array<string | string[]> }} options.share
|
|
8463
8619
|
* @param {number} [options.timeoutMs=50000]
|
|
8464
8620
|
* @param {number} [options.payloadSnapshotMaxLen=500]
|
|
8465
8621
|
* @param {string | string[]} [options.domSelectors='html']
|
|
8466
8622
|
* @param {'added' | 'changed' | 'all'} [options.domMode='all']
|
|
8467
|
-
* @param {() => Promise<void>} [options.performActions]
|
|
8468
|
-
* @returns {Promise<{ link: string | null; payloadText: string; payloadSnapshot: string; source: 'dom' | 'response' | 'none' }>}
|
|
8623
|
+
* @param {() => void | string | { link?: string | null; payloadText?: string } | Promise<void | string | { link?: string | null; payloadText?: string }>} [options.performActions]
|
|
8624
|
+
* @returns {Promise<{ link: string | null; payloadText: string; payloadSnapshot: string; source: 'dom' | 'response' | 'custom' | 'none' }>}
|
|
8469
8625
|
*/
|
|
8470
8626
|
async captureLink(page, options = {}) {
|
|
8471
8627
|
const share = normalizeShare(options.share);
|
|
@@ -8478,7 +8634,7 @@ var Share = {
|
|
|
8478
8634
|
if (share.prefix === void 0) {
|
|
8479
8635
|
throw new Error("Share.captureLink requires options.share.prefix");
|
|
8480
8636
|
}
|
|
8481
|
-
if (share.mode !== "dom" && share.mode !== "response") {
|
|
8637
|
+
if (share.mode !== "dom" && share.mode !== "response" && share.mode !== "custom") {
|
|
8482
8638
|
throw new Error(`Share.captureLink unsupported share.mode: ${share.mode}`);
|
|
8483
8639
|
}
|
|
8484
8640
|
const apiMatchers = share.mode === "response" ? share.xurl[0] || [] : [];
|
|
@@ -8500,7 +8656,8 @@ var Share = {
|
|
|
8500
8656
|
};
|
|
8501
8657
|
const candidates = {
|
|
8502
8658
|
dom: null,
|
|
8503
|
-
response: null
|
|
8659
|
+
response: null,
|
|
8660
|
+
custom: null
|
|
8504
8661
|
};
|
|
8505
8662
|
const setCandidate = (source, link, payloadText = "") => {
|
|
8506
8663
|
const safe = String(link || "").trim();
|
|
@@ -8600,37 +8757,47 @@ var Share = {
|
|
|
8600
8757
|
logger15.info(`\u5F53\u524D\u4E3A\u63A5\u53E3\u6A21\u5F0F\uFF0C\u6302\u8F7D response \u76D1\u542C: apiMatchers=${toJsonInline(apiMatchers, 160)}`);
|
|
8601
8758
|
page.on("response", onResponse);
|
|
8602
8759
|
}
|
|
8760
|
+
if (share.mode === "custom") {
|
|
8761
|
+
logger15.info("\u5F53\u524D\u4E3A custom \u6A21\u5F0F\uFF0C\u5C06\u4F7F\u7528 performActions \u8FD4\u56DE\u503C");
|
|
8762
|
+
}
|
|
8603
8763
|
const deadline = Date.now() + timeoutMs;
|
|
8604
8764
|
const getRemainingMs = () => Math.max(0, deadline - Date.now());
|
|
8605
8765
|
try {
|
|
8606
8766
|
const actionTimeout = getRemainingMs();
|
|
8607
8767
|
logger15.start("captureLink.performActions", `\u6267\u884C\u52A8\u4F5C\u9884\u7B97=${actionTimeout}ms`);
|
|
8768
|
+
let actionValue;
|
|
8608
8769
|
if (actionTimeout > 0) {
|
|
8609
8770
|
let timer = null;
|
|
8610
|
-
|
|
8611
|
-
const actionPromise = Promise.resolve().then(() => performActions()).then(() => "__ACTION_DONE__").catch((error) => {
|
|
8612
|
-
actionError = error;
|
|
8613
|
-
return "__ACTION_ERROR__";
|
|
8614
|
-
});
|
|
8771
|
+
const actionPromise = Promise.resolve().then(() => performActions()).then((result) => ({ type: "done", result })).catch((error) => ({ type: "error", error }));
|
|
8615
8772
|
const timeoutPromise = new Promise((resolve) => {
|
|
8616
|
-
timer = setTimeout(() => resolve("
|
|
8773
|
+
timer = setTimeout(() => resolve({ type: "timeout" }), actionTimeout);
|
|
8617
8774
|
});
|
|
8618
8775
|
const actionResult = await Promise.race([actionPromise, timeoutPromise]);
|
|
8619
8776
|
if (timer) clearTimeout(timer);
|
|
8620
|
-
if (actionResult === "
|
|
8621
|
-
logger15.fail("captureLink.performActions",
|
|
8622
|
-
throw
|
|
8777
|
+
if (actionResult.type === "error") {
|
|
8778
|
+
logger15.fail("captureLink.performActions", actionResult.error);
|
|
8779
|
+
throw actionResult.error;
|
|
8623
8780
|
}
|
|
8624
|
-
if (actionResult === "
|
|
8781
|
+
if (actionResult.type === "timeout") {
|
|
8625
8782
|
stats.actionTimedOut = true;
|
|
8626
8783
|
logger15.warning(`performActions \u5DF2\u8D85\u65F6 (${actionTimeout}ms)\uFF0C\u52A8\u4F5C\u53EF\u80FD\u4ECD\u5728\u5F02\u6B65\u6267\u884C`);
|
|
8627
8784
|
} else {
|
|
8785
|
+
actionValue = actionResult.result;
|
|
8628
8786
|
logger15.success("captureLink.performActions", "\u6267\u884C\u52A8\u4F5C\u5B8C\u6210");
|
|
8629
8787
|
}
|
|
8630
8788
|
}
|
|
8789
|
+
if (share.mode === "custom") {
|
|
8790
|
+
const customLink = typeof actionValue === "string" ? actionValue : actionValue?.link || actionValue?.payloadText;
|
|
8791
|
+
const customPayloadText = typeof actionValue === "string" ? actionValue : actionValue?.payloadText;
|
|
8792
|
+
if (setCandidate("custom", customLink, customPayloadText)) {
|
|
8793
|
+
logger15.success("captureLink.customResult", `custom \u547D\u4E2D\u5206\u4EAB\u94FE\u63A5: link=${candidates.custom.link}`);
|
|
8794
|
+
} else {
|
|
8795
|
+
logger15.warning("performActions \u6267\u884C\u5B8C\u6210\u4F46\u672A\u8FD4\u56DE\u6709\u6548\u5206\u4EAB\u94FE\u63A5");
|
|
8796
|
+
}
|
|
8797
|
+
}
|
|
8631
8798
|
let nextProgressLogTs = Date.now() + 3e3;
|
|
8632
8799
|
while (true) {
|
|
8633
|
-
const selected = share.mode
|
|
8800
|
+
const selected = candidates[share.mode];
|
|
8634
8801
|
if (selected?.link) {
|
|
8635
8802
|
logger15.success("captureLink", `\u6355\u83B7\u6210\u529F: source=${share.mode}, link=${selected.link}`);
|
|
8636
8803
|
return {
|
|
@@ -8640,6 +8807,7 @@ var Share = {
|
|
|
8640
8807
|
source: share.mode
|
|
8641
8808
|
};
|
|
8642
8809
|
}
|
|
8810
|
+
if (share.mode === "custom") break;
|
|
8643
8811
|
const remaining = getRemainingMs();
|
|
8644
8812
|
if (remaining <= 0) break;
|
|
8645
8813
|
const now = Date.now();
|