@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.cjs CHANGED
@@ -540,10 +540,11 @@ var Humanize = {
540
540
  * @param {Object} [options]
541
541
  * @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
542
542
  * @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
543
+ * @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
543
544
  */
544
545
  async humanClick(page, target, options = {}) {
545
546
  const cursor = $GetCursor(page);
546
- const { reactionDelay = 250, throwOnMissing = true } = options;
547
+ const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true } = options;
547
548
  const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
548
549
  logger4.start("humanClick", `target=${targetDesc}`);
549
550
  try {
@@ -574,11 +575,38 @@ var Humanize = {
574
575
  logger4.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
575
576
  return false;
576
577
  }
577
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
578
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
579
- await cursor.actions.move({ x, y });
580
- await (0, import_delay2.default)(this.jitterMs(reactionDelay, 0.4));
581
- await cursor.actions.click();
578
+ const viewport = page.viewportSize() || { width: 1920, height: 1080 };
579
+ const isInViewport = box.x >= 0 && box.y >= 0 && box.x + box.width <= viewport.width && box.y + box.height <= viewport.height;
580
+ let originalScrollY = null;
581
+ if (!isInViewport && scrollIfNeeded) {
582
+ logger4.debug(`\u5143\u7D20\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u6EDA\u52A8\u5230\u89C6\u53E3...`);
583
+ originalScrollY = await page.evaluate(() => window.scrollY);
584
+ await element.scrollIntoViewIfNeeded();
585
+ await (0, import_delay2.default)(this.jitterMs(300, 0.3));
586
+ const newBox = await element.boundingBox();
587
+ if (newBox) {
588
+ const x = newBox.x + newBox.width / 2 + (Math.random() - 0.5) * newBox.width * 0.3;
589
+ const y = newBox.y + newBox.height / 2 + (Math.random() - 0.5) * newBox.height * 0.3;
590
+ await cursor.actions.move({ x, y });
591
+ await (0, import_delay2.default)(this.jitterMs(reactionDelay, 0.4));
592
+ await cursor.actions.click();
593
+ } else {
594
+ throw new Error("\u6EDA\u52A8\u540E\u4ECD\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
595
+ }
596
+ if (originalScrollY !== null) {
597
+ await (0, import_delay2.default)(this.jitterMs(200, 0.3));
598
+ await page.evaluate((scrollY) => {
599
+ window.scrollTo({ top: scrollY, behavior: "smooth" });
600
+ }, originalScrollY);
601
+ logger4.debug(`\u5DF2\u6EDA\u52A8\u56DE\u539F\u4F4D\u7F6E: ${originalScrollY}`);
602
+ }
603
+ } else {
604
+ const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
605
+ const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
606
+ await cursor.actions.move({ x, y });
607
+ await (0, import_delay2.default)(this.jitterMs(reactionDelay, 0.4));
608
+ await cursor.actions.click();
609
+ }
582
610
  logger4.success("humanClick");
583
611
  return true;
584
612
  } catch (error) {
@@ -1307,6 +1335,84 @@ var Blocking = {
1307
1335
  css: { name: "CSS \u6837\u5F0F", extensions: CSS_EXTENSIONS },
1308
1336
  other: { name: "\u5176\u4ED6\u8D44\u6E90", extensions: OTHER_EXTENSIONS }
1309
1337
  };
1338
+ },
1339
+ /**
1340
+ * 设置 CDN 直连规则
1341
+ * 对于指定域名的请求,使用 Node.js 原生 fetch 直连获取(绕过浏览器代理)
1342
+ *
1343
+ * 适用场景:
1344
+ * - 代理 IP 无法访问某些 CDN 域名(如 statics.moonshot.cn)
1345
+ * - 需要加速静态资源加载
1346
+ *
1347
+ * 注意事项:
1348
+ * - 仅当代码运行在国内环境(本地/国内服务器)时,直连才能访问国内 CDN
1349
+ * - 如果在海外服务器(如 Apify 云端)运行,直连仍使用海外 IP,可能依然无法访问
1350
+ *
1351
+ * @param {import('playwright').Page} page - Playwright Page 对象
1352
+ * @param {Object} [options] - 配置选项
1353
+ * @param {string[]} [options.domains] - 需要直连的域名列表
1354
+ * @param {string[]} [options.extensions] - 需要直连的扩展名列表(可选,默认 CSS/JS/字体)
1355
+ * @param {boolean} [options.fallbackToProxy] - 直连失败时是否回退到代理(默认 true)
1356
+ * @returns {Promise<void>}
1357
+ */
1358
+ async setupDirectConnect(page, options = {}) {
1359
+ const {
1360
+ domains = [],
1361
+ extensions = [".css", ".js", ".woff", ".woff2", ".ttf", ".otf"],
1362
+ fallbackToProxy = true
1363
+ } = options;
1364
+ if (domains.length === 0) {
1365
+ logger9.warn("setupDirectConnect", "\u672A\u6307\u5B9A\u57DF\u540D\u5217\u8868\uFF0C\u8DF3\u8FC7\u76F4\u8FDE\u914D\u7F6E");
1366
+ return;
1367
+ }
1368
+ logger9.start("setupDirectConnect", `\u57DF\u540D: [${domains.join(", ")}]`);
1369
+ let directCount = 0;
1370
+ let fallbackCount = 0;
1371
+ await page.route("**/*", async (route) => {
1372
+ const request = route.request();
1373
+ const url = request.url();
1374
+ const urlLower = url.toLowerCase();
1375
+ const urlPath = urlLower.split("?")[0];
1376
+ const matchesDomain = domains.some((domain) => url.includes(domain));
1377
+ const matchesExtension = extensions.length === 0 || extensions.some((ext) => urlPath.endsWith(ext));
1378
+ if (matchesDomain && matchesExtension) {
1379
+ try {
1380
+ const response = await fetch(url, {
1381
+ method: request.method(),
1382
+ headers: request.headers()
1383
+ // 不传 agent 参数 = 直连(使用服务器本地网络)
1384
+ });
1385
+ if (!response.ok) {
1386
+ throw new Error(`HTTP ${response.status}`);
1387
+ }
1388
+ const buffer = await response.arrayBuffer();
1389
+ const body = Buffer.from(buffer);
1390
+ const headers = {};
1391
+ response.headers.forEach((value, key) => {
1392
+ headers[key] = value;
1393
+ });
1394
+ directCount++;
1395
+ logger9.debug(`direct: ${url.substring(0, 100)}`);
1396
+ await route.fulfill({
1397
+ status: response.status,
1398
+ headers,
1399
+ body
1400
+ });
1401
+ return;
1402
+ } catch (e) {
1403
+ if (fallbackToProxy) {
1404
+ fallbackCount++;
1405
+ logger9.debug(`fallback: ${url.substring(0, 80)} (${e.message})`);
1406
+ return route.continue();
1407
+ } else {
1408
+ logger9.warn("setupDirectConnect", `\u76F4\u8FDE\u5931\u8D25\u4E14\u4E0D\u56DE\u9000: ${url.substring(0, 80)}`);
1409
+ return route.abort();
1410
+ }
1411
+ }
1412
+ }
1413
+ return route.continue();
1414
+ });
1415
+ logger9.success("setupDirectConnect", `\u6269\u5C55\u540D: [${extensions.join(", ")}]`);
1310
1416
  }
1311
1417
  };
1312
1418