@skrillex1224/playwright-toolkit 2.1.30 → 2.1.31
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 +60 -124
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +60 -124
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -655,155 +655,91 @@ var Humanize = {
|
|
|
655
655
|
* @param {number} [options.maxStep=220] - 单次滚动最大步长
|
|
656
656
|
*/
|
|
657
657
|
async humanScroll(page, target, options = {}) {
|
|
658
|
-
const { maxSteps =
|
|
658
|
+
const { maxSteps = 30, minStep = 80, maxStep = 250 } = options;
|
|
659
659
|
const targetDesc = typeof target === "string" ? target : "ElementHandle";
|
|
660
|
-
logger4.debug(`humanScroll|
|
|
660
|
+
logger4.debug(`humanScroll | \u76EE\u6807=${targetDesc}`);
|
|
661
661
|
let element;
|
|
662
662
|
if (typeof target === "string") {
|
|
663
663
|
element = await page.$(target);
|
|
664
664
|
if (!element) {
|
|
665
|
-
logger4.warn(`humanScroll|
|
|
666
|
-
return { element: null, didScroll: false
|
|
667
|
-
} };
|
|
665
|
+
logger4.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${target}`);
|
|
666
|
+
return { element: null, didScroll: false };
|
|
668
667
|
}
|
|
669
668
|
} else {
|
|
670
669
|
element = target;
|
|
671
670
|
}
|
|
671
|
+
const cursor = $GetCursor(page);
|
|
672
672
|
let didScroll = false;
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
if (!node || node === document.body) return false;
|
|
676
|
-
const style = window.getComputedStyle(node);
|
|
677
|
-
const overflowY = style.overflowY || style.overflow;
|
|
678
|
-
return (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && node.scrollHeight > node.clientHeight + 1;
|
|
679
|
-
};
|
|
680
|
-
const scrollables = [];
|
|
681
|
-
const addNode = (node) => {
|
|
682
|
-
if (!node || scrollables.some((item) => item.el === node)) return;
|
|
683
|
-
scrollables.push({
|
|
684
|
-
el: node,
|
|
685
|
-
top: node.scrollTop
|
|
686
|
-
});
|
|
687
|
-
};
|
|
688
|
-
let current = el.parentElement;
|
|
689
|
-
while (current) {
|
|
690
|
-
if (isScrollable(current)) addNode(current);
|
|
691
|
-
current = current.parentElement;
|
|
692
|
-
}
|
|
693
|
-
const scrollingElement = document.scrollingElement || document.documentElement;
|
|
694
|
-
if (scrollingElement) addNode(scrollingElement);
|
|
695
|
-
return scrollables;
|
|
696
|
-
});
|
|
697
|
-
for (let i = 0; i < maxSteps; i++) {
|
|
698
|
-
const step = minStep + Math.floor(Math.random() * (maxStep - minStep));
|
|
699
|
-
const result = await element.evaluate((el, stepPx) => {
|
|
700
|
-
const isScrollable = (node) => {
|
|
701
|
-
if (!node || node === document.body) return false;
|
|
702
|
-
const style = window.getComputedStyle(node);
|
|
703
|
-
const overflowY = style.overflowY || style.overflow;
|
|
704
|
-
return (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && node.scrollHeight > node.clientHeight + 1;
|
|
705
|
-
};
|
|
673
|
+
const checkVisibility = async () => {
|
|
674
|
+
return await element.evaluate((el) => {
|
|
706
675
|
const rect = el.getBoundingClientRect();
|
|
707
676
|
if (!rect || rect.width === 0 || rect.height === 0) {
|
|
708
|
-
return {
|
|
677
|
+
return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6" };
|
|
709
678
|
}
|
|
710
|
-
const scrollables = [];
|
|
711
|
-
let current = el.parentElement;
|
|
712
|
-
while (current) {
|
|
713
|
-
if (isScrollable(current)) scrollables.push(current);
|
|
714
|
-
current = current.parentElement;
|
|
715
|
-
}
|
|
716
|
-
const scrollingElement = document.scrollingElement || document.documentElement;
|
|
717
|
-
if (scrollingElement) scrollables.push(scrollingElement);
|
|
718
679
|
const cx = rect.left + rect.width / 2;
|
|
719
680
|
const cy = rect.top + rect.height / 2;
|
|
720
681
|
const viewH = window.innerHeight;
|
|
721
|
-
|
|
722
|
-
if (cy < 0 || cy > viewH) {
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const pointElement = document.elementFromPoint(cx, cy);
|
|
726
|
-
if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
|
|
727
|
-
isObstructed = true;
|
|
728
|
-
}
|
|
682
|
+
const viewW = window.innerWidth;
|
|
683
|
+
if (cy < 0 || cy > viewH || cx < 0 || cx > viewW) {
|
|
684
|
+
const direction = cy < 0 ? "up" : cy > viewH ? "down" : "unknown";
|
|
685
|
+
return { code: "OUT_OF_VIEWPORT", reason: "\u4E0D\u5728\u89C6\u53E3\u5185", direction, cy, viewH };
|
|
729
686
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
break;
|
|
740
|
-
}
|
|
741
|
-
if (isObstructed) {
|
|
742
|
-
const containerCenter = (crect.top + crect.bottom) / 2;
|
|
743
|
-
const elementCenter = (rect.top + rect.bottom) / 2;
|
|
744
|
-
const forceDirection = elementCenter > containerCenter ? 1 : -1;
|
|
745
|
-
const canScroll = forceDirection === 1 ? container.scrollTop < container.scrollHeight - container.clientHeight : container.scrollTop > 0;
|
|
746
|
-
if (canScroll) {
|
|
747
|
-
target2 = { container, direction: forceDirection };
|
|
748
|
-
break;
|
|
687
|
+
const pointElement = document.elementFromPoint(cx, cy);
|
|
688
|
+
if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
|
|
689
|
+
return {
|
|
690
|
+
code: "OBSTRUCTED",
|
|
691
|
+
reason: "\u88AB\u906E\u6321",
|
|
692
|
+
obstruction: {
|
|
693
|
+
tag: pointElement.tagName,
|
|
694
|
+
id: pointElement.id,
|
|
695
|
+
className: pointElement.className
|
|
749
696
|
}
|
|
750
|
-
}
|
|
697
|
+
};
|
|
751
698
|
}
|
|
752
|
-
|
|
753
|
-
|
|
699
|
+
return { code: "VISIBLE" };
|
|
700
|
+
});
|
|
701
|
+
};
|
|
702
|
+
try {
|
|
703
|
+
for (let i = 0; i < maxSteps; i++) {
|
|
704
|
+
const status = await checkVisibility();
|
|
705
|
+
if (status.code === "VISIBLE") {
|
|
706
|
+
logger4.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
|
|
707
|
+
return { element, didScroll };
|
|
754
708
|
}
|
|
755
|
-
|
|
756
|
-
if (
|
|
757
|
-
|
|
709
|
+
logger4.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
|
|
710
|
+
if (status.code === "OBSTRUCTED" && status.obstruction) {
|
|
711
|
+
logger4.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
|
|
758
712
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
713
|
+
let deltaY = 0;
|
|
714
|
+
if (status.code === "OUT_OF_VIEWPORT") {
|
|
715
|
+
if (status.direction === "down") {
|
|
716
|
+
deltaY = minStep + Math.random() * (maxStep - minStep);
|
|
717
|
+
} else if (status.direction === "up") {
|
|
718
|
+
deltaY = -(minStep + Math.random() * (maxStep - minStep));
|
|
719
|
+
} else {
|
|
720
|
+
deltaY = 100;
|
|
721
|
+
}
|
|
722
|
+
} else if (status.code === "OBSTRUCTED") {
|
|
723
|
+
deltaY = (Math.random() > 0.5 ? 1 : -1) * (minStep + Math.random() * 50);
|
|
765
724
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
logger4.warn("humanScroll| Stuck: cannot scroll further but element still obstructed/hidden");
|
|
773
|
-
break;
|
|
774
|
-
}
|
|
775
|
-
await delay2(this.jitterMs(120, 0.4));
|
|
776
|
-
}
|
|
777
|
-
const restore = async () => {
|
|
778
|
-
if (!scrollStateHandle) return;
|
|
779
|
-
try {
|
|
780
|
-
const restoreOnce2 = async () => page.evaluate((state) => {
|
|
781
|
-
if (!Array.isArray(state) || state.length === 0) return true;
|
|
782
|
-
let done = true;
|
|
783
|
-
for (const item of state) {
|
|
784
|
-
if (!item || !item.el) continue;
|
|
785
|
-
const current = item.el.scrollTop;
|
|
786
|
-
const target2 = item.top;
|
|
787
|
-
if (Math.abs(current - target2) > 1) {
|
|
788
|
-
done = false;
|
|
789
|
-
const delta = target2 - current;
|
|
790
|
-
const step = Math.sign(delta) * Math.max(20, Math.min(120, Math.abs(delta) * 0.3));
|
|
791
|
-
item.el.scrollTop = current + step;
|
|
792
|
-
}
|
|
725
|
+
if (i === 0 || Math.random() < 0.2) {
|
|
726
|
+
const viewSize = page.viewportSize();
|
|
727
|
+
if (viewSize) {
|
|
728
|
+
const safeX = viewSize.width * 0.5 + (Math.random() - 0.5) * 100;
|
|
729
|
+
const safeY = viewSize.height * 0.5 + (Math.random() - 0.5) * 100;
|
|
730
|
+
await cursor.actions.move({ x: safeX, y: safeY });
|
|
793
731
|
}
|
|
794
|
-
return done;
|
|
795
|
-
}, scrollStateHandle);
|
|
796
|
-
for (let i = 0; i < 10; i++) {
|
|
797
|
-
const done = await restoreOnce2();
|
|
798
|
-
if (done) break;
|
|
799
|
-
await delay2(this.jitterMs(80, 0.4));
|
|
800
732
|
}
|
|
801
|
-
|
|
802
|
-
|
|
733
|
+
await page.mouse.wheel(0, deltaY);
|
|
734
|
+
didScroll = true;
|
|
735
|
+
await delay2(this.jitterMs(150 + Math.random() * 200, 0.2));
|
|
803
736
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
737
|
+
logger4.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
|
|
738
|
+
return { element, didScroll };
|
|
739
|
+
} catch (error) {
|
|
740
|
+
logger4.fail("humanScroll", error);
|
|
741
|
+
throw error;
|
|
742
|
+
}
|
|
807
743
|
},
|
|
808
744
|
/**
|
|
809
745
|
* 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
|