@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.cjs CHANGED
@@ -3460,23 +3460,6 @@ var initializedPages = /* @__PURE__ */ new WeakSet();
3460
3460
  var DEFAULT_TAP_TIMEOUT_MS = 2500;
3461
3461
  var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
3462
3462
  var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
3463
- var INTERACTIVE_SELECTOR = [
3464
- "button",
3465
- '[role="button"]',
3466
- "a[href]",
3467
- "label",
3468
- "input",
3469
- "textarea",
3470
- "select",
3471
- "summary",
3472
- '[contenteditable="true"]',
3473
- '[tabindex]:not([tabindex="-1"])'
3474
- ].join(",");
3475
- var EDITABLE_SELECTOR = [
3476
- "input",
3477
- "textarea",
3478
- '[contenteditable="true"]'
3479
- ].join(",");
3480
3463
  var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
3481
3464
  var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
3482
3465
  var describeTarget = (target) => {
@@ -3516,7 +3499,7 @@ var withTimeout = async (operation, timeoutMs, label) => {
3516
3499
  }
3517
3500
  };
3518
3501
  var checkElementVisibility = async (element) => {
3519
- return element.evaluate((el, interactiveSelector) => {
3502
+ return element.evaluate((el) => {
3520
3503
  const targetStyle = window.getComputedStyle(el);
3521
3504
  if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
3522
3505
  return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
@@ -3578,16 +3561,30 @@ var checkElementVisibility = async (element) => {
3578
3561
  positioning
3579
3562
  };
3580
3563
  }
3581
- const targetInteractive = typeof el.closest === "function" ? el.closest(interactiveSelector) : null;
3582
- const sameInteractiveTarget = (pointElement) => {
3564
+ const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
3565
+ const commonAncestor = (a, b) => {
3566
+ for (let current = a; current && !isRootNode(current); current = current.parentElement) {
3567
+ if (current.contains(b)) return current;
3568
+ }
3569
+ return null;
3570
+ };
3571
+ const sameTapTarget = (pointElement) => {
3583
3572
  if (!pointElement) return false;
3584
3573
  if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
3585
3574
  return true;
3586
3575
  }
3587
- if (!targetInteractive || typeof pointElement.closest !== "function") {
3576
+ const common = commonAncestor(el, pointElement);
3577
+ if (!common) return false;
3578
+ const commonRect = common.getBoundingClientRect?.();
3579
+ if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
3580
+ const commonArea = commonRect.width * commonRect.height;
3581
+ const targetArea = Math.max(1, rect.width * rect.height);
3582
+ const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
3583
+ if (commonArea > maxSharedRegionArea) return false;
3584
+ if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
3588
3585
  return false;
3589
3586
  }
3590
- return pointElement.closest(interactiveSelector) === targetInteractive;
3587
+ return common.contains(el) && common.contains(pointElement);
3591
3588
  };
3592
3589
  const describeElement = (node) => {
3593
3590
  if (!node) return null;
@@ -3616,7 +3613,7 @@ var checkElementVisibility = async (element) => {
3616
3613
  let obstruction = null;
3617
3614
  for (const point of samplePoints) {
3618
3615
  const pointElement = document.elementFromPoint(point.x, point.y);
3619
- if (sameInteractiveTarget(pointElement)) {
3616
+ if (sameTapTarget(pointElement)) {
3620
3617
  return { code: "VISIBLE", isFixed, positioning };
3621
3618
  }
3622
3619
  obstruction = obstruction || describeElement(pointElement);
@@ -3634,10 +3631,10 @@ var checkElementVisibility = async (element) => {
3634
3631
  };
3635
3632
  }
3636
3633
  return { code: "VISIBLE", isFixed, positioning };
3637
- }, INTERACTIVE_SELECTOR);
3634
+ });
3638
3635
  };
3639
3636
  var resolveSafeTapPoint = async (element) => {
3640
- return element.evaluate((el, interactiveSelector) => {
3637
+ return element.evaluate((el) => {
3641
3638
  const rect = el.getBoundingClientRect();
3642
3639
  if (!rect || rect.width <= 0 || rect.height <= 0) {
3643
3640
  return null;
@@ -3674,16 +3671,30 @@ var resolveSafeTapPoint = async (element) => {
3674
3671
  if (visibleWidth <= 1 || visibleHeight <= 1) {
3675
3672
  return null;
3676
3673
  }
3677
- const targetInteractive = typeof el.closest === "function" ? el.closest(interactiveSelector) : null;
3678
- const sameInteractiveTarget = (pointElement) => {
3674
+ const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
3675
+ const commonAncestor = (a, b) => {
3676
+ for (let current = a; current && !isRootNode(current); current = current.parentElement) {
3677
+ if (current.contains(b)) return current;
3678
+ }
3679
+ return null;
3680
+ };
3681
+ const sameTapTarget = (pointElement) => {
3679
3682
  if (!pointElement) return false;
3680
3683
  if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
3681
3684
  return true;
3682
3685
  }
3683
- if (!targetInteractive || typeof pointElement.closest !== "function") {
3686
+ const common = commonAncestor(el, pointElement);
3687
+ if (!common) return false;
3688
+ const commonRect = common.getBoundingClientRect?.();
3689
+ if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
3690
+ const commonArea = commonRect.width * commonRect.height;
3691
+ const targetArea = Math.max(1, rect.width * rect.height);
3692
+ const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
3693
+ if (commonArea > maxSharedRegionArea) return false;
3694
+ if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
3684
3695
  return false;
3685
3696
  }
3686
- return pointElement.closest(interactiveSelector) === targetInteractive;
3697
+ return common.contains(el) && common.contains(pointElement);
3687
3698
  };
3688
3699
  const cx = visibleLeft + visibleWidth / 2;
3689
3700
  const cy = visibleTop + visibleHeight / 2;
@@ -3702,7 +3713,7 @@ var resolveSafeTapPoint = async (element) => {
3702
3713
  ];
3703
3714
  const safePoints = points.filter((point) => {
3704
3715
  const pointElement = document.elementFromPoint(point.x, point.y);
3705
- return sameInteractiveTarget(pointElement);
3716
+ return sameTapTarget(pointElement);
3706
3717
  });
3707
3718
  const candidates = safePoints.length ? safePoints : points;
3708
3719
  const chosen = candidates[Math.floor(Math.random() * candidates.length)];
@@ -3711,46 +3722,57 @@ var resolveSafeTapPoint = async (element) => {
3711
3722
  x: chosen.x,
3712
3723
  y: chosen.y
3713
3724
  };
3714
- }, INTERACTIVE_SELECTOR);
3725
+ });
3715
3726
  };
3716
3727
  var activateElementFallback = async (element, point = null, options = {}) => {
3717
- return element.evaluate((el, { innerPoint, innerOptions, interactiveSelector, editableSelector }) => {
3718
- const pointElement = innerPoint ? document.elementFromPoint(innerPoint.x, innerPoint.y) : null;
3719
- const nearestInteractive = (node) => {
3720
- if (!node || typeof node.closest !== "function") return null;
3721
- return node.closest(interactiveSelector);
3728
+ return element.evaluate((el, { innerOptions }) => {
3729
+ const isEditable = (node) => {
3730
+ if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
3731
+ if (node.isContentEditable) return true;
3732
+ if (node instanceof HTMLTextAreaElement) return !node.disabled && !node.readOnly;
3733
+ if (node instanceof HTMLInputElement) {
3734
+ return !node.disabled && !node.readOnly && typeof node.select === "function";
3735
+ }
3736
+ return false;
3737
+ };
3738
+ const findEditable = (node) => {
3739
+ for (let current = node; current && current !== document.body; current = current.parentElement) {
3740
+ if (isEditable(current)) return current;
3741
+ }
3742
+ return null;
3722
3743
  };
3723
- const targetInteractive = nearestInteractive(el);
3724
- const pointInteractive = nearestInteractive(pointElement);
3725
- let target = null;
3726
- if (pointInteractive && (pointInteractive === targetInteractive || pointInteractive === el || pointInteractive.contains(el) || el.contains(pointInteractive))) {
3727
- target = pointInteractive;
3728
- }
3729
- target = target || targetInteractive || el;
3730
- const editable = typeof target.closest === "function" ? target.closest(editableSelector) : null;
3744
+ const editable = findEditable(el);
3731
3745
  if (editable && typeof editable.focus === "function") {
3732
3746
  editable.focus({ preventScroll: true });
3733
3747
  if (innerOptions.editableOnly) {
3734
3748
  return { activated: true, method: "focus", tag: editable.tagName || "" };
3735
3749
  }
3736
3750
  }
3737
- if (typeof target.focus === "function") {
3738
- target.focus({ preventScroll: true });
3751
+ if (innerOptions.editableOnly) {
3752
+ return { activated: false, method: "none", tag: el?.tagName || "" };
3739
3753
  }
3740
- if (!innerOptions.editableOnly && typeof target.click === "function") {
3741
- target.click();
3742
- return { activated: true, method: "dom-click", tag: target.tagName || "" };
3754
+ if (typeof el.focus === "function") {
3755
+ el.focus({ preventScroll: true });
3756
+ }
3757
+ if (typeof el.click === "function") {
3758
+ el.click();
3759
+ return { activated: true, method: "dom-click", tag: el.tagName || "" };
3760
+ }
3761
+ if (typeof el.dispatchEvent === "function") {
3762
+ el.dispatchEvent(new MouseEvent("click", {
3763
+ bubbles: true,
3764
+ cancelable: true,
3765
+ view: window
3766
+ }));
3767
+ return { activated: true, method: "dispatch-click", tag: el.tagName || "" };
3743
3768
  }
3744
3769
  return {
3745
3770
  activated: Boolean(editable),
3746
3771
  method: editable ? "focus" : "none",
3747
- tag: target?.tagName || ""
3772
+ tag: el?.tagName || ""
3748
3773
  };
3749
3774
  }, {
3750
- innerPoint: point,
3751
- innerOptions: options || {},
3752
- interactiveSelector: INTERACTIVE_SELECTOR,
3753
- editableSelector: EDITABLE_SELECTOR
3775
+ innerOptions: options || {}
3754
3776
  });
3755
3777
  };
3756
3778
  var getScrollableRect = async (element) => {
@@ -3854,6 +3876,47 @@ var scrollAwayFromObstruction = async (element, status) => {
3854
3876
  };
3855
3877
  }, status);
3856
3878
  };
3879
+ var getElementViewportSnapshot = async (element) => {
3880
+ return element.evaluate((el) => {
3881
+ const rect = el.getBoundingClientRect();
3882
+ return {
3883
+ top: rect.top,
3884
+ bottom: rect.bottom,
3885
+ left: rect.left,
3886
+ right: rect.right,
3887
+ width: rect.width,
3888
+ height: rect.height,
3889
+ scrollX: window.scrollX,
3890
+ scrollY: window.scrollY
3891
+ };
3892
+ });
3893
+ };
3894
+ var isTargetImmobileAfterScroll = (before, after) => {
3895
+ if (!before || !after) return false;
3896
+ const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
3897
+ const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
3898
+ const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
3899
+ const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
3900
+ const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
3901
+ const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
3902
+ if (!rectMoved && !pageMoved) return true;
3903
+ if (pageMoved && !rectMoved) return true;
3904
+ if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
3905
+ return true;
3906
+ }
3907
+ return false;
3908
+ };
3909
+ var restoreWindowFromSnapshot = async (page, before, after) => {
3910
+ if (!before || !after) return;
3911
+ if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
3912
+ return;
3913
+ }
3914
+ await page.evaluate(
3915
+ (state) => window.scrollTo(state.x, state.y),
3916
+ { x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
3917
+ ).catch(() => {
3918
+ });
3919
+ };
3857
3920
  var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
3858
3921
  const viewport = resolveViewport(page);
3859
3922
  const rawRect = options.rect || null;
@@ -4034,11 +4097,11 @@ var MobileHumanize = {
4034
4097
  const scrollRect = await getScrollableRect(element);
4035
4098
  if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
4036
4099
  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"})`);
4037
- return { element, didScroll, restore: null };
4100
+ return { element, didScroll, restore: null, unscrollable: true };
4038
4101
  }
4039
4102
  if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
4040
4103
  logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
4041
- return { element, didScroll, restore: null };
4104
+ return { element, didScroll, restore: null, unscrollable: true };
4042
4105
  }
4043
4106
  if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
4044
4107
  const moved = await scrollAwayFromObstruction(element, status);
@@ -4069,6 +4132,7 @@ var MobileHumanize = {
4069
4132
  }
4070
4133
  }
4071
4134
  const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
4135
+ const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4072
4136
  const beforeState = scrollRect ? await element.evaluate((el) => {
4073
4137
  const isScrollable = (node) => {
4074
4138
  const style = window.getComputedStyle(node);
@@ -4098,6 +4162,21 @@ var MobileHumanize = {
4098
4162
  logger7.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
4099
4163
  }
4100
4164
  }
4165
+ let afterElementSnapshot = null;
4166
+ const readAfterElementSnapshot = async () => {
4167
+ if (!afterElementSnapshot) {
4168
+ afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4169
+ }
4170
+ return afterElementSnapshot;
4171
+ };
4172
+ if (!scrollRect && beforeElementSnapshot) {
4173
+ const afterSnapshot = await readAfterElementSnapshot();
4174
+ if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
4175
+ await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
4176
+ 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"})`);
4177
+ return { element, didScroll, restore: null, unscrollable: true };
4178
+ }
4179
+ }
4101
4180
  if (scrollRect && beforeState) {
4102
4181
  const afterState = await element.evaluate((el) => {
4103
4182
  const isScrollable = (node) => {
@@ -4135,6 +4214,14 @@ var MobileHumanize = {
4135
4214
  }
4136
4215
  }
4137
4216
  }
4217
+ if (scrollRect && beforeElementSnapshot) {
4218
+ const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4219
+ if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
4220
+ await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
4221
+ 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"})`);
4222
+ return { element, didScroll, restore: null, unscrollable: true };
4223
+ }
4224
+ }
4138
4225
  didScroll = true;
4139
4226
  }
4140
4227
  try {
@@ -4184,21 +4271,28 @@ var MobileHumanize = {
4184
4271
  logger7.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
4185
4272
  return false;
4186
4273
  }
4187
- if (scrollIfNeeded) {
4188
- await MobileHumanize.humanScroll(page, element, { throwOnMissing });
4189
- }
4274
+ const scrollResult = scrollIfNeeded ? await MobileHumanize.humanScroll(page, element, { throwOnMissing }) : null;
4190
4275
  const status = await checkElementVisibility(element).catch(() => null);
4191
4276
  if (status && status.code !== "VISIBLE") {
4192
- if (fallbackDomClick && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
4193
- const fallback = await withTimeout(
4277
+ if (fallbackDomClick && (status.code === "OUT_OF_VIEWPORT" || status.code === "OBSTRUCTED") && (status.isFixed || scrollResult?.unscrollable)) {
4278
+ let fallback = await withTimeout(
4194
4279
  () => activateElementFallback(element, null, {
4195
4280
  editableOnly: true
4196
4281
  }),
4197
4282
  activateFallbackTimeoutMs,
4198
4283
  "focus fallback"
4199
4284
  ).catch(() => null);
4285
+ if (!fallback?.activated) {
4286
+ fallback = await withTimeout(
4287
+ () => activateElementFallback(element, null, {
4288
+ editableOnly: false
4289
+ }),
4290
+ activateFallbackTimeoutMs,
4291
+ "activation fallback"
4292
+ ).catch(() => null);
4293
+ }
4200
4294
  if (fallback?.activated) {
4201
- logger7.warn(`humanClick: fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
4295
+ logger7.warn(`humanClick: \u4E0D\u53EF\u6EDA\u52A8\u76EE\u6807\u4E0D\u53EF\u7269\u7406\u70B9\u51FB\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
4202
4296
  return true;
4203
4297
  }
4204
4298
  }
@@ -4315,6 +4409,20 @@ var MobileHumanize = {
4315
4409
  const locator = page.locator(selector);
4316
4410
  await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
4317
4411
  await waitJitter(160, 0.4);
4412
+ const readValue = async () => {
4413
+ try {
4414
+ return await locator.inputValue({ timeout: 600 });
4415
+ } catch {
4416
+ return await locator.evaluate((el) => "value" in el ? String(el.value || "") : String(el.textContent || "")).catch(() => "");
4417
+ }
4418
+ };
4419
+ const currentValue = await readValue();
4420
+ if (!currentValue) return;
4421
+ await page.keyboard.press("ControlOrMeta+A");
4422
+ await waitJitter(90, 0.35);
4423
+ await page.keyboard.press("Backspace");
4424
+ await waitJitter(120, 0.35);
4425
+ if (!await readValue()) return;
4318
4426
  await locator.evaluate((el) => {
4319
4427
  if ("value" in el) {
4320
4428
  el.value = "";