PRA-only-demo/scripts/demo-e2e.mjs

97 lines
4.5 KiB
JavaScript
Raw Normal View History

import { spawn, spawnSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const root = process.cwd();
const debugPort = 9231;
const fixturePort = 17963;
const profile = fs.mkdtempSync(path.join(os.tmpdir(), "rpa-studio-e2e-"));
const candidates = [process.env.RPA_ELECTRON_DIST, "C:/electron-rpa/dist", path.join(root, "node_modules", "electron", "dist")].filter(Boolean);
const dist = candidates.find((candidate) => fs.existsSync(path.join(candidate, "electron.exe")));
if (!dist) throw new Error("找不到 Electron 运行时,请设置 RPA_ELECTRON_DIST");
const env = { ...process.env, RPA_E2E: "1", RPA_FIXTURE_PORT: String(fixturePort) };
delete env.ELECTRON_RUN_AS_NODE;
const child = spawn(path.join(dist, "electron.exe"), [root, "--no-sandbox", `--remote-debugging-port=${debugPort}`, `--user-data-dir=${profile}`], {
cwd: root,
env,
stdio: ["ignore", "pipe", "pipe"],
windowsHide: true
});
let stderr = "";
child.stderr.on("data", (chunk) => { stderr += String(chunk); });
async function waitForTarget() {
const deadline = Date.now() + 30000;
while (Date.now() < deadline) {
if (child.exitCode !== null) throw new Error(`RPA Studio 提前退出:${child.exitCode}\n${stderr}`);
try {
const targets = await (await fetch(`http://127.0.0.1:${debugPort}/json/list`)).json();
const page = targets.find((item) => item.type === "page" && item.title === "RPA Studio");
if (page) return page;
} catch { /* wait for CDP */ }
await new Promise((resolve) => setTimeout(resolve, 200));
}
throw new Error(`等待 RPA Studio CDP 超时\n${stderr}`);
}
async function evaluate(page, expression) {
const socket = new WebSocket(page.webSocketDebuggerUrl);
await new Promise((resolve, reject) => { socket.onopen = resolve; socket.onerror = reject; });
const result = await new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error("E2E Runtime.evaluate 超时")), 60000);
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.id !== 1) return;
clearTimeout(timer);
if (message.error) reject(new Error(JSON.stringify(message.error)));
else if (message.result?.exceptionDetails) reject(new Error(message.result.exceptionDetails.text || "E2E 页面异常"));
else resolve(message.result?.result?.value);
};
socket.send(JSON.stringify({ id: 1, method: "Runtime.evaluate", params: { expression, returnByValue: true, awaitPromise: true } }));
});
socket.close();
return result;
}
const expression = `(async () => {
const flow = await window.rpa.flows.createDemo();
const events = [];
const off = window.rpa.run.onEvent((event) => events.push(event));
const started = await window.rpa.run.start(flow);
const finished = await new Promise((resolve, reject) => {
const deadline = Date.now() + 45000;
const timer = setInterval(() => {
const event = events.find((item) => item.type === 'run.finished');
if (event) { clearInterval(timer); resolve(event); }
else if (Date.now() > deadline) { clearInterval(timer); reject(new Error('demo flow timeout')); }
}, 100);
});
off();
await window.rpa.flows.delete(flow.id);
const result = events.find((item) => item.type === 'node.finished' && item.nodeId === 'node_result');
return { runId: started.runId, status: finished.status, durationMs: finished.durationMs, result: result?.output, failed: events.filter((item) => item.type === 'node.finished' && item.status === 'failed') };
})()`;
try {
const page = await waitForTarget();
const result = await evaluate(page, expression);
if (result?.status !== "succeeded") throw new Error(`演示流程失败:${JSON.stringify(result)}`);
if (!String(result?.result || "").includes("订单创建成功:RPA 客户,金额 128")) {
throw new Error(`演示结果不正确:${JSON.stringify(result)}`);
}
if (result.failed?.length) throw new Error(`存在失败节点:${JSON.stringify(result.failed)}`);
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
} finally {
if (process.platform === "win32") spawnSync("taskkill", ["/PID", String(child.pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" });
else child.kill("SIGKILL");
await new Promise((resolve) => setTimeout(resolve, 500));
try {
fs.rmSync(profile, { recursive: true, force: true, maxRetries: 12, retryDelay: 250 });
} catch (error) {
console.warn(`E2E 临时目录稍后由系统清理:${profile}`, (error).message);
}
}