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

97 lines
4.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}