@skrillex1224/playwright-toolkit 2.1.223 → 2.1.225

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 CHANGED
@@ -76,12 +76,18 @@ await Actor.exit();
76
76
 
77
77
  ## 🛡️ 反检测功能
78
78
 
79
+ ### Desktop / Mobile Device
80
+
81
+ `Constants.ActorInfo[actor].device` 是 visitor 的设备真源,只允许 `desktop` 和 `mobile`,未配置时默认 `desktop`。visitor 继续通过 `RuntimeEnv.parseInput(input, actor.key)` 和 `Launch.getPlaywrightCrawlerOptions({ runtimeState })` 传递状态;把某个 actor 的 `device` 改成 `mobile` 后,toolkit 会生成 Android Chrome 移动端指纹、移动端 viewport/touch context,并切到移动端 Humanize 行为。
82
+
83
+ 已有 `browser_profile.core` 会记录 `device`。当旧 core 的 `device` 与当前 ActorInfo 不一致时,Launch 会重建 core,避免配置看起来是移动端但实际仍复用桌面端指纹。
84
+
79
85
  ### 架构
80
86
 
81
87
  | 层次 | 问题 | 解决方案 |
82
88
  | ---------- | ----------------------- | --------------------------------------- |
83
89
  | **指纹层** | UA/屏幕/语言/时区一致性 | Crawlee `useFingerprints` + `AntiCheat` |
84
- | **行为层** | 机械输入/点击/滚动 | `ghost-cursor-playwright` + Humanize |
90
+ | **行为层** | 机械输入/点击/滚动 | 桌面端 `ghost-cursor-playwright`,移动端 touch Humanize |
85
91
  | **页面层** | 验证码/风控检测 | Captcha 监控器 |
86
92
 
87
93
  ### API 一览
@@ -210,14 +216,12 @@ const cookies = Utils.parseCookies('key=value; key2=value2', '.example.com');
210
216
  await page.context().addCookies(cookies);
211
217
 
212
218
  // 全页面滚动截图 (自动检测所有滚动元素,强制展开后截图,默认会执行 watermarkify)
213
- // 默认会将返回的 base64 压缩到 8MiB 以内,避免 Apify/Crawlee dataset 单条 item 超限
219
+ // 默认会将返回的 base64 压缩到 5MiB 以内,避免 Apify/Crawlee dataset 单条 item 超限
214
220
  const base64Image = await Share.captureScreen(page);
215
221
 
216
- // 移动端宽度截图(moblie 拼写保持兼容)
217
- const mobileImage = await Share.captureScreen(page, { moblie: true });
218
-
219
- // 仅恢复宽度(restore 支持 true / false / 'width-only' / 'height-only')
220
- const image2 = await Share.captureScreen(page, { restore: 'width-only' });
222
+ // 截图只使用当前页面运行时 viewport;移动端请通过 ActorInfo.device 切换,不再通过截图参数覆盖
223
+ // 仅在完成后恢复页面高度和展开过的滚动容器
224
+ const image2 = await Share.captureScreen(page, { restore: true });
221
225
 
222
226
  // 显式配置 watermarkify:全页淡水印 + 底部一行细条
