@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.cjs
CHANGED
|
@@ -83,7 +83,7 @@ var createActorInfo = (info) => {
|
|
|
83
83
|
const normalizeShare2 = (value) => {
|
|
84
84
|
const raw = value && typeof value === "object" ? value : {};
|
|
85
85
|
const modeRaw = String(raw.mode || "dom").trim().toLowerCase();
|
|
86
|
-
const mode =
|
|
86
|
+
const mode = ["dom", "response", "custom"].includes(modeRaw) ? modeRaw : "dom";
|
|
87
87
|
const prefix = String(raw.prefix || "").trim();
|
|
88
88
|
const rawXurl = Array.isArray(raw.xurl) ? raw.xurl : [];
|
|
89
89
|
const normalizeMatcherList = (input) => {
|
|
@@ -241,7 +241,7 @@ var ActorInfo = {
|
|
|
241
241
|
path: "/",
|
|
242
242
|
device: Device.Mobile,
|
|
243
243
|
share: {
|
|
244
|
-
mode: "
|
|
244
|
+
mode: "custom",
|
|
245
245
|
prefix: "",
|
|
246
246
|
xurl: []
|
|
247
247
|
}
|
|
@@ -750,14 +750,16 @@ var expandScrollableContent = async (page, options = {}) => {
|
|
|
750
750
|
expandDocumentElements: options.expandDocumentElements === true
|
|
751
751
|
});
|
|
752
752
|
};
|
|
753
|
-
var
|
|
753
|
+
var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
|
|
754
754
|
return await page.evaluate(({ className, targetHeight }) => {
|
|
755
|
+
const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || document.body?.clientWidth || 0;
|
|
755
756
|
const viewportHeight = window.innerHeight || document.documentElement?.clientHeight || document.body?.clientHeight || 0;
|
|
757
|
+
const scrollY = window.scrollY || window.pageYOffset || 0;
|
|
756
758
|
const safeTargetHeight = Math.ceil(Number(targetHeight) || 0);
|
|
757
|
-
|
|
758
|
-
if (deltaY <= 1) {
|
|
759
|
+
if (safeTargetHeight <= viewportHeight + 1) {
|
|
759
760
|
return 0;
|
|
760
761
|
}
|
|
762
|
+
const hasOwn = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
|
|
761
763
|
const isVisible = (el, style, rect) => {
|
|
762
764
|
if (!el || !style || !rect) return false;
|
|
763
765
|
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") {
|
|
@@ -766,7 +768,7 @@ var pinBottomAffixedElements = async (page, options = {}) => {
|
|
|
766
768
|
if (Number(style.opacity) === 0) return false;
|
|
767
769
|
return rect.width > 0 && rect.height > 0;
|
|
768
770
|
};
|
|
769
|
-
const
|
|
771
|
+
const edgeThreshold = Math.max(24, Math.min(96, viewportHeight * 0.12));
|
|
770
772
|
const maxAffixedHeight = Math.max(56, viewportHeight * 0.65);
|
|
771
773
|
const candidates = [];
|
|
772
774
|
document.querySelectorAll("*").forEach((el) => {
|
|
@@ -776,28 +778,49 @@ var pinBottomAffixedElements = async (page, options = {}) => {
|
|
|
776
778
|
const rect = el.getBoundingClientRect();
|
|
777
779
|
if (!isVisible(el, style, rect)) return;
|
|
778
780
|
if (rect.height > maxAffixedHeight) return;
|
|
779
|
-
if (rect.
|
|
781
|
+
if (rect.width < viewportWidth * 0.25 && rect.height < 80) return;
|
|
782
|
+
const top = Number.parseFloat(style.top);
|
|
780
783
|
const bottom = Number.parseFloat(style.bottom);
|
|
784
|
+
const hasTopRule = Number.isFinite(top) && top <= Math.max(160, viewportHeight * 0.25);
|
|
781
785
|
const hasBottomRule = Number.isFinite(bottom) && bottom <= Math.max(160, viewportHeight * 0.25);
|
|
782
|
-
const
|
|
783
|
-
|
|
784
|
-
|
|
786
|
+
const isNearViewportTop = rect.top <= edgeThreshold;
|
|
787
|
+
const isNearViewportBottom = rect.bottom >= viewportHeight - edgeThreshold;
|
|
788
|
+
const edge = (hasBottomRule || isNearViewportBottom) && !(hasTopRule || isNearViewportTop) ? "bottom" : "top";
|
|
789
|
+
if (position === "fixed" && edge === "top" && !hasTopRule && !isNearViewportTop) return;
|
|
790
|
+
if (position === "fixed" && edge === "bottom" && !hasBottomRule && !isNearViewportBottom) return;
|
|
791
|
+
if (position === "sticky" && !hasTopRule && !hasBottomRule && !isNearViewportTop && !isNearViewportBottom) return;
|
|
792
|
+
candidates.push({ el, position, edge });
|
|
785
793
|
});
|
|
786
|
-
const candidateSet = new Set(candidates);
|
|
787
|
-
const topLevelCandidates = candidates.filter((el) => {
|
|
794
|
+
const candidateSet = new Set(candidates.map(({ el }) => el));
|
|
795
|
+
const topLevelCandidates = candidates.filter(({ el }) => {
|
|
788
796
|
for (let parent = el.parentElement; parent; parent = parent.parentElement) {
|
|
789
797
|
if (candidateSet.has(parent)) return false;
|
|
790
798
|
}
|
|
791
799
|
return true;
|
|
792
800
|
});
|
|
793
|
-
topLevelCandidates.forEach((el) => {
|
|
794
|
-
if (!
|
|
795
|
-
el.dataset.
|
|
801
|
+
topLevelCandidates.forEach(({ el, position, edge }) => {
|
|
802
|
+
if (!hasOwn(el.dataset, "pkAffixedAdjusted")) {
|
|
803
|
+
el.dataset.pkAffixedAdjusted = "1";
|
|
804
|
+
el.dataset.pkOrigPosition = el.style.getPropertyValue("position") || "";
|
|
805
|
+
el.dataset.pkOrigPositionPriority = el.style.getPropertyPriority("position") || "";
|
|
806
|
+
el.dataset.pkOrigTop = el.style.getPropertyValue("top") || "";
|
|
807
|
+
el.dataset.pkOrigTopPriority = el.style.getPropertyPriority("top") || "";
|
|
808
|
+
el.dataset.pkOrigBottom = el.style.getPropertyValue("bottom") || "";
|
|
809
|
+
el.dataset.pkOrigBottomPriority = el.style.getPropertyPriority("bottom") || "";
|
|
796
810
|
el.dataset.pkOrigTranslate = el.style.getPropertyValue("translate") || "";
|
|
797
811
|
el.dataset.pkOrigTranslatePriority = el.style.getPropertyPriority("translate") || "";
|
|
798
812
|
}
|
|
799
813
|
el.classList.add(className);
|
|
800
|
-
|
|
814
|
+
if (position === "fixed") {
|
|
815
|
+
const deltaY = edge === "bottom" ? safeTargetHeight - viewportHeight - scrollY : -scrollY;
|
|
816
|
+
if (Math.abs(deltaY) > 1) {
|
|
817
|
+
el.style.setProperty("translate", `0 ${Math.round(deltaY)}px`, "important");
|
|
818
|
+
}
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
el.style.setProperty("position", "relative", "important");
|
|
822
|
+
el.style.setProperty("top", "auto", "important");
|
|
823
|
+
el.style.setProperty("bottom", "auto", "important");
|
|
801
824
|
});
|
|
802
825
|
return topLevelCandidates.length;
|
|
803
826
|
}, {
|
|
@@ -805,7 +828,7 @@ var pinBottomAffixedElements = async (page, options = {}) => {
|
|
|
805
828
|
targetHeight: options.targetHeight
|
|
806
829
|
});
|
|
807
830
|
};
|
|
808
|
-
var
|
|
831
|
+
var restoreAffixedElementsForExpandedScreenshot = async (page) => {
|
|
809
832
|
await page.evaluate((className) => {
|
|
810
833
|
const hasOwn = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
|
|
811
834
|
const expansionKeys = [
|
|
@@ -814,13 +837,28 @@ var restoreBottomAffixedElements = async (page) => {
|
|
|
814
837
|
"pkOrigMinHeight",
|
|
815
838
|
"pkOrigMaxHeight"
|
|
816
839
|
];
|
|
817
|
-
document.querySelectorAll('[data-pk-
|
|
840
|
+
document.querySelectorAll('[data-pk-affixed-adjusted="1"]').forEach((el) => {
|
|
841
|
+
if (hasOwn(el.dataset, "pkOrigPosition")) {
|
|
842
|
+
el.style.setProperty("position", el.dataset.pkOrigPosition || "", el.dataset.pkOrigPositionPriority || "");
|
|
843
|
+
delete el.dataset.pkOrigPosition;
|
|
844
|
+
delete el.dataset.pkOrigPositionPriority;
|
|
845
|
+
}
|
|
846
|
+
if (hasOwn(el.dataset, "pkOrigTop")) {
|
|
847
|
+
el.style.setProperty("top", el.dataset.pkOrigTop || "", el.dataset.pkOrigTopPriority || "");
|
|
848
|
+
delete el.dataset.pkOrigTop;
|
|
849
|
+
delete el.dataset.pkOrigTopPriority;
|
|
850
|
+
}
|
|
851
|
+
if (hasOwn(el.dataset, "pkOrigBottom")) {
|
|
852
|
+
el.style.setProperty("bottom", el.dataset.pkOrigBottom || "", el.dataset.pkOrigBottomPriority || "");
|
|
853
|
+
delete el.dataset.pkOrigBottom;
|
|
854
|
+
delete el.dataset.pkOrigBottomPriority;
|
|
855
|
+
}
|
|
818
856
|
if (hasOwn(el.dataset, "pkOrigTranslate")) {
|
|
819
857
|
el.style.setProperty("translate", el.dataset.pkOrigTranslate || "", el.dataset.pkOrigTranslatePriority || "");
|
|
820
858
|
delete el.dataset.pkOrigTranslate;
|
|
821
859
|
delete el.dataset.pkOrigTranslatePriority;
|
|
822
860
|
}
|
|
823
|
-
delete el.dataset.
|
|
861
|
+
delete el.dataset.pkAffixedAdjusted;
|
|
824
862
|
const stillExpanded = expansionKeys.some((key) => hasOwn(el.dataset, key));
|
|
825
863
|
if (!stillExpanded) {
|
|
826
864
|
el.classList.remove(className);
|
|
@@ -849,8 +887,8 @@ var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
849
887
|
});
|
|
850
888
|
viewportResized = true;
|
|
851
889
|
} else {
|
|
852
|
-
await
|
|
853
|
-
logger.warning(`\u79FB\u52A8\u7AEF\u5438\
|
|
890
|
+
await adjustAffixedElementsForExpandedScreenshot(page, { targetHeight }).catch((error) => {
|
|
891
|
+
logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u91CD\u5B9A\u4F4D\u5931\u8D25: ${error?.message || error}`);
|
|
854
892
|
});
|
|
855
893
|
}
|
|
856
894
|
if (settleMs > 0) {
|
|
@@ -956,8 +994,8 @@ var captureExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
956
994
|
maxClipHeight: state.targetHeight
|
|
957
995
|
});
|
|
958
996
|
} finally {
|
|
959
|
-
await
|
|
960
|
-
logger.warning(`\u79FB\u52A8\u7AEF\u5438\
|
|
997
|
+
await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
|
|
998
|
+
logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
|
|
961
999
|
});
|
|
962
1000
|
if (options.restore) {
|
|
963
1001
|
await restoreExpandedFullPageScreenshot(page, state);
|
|
@@ -3398,6 +3436,26 @@ var waitJitter = (base, jitterPercent = 0.3) => (0, import_delay3.default)(jitte
|
|
|
3398
3436
|
// src/internals/humanize/mobile.js
|
|
3399
3437
|
var logger7 = createInternalLogger("Humanize.Mobile");
|
|
3400
3438
|
var initializedPages = /* @__PURE__ */ new WeakSet();
|
|
3439
|
+
var DEFAULT_TAP_TIMEOUT_MS = 2500;
|
|
3440
|
+
var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
|
|
3441
|
+
var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
|
|
3442
|
+
var INTERACTIVE_SELECTOR = [
|
|
3443
|
+
"button",
|
|
3444
|
+
'[role="button"]',
|
|
3445
|
+
"a[href]",
|
|
3446
|
+
"label",
|
|
3447
|
+
"input",
|
|
3448
|
+
"textarea",
|
|
3449
|
+
"select",
|
|
3450
|
+
"summary",
|
|
3451
|
+
'[contenteditable="true"]',
|
|
3452
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
3453
|
+
].join(",");
|
|
3454
|
+
var EDITABLE_SELECTOR = [
|
|
3455
|
+
"input",
|
|
3456
|
+
"textarea",
|
|
3457
|
+
'[contenteditable="true"]'
|
|
3458
|
+
].join(",");
|
|
3401
3459
|
var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
3402
3460
|
var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
|
|
3403
3461
|
var describeTarget = (target) => {
|
|
@@ -3420,8 +3478,24 @@ var clipBoxToViewport = (box, viewport) => {
|
|
|
3420
3478
|
}
|
|
3421
3479
|
return box;
|
|
3422
3480
|
};
|
|
3481
|
+
var withTimeout = async (operation, timeoutMs, label) => {
|
|
3482
|
+
const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
|
|
3483
|
+
let timeoutId = null;
|
|
3484
|
+
try {
|
|
3485
|
+
return await Promise.race([
|
|
3486
|
+
Promise.resolve().then(operation),
|
|
3487
|
+
new Promise((_, reject) => {
|
|
3488
|
+
timeoutId = setTimeout(() => {
|
|
3489
|
+
reject(new Error(`${label} timeout after ${safeTimeoutMs}ms`));
|
|
3490
|
+
}, safeTimeoutMs);
|
|
3491
|
+
})
|
|
3492
|
+
]);
|
|
3493
|
+
} finally {
|
|
3494
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
3495
|
+
}
|
|
3496
|
+
};
|
|
3423
3497
|
var checkElementVisibility = async (element) => {
|
|
3424
|
-
return element.evaluate((el) => {
|
|
3498
|
+
return element.evaluate((el, interactiveSelector) => {
|
|
3425
3499
|
const targetStyle = window.getComputedStyle(el);
|
|
3426
3500
|
if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
|
|
3427
3501
|
return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
|
|
@@ -3438,10 +3512,12 @@ var checkElementVisibility = async (element) => {
|
|
|
3438
3512
|
let clipTop = 0;
|
|
3439
3513
|
let clipBottom = viewH;
|
|
3440
3514
|
let isFixed = false;
|
|
3515
|
+
let positioning = null;
|
|
3441
3516
|
for (let node = el; node && node !== document.body; node = node.parentElement) {
|
|
3442
3517
|
const style = window.getComputedStyle(node);
|
|
3443
3518
|
if (style && (style.position === "fixed" || style.position === "sticky")) {
|
|
3444
3519
|
isFixed = true;
|
|
3520
|
+
positioning = style.position;
|
|
3445
3521
|
break;
|
|
3446
3522
|
}
|
|
3447
3523
|
}
|
|
@@ -3477,21 +3553,10 @@ var checkElementVisibility = async (element) => {
|
|
|
3477
3553
|
direction: centerY < clipTop ? "up" : "down",
|
|
3478
3554
|
cy: centerY,
|
|
3479
3555
|
viewH,
|
|
3480
|
-
isFixed
|
|
3556
|
+
isFixed,
|
|
3557
|
+
positioning
|
|
3481
3558
|
};
|
|
3482
3559
|
}
|
|
3483
|
-
const interactiveSelector = [
|
|
3484
|
-
"button",
|
|
3485
|
-
'[role="button"]',
|
|
3486
|
-
"a[href]",
|
|
3487
|
-
"label",
|
|
3488
|
-
"input",
|
|
3489
|
-
"textarea",
|
|
3490
|
-
"select",
|
|
3491
|
-
"summary",
|
|
3492
|
-
'[contenteditable="true"]',
|
|
3493
|
-
'[tabindex]:not([tabindex="-1"])'
|
|
3494
|
-
].join(",");
|
|
3495
3560
|
const targetInteractive = typeof el.closest === "function" ? el.closest(interactiveSelector) : null;
|
|
3496
3561
|
const sameInteractiveTarget = (pointElement) => {
|
|
3497
3562
|
if (!pointElement) return false;
|
|
@@ -3513,6 +3578,7 @@ var checkElementVisibility = async (element) => {
|
|
|
3513
3578
|
id: node.id || "",
|
|
3514
3579
|
className,
|
|
3515
3580
|
isFixed: Boolean(style && (style.position === "fixed" || style.position === "sticky")),
|
|
3581
|
+
positioning: style?.position || "",
|
|
3516
3582
|
top: rect2 ? rect2.top : null,
|
|
3517
3583
|
bottom: rect2 ? rect2.bottom : null,
|
|
3518
3584
|
left: rect2 ? rect2.left : null,
|
|
@@ -3530,7 +3596,7 @@ var checkElementVisibility = async (element) => {
|
|
|
3530
3596
|
for (const point of samplePoints) {
|
|
3531
3597
|
const pointElement = document.elementFromPoint(point.x, point.y);
|
|
3532
3598
|
if (sameInteractiveTarget(pointElement)) {
|
|
3533
|
-
return { code: "VISIBLE", isFixed };
|
|
3599
|
+
return { code: "VISIBLE", isFixed, positioning };
|
|
3534
3600
|
}
|
|
3535
3601
|
obstruction = obstruction || describeElement(pointElement);
|
|
3536
3602
|
}
|
|
@@ -3542,14 +3608,15 @@ var checkElementVisibility = async (element) => {
|
|
|
3542
3608
|
obstruction,
|
|
3543
3609
|
cy,
|
|
3544
3610
|
viewH,
|
|
3545
|
-
isFixed
|
|
3611
|
+
isFixed,
|
|
3612
|
+
positioning
|
|
3546
3613
|
};
|
|
3547
3614
|
}
|
|
3548
|
-
return { code: "VISIBLE", isFixed };
|
|
3549
|
-
});
|
|
3615
|
+
return { code: "VISIBLE", isFixed, positioning };
|
|
3616
|
+
}, INTERACTIVE_SELECTOR);
|
|
3550
3617
|
};
|
|
3551
3618
|
var resolveSafeTapPoint = async (element) => {
|
|
3552
|
-
return element.evaluate((el) => {
|
|
3619
|
+
return element.evaluate((el, interactiveSelector) => {
|
|
3553
3620
|
const rect = el.getBoundingClientRect();
|
|
3554
3621
|
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
3555
3622
|
return null;
|
|
@@ -3586,18 +3653,6 @@ var resolveSafeTapPoint = async (element) => {
|
|
|
3586
3653
|
if (visibleWidth <= 1 || visibleHeight <= 1) {
|
|
3587
3654
|
return null;
|
|
3588
3655
|
}
|
|
3589
|
-
const interactiveSelector = [
|
|
3590
|
-
"button",
|
|
3591
|
-
'[role="button"]',
|
|
3592
|
-
"a[href]",
|
|
3593
|
-
"label",
|
|
3594
|
-
"input",
|
|
3595
|
-
"textarea",
|
|
3596
|
-
"select",
|
|
3597
|
-
"summary",
|
|
3598
|
-
'[contenteditable="true"]',
|
|
3599
|
-
'[tabindex]:not([tabindex="-1"])'
|
|
3600
|
-
].join(",");
|
|
3601
3656
|
const targetInteractive = typeof el.closest === "function" ? el.closest(interactiveSelector) : null;
|
|
3602
3657
|
const sameInteractiveTarget = (pointElement) => {
|
|
3603
3658
|
if (!pointElement) return false;
|
|
@@ -3635,6 +3690,46 @@ var resolveSafeTapPoint = async (element) => {
|
|
|
3635
3690
|
x: chosen.x,
|
|
3636
3691
|
y: chosen.y
|
|
3637
3692
|
};
|
|
3693
|
+
}, INTERACTIVE_SELECTOR);
|
|
3694
|
+
};
|
|
3695
|
+
var activateElementFallback = async (element, point = null, options = {}) => {
|
|
3696
|
+
return element.evaluate((el, { innerPoint, innerOptions, interactiveSelector, editableSelector }) => {
|
|
3697
|
+
const pointElement = innerPoint ? document.elementFromPoint(innerPoint.x, innerPoint.y) : null;
|
|
3698
|
+
const nearestInteractive = (node) => {
|
|
3699
|
+
if (!node || typeof node.closest !== "function") return null;
|
|
3700
|
+
return node.closest(interactiveSelector);
|
|
3701
|
+
};
|
|
3702
|
+
const targetInteractive = nearestInteractive(el);
|
|
3703
|
+
const pointInteractive = nearestInteractive(pointElement);
|
|
3704
|
+
let target = null;
|
|
3705
|
+
if (pointInteractive && (pointInteractive === targetInteractive || pointInteractive === el || pointInteractive.contains(el) || el.contains(pointInteractive))) {
|
|
3706
|
+
target = pointInteractive;
|
|
3707
|
+
}
|
|
3708
|
+
target = target || targetInteractive || el;
|
|
3709
|
+
const editable = typeof target.closest === "function" ? target.closest(editableSelector) : null;
|
|
3710
|
+
if (editable && typeof editable.focus === "function") {
|
|
3711
|
+
editable.focus({ preventScroll: true });
|
|
3712
|
+
if (innerOptions.editableOnly) {
|
|
3713
|
+
return { activated: true, method: "focus", tag: editable.tagName || "" };
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
if (typeof target.focus === "function") {
|
|
3717
|
+
target.focus({ preventScroll: true });
|
|
3718
|
+
}
|
|
3719
|
+
if (!innerOptions.editableOnly && typeof target.click === "function") {
|
|
3720
|
+
target.click();
|
|
3721
|
+
return { activated: true, method: "dom-click", tag: target.tagName || "" };
|
|
3722
|
+
}
|
|
3723
|
+
return {
|
|
3724
|
+
activated: Boolean(editable),
|
|
3725
|
+
method: editable ? "focus" : "none",
|
|
3726
|
+
tag: target?.tagName || ""
|
|
3727
|
+
};
|
|
3728
|
+
}, {
|
|
3729
|
+
innerPoint: point,
|
|
3730
|
+
innerOptions: options || {},
|
|
3731
|
+
interactiveSelector: INTERACTIVE_SELECTOR,
|
|
3732
|
+
editableSelector: EDITABLE_SELECTOR
|
|
3638
3733
|
});
|
|
3639
3734
|
};
|
|
3640
3735
|
var getScrollableRect = async (element) => {
|
|
@@ -3839,12 +3934,31 @@ var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
|
|
|
3839
3934
|
}
|
|
3840
3935
|
}
|
|
3841
3936
|
};
|
|
3842
|
-
var tapPoint = async (page, point) => {
|
|
3937
|
+
var tapPoint = async (page, point, options = {}) => {
|
|
3938
|
+
const {
|
|
3939
|
+
timeoutMs = DEFAULT_TAP_TIMEOUT_MS,
|
|
3940
|
+
mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
|
|
3941
|
+
allowMouseFallback = true
|
|
3942
|
+
} = options;
|
|
3843
3943
|
if (page.touchscreen && typeof page.touchscreen.tap === "function") {
|
|
3844
|
-
|
|
3845
|
-
|
|
3944
|
+
try {
|
|
3945
|
+
await withTimeout(
|
|
3946
|
+
() => page.touchscreen.tap(point.x, point.y),
|
|
3947
|
+
timeoutMs,
|
|
3948
|
+
"touchscreen.tap"
|
|
3949
|
+
);
|
|
3950
|
+
return { method: "touchscreen" };
|
|
3951
|
+
} catch (error) {
|
|
3952
|
+
logger7.warn(`tapPoint | touchscreen.tap \u5931\u8D25\u6216\u8D85\u65F6\uFF0C\u5C1D\u8BD5\u9F20\u6807\u515C\u5E95: ${error?.message || error}`);
|
|
3953
|
+
if (!allowMouseFallback) throw error;
|
|
3954
|
+
}
|
|
3846
3955
|
}
|
|
3847
|
-
await
|
|
3956
|
+
await withTimeout(
|
|
3957
|
+
() => page.mouse.click(point.x, point.y),
|
|
3958
|
+
mouseFallbackTimeoutMs,
|
|
3959
|
+
"mouse.click fallback"
|
|
3960
|
+
);
|
|
3961
|
+
return { method: "mouse" };
|
|
3848
3962
|
};
|
|
3849
3963
|
var MobileHumanize = {
|
|
3850
3964
|
jitterMs,
|
|
@@ -3897,6 +4011,10 @@ var MobileHumanize = {
|
|
|
3897
4011
|
return { element, didScroll, restore: null };
|
|
3898
4012
|
}
|
|
3899
4013
|
const scrollRect = await getScrollableRect(element);
|
|
4014
|
+
if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
|
|
4015
|
+
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"})`);
|
|
4016
|
+
return { element, didScroll, restore: null };
|
|
4017
|
+
}
|
|
3900
4018
|
if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
|
|
3901
4019
|
logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
|
|
3902
4020
|
return { element, didScroll, restore: null };
|
|
@@ -4017,7 +4135,12 @@ var MobileHumanize = {
|
|
|
4017
4135
|
const {
|
|
4018
4136
|
reactionDelay = 220,
|
|
4019
4137
|
throwOnMissing = true,
|
|
4020
|
-
scrollIfNeeded = true
|
|
4138
|
+
scrollIfNeeded = true,
|
|
4139
|
+
tapTimeoutMs = DEFAULT_TAP_TIMEOUT_MS,
|
|
4140
|
+
mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
|
|
4141
|
+
activateFallbackTimeoutMs = DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS,
|
|
4142
|
+
fallbackDomClick = true,
|
|
4143
|
+
fallbackDomClickOnTapError = true
|
|
4021
4144
|
} = options;
|
|
4022
4145
|
const targetDesc = describeTarget(target);
|
|
4023
4146
|
logger7.start("humanClick", `target=${targetDesc}`);
|
|
@@ -4028,6 +4151,9 @@ var MobileHumanize = {
|
|
|
4028
4151
|
await tapPoint(page, {
|
|
4029
4152
|
x: viewport.width * (0.45 + Math.random() * 0.1),
|
|
4030
4153
|
y: viewport.height * (0.48 + Math.random() * 0.12)
|
|
4154
|
+
}, {
|
|
4155
|
+
timeoutMs: tapTimeoutMs,
|
|
4156
|
+
mouseFallbackTimeoutMs
|
|
4031
4157
|
});
|
|
4032
4158
|
logger7.success("humanClick", "Tapped current position");
|
|
4033
4159
|
return true;
|
|
@@ -4042,6 +4168,19 @@ var MobileHumanize = {
|
|
|
4042
4168
|
}
|
|
4043
4169
|
const status = await checkElementVisibility(element).catch(() => null);
|
|
4044
4170
|
if (status && status.code !== "VISIBLE") {
|
|
4171
|
+
if (fallbackDomClick && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
|
|
4172
|
+
const fallback = await withTimeout(
|
|
4173
|
+
() => activateElementFallback(element, null, {
|
|
4174
|
+
editableOnly: true
|
|
4175
|
+
}),
|
|
4176
|
+
activateFallbackTimeoutMs,
|
|
4177
|
+
"focus fallback"
|
|
4178
|
+
).catch(() => null);
|
|
4179
|
+
if (fallback?.activated) {
|
|
4180
|
+
logger7.warn(`humanClick: fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
|
|
4181
|
+
return true;
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4045
4184
|
const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
|
|
4046
4185
|
if (throwOnMissing) throw new Error(message);
|
|
4047
4186
|
logger7.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
|
|
@@ -4056,7 +4195,23 @@ var MobileHumanize = {
|
|
|
4056
4195
|
await waitJitter(reactionDelay, 0.45);
|
|
4057
4196
|
const safePoint = await resolveSafeTapPoint(element).catch(() => null);
|
|
4058
4197
|
const tapTarget = safePoint || randomPointInBox(clipBoxToViewport(box, resolveViewport(page)), 0.2);
|
|
4059
|
-
|
|
4198
|
+
try {
|
|
4199
|
+
await tapPoint(page, tapTarget, {
|
|
4200
|
+
timeoutMs: tapTimeoutMs,
|
|
4201
|
+
mouseFallbackTimeoutMs
|
|
4202
|
+
});
|
|
4203
|
+
} catch (tapError) {
|
|
4204
|
+
if (!fallbackDomClickOnTapError) throw tapError;
|
|
4205
|
+
const fallback = await withTimeout(
|
|
4206
|
+
() => activateElementFallback(element, tapTarget, {
|
|
4207
|
+
editableOnly: false
|
|
4208
|
+
}),
|
|
4209
|
+
activateFallbackTimeoutMs,
|
|
4210
|
+
"activation fallback"
|
|
4211
|
+
).catch(() => null);
|
|
4212
|
+
if (!fallback?.activated) throw tapError;
|
|
4213
|
+
logger7.warn(`humanClick: tap \u5931\u8D25\u540E\u5DF2\u7528 ${fallback.method} \u515C\u5E95: ${tapError?.message || tapError}`);
|
|
4214
|
+
}
|
|
4060
4215
|
await waitJitter(120, 0.35);
|
|
4061
4216
|
logger7.success("humanClick");
|
|
4062
4217
|
return true;
|
|
@@ -6874,7 +7029,7 @@ var getHostname = (url) => {
|
|
|
6874
7029
|
return "\u672C\u5730\u9875\u9762";
|
|
6875
7030
|
}
|
|
6876
7031
|
};
|
|
6877
|
-
var
|
|
7032
|
+
var withTimeout2 = async (promise, timeoutMs) => {
|
|
6878
7033
|
const safeTimeoutMs = Math.max(0, Number(timeoutMs) || 0);
|
|
6879
7034
|
if (!safeTimeoutMs) {
|
|
6880
7035
|
return promise;
|
|
@@ -6959,7 +7114,7 @@ var resolveWithCustomResolver = async (page, baseMeta, options = {}) => {
|
|
|
6959
7114
|
const serverAddr = response && typeof response.serverAddr === "function" ? await response.serverAddr().catch(() => null) : null;
|
|
6960
7115
|
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
6961
7116
|
try {
|
|
6962
|
-
const resolved = await
|
|
7117
|
+
const resolved = await withTimeout2(
|
|
6963
7118
|
Promise.resolve(resolver({
|
|
6964
7119
|
page,
|
|
6965
7120
|
response,
|
|
@@ -7259,13 +7414,13 @@ var resolveWithIpLookup = async (page, options = {}) => {
|
|
|
7259
7414
|
const contentType = normalizeText(await pickHeaderValue(response, ["content-type"]));
|
|
7260
7415
|
let rawText = "";
|
|
7261
7416
|
if (response && typeof response.text === "function") {
|
|
7262
|
-
rawText = String(await
|
|
7417
|
+
rawText = String(await withTimeout2(
|
|
7263
7418
|
response.text().catch(() => ""),
|
|
7264
7419
|
timeoutMs
|
|
7265
7420
|
) || "");
|
|
7266
7421
|
}
|
|
7267
7422
|
if (!rawText) {
|
|
7268
|
-
rawText = String(await
|
|
7423
|
+
rawText = String(await withTimeout2(
|
|
7269
7424
|
probePage.evaluate(() => {
|
|
7270
7425
|
return document.body?.innerText || document.documentElement?.innerText || "";
|
|
7271
7426
|
}).catch(() => ""),
|
|
@@ -8368,7 +8523,7 @@ var normalizeXurl = (value) => {
|
|
|
8368
8523
|
var normalizeShare = (share) => {
|
|
8369
8524
|
const source = share && typeof share === "object" ? share : {};
|
|
8370
8525
|
const modeRaw = String(source.mode || "dom").trim().toLowerCase();
|
|
8371
|
-
const mode =
|
|
8526
|
+
const mode = ["dom", "response", "custom"].includes(modeRaw) ? modeRaw : "dom";
|
|
8372
8527
|
return {
|
|
8373
8528
|
mode,
|
|
8374
8529
|
prefix: normalizePrefix(source.prefix),
|
|
@@ -8484,16 +8639,17 @@ var Share = {
|
|
|
8484
8639
|
* 捕获分享链接(单模式)
|
|
8485
8640
|
* - mode='dom': 仅 DOM 监听
|
|
8486
8641
|
* - mode='response': 仅接口监听
|
|
8642
|
+
* - mode='custom': 使用 performActions 返回值作为链接
|
|
8487
8643
|
*
|
|
8488
8644
|
* @param {import('playwright').Page} page
|
|
8489
8645
|
* @param {Object} [options]
|
|
8490
|
-
* @param {{ mode?: 'dom' | 'response', prefix: string, xurl?: Array<string | string[]> }} options.share
|
|
8646
|
+
* @param {{ mode?: 'dom' | 'response' | 'custom', prefix: string, xurl?: Array<string | string[]> }} options.share
|
|
8491
8647
|
* @param {number} [options.timeoutMs=50000]
|
|
8492
8648
|
* @param {number} [options.payloadSnapshotMaxLen=500]
|
|
8493
8649
|
* @param {string | string[]} [options.domSelectors='html']
|
|
8494
8650
|
* @param {'added' | 'changed' | 'all'} [options.domMode='all']
|
|
8495
|
-
* @param {() => Promise<void>} [options.performActions]
|
|
8496
|
-
* @returns {Promise<{ link: string | null; payloadText: string; payloadSnapshot: string; source: 'dom' | 'response' | 'none' }>}
|
|
8651
|
+
* @param {() => void | string | { link?: string | null; payloadText?: string } | Promise<void | string | { link?: string | null; payloadText?: string }>} [options.performActions]
|
|
8652
|
+
* @returns {Promise<{ link: string | null; payloadText: string; payloadSnapshot: string; source: 'dom' | 'response' | 'custom' | 'none' }>}
|
|
8497
8653
|
*/
|
|
8498
8654
|
async captureLink(page, options = {}) {
|
|
8499
8655
|
const share = normalizeShare(options.share);
|
|
@@ -8506,7 +8662,7 @@ var Share = {
|
|
|
8506
8662
|
if (share.prefix === void 0) {
|
|
8507
8663
|
throw new Error("Share.captureLink requires options.share.prefix");
|
|
8508
8664
|
}
|
|
8509
|
-
if (share.mode !== "dom" && share.mode !== "response") {
|
|
8665
|
+
if (share.mode !== "dom" && share.mode !== "response" && share.mode !== "custom") {
|
|
8510
8666
|
throw new Error(`Share.captureLink unsupported share.mode: ${share.mode}`);
|
|
8511
8667
|
}
|
|
8512
8668
|
const apiMatchers = share.mode === "response" ? share.xurl[0] || [] : [];
|
|
@@ -8528,7 +8684,8 @@ var Share = {
|
|
|
8528
8684
|
};
|
|
8529
8685
|
const candidates = {
|
|
8530
8686
|
dom: null,
|
|
8531
|
-
response: null
|
|
8687
|
+
response: null,
|
|
8688
|
+
custom: null
|
|
8532
8689
|
};
|
|
8533
8690
|
const setCandidate = (source, link, payloadText = "") => {
|
|
8534
8691
|
const safe = String(link || "").trim();
|
|
@@ -8628,37 +8785,47 @@ var Share = {
|
|
|
8628
8785
|
logger15.info(`\u5F53\u524D\u4E3A\u63A5\u53E3\u6A21\u5F0F\uFF0C\u6302\u8F7D response \u76D1\u542C: apiMatchers=${toJsonInline(apiMatchers, 160)}`);
|
|
8629
8786
|
page.on("response", onResponse);
|
|
8630
8787
|
}
|
|
8788
|
+
if (share.mode === "custom") {
|
|
8789
|
+
logger15.info("\u5F53\u524D\u4E3A custom \u6A21\u5F0F\uFF0C\u5C06\u4F7F\u7528 performActions \u8FD4\u56DE\u503C");
|
|
8790
|
+
}
|
|
8631
8791
|
const deadline = Date.now() + timeoutMs;
|
|
8632
8792
|
const getRemainingMs = () => Math.max(0, deadline - Date.now());
|
|
8633
8793
|
try {
|
|
8634
8794
|
const actionTimeout = getRemainingMs();
|
|
8635
8795
|
logger15.start("captureLink.performActions", `\u6267\u884C\u52A8\u4F5C\u9884\u7B97=${actionTimeout}ms`);
|
|
8796
|
+
let actionValue;
|
|
8636
8797
|
if (actionTimeout > 0) {
|
|
8637
8798
|
let timer = null;
|
|
8638
|
-
|
|
8639
|
-
const actionPromise = Promise.resolve().then(() => performActions()).then(() => "__ACTION_DONE__").catch((error) => {
|
|
8640
|
-
actionError = error;
|
|
8641
|
-
return "__ACTION_ERROR__";
|
|
8642
|
-
});
|
|
8799
|
+
const actionPromise = Promise.resolve().then(() => performActions()).then((result) => ({ type: "done", result })).catch((error) => ({ type: "error", error }));
|
|
8643
8800
|
const timeoutPromise = new Promise((resolve) => {
|
|
8644
|
-
timer = setTimeout(() => resolve("
|
|
8801
|
+
timer = setTimeout(() => resolve({ type: "timeout" }), actionTimeout);
|
|
8645
8802
|
});
|
|
8646
8803
|
const actionResult = await Promise.race([actionPromise, timeoutPromise]);
|
|
8647
8804
|
if (timer) clearTimeout(timer);
|
|
8648
|
-
if (actionResult === "
|
|
8649
|
-
logger15.fail("captureLink.performActions",
|
|
8650
|
-
throw
|
|
8805
|
+
if (actionResult.type === "error") {
|
|
8806
|
+
logger15.fail("captureLink.performActions", actionResult.error);
|
|
8807
|
+
throw actionResult.error;
|
|
8651
8808
|
}
|
|
8652
|
-
if (actionResult === "
|
|
8809
|
+
if (actionResult.type === "timeout") {
|
|
8653
8810
|
stats.actionTimedOut = true;
|
|
8654
8811
|
logger15.warning(`performActions \u5DF2\u8D85\u65F6 (${actionTimeout}ms)\uFF0C\u52A8\u4F5C\u53EF\u80FD\u4ECD\u5728\u5F02\u6B65\u6267\u884C`);
|
|
8655
8812
|
} else {
|
|
8813
|
+
actionValue = actionResult.result;
|
|
8656
8814
|
logger15.success("captureLink.performActions", "\u6267\u884C\u52A8\u4F5C\u5B8C\u6210");
|
|
8657
8815
|
}
|
|
8658
8816
|
}
|
|
8817
|
+
if (share.mode === "custom") {
|
|
8818
|
+
const customLink = typeof actionValue === "string" ? actionValue : actionValue?.link || actionValue?.payloadText;
|
|
8819
|
+
const customPayloadText = typeof actionValue === "string" ? actionValue : actionValue?.payloadText;
|
|
8820
|
+
if (setCandidate("custom", customLink, customPayloadText)) {
|
|
8821
|
+
logger15.success("captureLink.customResult", `custom \u547D\u4E2D\u5206\u4EAB\u94FE\u63A5: link=${candidates.custom.link}`);
|
|
8822
|
+
} else {
|
|
8823
|
+
logger15.warning("performActions \u6267\u884C\u5B8C\u6210\u4F46\u672A\u8FD4\u56DE\u6709\u6548\u5206\u4EAB\u94FE\u63A5");
|
|
8824
|
+
}
|
|
8825
|
+
}
|
|
8659
8826
|
let nextProgressLogTs = Date.now() + 3e3;
|
|
8660
8827
|
while (true) {
|
|
8661
|
-
const selected = share.mode
|
|
8828
|
+
const selected = candidates[share.mode];
|
|
8662
8829
|
if (selected?.link) {
|
|
8663
8830
|
logger15.success("captureLink", `\u6355\u83B7\u6210\u529F: source=${share.mode}, link=${selected.link}`);
|
|
8664
8831
|
return {
|
|
@@ -8668,6 +8835,7 @@ var Share = {
|
|
|
8668
8835
|
source: share.mode
|
|
8669
8836
|
};
|
|
8670
8837
|
}
|
|
8838
|
+
if (share.mode === "custom") break;
|
|
8671
8839
|
const remaining = getRemainingMs();
|
|
8672
8840
|
if (remaining <= 0) break;
|
|
8673
8841
|
const now = Date.now();
|