@skrillex1224/playwright-toolkit 2.1.29 → 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 +61 -161
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +61 -161
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -684,191 +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(
|
|
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(
|
|
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
|
}
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const overflowY = style.overflowY || style.overflow;
|
|
706
|
-
return (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && node.scrollHeight > node.clientHeight + 1;
|
|
707
|
-
};
|
|
708
|
-
const rect = el.getBoundingClientRect();
|
|
709
|
-
if (!rect || rect.width === 0 || rect.height === 0) return true;
|
|
710
|
-
const inViewport = rect.top >= 0 && rect.bottom <= window.innerHeight;
|
|
711
|
-
if (!inViewport) return true;
|
|
712
|
-
let current = el.parentElement;
|
|
713
|
-
while (current) {
|
|
714
|
-
if (isScrollable(current)) {
|
|
715
|
-
const crect = current.getBoundingClientRect();
|
|
716
|
-
if (rect.top < crect.top + 2 || rect.bottom > crect.bottom - 2) {
|
|
717
|
-
return true;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
current = current.parentElement;
|
|
721
|
-
}
|
|
722
|
-
const cx = rect.left + rect.width / 2;
|
|
723
|
-
const cy = rect.top + rect.height / 2;
|
|
724
|
-
if (cx < 0 || cx > window.innerWidth || cy < 0 || cy > window.innerHeight) return true;
|
|
725
|
-
const pointElement = document.elementFromPoint(cx, cy);
|
|
726
|
-
if (pointElement) {
|
|
727
|
-
if (el.contains(pointElement) || pointElement.contains(el)) {
|
|
728
|
-
return false;
|
|
729
|
-
}
|
|
730
|
-
return true;
|
|
731
|
-
}
|
|
732
|
-
return false;
|
|
733
|
-
});
|
|
734
|
-
if (!needsScroll) {
|
|
735
|
-
logger4.debug("humanScroll", "Element already in view (and unobstructed)");
|
|
736
|
-
return { element, didScroll: false, restore: async () => {
|
|
737
|
-
} };
|
|
738
|
-
}
|
|
739
|
-
const scrollStateHandle = await element.evaluateHandle((el) => {
|
|
740
|
-
const isScrollable = (node) => {
|
|
741
|
-
if (!node || node === document.body) return false;
|
|
742
|
-
const style = window.getComputedStyle(node);
|
|
743
|
-
const overflowY = style.overflowY || style.overflow;
|
|
744
|
-
return (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && node.scrollHeight > node.clientHeight + 1;
|
|
745
|
-
};
|
|
746
|
-
const scrollables = [];
|
|
747
|
-
const addNode = (node) => {
|
|
748
|
-
if (!node || scrollables.some((item) => item.el === node)) return;
|
|
749
|
-
scrollables.push({
|
|
750
|
-
el: node,
|
|
751
|
-
top: node.scrollTop
|
|
752
|
-
});
|
|
753
|
-
};
|
|
754
|
-
let current = el.parentElement;
|
|
755
|
-
while (current) {
|
|
756
|
-
if (isScrollable(current)) addNode(current);
|
|
757
|
-
current = current.parentElement;
|
|
758
|
-
}
|
|
759
|
-
const scrollingElement = document.scrollingElement || document.documentElement;
|
|
760
|
-
if (scrollingElement) addNode(scrollingElement);
|
|
761
|
-
return scrollables;
|
|
762
|
-
});
|
|
763
|
-
for (let i = 0; i < maxSteps; i++) {
|
|
764
|
-
const step = minStep + Math.floor(Math.random() * (maxStep - minStep));
|
|
765
|
-
const result = await element.evaluate((el, stepPx) => {
|
|
766
|
-
const isScrollable = (node) => {
|
|
767
|
-
if (!node || node === document.body) return false;
|
|
768
|
-
const style = window.getComputedStyle(node);
|
|
769
|
-
const overflowY = style.overflowY || style.overflow;
|
|
770
|
-
return (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && node.scrollHeight > node.clientHeight + 1;
|
|
771
|
-
};
|
|
700
|
+
const cursor = $GetCursor(page);
|
|
701
|
+
let didScroll = false;
|
|
702
|
+
const checkVisibility = async () => {
|
|
703
|
+
return await element.evaluate((el) => {
|
|
772
704
|
const rect = el.getBoundingClientRect();
|
|
773
705
|
if (!rect || rect.width === 0 || rect.height === 0) {
|
|
774
|
-
return {
|
|
706
|
+
return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6" };
|
|
775
707
|
}
|
|
776
|
-
const scrollables = [];
|
|
777
|
-
let current = el.parentElement;
|
|
778
|
-
while (current) {
|
|
779
|
-
if (isScrollable(current)) scrollables.push(current);
|
|
780
|
-
current = current.parentElement;
|
|
781
|
-
}
|
|
782
|
-
const scrollingElement = document.scrollingElement || document.documentElement;
|
|
783
|
-
if (scrollingElement) scrollables.push(scrollingElement);
|
|
784
708
|
const cx = rect.left + rect.width / 2;
|
|
785
709
|
const cy = rect.top + rect.height / 2;
|
|
786
710
|
const viewH = window.innerHeight;
|
|
787
|
-
|
|
788
|
-
if (cy < 0 || cy > viewH) {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
const pointElement = document.elementFromPoint(cx, cy);
|
|
792
|
-
if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
|
|
793
|
-
isObstructed = true;
|
|
794
|
-
}
|
|
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 };
|
|
795
715
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
break;
|
|
806
|
-
}
|
|
807
|
-
if (isObstructed) {
|
|
808
|
-
const containerCenter = (crect.top + crect.bottom) / 2;
|
|
809
|
-
const elementCenter = (rect.top + rect.bottom) / 2;
|
|
810
|
-
const forceDirection = elementCenter > containerCenter ? 1 : -1;
|
|
811
|
-
const canScroll = forceDirection === 1 ? container.scrollTop < container.scrollHeight - container.clientHeight : container.scrollTop > 0;
|
|
812
|
-
if (canScroll) {
|
|
813
|
-
target2 = { container, direction: forceDirection };
|
|
814
|
-
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
|
|
815
725
|
}
|
|
816
|
-
}
|
|
726
|
+
};
|
|
817
727
|
}
|
|
818
|
-
|
|
819
|
-
|
|
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 };
|
|
820
737
|
}
|
|
821
|
-
|
|
822
|
-
if (
|
|
823
|
-
|
|
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}">`);
|
|
824
741
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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);
|
|
831
753
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
break;
|
|
839
|
-
}
|
|
840
|
-
await (0, import_delay2.default)(this.jitterMs(120, 0.4));
|
|
841
|
-
}
|
|
842
|
-
const restore = async () => {
|
|
843
|
-
if (!scrollStateHandle) return;
|
|
844
|
-
try {
|
|
845
|
-
const restoreOnce2 = async () => page.evaluate((state) => {
|
|
846
|
-
if (!Array.isArray(state) || state.length === 0) return true;
|
|
847
|
-
let done = true;
|
|
848
|
-
for (const item of state) {
|
|
849
|
-
if (!item || !item.el) continue;
|
|
850
|
-
const current = item.el.scrollTop;
|
|
851
|
-
const target2 = item.top;
|
|
852
|
-
if (Math.abs(current - target2) > 1) {
|
|
853
|
-
done = false;
|
|
854
|
-
const delta = target2 - current;
|
|
855
|
-
const step = Math.sign(delta) * Math.max(20, Math.min(120, Math.abs(delta) * 0.3));
|
|
856
|
-
item.el.scrollTop = current + step;
|
|
857
|
-
}
|
|
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 });
|
|
858
760
|
}
|
|
859
|
-
return done;
|
|
860
|
-
}, scrollStateHandle);
|
|
861
|
-
for (let i = 0; i < 10; i++) {
|
|
862
|
-
const done = await restoreOnce2();
|
|
863
|
-
if (done) break;
|
|
864
|
-
await (0, import_delay2.default)(this.jitterMs(80, 0.4));
|
|
865
761
|
}
|
|
866
|
-
|
|
867
|
-
|
|
762
|
+
await page.mouse.wheel(0, deltaY);
|
|
763
|
+
didScroll = true;
|
|
764
|
+
await (0, import_delay2.default)(this.jitterMs(150 + Math.random() * 200, 0.2));
|
|
868
765
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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
|
+
}
|
|
872
772
|
},
|
|
873
773
|
/**
|
|
874
774
|
* 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
|