|
|
/**
|
|
|
* Playwright codegen headed 录制 Priority1 公开询价演示路径(跨平台)
|
|
|
*/
|
|
|
import { spawn } from "node:child_process";
|
|
|
import fs from "node:fs";
|
|
|
import path from "node:path";
|
|
|
|
|
|
const ROOT = process.cwd();
|
|
|
const OUTPUT_DIR = path.join(ROOT, ".rpa", "recordings");
|
|
|
const STORAGE_PATH =
|
|
|
process.env.PRIORITY1_STORAGE_STATE_PATH?.trim() ||
|
|
|
path.join(ROOT, ".rpa", "priority1-storage.json");
|
|
|
|
|
|
/** 公开即时询价页(Feathery 两步表单) */
|
|
|
export const DEFAULT_PRIORITY1_QUOTE_URL =
|
|
|
"https://www.priority1.com/instant-freight-quotes/";
|
|
|
|
|
|
const FORBIDDEN_QUOTE_URL_PATTERNS: RegExp[] = [
|
|
|
/dashboard\.priority1\.com/i,
|
|
|
/api\.priority1\.com/i,
|
|
|
];
|
|
|
|
|
|
export function isForbiddenPriority1QuoteUrl(url: string): boolean {
|
|
|
return FORBIDDEN_QUOTE_URL_PATTERNS.some((pattern) => pattern.test(url));
|
|
|
}
|
|
|
|
|
|
function printHelp(): void {
|
|
|
console.log(`
|
|
|
用法: npm run record:priority1 [-- 起始URL]
|
|
|
|
|
|
启动 Playwright codegen(headed),录制 Priority1 公开询价完整路径。
|
|
|
|
|
|
默认起始页:
|
|
|
${DEFAULT_PRIORITY1_QUOTE_URL}
|
|
|
|
|
|
禁止入口:
|
|
|
- dashboard.priority1.com(Cabo TMS 登录后台)
|
|
|
- api.priority1.com(API 文档门户,非 UI)
|
|
|
|
|
|
产出:
|
|
|
.rpa/recordings/priority1-<时间戳>.js
|
|
|
.rpa/priority1-storage.json(若会话可持久化)
|
|
|
|
|
|
推荐演示样例(与截图一致):
|
|
|
Step 1: LTL | 10118 → 90017 | One Time Shipment | 取货日 | 邮箱 | 电话
|
|
|
Step 2: 48×40×48 in | 21 lbs | Pallet×1 | Class 70
|
|
|
Pickup: Residence + Liftgate + Residential
|
|
|
Delivery: Residence + Liftgate + Residential
|
|
|
提交后等待承运商报价列表出现再结束录制
|
|
|
|
|
|
也可从首页开始录制:
|
|
|
npm run record:priority1 -- https://www.priority1.com/
|
|
|
`);
|
|
|
}
|
|
|
|
|
|
function formatTimestamp(): string {
|
|
|
const d = new Date();
|
|
|
const pad = (n: number) => String(n).padStart(2, "0");
|
|
|
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
|
}
|
|
|
|
|
|
function main(): void {
|
|
|
const rawArg = process.argv[2];
|
|
|
const startUrl =
|
|
|
rawArg && rawArg !== "-h" && rawArg !== "--help"
|
|
|
? rawArg
|
|
|
: DEFAULT_PRIORITY1_QUOTE_URL;
|
|
|
|
|
|
if (rawArg === "-h" || rawArg === "--help") {
|
|
|
printHelp();
|
|
|
process.exit(0);
|
|
|
}
|
|
|
|
|
|
if (isForbiddenPriority1QuoteUrl(startUrl)) {
|
|
|
console.error("错误:禁止使用入口 URL");
|
|
|
console.error("请使用公开询价页或首页,勿使用 Cabo 后台 / API 门户");
|
|
|
process.exit(1);
|
|
|
}
|
|
|
|
|
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
|
fs.mkdirSync(path.dirname(STORAGE_PATH), { recursive: true });
|
|
|
|
|
|
const outputFile = path.join(OUTPUT_DIR, `priority1-${formatTimestamp()}.js`);
|
|
|
|
|
|
const args = [
|
|
|
"playwright",
|
|
|
"codegen",
|
|
|
"--target=javascript",
|
|
|
"-o",
|
|
|
outputFile,
|
|
|
`--save-storage=${STORAGE_PATH}`,
|
|
|
];
|
|
|
|
|
|
if (fs.existsSync(STORAGE_PATH)) {
|
|
|
args.push(`--load-storage=${STORAGE_PATH}`);
|
|
|
console.log(`已加载已有 storageState: ${STORAGE_PATH}`);
|
|
|
}
|
|
|
|
|
|
args.push(startUrl);
|
|
|
|
|
|
console.log("=== Priority1 RPA 录制(headed)===");
|
|
|
console.log("RPA_HEADLESS=false");
|
|
|
console.log("禁止入口: dashboard.priority1.com / api.priority1.com");
|
|
|
console.log(`起始 URL: ${startUrl}`);
|
|
|
console.log(`脚本产出: ${outputFile}`);
|
|
|
console.log(`storageState: ${STORAGE_PATH}`);
|
|
|
console.log("");
|
|
|
|
|
|
const child = spawn("npx", args, {
|
|
|
stdio: "inherit",
|
|
|
shell: true,
|
|
|
cwd: ROOT,
|
|
|
env: { ...process.env, RPA_HEADLESS: "false" },
|
|
|
});
|
|
|
|
|
|
child.on("error", (error) => {
|
|
|
console.error("启动 playwright codegen 失败:", error);
|
|
|
process.exit(1);
|
|
|
});
|
|
|
|
|
|
child.on("exit", (code) => {
|
|
|
process.exit(code ?? 0);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
const isDirectRun =
|
|
|
typeof process.argv[1] === "string" &&
|
|
|
path.resolve(process.argv[1]) === path.resolve(__filename);
|
|
|
|
|
|
if (isDirectRun) {
|
|
|
main();
|
|
|
}
|