97 lines
4.5 KiB
JavaScript
97 lines
4.5 KiB
JavaScript
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);
|
||
}
|
||
}
|