223
227
  const image3 = await Share.captureScreen(page, {
package/dist/browser.js CHANGED
@@ -2511,8 +2511,10 @@ var constants_exports = {};
2511
2511
  __export(constants_exports, {
2512
2512
  ActorInfo: () => ActorInfo,
2513
2513
  Code: () => Code,
2514
+ Device: () => Device,
2514
2515
  PresetOfLiveViewKey: () => PresetOfLiveViewKey,
2515
- Status: () => Status
2516
+ Status: () => Status,
2517
+ normalizeDevice: () => normalizeDevice
2516
2518
  });
2517
2519
  var Code = {
2518
2520
  Success: 0,
@@ -2528,6 +2530,17 @@ var Status = {
2528
2530
  Failed: "FAILED"
2529
2531
  };
2530
2532
  var PresetOfLiveViewKey = "LIVE_VIEW_SCREENSHOT";
2533
+ var Device = Object.freeze({
2534
+ Desktop: "desktop",
2535
+ Mobile: "mobile"
2536
+ });
2537
+ var normalizeDevice = (value, fallback = Device.Desktop) => {
2538
+ const normalizedFallback = String(fallback || "").trim().toLowerCase() === Device.Mobile ? Device.Mobile : Device.Desktop;
2539
+ const raw = String(value || "").trim().toLowerCase();
2540
+ if (raw === Device.Mobile) return Device.Mobile;
2541
+ if (raw === Device.Desktop) return Device.Desktop;
2542
+ return normalizedFallback;
2543
+ };
2531
2544
  var createActorInfo = (info) => {
2532
2545
  const normalizeDomain = (value) => {
2533
2546
  if (!value) return "";
@@ -2605,12 +2618,14 @@ var createActorInfo = (info) => {
2605
2618
  const domain = normalizeDomain(info.domain);
2606
2619
  const path = normalizePath(info.path);
2607
2620
  const share = normalizeShare(info.share);
2621
+ const device = normalizeDevice(info.device);
2608
2622
  return {
2609
2623
  ...info,
2610
2624
  protocol,
2611
2625
  domain,
2612
2626
  path,
2613
2627
  share,
2628
+ device,
2614
2629
  get icon() {
2615
2630
  if (info.icon) return info.icon;
2616
2631
  return buildIcon(this);
@@ -2662,6 +2677,7 @@ var ActorInfo = {
2662
2677
  name: "\u8C46\u5305",
2663
2678
  domain: "www.doubao.com",
2664
2679
  path: "/",
2680
+ device: Device.Mobile,
2665
2681
  share: {
2666
2682
  mode: "response",
2667
2683
  prefix: "https://www.doubao.com/thread/",
@@ -2677,6 +2693,7 @@ var ActorInfo = {
2677
2693
  name: "DeepSeek",
2678
2694
  domain: "chat.deepseek.com",
2679
2695
  path: "/",
2696
+ device: Device.Mobile,
2680
2697
  share: {
2681
2698
  mode: "response",
2682
2699
  prefix: "https://chat.deepseek.com/share/",
@@ -2892,6 +2909,12 @@ var PageRuntimeStateKey = "__playwright_toolkit_runtime_state__";
2892
2909
  var BROWSER_PROFILE_SCHEMA_VERSION = 1;
2893
2910
  var rememberedRuntimeState = null;
2894
2911
  var isPlainObject = (value) => value && typeof value === "object" && !Array.isArray(value);
2912
+ var normalizeKnownDevice = (value) => {
2913
+ const raw = String(value || "").trim().toLowerCase();
2914
+ if (raw === Device.Mobile) return Device.Mobile;
2915
+ if (raw === Device.Desktop) return Device.Desktop;
2916
+ return "";
2917
+ };
2895
2918
  var deepClone = (value) => {
2896
2919
  if (value == null) return value;
2897
2920
  try {
@@ -3246,6 +3269,10 @@ var normalizeObservedBrowserProfile = (value) => {
3246
3269
  var normalizeBrowserProfileCore = (value) => {
3247
3270
  const source = isPlainObject(value) ? value : {};
3248
3271
  const profile = {};
3272
+ const device = normalizeKnownDevice(source.device);
3273
+ if (device) {
3274
+ profile.device = device;
3275
+ }
3249
3276
  if (isPlainObject(source.fingerprint) && Object.keys(source.fingerprint).length > 0) {
3250
3277
  profile.fingerprint = deepClone(source.fingerprint);
3251
3278
  }
@@ -3303,9 +3330,11 @@ var mergeBrowserProfilePayload = (target = {}, source = {}) => {
3303
3330
  if (Object.keys(mergedCore).length === 0 && Object.keys(incomingCore).length > 0) {
3304
3331
  mergedCore = incomingCore;
3305
3332
  } else if (Object.keys(incomingCore).length > 0) {
3333
+ const currentDevice = normalizeKnownDevice(currentCore.device);
3334
+ const incomingDevice = normalizeKnownDevice(incomingCore.device);
3306
3335
  const currentVersion = Number(currentCore.browser_major_version || 0);
3307
3336
  const incomingVersion = Number(incomingCore.browser_major_version || 0);
3308
- if (currentVersion > 0 && incomingVersion > 0 && currentVersion !== incomingVersion) {
3337
+ if (incomingDevice && currentDevice !== incomingDevice || currentVersion > 0 && incomingVersion > 0 && currentVersion !== incomingVersion) {
3309
3338
  mergedCore = incomingCore;
3310
3339
  }
3311
3340
  }
@@ -3356,8 +3385,14 @@ var normalizeRuntimeState = (source = {}, actor = "") => {
3356
3385
  }
3357
3386
  return RuntimeEnv.parseInput(source, actor);
3358
3387
  };
3388
+ var resolveInputDevice = (input = {}, runtime = {}, actor = "") => {
3389
+ var _a, _b, _c;
3390
+ const actorDevice = (_b = (_a = ActorInfo) == null ? void 0 : _a[actor]) == null ? void 0 : _b.device;
3391
+ return normalizeDevice((_c = actorDevice != null ? actorDevice : input == null ? void 0 : input.device) != null ? _c : runtime == null ? void 0 : runtime.device);
3392
+ };
3359
3393
  var RuntimeEnv = {
3360
3394
  tryParseJSON,
3395
+ normalizeDevice,
3361
3396
  normalizeCookies,
3362
3397
  normalizeLocalStorage,
3363
3398
  normalizeSessionStorage,
@@ -3383,6 +3418,7 @@ var RuntimeEnv = {
3383
3418
  parseInput(input = {}, actor = "") {
3384
3419
  const runtime = tryParseJSON(input == null ? void 0 : input.runtime) || {};
3385
3420
  const resolvedActor = String(actor || (input == null ? void 0 : input.actor) || "").trim();
3421
+ const device = resolveInputDevice(input, runtime, resolvedActor);
3386
3422
  const query = String((input == null ? void 0 : input.query) || "").trim();
3387
3423
  const cookies = normalizeCookies(runtime == null ? void 0 : runtime.cookies);
3388
3424
  const cookieMap = buildCookieMap(cookies);
@@ -3402,6 +3438,7 @@ var RuntimeEnv = {
3402
3438
  }
3403
3439
  const state = {
3404
3440
  actor: resolvedActor,
3441
+ device,
3405
3442
  runtime: normalizedRuntime,
3406
3443
  envId,
3407
3444
  query,
@@ -3447,7 +3484,10 @@ var RuntimeEnv = {
3447
3484
  },
3448
3485
  setBrowserProfileCore(source = {}, core = {}, actor = "") {
3449
3486
  const state = normalizeRuntimeState(source, actor);
3450
- const normalizedCore = normalizeBrowserProfileCore(core);
3487
+ const normalizedCore = normalizeBrowserProfileCore({
3488
+ ...core,
3489
+ device: normalizeKnownDevice(core == null ? void 0 : core.device) || state.device
3490
+ });
3451
3491
  state.browserProfileCore = normalizedCore;
3452
3492
  state.browserProfile = buildBrowserProfilePayload(normalizedCore, state.browserProfileObserved);
3453
3493
  if (Object.keys(state.browserProfile).length > 0) {