@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 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 = 25, minStep = 80, maxStep = 220 } = options;
687
+ const { maxSteps = 30, minStep = 80, maxStep = 250 } = options;
688
688
  const targetDesc = typeof target === "string" ? target : "ElementHandle";
689
- logger4.debug("humanScroll", `target=${targetDesc}`);
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", `Element not found: ${target}`);
695
- return { element: null, didScroll: false, restore: async () => {
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 needsScroll = await element.evaluate((el) => {
702
- const isScrollable = (node) => {
703
- if (!node || node === document.body) return false;
704
- const style = window.getComputedStyle(node);
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 { moved: false, inView: false };
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
- let isObstructed = false;
788
- if (cy < 0 || cy > viewH) {
789
- isObstructed = true;
790
- } else {
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
- let target2 = null;
797
- for (const container of scrollables) {
798
- const crect = container === scrollingElement ? { top: 0, bottom: viewH } : container.getBoundingClientRect();
799
- if (rect.top < crect.top + 2) {
800
- target2 = { container, direction: -1 };
801
- break;
802
- }
803
- if (rect.bottom > crect.bottom - 2) {
804
- target2 = { container, direction: 1 };
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
- if (!target2) {
819
- return { moved: false, inView: !isObstructed };
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
- const maxScroll = target2.container.scrollHeight - target2.container.clientHeight;
822
- if (maxScroll <= 0) {
823
- return { moved: false, inView: !isObstructed };
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
- const before = target2.container.scrollTop;
826
- let next = before + target2.direction * stepPx;
827
- if (next < 0) next = 0;
828
- if (next > maxScroll) next = maxScroll;
829
- if (Math.abs(next - before) < 1) {
830
- return { moved: false, inView: !isObstructed };
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
- target2.container.scrollTop = next;
833
- return { moved: true, inView: false };
834
- }, step);
835
- if (result.inView) break;
836
- if (!result.moved && !result.inView) {
837
- logger4.warn("humanScroll", "Stuck: cannot scroll further but element still obstructed/hidden");
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
- } finally {
867
- await scrollStateHandle.dispose();
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
- logger4.success("humanScroll", "Scroll completed");
871
- return { element, didScroll: true, restore };
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 模拟人类鼠标移动轨迹并点击