@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 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 = 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
  }
700
+ const cursor = $GetCursor(page);
701
701
  let didScroll = false;
702
- const scrollStateHandle = await element.evaluateHandle((el) => {
703
- const isScrollable = (node) => {
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 { moved: false, inView: false };
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
- let isObstructed = false;
751
- if (cy < 0 || cy > viewH) {
752
- isObstructed = true;
753
- } else {
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
- let target2 = null;
760
- for (const container of scrollables) {
761
- const crect = container === scrollingElement ? { top: 0, bottom: viewH } : container.getBoundingClientRect();
762
- if (rect.top < crect.top + 2) {
763
- target2 = { container, direction: -1 };
764
- break;
765
- }
766
- if (rect.bottom > crect.bottom - 2) {
767
- target2 = { container, direction: 1 };
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
- if (!target2) {
782
- 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 };
783
737
  }
784
- const maxScroll = target2.container.scrollHeight - target2.container.clientHeight;
785
- if (maxScroll <= 0) {
786
- 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}">`);
787
741
  }
788
- const before = target2.container.scrollTop;
789
- let next = before + target2.direction * stepPx;
790
- if (next < 0) next = 0;
791
- if (next > maxScroll) next = maxScroll;
792
- if (Math.abs(next - before) < 1) {
793
- 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);
794
753
  }
795
- target2.container.scrollTop = next;
796
- return { moved: true, inView: false };
797
- }, step);
798
- if (result.moved) didScroll = true;
799
- if (result.inView) break;
800
- if (!result.moved && !result.inView) {
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
- } finally {
831
- 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));
832
765
  }
833
- };
834
- logger4.success("humanScroll", "Scroll completed");
835
- return { element, didScroll, 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
+ }
836
772
  },
837
773
  /**
838
774
  * 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击