You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

218 lines
6.2 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* Playwright codegen headed 录制跨平台Windows 无需 bash
* task-121 / GD-15匿名+ Step 12.61(登录后查价)
*/
import { spawn } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { isForbiddenQuoteUrl } from "@/lib/rpa/env";
const ROOT = process.cwd();
const OUTPUT_DIR = path.join(ROOT, ".rpa", "recordings");
export type RecordMode = "anonymous" | "logged-in";
export type ParsedRecordArgs = {
mode: RecordMode;
startUrl?: string;
help: boolean;
};
export const ANON_STORAGE_DEFAULT = path.join(
ROOT,
".rpa",
"mothership-storage.json",
);
export const LOGGED_IN_STORAGE_DEFAULT = path.join(
ROOT,
".rpa",
"mothership-logged-in-storage.json",
);
export function parseRecordArgs(argv: string[]): ParsedRecordArgs {
const tokens = argv.slice(2).filter(Boolean);
let mode: RecordMode = "anonymous";
let help = false;
let startUrl: string | undefined;
for (const token of tokens) {
if (token === "-h" || token === "--help") {
help = true;
continue;
}
if (token === "--logged-in" || token === "--login") {
mode = "logged-in";
continue;
}
if (token.startsWith("http://") || token.startsWith("https://")) {
startUrl = token;
continue;
}
// 忽略未知 flag避免误伤
}
return { mode, startUrl, help };
}
export function resolveStoragePath(mode: RecordMode): string {
if (mode === "logged-in") {
return (
process.env.RPA_LOGGED_IN_STORAGE_STATE_PATH?.trim() ||
LOGGED_IN_STORAGE_DEFAULT
);
}
return (
process.env.RPA_STORAGE_STATE_PATH?.trim() || ANON_STORAGE_DEFAULT
);
}
export function resolveOutputBasename(mode: RecordMode, ts: string): string {
return mode === "logged-in"
? `mothership-logged-in-${ts}.js`
: `mothership-${ts}.js`;
}
export function shouldEnforceGd15(mode: RecordMode): boolean {
return mode === "anonymous";
}
function printHelp(): void {
console.log(`
用法:
npm run record:mothership [-- 起始URL]
npm run record:mothership -- --logged-in [起始URL]
npm run record:mothership -- --login [起始URL]
模式:
匿名(默认) 录制公开报价 widget禁止 dashboard / www.mothership.com/quoteGD-15
--logged-in 录制账号登录后查价完整路径;允许登录/dashboard 入口
产出(匿名):
.rpa/recordings/mothership-<时间戳>.js
.rpa/mothership-storage.json
产出(登录后):
.rpa/recordings/mothership-logged-in-<时间戳>.js
.rpa/mothership-logged-in-storage.json
登录后录制说明:
- 请在 codegen 浏览器中手动粘贴 MOTHERSHIP_EMAIL / MOTHERSHIP_PASSWORD脚本不自动填
- SOP: docs/rpa-mothership-logged-in-sop.md
匿名标准样例: LA 90001 → Dallas 75201
`);
}
export function formatTimestamp(d = new Date()): string {
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 hasMothershipEnvCredentials(): boolean {
const email = process.env.MOTHERSHIP_EMAIL?.trim() ?? "";
const password = process.env.MOTHERSHIP_PASSWORD?.trim() ?? "";
return Boolean(email && password);
}
function main(): void {
const parsed = parseRecordArgs(process.argv);
if (parsed.help) {
printHelp();
process.exit(0);
}
const { mode, startUrl } = parsed;
const storagePath = resolveStoragePath(mode);
if (shouldEnforceGd15(mode) && startUrl && isForbiddenQuoteUrl(startUrl)) {
console.error("错误:禁止使用入口 URLGD-15");
console.error(
"请使用 headed 录制验证的匿名公开报价 URL登录后路径请加 --logged-in",
);
process.exit(1);
}
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
fs.mkdirSync(path.dirname(storagePath), { recursive: true });
const outputFile = path.join(
OUTPUT_DIR,
resolveOutputBasename(mode, formatTimestamp()),
);
const args = [
"playwright",
"codegen",
"--target=javascript",
"-o",
outputFile,
`--save-storage=${storagePath}`,
];
if (fs.existsSync(storagePath)) {
args.push(`--load-storage=${storagePath}`);
console.log(`已加载已有 storageState: ${storagePath}`);
}
if (startUrl) {
args.push(startUrl);
} else if (mode === "logged-in") {
console.log(
"未指定起始 URL请在 codegen 中导航至登录页或登录后查价页(允许 dashboard",
);
} else {
console.log("未指定起始 URL请在 codegen 浏览器中手动导航至匿名报价页");
}
if (mode === "logged-in") {
console.log("=== Mothership 登录后查价录制headed===");
console.log("RPA_HEADLESS=false");
console.log("模式: --logged-inGD-15 黑名单不生效)");
if (hasMothershipEnvCredentials()) {
console.log(
"已检测到 .env 中 MOTHERSHIP_EMAIL/PASSWORD请在浏览器中手动粘贴登录脚本不自动填",
);
} else {
console.log(
"未检测到 MOTHERSHIP_EMAIL/PASSWORD请准备账号后在浏览器中手动登录",
);
}
console.log(`脚本产出: ${outputFile}`);
console.log(`storageState: ${storagePath}`);
console.log("SOP: docs/rpa-mothership-logged-in-sop.md");
} else {
console.log("=== Mothership RPA 录制headed / 匿名)===");
console.log("RPA_HEADLESS=false");
console.log("禁止入口: dashboard.mothership.com / www.mothership.com/quote");
console.log(`脚本产出: ${outputFile}`);
console.log(`storageState: ${storagePath}`);
console.log("SOP: docs/rpa-mothership-logged-in-sop.md登录后");
}
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);
});
}
// vitest 导入本模块时勿启动 codegen
const isRecordEntrypoint = /record-mothership\.(ts|js|mjs|cjs)$/.test(
process.argv[1] ?? "",
);
if (isRecordEntrypoint && !process.env.VITEST && !process.env.VITEST_WORKER_ID) {
main();
}