123 lines
7.0 KiB
TypeScript
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(); }
|
|
});
|