Files
next-tjwater-frontend/tests/browser/startup-screens.e2e.ts
T
jiang 2d8506fea0
Generic Container CI/CD / test-build-publish (push) Successful in 2m9s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 2m39s
feat: add project-aware agent workspace
2026-10-22 18:52:18 +08:00

123 lines
7.0 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { mockAgentApi } from "./support/mock-agent-api";
import { mockWorkbenchContext } from "./support/mock-workbench-context";
import { TEST_PROJECT_ID } from "./support/mock-scada-api";
const project = { project_id: TEST_PROJECT_ID, name: "奉贤供水项目", code: "tjwater_v2", gs_workspace: "tjwater_next", status: "active" };
const headers = { "Access-Control-Allow-Origin": "*" };
const gate = () => {
let release = () => {};
const ready = new Promise<void>((resolve) => { release = resolve; });
return { ready, release: () => release() };
};
for (const width of [1440, 375, 320]) {
test(`project startup follows real loading stages at ${width}px`, async ({ page }, testInfo) => {
const projects = gate();
const context = gate();
await page.setViewportSize({ width, height: width === 1440 ? 900 : 740 });
await page.emulateMedia({ reducedMotion: "reduce" });
await mockWorkbenchContext(page);
await mockAgentApi(page);
await page.route("**/api/v1/projects?**", async (route) => {
if (route.request().method() === "OPTIONS") return route.fallback();
await projects.ready;
await route.fulfill({ headers, json: { items: [project], total: 1, limit: 1000, offset: 0 } });
});
for (const path of ["projects/current/metadata", "access-context"]) {
await page.route(`**/api/v1/${path}`, async (route) => {
if (route.request().method() === "OPTIONS") return route.fallback();
await context.ready;
await route.fallback();
});
}
try {
await page.goto("/", { waitUntil: "domcontentloaded" });
const startup = page.getByRole("main", { name: "应用启动" });
await expect(startup.getByRole("heading", { name: "正在加载授权项目" })).toBeVisible();
const steps = startup.getByRole("list", { name: "项目加载进度" }).getByRole("listitem");
await expect(steps.nth(0)).toHaveAttribute("aria-current", "step");
await expect(steps.nth(1)).toContainText("等待项目列表返回");
await expect(startup.locator('[data-activity="loading"]')).toHaveCount(1);
await expect(startup.locator('[data-activity="loading"]')).toHaveCSS("animation-name", "none");
const panel = await startup.locator('[data-slot="startup-panel"]').boundingBox();
expect(panel!.x).toBeGreaterThanOrEqual(16);
expect(panel!.x + panel!.width).toBeLessThanOrEqual(width - 16);
await page.screenshot({ path: testInfo.outputPath(`projects-loading-${width}.png`), animations: "disabled" });
projects.release();
await expect(startup.getByRole("heading", { name: "正在准备项目环境" })).toBeVisible();
await expect(steps.nth(0)).toContainText("已完成");
await expect(steps.nth(1)).toHaveAttribute("aria-current", "step");
await expect(steps.nth(1)).toContainText("奉贤供水项目");
await expect(page.getByTestId("workbench-main-frame")).toHaveCount(0);
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
await page.screenshot({ path: testInfo.outputPath(`project-preparing-${width}.png`), animations: "disabled" });
context.release();
await expect(startup).toHaveCount(0);
await expect(page.getByTestId("workbench-main-frame")).toBeVisible();
} finally { projects.release(); context.release(); }
});
test(`authentication startup and reconnect share the same surface at ${width}px`, async ({ page }, testInfo) => {
const authentication = gate();
let fail = true;
await page.setViewportSize({ width, height: width === 1440 ? 900 : 740 });
await page.emulateMedia({ reducedMotion: "no-preference" });
await mockWorkbenchContext(page);
await mockAgentApi(page);
await page.route("**/src/app/app.tsx*", async (route) => {
await authentication.ready;
if (fail) return route.abort("failed");
return route.fallback();
});
try {
await page.goto("/", { waitUntil: "domcontentloaded" });
const startup = page.getByRole("main", { name: "应用启动" });
await expect(startup.getByRole("heading", { name: "正在连接统一身份认证" })).toBeVisible();
await expect(startup.getByRole("list", { name: "身份认证进度" })).toContainText("检查登录状态");
await expect(startup.locator('[data-activity="loading"]')).not.toHaveCSS("animation-name", "none");
await page.screenshot({ path: testInfo.outputPath(`authentication-loading-${width}.png`), animations: "disabled" });
await page.emulateMedia({ reducedMotion: "reduce" });
await expect(startup.locator('[data-activity="loading"]')).toHaveCSS("animation-name", "none");
authentication.release();
await expect(startup.getByRole("heading", { name: "统一身份认证连接失败" })).toBeVisible();
await expect(startup.getByRole("alert")).toContainText("请检查网络连接后重试");
await expect(startup.locator('[data-activity="loading"]')).toHaveCount(0);
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
await page.screenshot({ path: testInfo.outputPath(`authentication-error-${width}.png`), animations: "disabled" });
fail = false;
await startup.getByRole("button", { name: "重新连接" }).focus();
await page.keyboard.press("Enter");
await expect(page.getByTestId("workbench-main-frame")).toBeVisible();
} finally { authentication.release(); }
});
}
test("project loading failure can retry without retaining a loading animation", async ({ page }, testInfo) => {
let fail = true;
const retry = gate();
await page.emulateMedia({ reducedMotion: "reduce" });
await mockWorkbenchContext(page);
await mockAgentApi(page);
await page.route("**/api/v1/projects?**", async (route) => {
if (route.request().method() === "OPTIONS") return route.fallback();
if (fail) return route.fulfill({ status: 503, headers, json: { detail: "项目服务暂时不可用,请稍后重试。" } });
await retry.ready;
return route.fulfill({ headers, json: { items: [project], total: 1, limit: 1000, offset: 0 } });
});
try {
await page.goto("/", { waitUntil: "domcontentloaded" });
const startup = page.getByRole("main", { name: "应用启动" });
await expect(startup.getByRole("heading", { name: "暂时无法进入工作台" })).toBeVisible();
await expect(startup.getByRole("alert")).toContainText("项目服务暂时不可用");
await expect(startup.locator('[data-activity="loading"]')).toHaveCount(0);
await page.screenshot({ path: testInfo.outputPath("project-loading-error.png"), animations: "disabled" });
fail = false;
await startup.getByRole("button", { name: "重新加载" }).press("Enter");
await expect(startup.getByRole("heading", { name: "正在加载授权项目" })).toBeVisible();
await expect(startup.getByRole("alert")).toHaveCount(0);
retry.release();
await expect(page.getByTestId("workbench-main-frame")).toBeVisible();
} finally { retry.release(); }
});