@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 +112 -6
- package/dist/index.cjs.map +2 -2
- package/dist/index.js +112 -6
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
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
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
|