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.
chajia/scripts/visual-flock-human-assist.ts

125 lines
4.0 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.

#!/usr/bin/env tsx
/**
* Flock 人机协作录制 — 有头浏览器 + 分步暂停,便于熟悉流程与截图
*
* 用法:
* npm run visual:flock-human-assist
* $env:FLOCK_PATH_ID="F02-register-after-quote"; npm run visual:flock-human-assist
*
* 操作:
* - 浏览器打开后按 Playwright Inspector 提示继续
* - 每步 pause 时人工点击/填写,熟悉按钮位置
* - Ctrl+C 结束仍会保存 storageState 与截图目录
*/
import fs from "node:fs";
import path from "node:path";
import { chromium } from "playwright";
import {
DEFAULT_FLOCK_DEMO,
DEFAULT_FLOCK_QUOTE_URL,
FLOCK_CANONICAL_PATHS,
formatFlockRecordingChecklist,
type FlockDemoInput,
} from "@/workers/rpa/flock/demo-input";
import { getFlockStorageStatePath } from "@/lib/flock/env";
const ROOT = process.cwd();
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())}`;
}
async function screenshotStep(
outDir: string,
page: import("playwright").Page,
name: string,
): Promise<void> {
const file = path.join(outDir, `${name}.png`);
await page.screenshot({ path: file, fullPage: true });
console.log(`[截图] ${file}`);
}
async function main(): Promise<void> {
const pathId = process.env.FLOCK_PATH_ID?.trim() ?? "F01-ltl-zip-quote";
const canonical = FLOCK_CANONICAL_PATHS.find((p) => p.id === pathId);
const demo: FlockDemoInput = canonical?.input ?? DEFAULT_FLOCK_DEMO;
const outDir = path.join(ROOT, ".rpa", "recordings", `flock-human-${pathId}-${formatTimestamp()}`);
fs.mkdirSync(outDir, { recursive: true });
const checklistPath = path.join(outDir, "checklist.txt");
fs.writeFileSync(checklistPath, formatFlockRecordingChecklist(demo), "utf8");
console.log(`\n=== Flock 人机协作录制 ===`);
console.log(`路径: ${pathId}${canonical ? `${canonical.title}` : ""}`);
if (canonical?.notes) console.log(`说明: ${canonical.notes}`);
console.log(`产出目录: ${outDir}`);
console.log(`\n${formatFlockRecordingChecklist(demo)}\n`);
const statePath = getFlockStorageStatePath();
fs.mkdirSync(path.dirname(statePath), { recursive: true });
const browser = await chromium.launch({
headless: false,
slowMo: Number(process.env.RPA_SLOW_MO_MS ?? 80),
});
const context = await browser.newContext(
fs.existsSync(statePath) ? { storageState: statePath } : {},
);
const page = await context.newPage();
const saveAll = async () => {
try {
await context.storageState({ path: statePath });
console.log(`[storage] ${statePath}`);
} catch {
/* ignore */
}
};
process.on("SIGINT", async () => {
console.log("\n[中断] 保存会话…");
await saveAll();
await browser.close();
process.exit(130);
});
try {
await page.goto(DEFAULT_FLOCK_QUOTE_URL, {
waitUntil: "domcontentloaded",
timeout: 60_000,
});
await screenshotStep(outDir, page, "00-entry");
console.log("\n【步骤 1】请在浏览器中填写取货日期、ZIP、托盘、总重、尺寸");
console.log("参考值见上方清单;填完后在 Inspector 点 Resume");
await page.pause();
await screenshotStep(outDir, page, "01-form-filled");
console.log("\n【步骤 2】点击 Next / 下一个,等待进入报价或注册页");
await page.pause();
await screenshotStep(outDir, page, "02-after-next");
console.log("\n【步骤 3】若出现两档报价录制完成勿点 Complete your order");
console.log("若出现 Create your free account请填写测试账号并点 Get instant quote");
await page.pause();
await screenshotStep(outDir, page, "03-final");
await saveAll();
console.log("\n完成。可对照截图 + checklist再运行 npm run record:flock 生成 codegen 脚本");
} finally {
await browser.close();
}
}
main().catch((error) => {
console.error(error);
process.exit(1);
});