@skrillex1224/playwright-toolkit 2.1.20 → 2.1.21
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 +98 -107
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +98 -107
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -535,7 +535,7 @@ var Humanize = {
|
|
|
535
535
|
},
|
|
536
536
|
/**
|
|
537
537
|
* 渐进式滚动到元素可见(仅处理 Y 轴滚动)
|
|
538
|
-
*
|
|
538
|
+
* 返回 restore 方法,用于将滚动容器恢复到原位置
|
|
539
539
|
*
|
|
540
540
|
* @param {import('playwright').Page} page
|
|
541
541
|
* @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
|
|
@@ -545,7 +545,6 @@ var Humanize = {
|
|
|
545
545
|
* @param {number} [options.maxStep=220] - 单次滚动最大步长
|
|
546
546
|
*/
|
|
547
547
|
async humanScroll(page, target, options = {}) {
|
|
548
|
-
const cursor = $GetCursor(page);
|
|
549
548
|
const { maxSteps = 25, minStep = 80, maxStep = 220 } = options;
|
|
550
549
|
let element;
|
|
551
550
|
if (typeof target === "string") {
|
|
@@ -557,13 +556,13 @@ var Humanize = {
|
|
|
557
556
|
} else {
|
|
558
557
|
element = target;
|
|
559
558
|
}
|
|
560
|
-
const isScrollable = (node) => {
|
|
561
|
-
if (!node || node === document.body) return false;
|
|
562
|
-
const style = window.getComputedStyle(node);
|
|
563
|
-
const overflowY = style.overflowY || style.overflow;
|
|
564
|
-
return (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && node.scrollHeight > node.clientHeight + 1;
|
|
565
|
-
};
|
|
566
559
|
const needsScroll = await element.evaluate((el) => {
|
|
560
|
+
const isScrollable = (node) => {
|
|
561
|
+
if (!node || node === document.body) return false;
|
|
562
|
+
const style = window.getComputedStyle(node);
|
|
563
|
+
const overflowY = style.overflowY || style.overflow;
|
|
564
|
+
return (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && node.scrollHeight > node.clientHeight + 1;
|
|
565
|
+
};
|
|
567
566
|
const rect = el.getBoundingClientRect();
|
|
568
567
|
if (!rect || rect.width === 0 || rect.height === 0) return true;
|
|
569
568
|
const inViewport = rect.top >= 0 && rect.bottom <= window.innerHeight;
|
|
@@ -584,118 +583,110 @@ var Humanize = {
|
|
|
584
583
|
return { element, didScroll: false, restore: async () => {
|
|
585
584
|
} };
|
|
586
585
|
}
|
|
587
|
-
const
|
|
588
|
-
const
|
|
586
|
+
const scrollStateHandle = await element.evaluateHandle((el) => {
|
|
587
|
+
const isScrollable = (node) => {
|
|
588
|
+
if (!node || node === document.body) return false;
|
|
589
|
+
const style = window.getComputedStyle(node);
|
|
590
|
+
const overflowY = style.overflowY || style.overflow;
|
|
591
|
+
return (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && node.scrollHeight > node.clientHeight + 1;
|
|
592
|
+
};
|
|
593
|
+
const scrollables = [];
|
|
594
|
+
const addNode = (node) => {
|
|
595
|
+
if (!node || scrollables.some((item) => item.el === node)) return;
|
|
596
|
+
scrollables.push({
|
|
597
|
+
el: node,
|
|
598
|
+
top: node.scrollTop
|
|
599
|
+
});
|
|
600
|
+
};
|
|
589
601
|
let current = el.parentElement;
|
|
590
602
|
while (current) {
|
|
591
|
-
if (isScrollable(current))
|
|
603
|
+
if (isScrollable(current)) addNode(current);
|
|
592
604
|
current = current.parentElement;
|
|
593
605
|
}
|
|
594
606
|
const scrollingElement = document.scrollingElement || document.documentElement;
|
|
595
|
-
if (scrollingElement)
|
|
596
|
-
return
|
|
607
|
+
if (scrollingElement) addNode(scrollingElement);
|
|
608
|
+
return scrollables;
|
|
597
609
|
});
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
const top = await itemEl.evaluate((el) => el.scrollTop);
|
|
608
|
-
scrollables.push({ el: itemEl, top });
|
|
609
|
-
} else {
|
|
610
|
-
await itemHandle.dispose();
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
} finally {
|
|
614
|
-
await scrollablesHandle.dispose();
|
|
615
|
-
}
|
|
616
|
-
const pickTarget = async () => {
|
|
617
|
-
const handle = await element.evaluateHandle((el) => {
|
|
610
|
+
for (let i = 0; i < maxSteps; i++) {
|
|
611
|
+
const step = minStep + Math.floor(Math.random() * (maxStep - minStep));
|
|
612
|
+
const result = await element.evaluate((el, stepPx) => {
|
|
613
|
+
const isScrollable = (node) => {
|
|
614
|
+
if (!node || node === document.body) return false;
|
|
615
|
+
const style = window.getComputedStyle(node);
|
|
616
|
+
const overflowY = style.overflowY || style.overflow;
|
|
617
|
+
return (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && node.scrollHeight > node.clientHeight + 1;
|
|
618
|
+
};
|
|
618
619
|
const rect = el.getBoundingClientRect();
|
|
619
620
|
if (!rect || rect.width === 0 || rect.height === 0) {
|
|
620
|
-
return {
|
|
621
|
+
return { moved: false, inView: false };
|
|
621
622
|
}
|
|
622
|
-
const
|
|
623
|
+
const scrollables = [];
|
|
623
624
|
let current = el.parentElement;
|
|
624
625
|
while (current) {
|
|
625
|
-
if (isScrollable(current))
|
|
626
|
+
if (isScrollable(current)) scrollables.push(current);
|
|
626
627
|
current = current.parentElement;
|
|
627
628
|
}
|
|
628
629
|
const scrollingElement = document.scrollingElement || document.documentElement;
|
|
629
|
-
if (scrollingElement)
|
|
630
|
-
|
|
630
|
+
if (scrollingElement) scrollables.push(scrollingElement);
|
|
631
|
+
let target2 = null;
|
|
632
|
+
for (const container of scrollables) {
|
|
631
633
|
const crect = container === scrollingElement ? { top: 0, bottom: window.innerHeight } : container.getBoundingClientRect();
|
|
632
634
|
if (rect.top < crect.top + 2) {
|
|
633
|
-
|
|
635
|
+
target2 = { container, direction: -1 };
|
|
636
|
+
break;
|
|
634
637
|
}
|
|
635
638
|
if (rect.bottom > crect.bottom - 2) {
|
|
636
|
-
|
|
639
|
+
target2 = { container, direction: 1 };
|
|
640
|
+
break;
|
|
637
641
|
}
|
|
638
642
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const inViewHandle = await handle.getProperty("inView");
|
|
642
|
-
const inView = await inViewHandle.jsonValue();
|
|
643
|
-
await inViewHandle.dispose();
|
|
644
|
-
if (inView) {
|
|
645
|
-
await handle.dispose();
|
|
646
|
-
return { inView: true };
|
|
647
|
-
}
|
|
648
|
-
const directionHandle = await handle.getProperty("direction");
|
|
649
|
-
const direction = await directionHandle.jsonValue();
|
|
650
|
-
await directionHandle.dispose();
|
|
651
|
-
const containerHandle = (await handle.getProperty("container")).asElement();
|
|
652
|
-
await handle.dispose();
|
|
653
|
-
return { inView: false, direction, container: containerHandle };
|
|
654
|
-
};
|
|
655
|
-
for (let i = 0; i < maxSteps; i++) {
|
|
656
|
-
const { inView, direction, container } = await pickTarget();
|
|
657
|
-
if (inView) break;
|
|
658
|
-
if (!container || !direction) break;
|
|
659
|
-
try {
|
|
660
|
-
const box = await container.boundingBox();
|
|
661
|
-
if (!box) break;
|
|
662
|
-
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
|
|
663
|
-
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
|
|
664
|
-
await cursor.actions.move({ x, y });
|
|
665
|
-
const step = minStep + Math.floor(Math.random() * (maxStep - minStep));
|
|
666
|
-
await page.mouse.wheel(0, direction * step);
|
|
667
|
-
if (Math.random() < 0.12) {
|
|
668
|
-
await delay2(this.jitterMs(60, 0.5));
|
|
669
|
-
const backStep = Math.max(20, Math.floor(step * (0.15 + Math.random() * 0.2)));
|
|
670
|
-
await page.mouse.wheel(0, -direction * backStep);
|
|
643
|
+
if (!target2) {
|
|
644
|
+
return { moved: false, inView: true };
|
|
671
645
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
646
|
+
const maxScroll = target2.container.scrollHeight - target2.container.clientHeight;
|
|
647
|
+
if (maxScroll <= 0) {
|
|
648
|
+
return { moved: false, inView: false };
|
|
649
|
+
}
|
|
650
|
+
const before = target2.container.scrollTop;
|
|
651
|
+
let next = before + target2.direction * stepPx;
|
|
652
|
+
if (next < 0) next = 0;
|
|
653
|
+
if (next > maxScroll) next = maxScroll;
|
|
654
|
+
if (next === before) {
|
|
655
|
+
return { moved: false, inView: false };
|
|
656
|
+
}
|
|
657
|
+
target2.container.scrollTop = next;
|
|
658
|
+
return { moved: true, inView: false };
|
|
659
|
+
}, step);
|
|
660
|
+
if (result.inView) break;
|
|
661
|
+
if (!result.moved) break;
|
|
675
662
|
await delay2(this.jitterMs(120, 0.4));
|
|
676
663
|
}
|
|
677
664
|
const restore = async () => {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
if (!
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
665
|
+
if (!scrollStateHandle) return;
|
|
666
|
+
try {
|
|
667
|
+
const restoreOnce = async () => page.evaluate((state) => {
|
|
668
|
+
if (!Array.isArray(state) || state.length === 0) return true;
|
|
669
|
+
let done = true;
|
|
670
|
+
for (const item of state) {
|
|
671
|
+
if (!item || !item.el) continue;
|
|
672
|
+
const current = item.el.scrollTop;
|
|
673
|
+
const target2 = item.top;
|
|
674
|
+
if (Math.abs(current - target2) > 1) {
|
|
675
|
+
done = false;
|
|
676
|
+
const delta = target2 - current;
|
|
677
|
+
const step = Math.sign(delta) * Math.max(20, Math.min(120, Math.abs(delta) * 0.3));
|
|
678
|
+
item.el.scrollTop = current + step;
|
|
679
|
+
}
|
|
693
680
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
await
|
|
681
|
+
return done;
|
|
682
|
+
}, scrollStateHandle);
|
|
683
|
+
for (let i = 0; i < 10; i++) {
|
|
684
|
+
const done = await restoreOnce();
|
|
685
|
+
if (done) break;
|
|
686
|
+
await delay2(this.jitterMs(80, 0.4));
|
|
698
687
|
}
|
|
688
|
+
} finally {
|
|
689
|
+
await scrollStateHandle.dispose();
|
|
699
690
|
}
|
|
700
691
|
};
|
|
701
692
|
return { element, didScroll: true, restore };
|
|
@@ -715,18 +706,6 @@ var Humanize = {
|
|
|
715
706
|
const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true } = options;
|
|
716
707
|
const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
|
|
717
708
|
logger4.start("humanClick", `target=${targetDesc}`);
|
|
718
|
-
let restoreScroll = async () => {
|
|
719
|
-
};
|
|
720
|
-
let restored = false;
|
|
721
|
-
const restoreOnce = async () => {
|
|
722
|
-
if (restored) return;
|
|
723
|
-
restored = true;
|
|
724
|
-
try {
|
|
725
|
-
await restoreScroll();
|
|
726
|
-
} catch (restoreError) {
|
|
727
|
-
logger4.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
|
|
728
|
-
}
|
|
729
|
-
};
|
|
730
709
|
try {
|
|
731
710
|
if (target == null) {
|
|
732
711
|
await delay2(this.jitterMs(reactionDelay, 0.4));
|
|
@@ -747,6 +726,18 @@ var Humanize = {
|
|
|
747
726
|
} else {
|
|
748
727
|
element = target;
|
|
749
728
|
}
|
|
729
|
+
let restoreScroll = async () => {
|
|
730
|
+
};
|
|
731
|
+
let restored = false;
|
|
732
|
+
const restoreOnce = async () => {
|
|
733
|
+
if (restored) return;
|
|
734
|
+
restored = true;
|
|
735
|
+
try {
|
|
736
|
+
await restoreScroll();
|
|
737
|
+
} catch (restoreError) {
|
|
738
|
+
logger4.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
|
|
739
|
+
}
|
|
740
|
+
};
|
|
750
741
|
if (scrollIfNeeded) {
|
|
751
742
|
const scrollResult = await this.humanScroll(page, element);
|
|
752
743
|
restoreScroll = scrollResult.restore || restoreScroll;
|
|
@@ -770,7 +761,7 @@ var Humanize = {
|
|
|
770
761
|
logger4.success("humanClick");
|
|
771
762
|
return true;
|
|
772
763
|
} catch (error) {
|
|
773
|
-
|
|
764
|
+
logger4.fail("humanClick", error);
|
|
774
765
|
logger4.fail("humanClick", error);
|
|
775
766
|
throw error;
|
|
776
767
|
}
|