Files
SystemSimulationApp/frontend/tests/e2e/runtime-check.spec.ts
T

128 lines
7.0 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { expandSimulationConsole, prepareApp } from "./fixtures";
const report = {
checkId: "backend-start-1", status: "running", stage: "compiling",
message: "正在编译最小自检程序", durationMs: 100,
compiler: "gcc", compilerVersion: "gcc test", sundialsRoot: "/native/sundials",
error: null, failure: null,
};
test("self-check progress does not block editing and completion is logged once", async ({ page }) => {
await prepareApp(page);
let completed = false;
let requests = 0;
await page.route("**/api/simulation/runtime-check", route => {
requests++;
return route.fulfill({ json: completed ? { ...report, status: "completed", stage: "complete",
message: "仿真环境自检通过:最小程序预处理、编译、链接和运行均成功", durationMs: 1250 } : report });
});
await page.goto("/");
await expandSimulationConsole(page);
const panel = page.getByRole("complementary", { name: "仿真控制台", exact: true });
await expect(panel).toContainText("正在检测仿真环境");
const name = page.getByRole("textbox", { name: "工程", exact: true });
await name.fill("检测期间仍可编辑");
await expect(name).toHaveValue("检测期间仍可编辑");
await expect.poll(() => requests).toBeGreaterThanOrEqual(3);
await expect(panel.locator(".simulation-console-dock-entry-message").filter({ hasText: "正在检测仿真环境" })).toHaveCount(1);
completed = true;
await expect(panel).toContainText("仿真环境自检通过");
await expect(panel).toContainText("1.25 秒");
await page.evaluate(() => window.dispatchEvent(new Event("focus")));
await expect(panel.locator(".simulation-console-dock-entry-message").filter({ hasText: "仿真环境自检通过" })).toHaveCount(1);
});
test("link failure shows diagnostics and a backend restart reports recovery", async ({ page }) => {
await prepareApp(page);
let recovered = false;
await page.route("**/api/simulation/runtime-check", route => route.fulfill({ json: recovered
? { ...report, checkId: "backend-start-2", status: "completed", stage: "complete", message: "仿真环境自检通过" }
: { ...report, status: "failed", stage: "linking", message: "仿真环境自检失败,编辑器仍可使用",
error: "命令执行失败", failure: { command: ["gcc", "check.o", "-o", "model"], cwd: "/native/check",
exitCode: 7, stderr: "cannot find library", stdout: "", errorType: "CalledProcessError" } } }));
await page.goto("/");
await expandSimulationConsole(page);
const panel = page.getByRole("complementary", { name: "仿真控制台", exact: true });
await expect(panel).toContainText("失败阶段:链接");
await expect(panel).toContainText("退出码:7");
await expect(panel).toContainText("cannot find library");
await expect(panel).toContainText('"gcc" "check.o" "-o" "model"');
await page.getByRole("textbox", { name: "工程", exact: true }).fill("失败后仍可编辑");
recovered = true;
await page.evaluate(() => window.dispatchEvent(new Event("focus")));
await expect(panel).toContainText("仿真环境自检通过");
});
test("backend unavailable at page load is retried without duplicate warnings", async ({ page }) => {
await prepareApp(page);
let available = false;
await page.route("**/api/simulation/runtime-check", route => available
? route.fulfill({ json: { ...report, status: "completed", message: "仿真环境自检通过" } })
: route.fulfill({ status: 503 }));
await page.goto("/");
await expandSimulationConsole(page);
const panel = page.getByRole("complementary", { name: "仿真控制台", exact: true });
await expect(panel).toContainText("暂时无法获取仿真环境自检状态");
available = true;
await expect(panel).toContainText("仿真环境自检通过");
});
for (const finalStatus of ["completed", "failed"] as const) {
test(`automatic retries suppress diagnostics until ${finalStatus}`, async ({ page }) => {
await prepareApp(page);
let attempt = 1;
let finished = false;
const failure = { command: ["gcc", "check.c"], cwd: "/native/check", exitCode: 1,
stderr: "CreateProcess: No such file or directory", stdout: "", errorType: "CalledProcessError" };
await page.route("**/api/simulation/runtime-check", route => route.fulfill({ json: finished
? { ...report, status: finalStatus, attempt: 6, stage: finalStatus === "failed" ? "preprocessing" : "complete",
message: finalStatus === "failed" ? "本次启动自检未通过:首次检测及 5 轮重试均失败;编辑器仍可使用"
: "本次启动自检通过,仿真环境可用(第 5 轮重试成功)",
error: finalStatus === "failed" ? "命令执行失败" : null, failure: finalStatus === "failed" ? failure : null }
: { ...report, status: "retrying", attempt, stage: "retry-wait", error: null, failure: null,
message: `本轮检测未通过,正在准备第 ${attempt}/5 轮重试(间隔 0.5 秒);编辑器可正常使用` } }));
await page.goto("/");
await expandSimulationConsole(page);
const panel = page.getByRole("complementary", { name: "仿真控制台", exact: true });
await expect(panel).toContainText("第 1/5 轮重试");
await expect(panel).not.toContainText("退出码");
await expect(panel).not.toContainText("CreateProcess");
await page.getByRole("textbox", { name: "工程", exact: true }).fill("重试时继续编辑");
attempt = 2;
await expect(panel).toContainText("第 2/5 轮重试");
await expect(panel).not.toContainText("错误类型");
finished = true;
if (finalStatus === "completed") {
await expect(panel).toContainText("仿真环境可用(第 5 轮重试成功)");
await expect(panel).not.toContainText("CreateProcess");
await expect(panel.locator(".simulation-console-dock-entry.error")).toHaveCount(0);
} else {
await expect(panel).toContainText("首次检测及 5 轮重试均失败");
await expect(panel).toContainText("退出码:1");
await expect(panel).toContainText("CreateProcess: No such file or directory");
await expect(panel.locator(".simulation-console-dock-entry.error")).toHaveCount(1);
}
});
}
test("finished startup report stops timed polling and is refreshed on focus", async ({ page }) => {
await prepareApp(page);
await page.clock.install();
let requests = 0;
await page.route("**/api/simulation/runtime-check", route => {
requests++;
return route.fulfill({ json: { ...report, status: "completed", stage: "complete",
message: "本次启动自检通过,仿真环境可用" } });
});
await page.goto("/");
await expandSimulationConsole(page);
const panel = page.getByRole("complementary", { name: "仿真控制台", exact: true });
await expect(panel).toContainText("本次启动自检通过");
const completedRequests = requests;
await page.clock.runFor(31000);
expect(requests).toBe(completedRequests);
await page.evaluate(() => window.dispatchEvent(new Event("focus")));
await expect.poll(() => requests).toBe(completedRequests + 1);
});