@skrillex1224/playwright-toolkit 2.0.90 → 2.0.92

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
@@ -511,10 +511,11 @@ var Humanize = {
511
511
  * @param {Object} [options]
512
512
  * @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
513
513
  * @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
514
+ * @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
514
515
  */
515
516
  async humanClick(page, target, options = {}) {
516
517
  const cursor = $GetCursor(page);
517
- const { reactionDelay = 250, throwOnMissing = true } = options;
518
+ const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true } = options;
518
519
  const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
519
520
  logger4.start("humanClick", `target=${targetDesc}`);
520
521
  try {
@@ -545,11 +546,38 @@ var Humanize = {
545
546
  logger4.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
546
547
  return false;
547
548
  }
548
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
549
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
550
- await cursor.actions.move({ x, y });
551
- await delay2(this.jitterMs(reactionDelay, 0.4));
552
- await cursor.actions.click();
549
+ const viewport = page.viewportSize() || { width: 1920, height: 1080 };
550
+ const isInViewport = box.x >= 0 && box.y >= 0 && box.x + box.width <= viewport.width && box.y + box.height <= viewport.height;
551
+ let originalScrollY = null;
552
+ if (!isInViewport && scrollIfNeeded) {
553
+ logger4.debug(`\u5143\u7D20\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u6EDA\u52A8\u5230\u89C6\u53E3...`);
554
+ originalScrollY = await page.evaluate(() => window.scrollY);
555
+ await element.scrollIntoViewIfNeeded();
556
+ await delay2(this.jitterMs(300, 0.3));
557
+ const newBox = await element.boundingBox();
558
+ if (newBox) {
559
+ const x = newBox.x + newBox.width / 2 + (Math.random() - 0.5) * newBox.width * 0.3;
560
+ const y = newBox.y + newBox.height / 2 + (Math.random() - 0.5) * newBox.height * 0.3;
561
+ await cursor.actions.move({ x, y });
562
+ await delay2(this.jitterMs(reactionDelay, 0.4));
563
+ await cursor.actions.click();
564
+ } else {
565
+ throw new Error("\u6EDA\u52A8\u540E\u4ECD\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
566
+ }
567
+ if (originalScrollY !== null) {
568
+ await delay2(this.jitterMs(200, 0.3));
569
+ await page.evaluate((scrollY) => {
570
+ window.scrollTo({ top: scrollY, behavior: "smooth" });
571
+ }, originalScrollY);
572
+ logger4.debug(`\u5DF2\u6EDA\u52A8\u56DE\u539F\u4F4D\u7F6E: ${originalScrollY}`);
573
+ }
574
+ } else {
575
+ const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
576
+ const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
577
+ await cursor.actions.move({ x, y });
578
+ await delay2(this.jitterMs(reactionDelay, 0.4));
579
+ await cursor.actions.click();
580
+ }
553
581
  logger4.success("humanClick");
554
582
  return true;
555
583
  } catch (error) {
@@ -1278,6 +1306,84 @@ var Blocking = {
1278
1306
  css: { name: "CSS \u6837\u5F0F", extensions: CSS_EXTENSIONS },
1279
1307
  other: { name: "\u5176\u4ED6\u8D44\u6E90", extensions: OTHER_EXTENSIONS }
1280
1308
  };
1309
+ },
1310
+ /**
1311
+ * 设置 CDN 直连规则
1312
+ * 对于指定域名的请求,使用 Node.js 原生 fetch 直连获取(绕过浏览器代理)
1313
+ *
1314
+ * 适用场景:
1315
+ * - 代理 IP 无法访问某些 CDN 域名(如 statics.moonshot.cn)
1316
+ * - 需要加速静态资源加载
1317
+ *
1318
+ * 注意事项:
1319
+ * - 仅当代码运行在国内环境(本地/国内服务器)时,直连才能访问国内 CDN
1320
+ * - 如果在海外服务器(如 Apify 云端)运行,直连仍使用海外 IP,可能依然无法访问
1321
+ *
1322
+ * @param {import('playwright').Page} page - Playwright Page 对象
1323
+ * @param {Object} [options] - 配置选项
1324
+ * @param {string[]} [options.domains] - 需要直连的域名列表
1325
+ * @param {string[]} [options.extensions] - 需要直连的扩展名列表(可选,默认 CSS/JS/字体)
1326
+ * @param {boolean} [options.fallbackToProxy] - 直连失败时是否回退到代理(默认 true)
1327
+ * @returns {Promise<void>}
1328
+ */
1329
+ async setupDirectConnect(page, options = {}) {
1330
+ const {
1331
+ domains = [],
1332
+ extensions = [".css", ".js", ".woff", ".woff2", ".ttf", ".otf"],
1333
+ fallbackToProxy = true
1334
+ } = options;
1335
+ if (domains.length === 0) {
1336
+ logger9.warn("setupDirectConnect", "\u672A\u6307\u5B9A\u57DF\u540D\u5217\u8868\uFF0C\u8DF3\u8FC7\u76F4\u8FDE\u914D\u7F6E");
1337
+ return;
1338
+ }
1339
+ logger9.start("setupDirectConnect", `\u57DF\u540D: [${domains.join(", ")}]`);
1340
+ let directCount = 0;
1341
+ let fallbackCount = 0;
1342
+ await page.route("**/*", async (route) => {
1343
+ const request = route.request();
1344
+ const url = request.url();
1345
+ const urlLower = url.toLowerCase();
1346
+ const urlPath = urlLower.split("?")[0];
1347
+ const matchesDomain = domains.some((domain) => url.includes(domain));
1348
+ const matchesExtension = extensions.length === 0 || extensions.some((ext) => urlPath.endsWith(ext));
1349
+ if (matchesDomain && matchesExtension) {
1350
+ try {
1351
+ const response = await fetch(url, {
1352
+ method: request.method(),
1353
+ headers: request.headers()
1354
+ // 不传 agent 参数 = 直连(使用服务器本地网络)
1355
+ });
1356
+ if (!response.ok) {
1357
+ throw new Error(`HTTP ${response.status}`);
1358
+ }
1359
+ const buffer = await response.arrayBuffer();
1360
+ const body = Buffer.from(buffer);
1361
+ const headers = {};
1362
+ response.headers.forEach((value, key) => {
1363
+ headers[key] = value;
1364
+ });
1365
+ directCount++;
1366
+ logger9.debug(`direct: ${url.substring(0, 100)}`);
1367
+ await route.fulfill({
1368
+ status: response.status,
1369
+ headers,
1370
+ body
1371
+ });
1372
+ return;
1373
+ } catch (e) {
1374
+ if (fallbackToProxy) {
1375
+ fallbackCount++;
1376
+ logger9.debug(`fallback: ${url.substring(0, 80)} (${e.message})`);
1377
+ return route.continue();
1378
+ } else {
1379
+ logger9.warn("setupDirectConnect", `\u76F4\u8FDE\u5931\u8D25\u4E14\u4E0D\u56DE\u9000: ${url.substring(0, 80)}`);
1380
+ return route.abort();
1381
+ }
1382
+ }
1383
+ }
1384
+ return route.continue();
1385
+ });
1386
+ logger9.success("setupDirectConnect", `\u6269\u5C55\u540D: [${extensions.join(", ")}]`);
1281
1387
  }
1282
1388
  };
1283
1389