文件
hyb-oa/client/tests/e2e/r103-cross-page-ui-consistency.spec.ts
T

382 行
13 KiB
TypeScript

import { mkdirSync } from "node:fs";
import path from "node:path";
import { expect, test, type Page, type Route } from "@playwright/test";
function envelope(data: unknown) {
return { requestId: "mock-r103-ui-consistency", data };
}
async function fulfillJson(route: Route, data: unknown, status = 200) {
await route.fulfill({
status,
contentType: "application/json",
body: JSON.stringify(envelope(data)),
});
}
const screenshotDir = path.resolve(process.cwd(), "../output/playwright/r103");
function screenshotPath(fileName: string) {
mkdirSync(screenshotDir, { recursive: true });
return path.join(screenshotDir, fileName);
}
const me = {
id: "employee-r103",
employeeNo: "E103",
name: "Employee",
phone: "13800000000",
email: "[email protected]",
departmentId: "dept-r103",
directManagerId: null,
status: "active",
notificationPushEnabled: true,
roleCodes: ["employee"],
profileCompleteness: {
hasEmergencyContact: true,
hasBankAccount: true,
hasAvatar: false,
hasIdNumber: false,
score: 50,
},
wecomBinding: null,
};
const purchaseId = "purchase-r103";
const reimbursementId = "11111111-1111-4111-8111-111111111111";
const invoiceId = "invoice-r103";
function navigationItem(key: string, href: string, label: string, icon: string) {
return { key, href, label, icon, placement: "main", permissionCodes: [], featureFlagKey: null };
}
function purchaseSummary() {
return {
id: purchaseId,
requestNo: "CG-20260506-0001",
applicantId: me.id,
submitterId: me.id,
departmentId: me.departmentId,
legalEntityId: "legal-r103",
costCenterId: "cost-r103",
supplierId: "supplier-r103",
documentCategoryId: "category-r103",
budgetPoolId: null,
title: "办公用品采购",
reason: "日常办公",
currencyCode: "CNY",
totalAmount: "299.00",
expectedDeliveryDate: "2026-05-10",
status: "draft",
createdAt: "2026-05-06T09:00:00.000Z",
updatedAt: "2026-05-06T09:10:00.000Z",
applicant: { id: me.id, employeeNo: me.employeeNo, name: me.name },
submitter: { id: me.id, employeeNo: me.employeeNo, name: me.name },
department: { id: me.departmentId, name: "研发部" },
legalEntity: { id: "legal-r103", code: "HYD", name: "海音达" },
costCenter: { id: "cost-r103", code: "RD", name: "研发部", legalEntityId: "legal-r103" },
supplier: { id: "supplier-r103", code: "SUP", name: "测试供应商" },
documentCategory: { id: "category-r103", code: "CG", name: "采购申请", entityType: "purchase_request" },
budgetPool: null,
};
}
function purchaseDetail() {
return {
...purchaseSummary(),
items: [
{
id: "purchase-item-r103",
description: "键盘",
specification: "无线",
quantity: "1.00",
unitPrice: "299.00",
lineAmount: "299.00",
isAssetCandidate: false,
createdAt: "2026-05-06T09:00:00.000Z",
updatedAt: "2026-05-06T09:10:00.000Z",
},
],
attachments: [],
pendingWorkflow: null,
archiveOrder: null,
permissions: {
canEditDraft: true,
canSubmit: true,
canWithdraw: false,
canArchiveOrder: false,
},
riskFlags: [],
};
}
function invoiceItem() {
return {
id: invoiceId,
status: "available",
ocrType: "mixed",
ocrProvider: "mock",
ocrAttempts: 1,
ocrLastError: null,
ocrCompletedAt: "2026-05-06T09:00:00.000Z",
invoiceCode: "044002600111",
invoiceNumber: "12345678",
invoiceDate: "2026-05-06",
amount: "100.00",
taxAmount: "6.00",
totalAmount: "106.00",
linkedReimbursementId: null,
linkedItemId: null,
linkedTravelOrderId: null,
originalName: "发票.pdf",
mimeType: "application/pdf",
fileSize: 1024,
verification: null,
owner: { id: me.id, employeeNo: me.employeeNo, name: me.name },
uploadedBy: { id: me.id, employeeNo: me.employeeNo, name: me.name },
createdAt: "2026-05-06T09:00:00.000Z",
updatedAt: "2026-05-06T09:10:00.000Z",
};
}
function reimbursementForBind() {
return {
id: reimbursementId,
formNumber: null,
status: "draft",
reimbursementDate: "2026-05-06",
totalAmount: "106.00",
description: "发票绑定测试",
payment: {
paymentVoucherNo: null,
paymentDate: null,
paymentChannel: null,
paidAt: null,
paidBy: null,
},
owner: { id: me.id, employeeNo: me.employeeNo, name: me.name },
submitter: { id: me.id, employeeNo: me.employeeNo, name: me.name },
project: null,
pendingWorkflow: null,
createdAt: "2026-05-06T09:00:00.000Z",
updatedAt: "2026-05-06T09:10:00.000Z",
};
}
async function mockApis(page: Page) {
await page.route((url) => url.pathname.startsWith("/api/"), async (route) => {
const request = route.request();
const url = new URL(request.url());
const pathName = url.pathname;
if (pathName === "/api/auth/sso/providers") {
await fulfillJson(route, []);
return;
}
if (pathName === "/api/auth/wecom/config") {
await fulfillJson(route, {
enabled: false,
entryEnabled: false,
oauthEnabled: false,
corpId: "",
agentId: "",
loginUrl: null,
bindEnabled: false,
defaultReturnTo: "/portal",
status: "disabled",
statusReasons: [],
});
return;
}
if (pathName === "/api/auth/login" || pathName === "/api/auth/refresh") {
await route.fulfill({
status: 200,
contentType: "application/json",
headers: { "set-cookie": "has_session=1; Path=/; SameSite=Lax" },
body: JSON.stringify(envelope({ accessToken: "mock-r103-token", expiresIn: 900, mustChangePassword: false })),
});
return;
}
if (pathName === "/api/me") {
await fulfillJson(route, me);
return;
}
if (pathName === "/api/system/navigation") {
await fulfillJson(route, {
workspace: {
id: "workspace-r103",
persisted: true,
profile: "small_team_internal",
orgMode: "single_company",
primaryEntryMode: "password",
notificationStrategy: "in_app",
devicePriority: "desktop",
moduleVisibility: {},
createdAt: null,
updatedAt: null,
updatedBy: null,
},
sections: {
main: [
navigationItem("portal", "/portal", "工作台", "home_app_logo"),
navigationItem("purchase", "/purchase-requests", "采购申请", "shopping_cart"),
navigationItem("invoice", "/invoice-pool", "票据中心", "receipt_long"),
],
admin: [],
backup: [
navigationItem("payment", "/payment-batches", "付款批次", "payments"),
navigationItem("dashboard", "/dashboard", "数据看板", "monitoring"),
],
},
recommendedRolePresets: [],
});
return;
}
if (pathName === "/api/notifications") {
await fulfillJson(route, { page: 1, pageSize: 1, total: 0, unreadOnly: true, unreadCount: 0, items: [] });
return;
}
if (pathName === "/api/portal/home") {
await fulfillJson(route, {
workspace: { id: "workspace-r103", persisted: true, profile: "small_team_internal" },
greeting: { name: me.name },
cards: [],
pendingTasks: { count: 0, href: "/workflow/tasks" },
myApplications: [],
riskSummary: null,
announcements: [],
quickLinks: [],
todayPresence: null,
teamPresence: null,
weeklyReport: null,
weeklySummary: null,
todoSummary: null,
projectSummary: null,
peopleLifecycle: null,
adoptionSummary: null,
recentActivities: [],
teamStatusBoard: [],
});
return;
}
if (pathName === "/api/master-data/legal-entities") {
await fulfillJson(route, { includeArchived: false, items: [{ id: "legal-r103", code: "HYD", name: "海音达", shortName: null, currencyCode: "CNY", taxNo: null, enabled: true, archivedAt: null, createdAt: null, updatedAt: null }] });
return;
}
if (pathName === "/api/master-data/cost-centers") {
await fulfillJson(route, { includeArchived: false, items: [{ id: "cost-r103", legalEntityId: "legal-r103", code: "RD", name: "研发部", parentId: null, ownerEmployeeId: null, enabled: true, archivedAt: null, createdAt: null, updatedAt: null }] });
return;
}
if (pathName === "/api/master-data/suppliers") {
await fulfillJson(route, { includeArchived: false, items: [{ id: "supplier-r103", code: "SUP", name: "测试供应商", taxNo: null, contactName: null, contactPhone: null, bankName: null, bankAccount: null, enabled: true, archivedAt: null, createdAt: null, updatedAt: null }] });
return;
}
if (pathName === "/api/master-data/document-categories") {
await fulfillJson(route, { includeArchived: false, items: [{ id: "category-r103", code: "CG", name: "采购申请", entityType: "purchase_request", sequencePrefix: "CG", numberLength: 4, resetPeriod: "daily", enabled: true, archivedAt: null, createdAt: null, updatedAt: null }] });
return;
}
if (pathName === "/api/purchase-requests" && request.method() === "GET") {
await fulfillJson(route, { page: 1, pageSize: 20, total: 1, mine: true, items: [purchaseSummary()] });
return;
}
if (pathName === `/api/purchase-requests/${purchaseId}`) {
await fulfillJson(route, purchaseDetail());
return;
}
if (pathName === `/api/purchase-requests/${purchaseId}/attachments` && request.method() === "POST") {
await fulfillJson(route, {
attachment: {
id: "attachment-r103",
originalName: "采购报价.pdf",
mimeType: "application/pdf",
fileSize: 4,
uploadedBy: me.id,
createdAt: "2026-05-06T09:20:00.000Z",
uploader: { id: me.id, employeeNo: me.employeeNo, name: me.name },
},
});
return;
}
if (pathName === "/api/invoice-pool") {
await fulfillJson(route, { page: 1, pageSize: 20, total: 1, ownerScope: "own", items: [invoiceItem()] });
return;
}
if (pathName === "/api/reimbursements") {
await fulfillJson(route, { page: 1, pageSize: 50, total: 1, scope: "own", items: [reimbursementForBind()] });
return;
}
await fulfillJson(route, {});
});
}
async function login(page: Page) {
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/);
}
test.describe("R103 cross page UI consistency", () => {
test.beforeEach(async ({ page }) => {
await mockApis(page);
await login(page);
});
test("采购页显示中文标题并使用中文附件上传控件", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 960 });
await page.goto(`/purchase-requests?id=${purchaseId}`);
await expect(page.locator("header").filter({ hasText: "备用模块" }).getByText("采购申请")).toBeVisible();
await expect(page.getByRole("heading", { name: "采购申请" })).toBeVisible();
await expect(page.getByText("/purchase-requests")).toHaveCount(0);
await expect(page.getByTestId("purchase-attachment-upload")).toHaveText("上传附件");
await expect(page.getByText("Choose File")).toHaveCount(0);
await page.getByTestId("purchase-attachment-file-input").setInputFiles({
name: "采购报价.pdf",
mimeType: "application/pdf",
buffer: Buffer.from("r103"),
});
await expect(page.getByTestId("purchase-attachment-selected-name")).toContainText("采购报价.pdf");
await page.screenshot({ path: screenshotPath("purchase-desktop.png"), fullPage: true });
});
test("票据中心绑定报销单不展示 UUID 兜底", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 960 });
await page.goto("/invoice-pool");
await expect(page.locator("header").filter({ hasText: "备用模块" }).getByText("票据中心")).toBeVisible();
await expect(page.getByTestId("invoice-bind-reimbursement")).toContainText("草稿单 · 2026-05-06");
await expect(page.getByTestId("invoice-bind-reimbursement")).not.toContainText(reimbursementId);
await expect(page.getByText("/invoice-pool")).toHaveCount(0);
await page.screenshot({ path: screenshotPath("invoice-pool-desktop.png"), fullPage: true });
});
test("移动宽度下关键页面仍保持浅色中文工作台风格", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto("/invoice-pool");
await expect(page.getByRole("heading", { name: "票据中心" })).toBeVisible();
await expect(page.getByTestId("invoice-pool-mobile-list")).toBeVisible();
await expect(page.getByText("Choose File")).toHaveCount(0);
await page.screenshot({ path: screenshotPath("invoice-pool-mobile.png"), fullPage: true });
});
});