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