@skrillex1224/playwright-toolkit 2.1.226 → 2.1.227
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/README.md +2 -0
- package/dist/index.cjs +270 -3
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +270 -3
- package/dist/index.js.map +4 -4
- package/index.d.ts +39 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2485,6 +2485,43 @@ var Humanize = {
|
|
|
2485
2485
|
throw error;
|
|
2486
2486
|
}
|
|
2487
2487
|
},
|
|
2488
|
+
/**
|
|
2489
|
+
* 人类化按键 - 模拟用户在当前焦点或指定目标上按下一次键。
|
|
2490
|
+
* @param {import('playwright').Page} page
|
|
2491
|
+
* @param {string|import('playwright').ElementHandle|import('playwright').Locator} targetOrKey - 目标或按键
|
|
2492
|
+
* @param {string|Object} [maybeKey] - 按键或选项
|
|
2493
|
+
* @param {Object} [options]
|
|
2494
|
+
*/
|
|
2495
|
+
async humanPress(page, targetOrKey, maybeKey, options = {}) {
|
|
2496
|
+
const hasTarget = typeof maybeKey === "string";
|
|
2497
|
+
const key = hasTarget ? maybeKey : targetOrKey;
|
|
2498
|
+
const pressOptions = hasTarget ? options : maybeKey || options;
|
|
2499
|
+
const {
|
|
2500
|
+
reactionDelay = 180,
|
|
2501
|
+
holdDelay = 45,
|
|
2502
|
+
focusDelay = 180,
|
|
2503
|
+
scrollIfNeeded = true,
|
|
2504
|
+
throwOnMissing = true,
|
|
2505
|
+
keyboardOptions = {}
|
|
2506
|
+
} = pressOptions || {};
|
|
2507
|
+
const targetDesc = hasTarget ? typeof targetOrKey === "string" ? targetOrKey : "ElementHandle" : "current focus";
|
|
2508
|
+
logger6.start("humanPress", `key=${key}, target=${targetDesc}`);
|
|
2509
|
+
try {
|
|
2510
|
+
if (hasTarget) {
|
|
2511
|
+
await this.humanClick(page, targetOrKey, { reactionDelay: focusDelay, scrollIfNeeded, throwOnMissing });
|
|
2512
|
+
}
|
|
2513
|
+
await delay(this.jitterMs(reactionDelay, 0.45));
|
|
2514
|
+
await page.keyboard.press(key, {
|
|
2515
|
+
...keyboardOptions,
|
|
2516
|
+
delay: this.jitterMs(holdDelay, 0.5)
|
|
2517
|
+
});
|
|
2518
|
+
logger6.success("humanPress");
|
|
2519
|
+
return true;
|
|
2520
|
+
} catch (error) {
|
|
2521
|
+
logger6.fail("humanPress", error);
|
|
2522
|
+
throw error;
|
|
2523
|
+
}
|
|
2524
|
+
},
|
|
2488
2525
|
/**
|
|
2489
2526
|
* 人类化清空输入框 - 模拟人类删除文本的行为
|
|
2490
2527
|
* @param {import('playwright').Page} page
|
|
@@ -2573,6 +2610,141 @@ var Humanize = {
|
|
|
2573
2610
|
}
|
|
2574
2611
|
};
|
|
2575
2612
|
|
|
2613
|
+
// src/internals/humanize/machine.js
|
|
2614
|
+
var resolveDeviceFromPage = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
|
|
2615
|
+
var isPoint = (value) => value && typeof value === "object" && Number.isFinite(Number(value.x)) && Number.isFinite(Number(value.y));
|
|
2616
|
+
var resolveNativeTarget = (page, target) => {
|
|
2617
|
+
if (!target) return null;
|
|
2618
|
+
if (typeof target === "string") {
|
|
2619
|
+
return page.locator(target);
|
|
2620
|
+
}
|
|
2621
|
+
return target;
|
|
2622
|
+
};
|
|
2623
|
+
var normalizeSelectorOptions = (options = {}) => ({
|
|
2624
|
+
targetParent: Boolean(options.targetParent ?? options.parent ?? false),
|
|
2625
|
+
pick: options.pick === "last" || options.last ? "last" : "first",
|
|
2626
|
+
visibleOnly: options.visibleOnly !== false,
|
|
2627
|
+
fallbackDomClick: Boolean(options.fallbackDomClick),
|
|
2628
|
+
forceMouse: Boolean(options.forceMouse)
|
|
2629
|
+
});
|
|
2630
|
+
var MachineHumanize = {
|
|
2631
|
+
async clickPoint(page, pointOrX, maybeY, options = {}) {
|
|
2632
|
+
const clickOptions = isPoint(pointOrX) && maybeY && typeof maybeY === "object" ? maybeY : options;
|
|
2633
|
+
const point = isPoint(pointOrX) ? pointOrX : { x: pointOrX, y: maybeY };
|
|
2634
|
+
const x = Number(point.x);
|
|
2635
|
+
const y = Number(point.y);
|
|
2636
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
2637
|
+
throw new Error(`Invalid click point: ${JSON.stringify(point)}`);
|
|
2638
|
+
}
|
|
2639
|
+
const resolvedDevice = resolveDeviceFromPage(page);
|
|
2640
|
+
if (resolvedDevice === Device.Mobile && !clickOptions.forceMouse && page.touchscreen && typeof page.touchscreen.tap === "function") {
|
|
2641
|
+
await page.touchscreen.tap(x, y);
|
|
2642
|
+
return true;
|
|
2643
|
+
}
|
|
2644
|
+
await page.mouse.click(x, y, clickOptions.mouseOptions || {});
|
|
2645
|
+
return true;
|
|
2646
|
+
},
|
|
2647
|
+
async click(page, target, options = {}) {
|
|
2648
|
+
if (isPoint(target)) {
|
|
2649
|
+
return MachineHumanize.clickPoint(page, target, void 0, options);
|
|
2650
|
+
}
|
|
2651
|
+
const nativeTarget = resolveNativeTarget(page, target);
|
|
2652
|
+
if (!nativeTarget || typeof nativeTarget.click !== "function") {
|
|
2653
|
+
throw new Error("Machine click target does not expose click()");
|
|
2654
|
+
}
|
|
2655
|
+
await nativeTarget.click(options.clickOptions || options);
|
|
2656
|
+
return true;
|
|
2657
|
+
},
|
|
2658
|
+
async focus(page, target, options = {}) {
|
|
2659
|
+
const nativeTarget = resolveNativeTarget(page, target);
|
|
2660
|
+
if (!nativeTarget || typeof nativeTarget.focus !== "function") {
|
|
2661
|
+
throw new Error("Machine focus target does not expose focus()");
|
|
2662
|
+
}
|
|
2663
|
+
await nativeTarget.focus(options);
|
|
2664
|
+
return true;
|
|
2665
|
+
},
|
|
2666
|
+
async fill(page, target, value, options = {}) {
|
|
2667
|
+
const nativeTarget = resolveNativeTarget(page, target);
|
|
2668
|
+
if (!nativeTarget || typeof nativeTarget.fill !== "function") {
|
|
2669
|
+
throw new Error("Machine fill target does not expose fill()");
|
|
2670
|
+
}
|
|
2671
|
+
await nativeTarget.fill(value, options);
|
|
2672
|
+
return true;
|
|
2673
|
+
},
|
|
2674
|
+
async keyboardType(page, text, options = {}) {
|
|
2675
|
+
await page.keyboard.type(text, options);
|
|
2676
|
+
return true;
|
|
2677
|
+
},
|
|
2678
|
+
async type(page, targetOrText, maybeText, options = {}) {
|
|
2679
|
+
if (maybeText == null || typeof maybeText === "object" && !Array.isArray(maybeText)) {
|
|
2680
|
+
return MachineHumanize.keyboardType(page, targetOrText, maybeText || options);
|
|
2681
|
+
}
|
|
2682
|
+
const nativeTarget = resolveNativeTarget(page, targetOrText);
|
|
2683
|
+
if (!nativeTarget || typeof nativeTarget.type !== "function") {
|
|
2684
|
+
throw new Error("Machine type target does not expose type()");
|
|
2685
|
+
}
|
|
2686
|
+
await nativeTarget.type(maybeText, options);
|
|
2687
|
+
return true;
|
|
2688
|
+
},
|
|
2689
|
+
async selectorCenterPoint(page, selector, options = {}) {
|
|
2690
|
+
const normalizedOptions = normalizeSelectorOptions(options);
|
|
2691
|
+
return page.evaluate(({ innerSelector, innerOptions }) => {
|
|
2692
|
+
const nodes = Array.from(document.querySelectorAll(innerSelector));
|
|
2693
|
+
const ordered = innerOptions.pick === "last" ? nodes.reverse() : nodes;
|
|
2694
|
+
let rect = null;
|
|
2695
|
+
for (const node of ordered) {
|
|
2696
|
+
if (!node) continue;
|
|
2697
|
+
const target = innerOptions.targetParent ? node.parentElement || node : node;
|
|
2698
|
+
rect = target.getBoundingClientRect?.() || null;
|
|
2699
|
+
const isVisible = Boolean(rect && rect.width > 0 && rect.height > 0);
|
|
2700
|
+
if (innerOptions.visibleOnly && !isVisible) continue;
|
|
2701
|
+
break;
|
|
2702
|
+
}
|
|
2703
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return null;
|
|
2704
|
+
return {
|
|
2705
|
+
x: rect.left + rect.width / 2,
|
|
2706
|
+
y: rect.top + rect.height / 2
|
|
2707
|
+
};
|
|
2708
|
+
}, { innerSelector: selector, innerOptions: normalizedOptions });
|
|
2709
|
+
},
|
|
2710
|
+
async domClick(page, selector, options = {}) {
|
|
2711
|
+
const normalizedOptions = normalizeSelectorOptions(options);
|
|
2712
|
+
return page.evaluate(({ innerSelector, innerOptions }) => {
|
|
2713
|
+
const nodes = Array.from(document.querySelectorAll(innerSelector));
|
|
2714
|
+
const ordered = innerOptions.pick === "last" ? nodes.reverse() : nodes;
|
|
2715
|
+
let target = null;
|
|
2716
|
+
for (const node of ordered) {
|
|
2717
|
+
if (!node) continue;
|
|
2718
|
+
const candidate = innerOptions.targetParent ? node.parentElement || node : node;
|
|
2719
|
+
const rect = candidate.getBoundingClientRect?.() || null;
|
|
2720
|
+
const isVisible = Boolean(rect && rect.width > 0 && rect.height > 0);
|
|
2721
|
+
if (innerOptions.visibleOnly && !isVisible) continue;
|
|
2722
|
+
target = candidate;
|
|
2723
|
+
break;
|
|
2724
|
+
}
|
|
2725
|
+
if (!target || typeof target.click !== "function") return false;
|
|
2726
|
+
target.click();
|
|
2727
|
+
return true;
|
|
2728
|
+
}, { innerSelector: selector, innerOptions: normalizedOptions });
|
|
2729
|
+
},
|
|
2730
|
+
async clickSelectorCenter(page, selector, options = {}) {
|
|
2731
|
+
const normalizedOptions = normalizeSelectorOptions(options);
|
|
2732
|
+
try {
|
|
2733
|
+
const point = await MachineHumanize.selectorCenterPoint(page, selector, normalizedOptions);
|
|
2734
|
+
if (point) {
|
|
2735
|
+
await MachineHumanize.clickPoint(page, point, void 0, normalizedOptions);
|
|
2736
|
+
return true;
|
|
2737
|
+
}
|
|
2738
|
+
} catch (error) {
|
|
2739
|
+
if (!normalizedOptions.fallbackDomClick) throw error;
|
|
2740
|
+
}
|
|
2741
|
+
if (normalizedOptions.fallbackDomClick) {
|
|
2742
|
+
return MachineHumanize.domClick(page, selector, normalizedOptions);
|
|
2743
|
+
}
|
|
2744
|
+
return false;
|
|
2745
|
+
}
|
|
2746
|
+
};
|
|
2747
|
+
|
|
2576
2748
|
// src/internals/humanize/shared.js
|
|
2577
2749
|
import delay2 from "delay";
|
|
2578
2750
|
var jitterMs = (base, jitterPercent = 0.3) => {
|
|
@@ -2902,6 +3074,49 @@ var scrollScrollableAncestor = async (element, deltaY) => {
|
|
|
2902
3074
|
};
|
|
2903
3075
|
}, deltaY);
|
|
2904
3076
|
};
|
|
3077
|
+
var scrollAwayFromObstruction = async (element, status) => {
|
|
3078
|
+
return element.evaluate((el, innerStatus) => {
|
|
3079
|
+
const isScrollable = (node) => {
|
|
3080
|
+
const style = window.getComputedStyle(node);
|
|
3081
|
+
if (!style) return false;
|
|
3082
|
+
const overflowY = style.overflowY;
|
|
3083
|
+
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
3084
|
+
return node.scrollHeight > node.clientHeight + 1;
|
|
3085
|
+
};
|
|
3086
|
+
let scroller = el;
|
|
3087
|
+
while (scroller && scroller !== document.body) {
|
|
3088
|
+
if (isScrollable(scroller)) break;
|
|
3089
|
+
scroller = scroller.parentElement;
|
|
3090
|
+
}
|
|
3091
|
+
if (!scroller || scroller === document.body) {
|
|
3092
|
+
return { moved: false, scrollTop: 0, deltaY: 0 };
|
|
3093
|
+
}
|
|
3094
|
+
const rect = el.getBoundingClientRect();
|
|
3095
|
+
const scrollerRect = scroller.getBoundingClientRect();
|
|
3096
|
+
const obstruction = innerStatus?.obstruction || {};
|
|
3097
|
+
const obstructionTop = Number(obstruction.top);
|
|
3098
|
+
const obstructionBottom = Number(obstruction.bottom);
|
|
3099
|
+
const obstructionMiddle = Number.isFinite(obstructionTop) && Number.isFinite(obstructionBottom) ? (obstructionTop + obstructionBottom) / 2 : scrollerRect.top + scrollerRect.height / 2;
|
|
3100
|
+
const scrollerMiddle = scrollerRect.top + scrollerRect.height / 2;
|
|
3101
|
+
const padding = 18;
|
|
3102
|
+
let deltaY = 0;
|
|
3103
|
+
if (Number.isFinite(obstructionTop) && rect.bottom > obstructionTop && obstructionTop >= scrollerRect.top && obstructionTop <= scrollerRect.bottom && obstructionMiddle >= scrollerMiddle) {
|
|
3104
|
+
deltaY = rect.bottom - obstructionTop + padding;
|
|
3105
|
+
} else if (Number.isFinite(obstructionBottom) && rect.top < obstructionBottom && obstructionBottom >= scrollerRect.top && obstructionBottom <= scrollerRect.bottom && obstructionMiddle < scrollerMiddle) {
|
|
3106
|
+
deltaY = rect.top - obstructionBottom - padding;
|
|
3107
|
+
} else {
|
|
3108
|
+
const fallbackDistance = Math.max(48, Math.min(180, scrollerRect.height * 0.45));
|
|
3109
|
+
deltaY = obstructionMiddle >= scrollerMiddle ? fallbackDistance : -fallbackDistance;
|
|
3110
|
+
}
|
|
3111
|
+
const beforeTop = scroller.scrollTop;
|
|
3112
|
+
scroller.scrollTop = beforeTop + deltaY;
|
|
3113
|
+
return {
|
|
3114
|
+
moved: Math.abs(scroller.scrollTop - beforeTop) > 2,
|
|
3115
|
+
scrollTop: scroller.scrollTop,
|
|
3116
|
+
deltaY
|
|
3117
|
+
};
|
|
3118
|
+
}, status);
|
|
3119
|
+
};
|
|
2905
3120
|
var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
|
|
2906
3121
|
const viewport = resolveViewport(page);
|
|
2907
3122
|
const rawRect = options.rect || null;
|
|
@@ -3065,6 +3280,15 @@ var MobileHumanize = {
|
|
|
3065
3280
|
logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
|
|
3066
3281
|
return { element, didScroll, restore: null };
|
|
3067
3282
|
}
|
|
3283
|
+
if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
|
|
3284
|
+
const moved = await scrollAwayFromObstruction(element, status);
|
|
3285
|
+
if (moved.moved) {
|
|
3286
|
+
logger7.debug(`humanScroll | sticky/fixed \u906E\u6321\u8865\u507F\u6EDA\u52A8 top=${Math.round(moved.scrollTop || 0)}`);
|
|
3287
|
+
await waitJitter(90, 0.3);
|
|
3288
|
+
didScroll = true;
|
|
3289
|
+
continue;
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3068
3292
|
if (Date.now() - startTime > maxDurationMs) {
|
|
3069
3293
|
logger7.warn(`humanScroll | mobile timeout (${maxDurationMs}ms, status=${status.code}, direction=${status.direction || "unknown"}, fixed=${Boolean(status.isFixed)})`);
|
|
3070
3294
|
return { element, didScroll, restore: null };
|
|
@@ -3196,7 +3420,7 @@ var MobileHumanize = {
|
|
|
3196
3420
|
await MobileHumanize.humanScroll(page, element, { throwOnMissing });
|
|
3197
3421
|
}
|
|
3198
3422
|
const status = await checkElementVisibility(element).catch(() => null);
|
|
3199
|
-
if (status &&
|
|
3423
|
+
if (status && status.code !== "VISIBLE") {
|
|
3200
3424
|
const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
|
|
3201
3425
|
if (throwOnMissing) throw new Error(message);
|
|
3202
3426
|
logger7.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
|
|
@@ -3256,6 +3480,40 @@ var MobileHumanize = {
|
|
|
3256
3480
|
}
|
|
3257
3481
|
}
|
|
3258
3482
|
},
|
|
3483
|
+
async humanPress(page, targetOrKey, maybeKey, options = {}) {
|
|
3484
|
+
const hasTarget = typeof maybeKey === "string";
|
|
3485
|
+
const key = hasTarget ? maybeKey : targetOrKey;
|
|
3486
|
+
const pressOptions = hasTarget ? options : maybeKey || options;
|
|
3487
|
+
const {
|
|
3488
|
+
reactionDelay = 170,
|
|
3489
|
+
holdDelay = 42,
|
|
3490
|
+
focusDelay = 180,
|
|
3491
|
+
scrollIfNeeded = true,
|
|
3492
|
+
throwOnMissing = true,
|
|
3493
|
+
keyboardOptions = {}
|
|
3494
|
+
} = pressOptions || {};
|
|
3495
|
+
const targetDesc = hasTarget ? describeTarget(targetOrKey) : "current focus";
|
|
3496
|
+
logger7.start("humanPress", `key=${key}, target=${targetDesc}`);
|
|
3497
|
+
try {
|
|
3498
|
+
if (hasTarget) {
|
|
3499
|
+
await MobileHumanize.humanClick(page, targetOrKey, {
|
|
3500
|
+
reactionDelay: focusDelay,
|
|
3501
|
+
scrollIfNeeded,
|
|
3502
|
+
throwOnMissing
|
|
3503
|
+
});
|
|
3504
|
+
}
|
|
3505
|
+
await waitJitter(reactionDelay, 0.45);
|
|
3506
|
+
await page.keyboard.press(key, {
|
|
3507
|
+
...keyboardOptions,
|
|
3508
|
+
delay: jitterMs(holdDelay, 0.5)
|
|
3509
|
+
});
|
|
3510
|
+
logger7.success("humanPress");
|
|
3511
|
+
return true;
|
|
3512
|
+
} catch (error) {
|
|
3513
|
+
logger7.fail("humanPress", error);
|
|
3514
|
+
throw error;
|
|
3515
|
+
}
|
|
3516
|
+
},
|
|
3259
3517
|
async humanClear(page, selector) {
|
|
3260
3518
|
const locator = page.locator(selector);
|
|
3261
3519
|
await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
|
|
@@ -3306,15 +3564,18 @@ var MobileHumanize = {
|
|
|
3306
3564
|
};
|
|
3307
3565
|
|
|
3308
3566
|
// src/humanize.js
|
|
3309
|
-
var
|
|
3567
|
+
var resolveDeviceFromPage2 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
|
|
3310
3568
|
var resolveDelegate = (page) => {
|
|
3311
|
-
return
|
|
3569
|
+
return resolveDeviceFromPage2(page) === Device.Mobile ? MobileHumanize : Humanize;
|
|
3312
3570
|
};
|
|
3313
3571
|
var callDelegate = (method, page, args) => {
|
|
3314
3572
|
const delegate = resolveDelegate(page);
|
|
3315
3573
|
return delegate[method](page, ...args);
|
|
3316
3574
|
};
|
|
3317
3575
|
var Humanize2 = {
|
|
3576
|
+
// M = Machine: native/mechanical operations for compatibility paths.
|
|
3577
|
+
// These APIs intentionally do not humanize timing, motion, or intent.
|
|
3578
|
+
M: MachineHumanize,
|
|
3318
3579
|
jitterMs(base, jitterPercent = 0.3) {
|
|
3319
3580
|
return Humanize.jitterMs(base, jitterPercent);
|
|
3320
3581
|
},
|
|
@@ -3343,6 +3604,12 @@ var Humanize2 = {
|
|
|
3343
3604
|
humanType(page, selector, text, options = {}) {
|
|
3344
3605
|
return callDelegate("humanType", page, [selector, text, options]);
|
|
3345
3606
|
},
|
|
3607
|
+
humanPress(page, targetOrKey, maybeKey, options = {}) {
|
|
3608
|
+
if (typeof maybeKey === "string") {
|
|
3609
|
+
return callDelegate("humanPress", page, [targetOrKey, maybeKey, options]);
|
|
3610
|
+
}
|
|
3611
|
+
return callDelegate("humanPress", page, [targetOrKey, maybeKey || options]);
|
|
3612
|
+
},
|
|
3346
3613
|
humanClear(page, selector) {
|
|
3347
3614
|
return callDelegate("humanClear", page, [selector]);
|
|
3348
3615
|
},
|