DG9 统一全站 UI 布局样板迁移
关联 Issue: DG9-0..DG9-12
这个提交包含在:
@@ -1,5 +1,5 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { expect, test, type Browser, type Page } from "@playwright/test";
|
||||
|
||||
@@ -30,7 +30,11 @@ type PreparedRoutes = {
|
||||
};
|
||||
|
||||
const ROOT_DIR = path.resolve(process.cwd(), "..");
|
||||
const MATRIX_PATH = path.join(ROOT_DIR, "docs/trialrun/project-wide-flow-matrix.csv");
|
||||
const MATRIX_PATH_CANDIDATES = [
|
||||
path.join(ROOT_DIR, "docs/trialrun/项目级流程覆盖矩阵.csv"),
|
||||
path.join(ROOT_DIR, "docs/trialrun/project-wide-flow-matrix.csv"),
|
||||
];
|
||||
const MATRIX_PATH = MATRIX_PATH_CANDIDATES.find((candidate) => existsSync(candidate)) ?? MATRIX_PATH_CANDIDATES[0];
|
||||
const ISSUE_ID = process.env.QA_ISSUE_ID?.trim() || "project-wide-visual";
|
||||
const BASE_URL = process.env.E2E_BASE_URL?.trim() || null;
|
||||
const SCREENSHOT_DIR = resolveRootPath(
|
||||
|
||||
@@ -1,43 +1,77 @@
|
||||
import type { APIRequestContext } from "@playwright/test";
|
||||
|
||||
import { test, expect } from "./helpers/fixtures";
|
||||
import { EMPLOYEE, login, logout } from "./helpers/auth";
|
||||
import { EMPLOYEE, createAuthenticatedApiClient, login, logout } from "./helpers/auth";
|
||||
import { openNavLink } from "./helpers/navigation";
|
||||
|
||||
async function firstEnabledExpenseTypeId(api: APIRequestContext) {
|
||||
const response = await api.get("/api/expense-types?enabled=true");
|
||||
const payload = await response.json() as { data?: { items?: Array<{ id: string; enabled?: boolean }> } };
|
||||
const item = payload.data?.items?.find((candidate) => candidate.enabled !== false) ?? payload.data?.items?.[0];
|
||||
if (!response.ok() || !item?.id) {
|
||||
throw new Error(`无法读取费用类型:HTTP ${response.status()}`);
|
||||
}
|
||||
return item.id;
|
||||
}
|
||||
|
||||
async function createReimbursementItem(api: APIRequestContext, reimbursementId: string) {
|
||||
const expenseTypeId = await firstEnabledExpenseTypeId(api);
|
||||
const response = await api.post(`/api/reimbursements/${reimbursementId}/items`, {
|
||||
data: {
|
||||
expenseTypeId,
|
||||
expenseDate: "2026-05-13",
|
||||
amount: "168.30",
|
||||
description: "E2E 报销审批进度明细",
|
||||
},
|
||||
});
|
||||
if (!response.ok()) {
|
||||
throw new Error(`创建报销明细失败:HTTP ${response.status()}`);
|
||||
}
|
||||
}
|
||||
|
||||
test("E2E 报销审批进度:提交后详情页展示当前步骤进度", async ({ page, reimbursementDetail }) => {
|
||||
const apiClient = await createAuthenticatedApiClient(EMPLOYEE);
|
||||
await login(page, EMPLOYEE);
|
||||
|
||||
await (await openNavLink(page, "我的报销")).click();
|
||||
await expect(page).toHaveURL(/\/reimbursements/);
|
||||
await expect(page.getByTestId("reimbursements-create")).toBeVisible();
|
||||
try {
|
||||
await (await openNavLink(page, "我的报销")).click();
|
||||
await expect(page).toHaveURL(/\/reimbursements/);
|
||||
await expect(page.getByTestId("reimbursements-create")).toBeVisible();
|
||||
|
||||
const createResponsePromise = page.waitForResponse((response) => {
|
||||
const url = new URL(response.url());
|
||||
return response.request().method() === "POST" && url.pathname === "/api/reimbursements" && response.ok();
|
||||
});
|
||||
await page.getByTestId("reimbursements-create").click();
|
||||
await page.getByRole("menuitem", { name: "空白报销" }).click();
|
||||
const createPayload = (await (await createResponsePromise).json()) as { data?: { id?: string } };
|
||||
const reimbursementId = createPayload.data?.id;
|
||||
if (!reimbursementId) {
|
||||
throw new Error(`创建报销单后未返回 id: ${JSON.stringify(createPayload)}`);
|
||||
const createResponsePromise = page.waitForResponse((response) => {
|
||||
const url = new URL(response.url());
|
||||
return response.request().method() === "POST" && url.pathname === "/api/reimbursements" && response.ok();
|
||||
});
|
||||
await page.getByTestId("reimbursements-create").click();
|
||||
await page.getByRole("menuitem", { name: "空白报销" }).click();
|
||||
const createPayload = (await (await createResponsePromise).json()) as { data?: { id?: string } };
|
||||
const reimbursementId = createPayload.data?.id;
|
||||
if (!reimbursementId) {
|
||||
throw new Error(`创建报销单后未返回 id: ${JSON.stringify(createPayload)}`);
|
||||
}
|
||||
|
||||
await createReimbursementItem(apiClient.api, reimbursementId);
|
||||
|
||||
await page.goto(`/reimbursements/${reimbursementId}`);
|
||||
await reimbursementDetail.assertLoaded();
|
||||
|
||||
const marker = `MOCK_E2E_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
||||
await reimbursementDetail.uploadInvoicePdf(
|
||||
`progress-${Date.now()}.pdf`,
|
||||
Buffer.from(`%PDF-1.4\n${marker}\n%%EOF\n`, "utf8"),
|
||||
);
|
||||
|
||||
await page.goto(`/reimbursements/${reimbursementId}`);
|
||||
await reimbursementDetail.assertLoaded();
|
||||
await reimbursementDetail.submitReimbursement();
|
||||
|
||||
await expect(page.getByText(/reviewing|审批中/).first()).toBeVisible({ timeout: 30_000 });
|
||||
await expect(reimbursementDetail.workflowProgress).toBeVisible({ timeout: 30_000 });
|
||||
await expect(reimbursementDetail.workflowProgress).toContainText("当前节点");
|
||||
await expect(reimbursementDetail.workflowProgress).toContainText(/第 1 步|第 2 步|第 3 步/);
|
||||
|
||||
await logout(page, EMPLOYEE.name);
|
||||
} finally {
|
||||
await apiClient.dispose();
|
||||
}
|
||||
|
||||
await page.goto(`/reimbursements/${reimbursementId}`);
|
||||
await reimbursementDetail.assertLoaded();
|
||||
|
||||
const marker = `MOCK_E2E_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
||||
await reimbursementDetail.uploadInvoicePdf(
|
||||
`progress-${Date.now()}.pdf`,
|
||||
Buffer.from(`%PDF-1.4\n${marker}\n%%EOF\n`, "utf8"),
|
||||
);
|
||||
|
||||
await page.goto(`/reimbursements/${reimbursementId}`);
|
||||
await reimbursementDetail.assertLoaded();
|
||||
await reimbursementDetail.submitReimbursement();
|
||||
|
||||
await expect(page.getByText(/状态:(reviewing|审批中)/)).toBeVisible({ timeout: 30_000 });
|
||||
await expect(reimbursementDetail.workflowProgress).toBeVisible({ timeout: 30_000 });
|
||||
await expect(reimbursementDetail.workflowProgress).toContainText("当前节点");
|
||||
await expect(reimbursementDetail.workflowProgress).toContainText(/第 1 步|第 2 步|第 3 步/);
|
||||
|
||||
await logout(page, EMPLOYEE.name);
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户