@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.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
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
|