@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.js CHANGED
@@ -535,7 +535,7 @@ var Humanize = {
535
535
  },
536
536
  /**
537
537
  * 渐进式滚动到元素可见(仅处理 Y 轴滚动)
538
- * 使用鼠标滚轮模拟人类滚动,并返回 restore 用于回滚
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 scrollablesHandle = await element.evaluateHandle((el) => {
588
- const scrollables2 = [];
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)) scrollables2.push(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) scrollables2.push(scrollingElement);
596
- return scrollables2;
607
+ if (scrollingElement) addNode(scrollingElement);
608
+ return scrollables;
597
609
  });
598
- const scrollables = [];
599
- try {
600
- const lengthHandle = await scrollablesHandle.getProperty("length");
601
- const length = await lengthHandle.jsonValue();
602
- await lengthHandle.dispose();
603
- for (let i = 0; i < length; i++) {
604
- const itemHandle = await scrollablesHandle.getProperty(String(i));
605
- const itemEl = itemHandle.asElement();
606
- if (itemEl) {
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 { inView: false, container: null, direction: 0 };
621
+ return { moved: false, inView: false };
621
622
  }
622
- const scrollables2 = [];
623
+ const scrollables = [];
623
624
  let current = el.parentElement;
624
625
  while (current) {
625
- if (isScrollable(current)) scrollables2.push(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) scrollables2.push(scrollingElement);
630
- for (const container of scrollables2) {
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
- return { inView: false, container, direction: -1 };
635
+ target2 = { container, direction: -1 };
636
+ break;
634
637
  }
635
638
  if (rect.bottom > crect.bottom - 2) {
636
- return { inView: false, container, direction: 1 };
639
+ target2 = { container, direction: 1 };
640
+ break;
637
641
  }
638
642
  }
639
- return { inView: true, container: null, direction: 0 };
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
- } finally {
673
- await container.dispose();
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
- for (const item of scrollables) {
679
- try {
680
- const box = await item.el.boundingBox();
681
- if (!box) continue;
682
- for (let i = 0; i < 12; i++) {
683
- const current = await item.el.evaluate((el) => el.scrollTop);
684
- const delta = item.top - current;
685
- if (Math.abs(delta) <= 1) break;
686
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
687
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
688
- await cursor.actions.move({ x, y });
689
- const step = minStep + Math.floor(Math.random() * (maxStep - minStep));
690
- const move = Math.sign(delta) * Math.min(Math.abs(delta), step);
691
- await page.mouse.wheel(0, move);
692
- await delay2(this.jitterMs(90, 0.4));
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
- } catch (error) {
695
- logger4.warn(`humanScroll: restore failed: ${error.message}`);
696
- } finally {
697
- await item.el.dispose();
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
- await restoreOnce();
764
+ logger4.fail("humanClick", error);
774
765
  logger4.fail("humanClick", error);
775
766
  throw error;
776
767
  }