收敛 PC 工作台视觉 05-27-pc-frontend-visual-consistency-wave1
这个提交包含在:
@@ -526,7 +526,7 @@ test("Portal 员工视角优先显示今日动作顺序", async ({ page }) => {
|
||||
"portal-card-my_projects",
|
||||
]);
|
||||
|
||||
await expect(page.locator("summary").filter({ hasText: "我的申请与资产 / 继续工作" })).toBeVisible();
|
||||
await expect(page.locator("summary").filter({ hasText: "更多入口" })).toBeVisible();
|
||||
await expect(page.getByRole("tab", { name: "公告" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "常用" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "团队状态" })).toBeVisible();
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { expect, test, type Page, type Route, type TestInfo } from "@playwright/test";
|
||||
|
||||
const SCREENSHOT_DIR = path.resolve(process.cwd(), "../output/playwright/pc-frontend-visual-consistency-wave1");
|
||||
|
||||
function envelope(data: unknown) {
|
||||
return { requestId: "mock-r96-ui", data };
|
||||
}
|
||||
@@ -48,6 +52,22 @@ async function mockVisualApis(page: Page) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/auth/capabilities") {
|
||||
await fulfillJson(route, {
|
||||
emailDelivery: {
|
||||
ready: true,
|
||||
statusReasons: [],
|
||||
},
|
||||
emailLoginCode: {
|
||||
enabled: true,
|
||||
},
|
||||
passwordReset: {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/auth/wecom/config") {
|
||||
await fulfillJson(route, {
|
||||
enabled: true,
|
||||
@@ -166,11 +186,32 @@ async function mockVisualApis(page: Page) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/workflow/tasks/summary") {
|
||||
await fulfillJson(route, {
|
||||
scope: url.searchParams.get("scope") === "system" ? "system" : "mine",
|
||||
pendingTotal: 1,
|
||||
overdueTotal: 0,
|
||||
earliestPendingAt: "2026-05-06T09:00:00.000Z",
|
||||
averagePendingAgeHours: 2,
|
||||
byEntityType: [
|
||||
{
|
||||
key: "reimbursement",
|
||||
pendingTotal: 1,
|
||||
overdueTotal: 0,
|
||||
},
|
||||
],
|
||||
byStep: [],
|
||||
byAssigneeRole: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/notifications") {
|
||||
await fulfillJson(route, {
|
||||
items: [
|
||||
{
|
||||
id: "notice-r96",
|
||||
type: "workflow.task_assigned",
|
||||
title: "审批提醒",
|
||||
body: "有 1 条待办。",
|
||||
targetType: "workflow_task",
|
||||
@@ -182,6 +223,59 @@ async function mockVisualApis(page: Page) {
|
||||
total: 1,
|
||||
page: 1,
|
||||
pageSize: 3,
|
||||
unreadOnly: false,
|
||||
unreadCount: 1,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/reimbursement-templates") {
|
||||
await fulfillJson(route, { enabled: true, items: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/reimbursements/linkable-travel-requests") {
|
||||
await fulfillJson(route, { items: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/reimbursements") {
|
||||
await fulfillJson(route, {
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 1,
|
||||
scope: "own",
|
||||
items: [
|
||||
{
|
||||
id: "rb-r96",
|
||||
formNumber: "BX20260506001",
|
||||
status: "draft",
|
||||
reimbursementDate: "2026-05-06",
|
||||
totalAmount: "168.30",
|
||||
description: "视觉基线报销",
|
||||
payment: {
|
||||
paymentVoucherNo: null,
|
||||
paymentDate: null,
|
||||
paymentChannel: null,
|
||||
paidAt: null,
|
||||
paidBy: null,
|
||||
},
|
||||
owner: {
|
||||
id: "employee-r96",
|
||||
employeeNo: "R96001",
|
||||
name: "Employee",
|
||||
},
|
||||
submitter: {
|
||||
id: "employee-r96",
|
||||
employeeNo: "R96001",
|
||||
name: "Employee",
|
||||
},
|
||||
project: null,
|
||||
pendingWorkflow: null,
|
||||
createdAt: "2026-05-06T09:00:00.000Z",
|
||||
updatedAt: "2026-05-06T09:00:00.000Z",
|
||||
},
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -191,10 +285,22 @@ async function mockVisualApis(page: Page) {
|
||||
}
|
||||
|
||||
async function attachScreenshot(page: Page, testInfo: TestInfo, name: string) {
|
||||
const body = await page.screenshot({ fullPage: true });
|
||||
await testInfo.attach(name, {
|
||||
body: await page.screenshot({ fullPage: true }),
|
||||
body,
|
||||
contentType: "image/png",
|
||||
});
|
||||
await mkdir(SCREENSHOT_DIR, { recursive: true });
|
||||
await writeFile(path.join(SCREENSHOT_DIR, name), body);
|
||||
}
|
||||
|
||||
async function loginVisualUser(page: Page) {
|
||||
await page.goto("/login");
|
||||
await expect(page.getByTestId("login-password-primary-panel")).toBeVisible();
|
||||
await page.getByTestId("login-identifier").fill("[email protected]");
|
||||
await page.getByTestId("login-password").fill("EmployeePassword1234");
|
||||
await page.getByTestId("login-submit").click();
|
||||
await expect(page).toHaveURL(/\/portal/);
|
||||
}
|
||||
|
||||
test.describe("visual shell", () => {
|
||||
@@ -213,11 +319,7 @@ test.describe("visual shell", () => {
|
||||
test("登录后壳层布局基线", async ({ page }, testInfo) => {
|
||||
await mockVisualApis(page);
|
||||
await page.setViewportSize({ width: 1440, height: 1024 });
|
||||
await page.goto("/login");
|
||||
await page.getByTestId("login-identifier").fill("[email protected]");
|
||||
await page.getByTestId("login-password").fill("EmployeePassword1234");
|
||||
await page.getByTestId("login-submit").click();
|
||||
await expect(page).toHaveURL(/\/portal/);
|
||||
await loginVisualUser(page);
|
||||
|
||||
await expect(page.locator("aside")).toBeVisible();
|
||||
await expect(page.getByTestId("topbar-profile-trigger")).toBeVisible();
|
||||
@@ -227,13 +329,29 @@ test.describe("visual shell", () => {
|
||||
await attachScreenshot(page, testInfo, "app-shell.png");
|
||||
});
|
||||
|
||||
test("PC 首页与报销列表收敛布局基线", async ({ page }, testInfo) => {
|
||||
await mockVisualApis(page);
|
||||
await page.setViewportSize({ width: 1440, height: 1024 });
|
||||
await loginVisualUser(page);
|
||||
|
||||
await expect(page.getByRole("heading", { name: "待处理" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "常用" })).toBeVisible();
|
||||
await expect(page.locator("summary").filter({ hasText: /角色重点|更多入口/ })).toBeVisible();
|
||||
await attachScreenshot(page, testInfo, "pc-portal-wave1.png");
|
||||
|
||||
await page.goto("/reimbursements");
|
||||
await expect(page.getByRole("heading", { name: "我的报销" })).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursements-create")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursements-export")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursements-status-tabs")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursements-table")).toBeVisible();
|
||||
await attachScreenshot(page, testInfo, "pc-reimbursements-wave1.png");
|
||||
});
|
||||
|
||||
test("移动首页任务化布局基线", async ({ page }, testInfo) => {
|
||||
await mockVisualApis(page);
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.goto("/login");
|
||||
await page.getByTestId("login-identifier").fill("[email protected]");
|
||||
await page.getByTestId("login-password").fill("EmployeePassword1234");
|
||||
await page.getByTestId("login-submit").click();
|
||||
await loginVisualUser(page);
|
||||
await page.goto("/mobile");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "今天" })).toBeVisible();
|
||||
@@ -247,10 +365,7 @@ test.describe("visual shell", () => {
|
||||
test("审批待办手机卡片布局基线", async ({ page }, testInfo) => {
|
||||
await mockVisualApis(page);
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.goto("/login");
|
||||
await page.getByTestId("login-identifier").fill("[email protected]");
|
||||
await page.getByTestId("login-password").fill("EmployeePassword1234");
|
||||
await page.getByTestId("login-submit").click();
|
||||
await loginVisualUser(page);
|
||||
await page.goto("/workflow/tasks");
|
||||
|
||||
await expect(page.getByTestId("workflow-tasks-mobile-list")).toBeVisible();
|
||||
|
||||
在新工单中引用
屏蔽一个用户