@skrillex1224/playwright-toolkit 2.1.20 → 2.1.22

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
@@ -564,7 +564,7 @@ var Humanize = {
564
564
  },
565
565
  /**
566
566
  * 渐进式滚动到元素可见(仅处理 Y 轴滚动)
567
- * 使用鼠标滚轮模拟人类滚动,并返回 restore 用于回滚
567
+ * 返回 restore 方法,用于将滚动容器恢复到原位置
568
568
  *
569
569
  * @param {import('playwright').Page} page
570
570
  * @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
@@ -574,7 +574,6 @@ var Humanize = {
574
574
  * @param {number} [options.maxStep=220] - 单次滚动最大步长
575
575
  */
576
576
  async humanScroll(page, target, options = {}) {
577
- const cursor = $GetCursor(page);
578
577
  const { maxSteps = 25, minStep = 80, maxStep = 220 } = options;
579
578
  let element;
580
579
  if (typeof target === "string") {
@@ -586,13 +585,13 @@ var Humanize = {
586
585
  } else {
587
586
  element = target;
588
587
  }
589
- const isScrollable = (node) => {
590
- if (!node || node === document.body) return false;
591
- const style = window.getComputedStyle(node);
592
- const overflowY = style.overflowY || style.overflow;
593
- return (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && node.scrollHeight > node.clientHeight + 1;
594
- };
595
588
  const needsScroll = await element.evaluate((el) => {
589
+ const isScrollable = (node) => {
590
+ if (!node || node === document.body) return false;
591
+ const style = window.getComputedStyle(node);
592
+ const overflowY = style.overflowY || style.overflow;
593
+ return (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && node.scrollHeight > node.clientHeight + 1;
594
+ };
596
595
  const rect = el.getBoundingClientRect();
597
596
  if (!rect || rect.width === 0 || rect.height === 0) return true;
598
597
  const inViewport = rect.top >= 0 && rect.bottom <= window.innerHeight;
@@ -613,118 +612,110 @@ var Humanize = {
613
612
  return { element, didScroll: false, restore: async () => {
614
613
  } };
615
614
  }
616
- const scrollablesHandle = await element.evaluateHandle((el) => {
617
- const scrollables2 = [];
615
+ const scrollStateHandle = await element.evaluateHandle((el) => {
616
+ const isScrollable = (node) => {
617
+ if (!node || node === document.body) return false;
618
+ const style = window.getComputedStyle(node);
619
+ const overflowY = style.overflowY || style.overflow;
620
+ return (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && node.scrollHeight > node.clientHeight + 1;
621
+ };
622
+ const scrollables = [];
623
+ const addNode = (node) => {
624
+ if (!node || scrollables.some((item) => item.el === node)) return;
625
+ scrollables.push({
626
+ el: node,
627
+ top: node.scrollTop
628
+ });
629
+ };
618
630
  let current = el.parentElement;
619
631
  while (current) {
620
- if (isScrollable(current)) scrollables2.push(current);
632
+ if (isScrollable(current)) addNode(current);
621
633
  current = current.parentElement;
622
634
  }
623
635
  const scrollingElement = document.scrollingElement || document.documentElement;
624
- if (scrollingElement) scrollables2.push(scrollingElement);
625
- return scrollables2;
636
+ if (scrollingElement) addNode(scrollingElement);
637
+ return scrollables;
626
638
  });
627
- const scrollables = [];
628
- try {
629
- const lengthHandle = await scrollablesHandle.getProperty("length");
630
- const length = await lengthHandle.jsonValue();
631
- await lengthHandle.dispose();
632
- for (let i = 0; i < length; i++) {
633
- const itemHandle = await scrollablesHandle.getProperty(String(i));
634
- const itemEl = itemHandle.asElement();
635
- if (itemEl) {
636
- const top = await itemEl.evaluate((el) => el.scrollTop);
637
- scrollables.push({ el: itemEl, top });
638
- } else {
639
- await itemHandle.dispose();
640
- }
641
- }
642
- } finally {
643
- await scrollablesHandle.dispose();
644
- }
645
- const pickTarget = async () => {
646
- const handle = await element.evaluateHandle((el) => {
639
+ for (let i = 0; i < maxSteps; i++) {
640
+ const step = minStep + Math.floor(Math.random() * (maxStep - minStep));
641
+ const result = await element.evaluate((el, stepPx) => {
642
+ const isScrollable = (node) => {
643
+ if (!node || node === document.body) return false;
644
+ const style = window.getComputedStyle(node);
645
+ const overflowY = style.overflowY || style.overflow;
646
+ return (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && node.scrollHeight > node.clientHeight + 1;
647
+ };
647
648
  const rect = el.getBoundingClientRect();
648
649
  if (!rect || rect.width === 0 || rect.height === 0) {
649
- return { inView: false, container: null, direction: 0 };
650
+ return { moved: false, inView: false };
650
651
  }
651
- const scrollables2 = [];
652
+ const scrollables = [];
652
653
  let current = el.parentElement;
653
654
  while (current) {
654
- if (isScrollable(current)) scrollables2.push(current);
655
+ if (isScrollable(current)) scrollables.push(current);
655
656
  current = current.parentElement;
656
657
  }
657
658
  const scrollingElement = document.scrollingElement || document.documentElement;
658
- if (scrollingElement) scrollables2.push(scrollingElement);
659
- for (const container of scrollables2) {
659
+ if (scrollingElement) scrollables.push(scrollingElement);
660
+ let target2 = null;
661
+ for (const container of scrollables) {
660
662
  const crect = container === scrollingElement ? { top: 0, bottom: window.innerHeight } : container.getBoundingClientRect();
661
663
  if (rect.top < crect.top + 2) {
662
- return { inView: false, container, direction: -1 };
664
+ target2 = { container, direction: -1 };
665
+ break;
663
666
  }
664
667
  if (rect.bottom > crect.bottom - 2) {
665
- return { inView: false, container, direction: 1 };
668
+ target2 = { container, direction: 1 };
669
+ break;
666
670
  }
667
671
  }
668
- return { inView: true, container: null, direction: 0 };
669
- });
670
- const inViewHandle = await handle.getProperty("inView");
671
- const inView = await inViewHandle.jsonValue();
672
- await inViewHandle.dispose();
673
- if (inView) {
674
- await handle.dispose();
675
- return { inView: true };
676
- }
677
- const directionHandle = await handle.getProperty("direction");
678
- const direction = await directionHandle.jsonValue();
679
- await directionHandle.dispose();
680
- const containerHandle = (await handle.getProperty("container")).asElement();
681
- await handle.dispose();
682
- return { inView: false, direction, container: containerHandle };
683
- };
684
- for (let i = 0; i < maxSteps; i++) {
685
- const { inView, direction, container } = await pickTarget();
686
- if (inView) break;
687
- if (!container || !direction) break;
688
- try {
689
- const box = await container.boundingBox();
690
- if (!box) break;
691
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
692
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
693
- await cursor.actions.move({ x, y });
694
- const step = minStep + Math.floor(Math.random() * (maxStep - minStep));
695
- await page.mouse.wheel(0, direction * step);
696
- if (Math.random() < 0.12) {
697
- await (0, import_delay2.default)(this.jitterMs(60, 0.5));
698
- const backStep = Math.max(20, Math.floor(step * (0.15 + Math.random() * 0.2)));
699
- await page.mouse.wheel(0, -direction * backStep);
672
+ if (!target2) {
673
+ return { moved: false, inView: true };
700
674
  }
701
- } finally {
702
- await container.dispose();
703
- }
675
+ const maxScroll = target2.container.scrollHeight - target2.container.clientHeight;
676
+ if (maxScroll <= 0) {
677
+ return { moved: false, inView: false };
678
+ }
679
+ const before = target2.container.scrollTop;
680
+ let next = before + target2.direction * stepPx;
681
+ if (next < 0) next = 0;
682
+ if (next > maxScroll) next = maxScroll;
683
+ if (next === before) {
684
+ return { moved: false, inView: false };
685
+ }
686
+ target2.container.scrollTop = next;
687
+ return { moved: true, inView: false };
688
+ }, step);
689
+ if (result.inView) break;
690
+ if (!result.moved) break;
704
691
  await (0, import_delay2.default)(this.jitterMs(120, 0.4));
705
692
  }
706
693
  const restore = async () => {
707
- for (const item of scrollables) {
708
- try {
709
- const box = await item.el.boundingBox();
710
- if (!box) continue;
711
- for (let i = 0; i < 12; i++) {
712
- const current = await item.el.evaluate((el) => el.scrollTop);
713
- const delta = item.top - current;
714
- if (Math.abs(delta) <= 1) break;
715
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
716
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
717
- await cursor.actions.move({ x, y });
718
- const step = minStep + Math.floor(Math.random() * (maxStep - minStep));
719
- const move = Math.sign(delta) * Math.min(Math.abs(delta), step);
720
- await page.mouse.wheel(0, move);
721
- await (0, import_delay2.default)(this.jitterMs(90, 0.4));
694
+ if (!scrollStateHandle) return;
695
+ try {
696
+ const restoreOnce2 = async () => page.evaluate((state) => {
697
+ if (!Array.isArray(state) || state.length === 0) return true;
698
+ let done = true;
699
+ for (const item of state) {
700
+ if (!item || !item.el) continue;
701
+ const current = item.el.scrollTop;
702
+ const target2 = item.top;
703
+ if (Math.abs(current - target2) > 1) {
704
+ done = false;
705
+ const delta = target2 - current;
706
+ const step = Math.sign(delta) * Math.max(20, Math.min(120, Math.abs(delta) * 0.3));
707
+ item.el.scrollTop = current + step;
708
+ }
722
709
  }
723
- } catch (error) {
724
- logger4.warn(`humanScroll: restore failed: ${error.message}`);
725
- } finally {
726
- await item.el.dispose();
710
+ return done;
711
+ }, scrollStateHandle);
712
+ for (let i = 0; i < 10; i++) {
713
+ const done = await restoreOnce2();
714
+ if (done) break;
715
+ await (0, import_delay2.default)(this.jitterMs(80, 0.4));
727
716
  }
717
+ } finally {
718
+ await scrollStateHandle.dispose();
728
719
  }
729
720
  };
730
721
  return { element, didScroll: true, restore };
@@ -744,18 +735,6 @@ var Humanize = {
744
735
  const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true } = options;
745
736
  const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
746
737
  logger4.start("humanClick", `target=${targetDesc}`);
747
- let restoreScroll = async () => {
748
- };
749
- let restored = false;
750
- const restoreOnce = async () => {
751
- if (restored) return;
752
- restored = true;
753
- try {
754
- await restoreScroll();
755
- } catch (restoreError) {
756
- logger4.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
757
- }
758
- };
759
738
  try {
760
739
  if (target == null) {
761
740
  await (0, import_delay2.default)(this.jitterMs(reactionDelay, 0.4));
@@ -776,13 +755,24 @@ var Humanize = {
776
755
  } else {
777
756
  element = target;
778
757
  }
758
+ const restoreOnce2 = async () => {
759
+ if (restoreOnce2.restored) return;
760
+ restoreOnce2.restored = true;
761
+ if (typeof restoreOnce2.do !== "function") return;
762
+ try {
763
+ await (0, import_delay2.default)(this.jitterMs(1e3));
764
+ await restoreOnce2.do();
765
+ } catch (restoreError) {
766
+ logger4.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
767
+ }
768
+ };
779
769
  if (scrollIfNeeded) {
780
- const scrollResult = await this.humanScroll(page, element);
781
- restoreScroll = scrollResult.restore || restoreScroll;
770
+ const { restore, didScroll } = await this.humanScroll(page, element);
771
+ restoreOnce2.do = didScroll ? restore : null;
782
772
  }
783
773
  const box = await element.boundingBox();
784
774
  if (!box) {
785
- await restoreOnce();
775
+ await restoreOnce2();
786
776
  if (throwOnMissing) {
787
777
  throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
788
778
  }
@@ -794,8 +784,7 @@ var Humanize = {
794
784
  await cursor.actions.move({ x, y });
795
785
  await (0, import_delay2.default)(this.jitterMs(reactionDelay, 0.4));
796
786
  await cursor.actions.click();
797
- await (0, import_delay2.default)(this.jitterMs(180, 0.4));
798
- await restoreOnce();
787
+ await restoreOnce2();
799
788
  logger4.success("humanClick");
800
789
  return true;
801
790
  } catch (error) {