@skrillex1224/playwright-toolkit 2.1.246 → 2.1.248

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
@@ -3432,23 +3432,6 @@ var initializedPages = /* @__PURE__ */ new WeakSet();
3432
3432
  var DEFAULT_TAP_TIMEOUT_MS = 2500;
3433
3433
  var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
3434
3434
  var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
3435
- var INTERACTIVE_SELECTOR = [
3436
- "button",
3437
- '[role="button"]',
3438
- "a[href]",
3439
- "label",
3440
- "input",
3441
- "textarea",
3442
- "select",
3443
- "summary",
3444
- '[contenteditable="true"]',
3445
- '[tabindex]:not([tabindex="-1"])'
3446
- ].join(",");
3447
- var EDITABLE_SELECTOR = [
3448
- "input",
3449
- "textarea",
3450
- '[contenteditable="true"]'
3451
- ].join(",");
3452
3435
  var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
3453
3436
  var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
3454
3437
  var describeTarget = (target) => {
@@ -3488,7 +3471,7 @@ var withTimeout = async (operation, timeoutMs, label) => {
3488
3471
  }
3489
3472
  };
3490
3473
  var checkElementVisibility = async (element) => {
3491
- return element.evaluate((el, interactiveSelector) => {
3474
+ return element.evaluate((el) => {
3492
3475
  const targetStyle = window.getComputedStyle(el);
3493
3476
  if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
3494
3477
  return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
@@ -3550,16 +3533,30 @@ var checkElementVisibility = async (element) => {
3550
3533
  positioning
3551
3534
  };
3552
3535
  }
3553
- const targetInteractive = typeof el.closest === "function" ? el.closest(interactiveSelector) : null;
3554
- const sameInteractiveTarget = (pointElement) => {
3536
+ const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
3537
+ const commonAncestor = (a, b) => {
3538
+ for (let current = a; current && !isRootNode(current); current = current.parentElement) {
3539
+ if (current.contains(b)) return current;
3540
+ }
3541
+ return null;
3542
+ };
3543
+ const sameTapTarget = (pointElement) => {
3555
3544
  if (!pointElement) return false;
3556
3545
  if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
3557
3546
  return true;
3558
3547
  }
3559
- if (!targetInteractive || typeof pointElement.closest !== "function") {
3548
+ const common = commonAncestor(el, pointElement);
3549
+ if (!common) return false;
3550
+ const commonRect = common.getBoundingClientRect?.();
3551
+ if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
3552
+ const commonArea = commonRect.width * commonRect.height;
3553
+ const targetArea = Math.max(1, rect.width * rect.height);
3554
+ const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
3555
+ if (commonArea > maxSharedRegionArea) return false;
3556
+ if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
3560
3557
  return false;
3561
3558
  }
3562
- return pointElement.closest(interactiveSelector) === targetInteractive;
3559
+ return common.contains(el) && common.contains(pointElement);
3563
3560
  };
3564
3561
  const describeElement = (node) => {
3565
3562
  if (!node) return null;
@@ -3588,7 +3585,7 @@ var checkElementVisibility = async (element) => {
3588
3585
  let obstruction = null;
3589
3586
  for (const point of samplePoints) {
3590
3587
  const pointElement = document.elementFromPoint(point.x, point.y);
3591
- if (sameInteractiveTarget(pointElement)) {
3588
+ if (sameTapTarget(pointElement)) {
3592
3589
  return { code: "VISIBLE", isFixed, positioning };
3593
3590
  }
3594
3591
  obstruction = obstruction || describeElement(pointElement);
@@ -3606,10 +3603,10 @@ var checkElementVisibility = async (element) => {
3606
3603
  };
3607
3604
  }
3608
3605
  return { code: "VISIBLE", isFixed, positioning };
3609
- }, INTERACTIVE_SELECTOR);
3606
+ });
3610
3607
  };
3611
3608
  var resolveSafeTapPoint = async (element) => {
3612
- return element.evaluate((el, interactiveSelector) => {
3609
+ return element.evaluate((el) => {
3613
3610
  const rect = el.getBoundingClientRect();
3614
3611
  if (!rect || rect.width <= 0 || rect.height <= 0) {
3615
3612
  return null;
@@ -3646,16 +3643,30 @@ var resolveSafeTapPoint = async (element) => {
3646
3643
  if (visibleWidth <= 1 || visibleHeight <= 1) {
3647
3644
  return null;
3648
3645
  }
3649
- const targetInteractive = typeof el.closest === "function" ? el.closest(interactiveSelector) : null;
3650
- const sameInteractiveTarget = (pointElement) => {
3646
+ const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
3647
+ const commonAncestor = (a, b) => {
3648
+ for (let current = a; current && !isRootNode(current); current = current.parentElement) {
3649
+ if (current.contains(b)) return current;
3650
+ }
3651
+ return null;
3652
+ };
3653
+ const sameTapTarget = (pointElement) => {
3651
3654
  if (!pointElement) return false;
3652
3655
  if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
3653
3656
  return true;
3654
3657
  }
3655
- if (!targetInteractive || typeof pointElement.closest !== "function") {
3658
+ const common = commonAncestor(el, pointElement);
3659
+ if (!common) return false;
3660
+ const commonRect = common.getBoundingClientRect?.();
3661
+ if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
3662
+ const commonArea = commonRect.width * commonRect.height;
3663
+ const targetArea = Math.max(1, rect.width * rect.height);
3664
+ const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
3665
+ if (commonArea > maxSharedRegionArea) return false;
3666
+ if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
3656
3667
  return false;
3657
3668
  }
3658
- return pointElement.closest(interactiveSelector) === targetInteractive;
3669
+ return common.contains(el) && common.contains(pointElement);
3659
3670
  };
3660
3671
  const cx = visibleLeft + visibleWidth / 2;
3661
3672
  const cy = visibleTop + visibleHeight / 2;
@@ -3674,7 +3685,7 @@ var resolveSafeTapPoint = async (element) => {
3674
3685
  ];
3675
3686
  const safePoints = points.filter((point) => {
3676
3687
  const pointElement = document.elementFromPoint(point.x, point.y);
3677
- return sameInteractiveTarget(pointElement);
3688
+ return sameTapTarget(pointElement);
3678
3689
  });
3679
3690
  const candidates = safePoints.length ? safePoints : points;
3680
3691
  const chosen = candidates[Math.floor(Math.random() * candidates.length)];
@@ -3683,46 +3694,57 @@ var resolveSafeTapPoint = async (element) => {
3683
3694
  x: chosen.x,
3684
3695
  y: chosen.y
3685
3696
  };
3686
- }, INTERACTIVE_SELECTOR);
3697
+ });
3687
3698
  };
3688
3699
  var activateElementFallback = async (element, point = null, options = {}) => {
3689
- return element.evaluate((el, { innerPoint, innerOptions, interactiveSelector, editableSelector }) => {
3690
- const pointElement = innerPoint ? document.elementFromPoint(innerPoint.x, innerPoint.y) : null;
3691
- const nearestInteractive = (node) => {
3692
- if (!node || typeof node.closest !== "function") return null;
3693
- return node.closest(interactiveSelector);
3700
+ return element.evaluate((el, { innerOptions }) => {
3701
+ const isEditable = (node) => {
3702
+ if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
3703
+ if (node.isContentEditable) return true;
3704
+ if (node instanceof HTMLTextAreaElement) return !node.disabled && !node.readOnly;
3705
+ if (node instanceof HTMLInputElement) {
3706
+ return !node.disabled && !node.readOnly && typeof node.select === "function";
3707
+ }
3708
+ return false;
3709
+ };
3710
+ const findEditable = (node) => {
3711
+ for (let current = node; current && current !== document.body; current = current.parentElement) {
3712
+ if (isEditable(current)) return current;
3713
+ }
3714
+ return null;
3694
3715
  };
3695
- const targetInteractive = nearestInteractive(el);
3696
- const pointInteractive = nearestInteractive(pointElement);
3697
- let target = null;
3698
- if (pointInteractive && (pointInteractive === targetInteractive || pointInteractive === el || pointInteractive.contains(el) || el.contains(pointInteractive))) {
3699
- target = pointInteractive;
3700
- }
3701
- target = target || targetInteractive || el;
3702
- const editable = typeof target.closest === "function" ? target.closest(editableSelector) : null;
3716
+ const editable = findEditable(el);
3703
3717
  if (editable && typeof editable.focus === "function") {
3704
3718
  editable.focus({ preventScroll: true });
3705
3719
  if (innerOptions.editableOnly) {
3706
3720
  return { activated: true, method: "focus", tag: editable.tagName || "" };
3707
3721
  }
3708
3722
  }
3709
- if (typeof target.focus === "function") {
3710
- target.focus({ preventScroll: true });
3723
+ if (innerOptions.editableOnly) {
3724
+ return { activated: false, method: "none", tag: el?.tagName || "" };
3711
3725
  }
3712
- if (!innerOptions.editableOnly && typeof target.click === "function") {
3713
- target.click();
3714
- return { activated: true, method: "dom-click", tag: target.tagName || "" };
3726
+ if (typeof el.focus === "function") {
3727
+ el.focus({ preventScroll: true });
3728
+ }
3729
+ if (typeof el.click === "function") {
3730
+ el.click();
3731
+ return { activated: true, method: "dom-click", tag: el.tagName || "" };
3732
+ }
3733
+ if (typeof el.dispatchEvent === "function") {
3734
+ el.dispatchEvent(new MouseEvent("click", {
3735
+ bubbles: true,
3736
+ cancelable: true,
3737
+ view: window
3738
+ }));
3739
+ return { activated: true, method: "dispatch-click", tag: el.tagName || "" };
3715
3740
  }
3716
3741
  return {
3717
3742
  activated: Boolean(editable),
3718
3743
  method: editable ? "focus" : "none",
3719
- tag: target?.tagName || ""
3744
+ tag: el?.tagName || ""
3720
3745
  };
3721
3746
  }, {
3722
- innerPoint: point,
3723
- innerOptions: options || {},
3724
- interactiveSelector: INTERACTIVE_SELECTOR,
3725
- editableSelector: EDITABLE_SELECTOR
3747
+ innerOptions: options || {}
3726
3748
  });
3727
3749
  };
3728
3750
  var getScrollableRect = async (element) => {
@@ -3826,6 +3848,47 @@ var scrollAwayFromObstruction = async (element, status) => {
3826
3848
  };
3827
3849
  }, status);
3828
3850
  };
3851
+ var getElementViewportSnapshot = async (element) => {
3852
+ return element.evaluate((el) => {
3853
+ const rect = el.getBoundingClientRect();
3854
+ return {
3855
+ top: rect.top,
3856
+ bottom: rect.bottom,
3857
+ left: rect.left,
3858
+ right: rect.right,
3859
+ width: rect.width,
3860
+ height: rect.height,
3861
+ scrollX: window.scrollX,
3862
+ scrollY: window.scrollY
3863
+ };
3864
+ });
3865
+ };
3866
+ var isTargetImmobileAfterScroll = (before, after) => {
3867
+ if (!before || !after) return false;
3868
+ const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
3869
+ const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
3870
+ const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
3871
+ const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
3872
+ const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
3873
+ const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
3874
+ if (!rectMoved && !pageMoved) return true;
3875
+ if (pageMoved && !rectMoved) return true;
3876
+ if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
3877
+ return true;
3878
+ }
3879
+ return false;
3880
+ };
3881
+ var restoreWindowFromSnapshot = async (page, before, after) => {
3882
+ if (!before || !after) return;
3883
+ if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
3884
+ return;
3885
+ }
3886
+ await page.evaluate(
3887
+ (state) => window.scrollTo(state.x, state.y),
3888
+ { x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
3889
+ ).catch(() => {
3890
+ });
3891
+ };
3829
3892
  var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
3830
3893
  const viewport = resolveViewport(page);
3831
3894
  const rawRect = options.rect || null;
@@ -4006,11 +4069,11 @@ var MobileHumanize = {
4006
4069
  const scrollRect = await getScrollableRect(element);
4007
4070
  if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
4008
4071
  logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (direction=${status.direction || "unknown"})`);
4009
- return { element, didScroll, restore: null };
4072
+ return { element, didScroll, restore: null, unscrollable: true };
4010
4073
  }
4011
4074
  if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
4012
4075
  logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
4013
- return { element, didScroll, restore: null };
4076
+ return { element, didScroll, restore: null, unscrollable: true };
4014
4077
  }
4015
4078
  if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
4016
4079
  const moved = await scrollAwayFromObstruction(element, status);
@@ -4041,6 +4104,7 @@ var MobileHumanize = {
4041
4104
  }
4042
4105
  }
4043
4106
  const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
4107
+ const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4044
4108
  const beforeState = scrollRect ? await element.evaluate((el) => {
4045
4109
  const isScrollable = (node) => {
4046
4110
  const style = window.getComputedStyle(node);
@@ -4070,6 +4134,21 @@ var MobileHumanize = {
4070
4134
  logger7.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
4071
4135
  }
4072
4136
  }
4137
+ let afterElementSnapshot = null;
4138
+ const readAfterElementSnapshot = async () => {
4139
+ if (!afterElementSnapshot) {
4140
+ afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4141
+ }
4142
+ return afterElementSnapshot;
4143
+ };
4144
+ if (!scrollRect && beforeElementSnapshot) {
4145
+ const afterSnapshot = await readAfterElementSnapshot();
4146
+ if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
4147
+ await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
4148
+ logger7.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u9875\u9762\u6EDA\u52A8\u79FB\u52A8\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
4149
+ return { element, didScroll, restore: null, unscrollable: true };
4150
+ }
4151
+ }
4073
4152
  if (scrollRect && beforeState) {
4074
4153
  const afterState = await element.evaluate((el) => {
4075
4154
  const isScrollable = (node) => {
@@ -4107,6 +4186,14 @@ var MobileHumanize = {
4107
4186
  }
4108
4187
  }
4109
4188
  }
4189
+ if (scrollRect && beforeElementSnapshot) {
4190
+ const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4191
+ if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
4192
+ await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
4193
+ logger7.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u6EDA\u52A8\u5BB9\u5668\u79FB\u52A8\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
4194
+ return { element, didScroll, restore: null, unscrollable: true };
4195
+ }
4196
+ }
4110
4197
  didScroll = true;
4111
4198
  }
4112
4199
  try {
@@ -4156,21 +4243,28 @@ var MobileHumanize = {
4156
4243
  logger7.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
4157
4244
  return false;
4158
4245
  }
4159
- if (scrollIfNeeded) {
4160
- await MobileHumanize.humanScroll(page, element, { throwOnMissing });
4161
- }
4246
+ const scrollResult = scrollIfNeeded ? await MobileHumanize.humanScroll(page, element, { throwOnMissing }) : null;
4162
4247
  const status = await checkElementVisibility(element).catch(() => null);
4163
4248
  if (status && status.code !== "VISIBLE") {
4164
- if (fallbackDomClick && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
4165
- const fallback = await withTimeout(
4249
+ if (fallbackDomClick && (status.code === "OUT_OF_VIEWPORT" || status.code === "OBSTRUCTED") && (status.isFixed || scrollResult?.unscrollable)) {
4250
+ let fallback = await withTimeout(
4166
4251
  () => activateElementFallback(element, null, {
4167
4252
  editableOnly: true
4168
4253
  }),
4169
4254
  activateFallbackTimeoutMs,
4170
4255
  "focus fallback"
4171
4256
  ).catch(() => null);
4257
+ if (!fallback?.activated) {
4258
+ fallback = await withTimeout(
4259
+ () => activateElementFallback(element, null, {
4260
+ editableOnly: false
4261
+ }),
4262
+ activateFallbackTimeoutMs,
4263
+ "activation fallback"
4264
+ ).catch(() => null);
4265
+ }
4172
4266
  if (fallback?.activated) {
4173
- logger7.warn(`humanClick: fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
4267
+ logger7.warn(`humanClick: \u4E0D\u53EF\u6EDA\u52A8\u76EE\u6807\u4E0D\u53EF\u7269\u7406\u70B9\u51FB\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
4174
4268
  return true;
4175
4269
  }
4176
4270
  }
@@ -4287,6 +4381,20 @@ var MobileHumanize = {
4287
4381
  const locator = page.locator(selector);
4288
4382
  await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
4289
4383
  await waitJitter(160, 0.4);
4384
+ const readValue = async () => {
4385
+ try {
4386
+ return await locator.inputValue({ timeout: 600 });
4387
+ } catch {
4388
+ return await locator.evaluate((el) => "value" in el ? String(el.value || "") : String(el.textContent || "")).catch(() => "");
4389
+ }
4390
+ };
4391
+ const currentValue = await readValue();
4392
+ if (!currentValue) return;
4393
+ await page.keyboard.press("ControlOrMeta+A");
4394
+ await waitJitter(90, 0.35);
4395
+ await page.keyboard.press("Backspace");
4396
+ await waitJitter(120, 0.35);
4397
+ if (!await readValue()) return;
4290
4398
  await locator.evaluate((el) => {
4291
4399
  if ("value" in el) {
4292
4400
  el.value = "";