@skrillex1224/playwright-toolkit 2.1.58 → 2.1.59

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
@@ -582,6 +582,17 @@ var Humanize = {
582
582
  throw error;
583
583
  }
584
584
  },
585
+ /**
586
+ * 渐进式滚动到元素可见(仅处理 Y 轴滚动)
587
+ * 返回 restore 方法,用于将滚动容器恢复到原位置
588
+ *
589
+ * @param {import('playwright').Page} page
590
+ * @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
591
+ * @param {Object} [options]
592
+ * @param {number} [options.maxSteps=25] - 最大滚动步数
593
+ * @param {number} [options.minStep=80] - 单次滚动最小步长
594
+ * @param {number} [options.maxStep=220] - 单次滚动最大步长
595
+ */
585
596
  /**
586
597
  * 渐进式滚动到元素可见(仅处理 Y 轴滚动)
587
598
  * 返回 restore 方法,用于将滚动容器恢复到原位置
@@ -596,7 +607,7 @@ var Humanize = {
596
607
  async humanScroll(page, target, options = {}) {
597
608
  const { maxSteps = 30, minStep = 150, maxStep = 400 } = options;
598
609
  const targetDesc = typeof target === "string" ? target : "ElementHandle";
599
- logger5.debug(`humanScroll | \u76EE\u6807=${targetDesc}`);
610
+ logger5.info(`humanScroll | \u5F00\u59CB\u6EDA\u52A8\u76EE\u6807: ${targetDesc}`);
600
611
  let element;
601
612
  if (typeof target === "string") {
602
613
  element = await page.$(target);
@@ -609,23 +620,27 @@ var Humanize = {
609
620
  }
610
621
  const cursor = $GetCursor(page);
611
622
  let didScroll = false;
623
+ let lastRect = null;
624
+ let samePositionCount = 0;
612
625
  const checkVisibility = async () => {
613
626
  return await element.evaluate((el) => {
614
627
  const rect = el.getBoundingClientRect();
615
628
  if (!rect || rect.width === 0 || rect.height === 0) {
616
629
  return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6" };
617
630
  }
618
- const cx = rect.left + rect.width / 2;
619
- const cy = rect.top + rect.height / 2;
620
631
  const viewH = window.innerHeight;
621
632
  const viewW = window.innerWidth;
633
+ const resultBase = { rect, viewH, viewW };
634
+ const cx = rect.left + rect.width / 2;
635
+ const cy = rect.top + rect.height / 2;
622
636
  if (cy < 0 || cy > viewH || cx < 0 || cx > viewW) {
623
637
  const direction = cy < 0 ? "up" : cy > viewH ? "down" : "unknown";
624
- return { code: "OUT_OF_VIEWPORT", reason: "\u4E0D\u5728\u89C6\u53E3\u5185", direction, cy, viewH };
638
+ return { ...resultBase, code: "OUT_OF_VIEWPORT", reason: "\u4E0D\u5728\u89C6\u53E3\u5185", direction, cy };
625
639
  }
626
640
  const pointElement = document.elementFromPoint(cx, cy);
627
641
  if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
628
642
  return {
643
+ ...resultBase,
629
644
  code: "OBSTRUCTED",
630
645
  reason: "\u88AB\u906E\u6321",
631
646
  obstruction: {
@@ -633,24 +648,39 @@ var Humanize = {
633
648
  id: pointElement.id,
634
649
  className: pointElement.className
635
650
  },
636
- cy,
637
- // Return Center Y for smart direction calculation
638
- viewH
651
+ cy
639
652
  };
640
653
  }
641
- return { code: "VISIBLE" };
654
+ return { ...resultBase, code: "VISIBLE" };
642
655
  });
643
656
  };
644
657
  try {
645
658
  for (let i = 0; i < maxSteps; i++) {
646
659
  const status = await checkVisibility();
647
660
  if (status.code === "VISIBLE") {
648
- logger5.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
661
+ logger5.info("humanScroll | \u5143\u7D20\u5DF2\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
649
662
  return { element, didScroll };
650
663
  }
651
- logger5.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
664
+ const directionStr = status.direction ? `(${status.direction})` : "";
665
+ logger5.info(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${directionStr}`);
666
+ const currentRect = status.rect;
667
+ if (i > 0 && lastRect && currentRect) {
668
+ const dy = Math.abs(currentRect.top - lastRect.top);
669
+ const dx = Math.abs(currentRect.left - lastRect.left);
670
+ if (dy < 2 && dx < 2) {
671
+ samePositionCount++;
672
+ logger5.info(`humanScroll | \u26A0\uFE0F \u8B66\u544A: \u6EDA\u52A8\u540E\u4F4D\u7F6E\u672A\u53D8 (${samePositionCount}/3). dy=${dy}`);
673
+ if (samePositionCount >= 3) {
674
+ logger5.warn("humanScroll | \u26A0\uFE0F \u68C0\u6D4B\u5230\u65E0\u6548\u6EDA\u52A8 (\u5143\u7D20\u53EF\u80FD\u662F Fixed \u6216\u4F4D\u4E8E\u4E0D\u53EF\u6EDA\u52A8\u7684\u5BB9\u5668)\uFF0C\u5F3A\u5236\u505C\u6B62\uFF0C\u5C1D\u8BD5\u76F4\u63A5\u4EA4\u4E92");
675
+ return { element, didScroll };
676
+ }
677
+ } else {
678
+ samePositionCount = 0;
679
+ }
680
+ }
681
+ if (currentRect) lastRect = currentRect;
652
682
  if (status.code === "OBSTRUCTED" && status.obstruction) {
653
- logger5.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
683
+ logger5.info(`humanScroll | \u88AB\u906E\u6321: <${status.obstruction.tag}.${status.obstruction.className}>`);
654
684
  }
655
685
  let deltaY = 0;
656
686
  if (status.code === "OUT_OF_VIEWPORT") {
@@ -663,22 +693,23 @@ var Humanize = {
663
693
  }
664
694
  } else if (status.code === "OBSTRUCTED") {
665
695
  const isBottomHalf = status.cy > status.viewH / 2;
666
- const direction = isBottomHalf ? 1 : -1;
667
- deltaY = direction * (minStep + Math.random() * 50);
696
+ const dir = isBottomHalf ? 1 : -1;
697
+ deltaY = dir * (minStep + Math.random() * 50);
668
698
  }
669
699
  if (i === 0 || Math.random() < 0.2) {
670
700
  const viewSize = page.viewportSize();
671
701
  if (viewSize) {
672
702
  const safeX = viewSize.width * 0.5 + (Math.random() - 0.5) * 100;
673
703
  const safeY = viewSize.height * 0.5 + (Math.random() - 0.5) * 100;
674
- await cursor.actions.move({ x: safeX, y: safeY });
704
+ await cursor.actions.move({ x: safeX, y: safeY }).catch(() => {
705
+ });
675
706
  }
676
707
  }
677
708
  await page.mouse.wheel(0, deltaY);
678
709
  didScroll = true;
679
- await delay2(this.jitterMs(100 + Math.random() * 150, 0.2));
710
+ await delay2(this.jitterMs(150 + Math.random() * 100, 0.2));
680
711
  }
681
- logger5.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
712
+ logger5.warn(`humanScroll | \u26A0\uFE0F \u8FBE\u5230\u6700\u5927\u6B65\u6570 (${maxSteps}) \u4ECD\u65E0\u6CD5\u5B8C\u5168\u53EF\u89C1\uFF0C\u653E\u5F03\u6EDA\u52A8`);
682
713
  return { element, didScroll };
683
714
  } catch (error) {
684
715
  logger5.fail("humanScroll", error);
@@ -890,34 +921,6 @@ var Humanize = {
890
921
  logger5.fail("warmUpBrowsing", error);
891
922
  throw error;
892
923
  }
893
- },
894
- /**
895
- * 自然滚动 - 带惯性、减速效果和随机抖动
896
- * @param {import('playwright').Page} page
897
- * @param {'up' | 'down'} [direction='down'] - 滚动方向
898
- * @param {number} [distance=300] - 总滚动距离基础值 (px),±15% 抖动
899
- * @param {number} [baseSteps=5] - 分几步完成基础值,±1 随机
900
- */
901
- async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
902
- const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
903
- const actualDistance = this.jitterMs(distance, 0.15);
904
- logger5.start("naturalScroll", `dir=${direction}, dist=${actualDistance}, steps=${steps}`);
905
- const sign = direction === "down" ? 1 : -1;
906
- const stepDistance = actualDistance / steps;
907
- try {
908
- for (let i = 0; i < steps; i++) {
909
- const factor = 1 - i / steps * 0.5;
910
- const jitter = 0.9 + Math.random() * 0.2;
911
- const scrollAmount = stepDistance * factor * sign * jitter;
912
- await page.mouse.wheel(0, scrollAmount);
913
- const baseDelay = 60 + i * 25;
914
- await delay2(this.jitterMs(baseDelay, 0.3));
915
- }
916
- logger5.success("naturalScroll");
917
- } catch (error) {
918
- logger5.fail("naturalScroll", error);
919
- throw error;
920
- }
921
924
  }
922
925
  };
923
926