@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.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 = 25, minStep = 80, maxStep = 220 } = options;
658
+ const { maxSteps = 30, minStep = 80, maxStep = 250 } = options;
659
659
  const targetDesc = typeof target === "string" ? target : "ElementHandle";
660
- logger4.debug(`humanScroll| target=${targetDesc}`);
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| Element not found: ${target}`);
666
- return { element: null, didScroll: false, restore: async () => {
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 scrollStateHandle = await element.evaluateHandle((el) => {
674
- const isScrollable = (node) => {
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 { moved: false, inView: false };
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
- let isObstructed = false;
722
- if (cy < 0 || cy > viewH) {
723
- isObstructed = true;
724
- } else {
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
- let target2 = null;
731
- for (const container of scrollables) {
732
- const crect = container === scrollingElement ? { top: 0, bottom: viewH } : container.getBoundingClientRect();
733
- if (rect.top < crect.top + 2) {
734
- target2 = { container, direction: -1 };
735
- break;
736
- }
737
- if (rect.bottom > crect.bottom - 2) {
738
- target2 = { container, direction: 1 };
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
- if (!target2) {
753
- return { moved: false, inView: !isObstructed };
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
- const maxScroll = target2.container.scrollHeight - target2.container.clientHeight;
756
- if (maxScroll <= 0) {
757
- return { moved: false, inView: !isObstructed };
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
- const before = target2.container.scrollTop;
760
- let next = before + target2.direction * stepPx;
761
- if (next < 0) next = 0;
762
- if (next > maxScroll) next = maxScroll;
763
- if (Math.abs(next - before) < 1) {
764
- return { moved: false, inView: !isObstructed };
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
- target2.container.scrollTop = next;
767
- return { moved: true, inView: false };
768
- }, step);
769
- if (result.moved) didScroll = true;
770
- if (result.inView) break;
771
- if (!result.moved && !result.inView) {
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
- } finally {
802
- await scrollStateHandle.dispose();
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
- logger4.success("humanScroll", "Scroll completed");
806
- return { element, didScroll, restore };
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 模拟人类鼠标移动轨迹并点击