R97 B6 补充高频页面视觉回归证据
这个提交包含在:
@@ -0,0 +1,42 @@
|
||||
# R97 高频页面视觉回归报告
|
||||
|
||||
日期:2026-05-06
|
||||
|
||||
## 范围
|
||||
|
||||
- 报销列表:手机草稿卡片、继续编辑入口、桌面操作列。
|
||||
- 票据中心:手机列表、详情面板、批量上传入口、识别文案。
|
||||
- 移动审批:待办列表、详情处理区、筛选与批量处理收起态。
|
||||
- 审批待办:手机卡片兜底与桌面表格保留。
|
||||
|
||||
## 自动化验证
|
||||
|
||||
| 命令 | 结果 |
|
||||
| --- | --- |
|
||||
| `python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-06_17-37-53-r97-high-frequency-ui-polish.csv` | 通过 |
|
||||
| `git diff --check` | 通过 |
|
||||
| `cd client && npm run typecheck` | 通过 |
|
||||
| `cd client && npm run build` | 通过;Vite 仍有既有 circular chunk 警告 |
|
||||
| `cd client && E2E_BASE_URL=http://127.0.0.1:5177 npx playwright test tests/e2e/r89-mobile-login-navigation.spec.ts --workers=1 --reporter=list` | 3 passed |
|
||||
| `cd client && E2E_BASE_URL=http://127.0.0.1:5177 npx playwright test tests/e2e/r92-mobile-invoice-usability.spec.ts --workers=1 --reporter=list` | 3 passed |
|
||||
| `cd client && E2E_BASE_URL=http://127.0.0.1:5177 npx playwright test tests/e2e/visual-shell.spec.ts --workers=1 --reporter=list` | 4 passed |
|
||||
|
||||
## 视觉证据
|
||||
|
||||
Playwright 在测试报告中附加了以下截图:
|
||||
|
||||
- `r97-wecom-login.png`
|
||||
- `r97-mobile-today.png`
|
||||
- `r97-mobile-approvals.png`
|
||||
- `r97-mobile-capture.png`
|
||||
- `r97-reimbursements-mobile-draft.png`
|
||||
- `r97-invoice-pool-mobile.png`
|
||||
- `r97-mobile-capture-multi-upload.png`
|
||||
- `r97-workflow-tasks-mobile.png`
|
||||
- `login-shell.png`
|
||||
- `app-shell.png`
|
||||
- `mobile-today.png`
|
||||
|
||||
## 结论
|
||||
|
||||
本轮高频页面精修已覆盖主要手机与桌面入口。自动化验证未发现横向溢出、核心按钮不可见或手机菜单不可操作问题。后续如继续做视觉优化,建议进入表单详情页和管理后台低频页,而不是继续扩大本轮高频页范围。
|
||||
@@ -373,7 +373,7 @@ test.describe("R89 企业微信移动入口", () => {
|
||||
await expect(page.getByTestId("login-wecom-primary-panel")).toBeVisible();
|
||||
await expect(page.getByTestId("wecom-entry-primary")).toBeVisible();
|
||||
await expect(page.getByTestId("login-password-primary-panel")).toHaveCount(0);
|
||||
await attachScreenshot(page, testInfo, "r96-wecom-login.png");
|
||||
await attachScreenshot(page, testInfo, "r97-wecom-login.png");
|
||||
|
||||
await page.getByTestId("wecom-entry-primary").click();
|
||||
await expect(page).toHaveURL(/\/api\/auth\/wecom\/login\?returnTo=%2Fmobile/);
|
||||
@@ -390,7 +390,7 @@ test.describe("R89 移动底部导航与核心页面", () => {
|
||||
await expect(page).toHaveURL(/\/mobile$/);
|
||||
await expect(page.getByTestId("mobile-today-focus")).toBeVisible();
|
||||
await expect(page.getByTestId("topbar-profile-trigger")).toBeHidden();
|
||||
await attachScreenshot(page, testInfo, "r96-mobile-today.png");
|
||||
await attachScreenshot(page, testInfo, "r97-mobile-today.png");
|
||||
|
||||
await expect(page.getByTestId("mobile-nav-home")).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-nav-approvals")).toBeVisible();
|
||||
@@ -404,7 +404,7 @@ test.describe("R89 移动底部导航与核心页面", () => {
|
||||
await expect(page.getByTestId("mobile-approvals-detail")).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-approvals-approve")).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-approvals-reject")).toBeVisible();
|
||||
await attachScreenshot(page, testInfo, "r96-mobile-approvals.png");
|
||||
await attachScreenshot(page, testInfo, "r97-mobile-approvals.png");
|
||||
|
||||
await page.getByTestId("mobile-nav-capture").click();
|
||||
await expect(page).toHaveURL(/\/mobile\/capture/);
|
||||
@@ -415,7 +415,7 @@ test.describe("R89 移动底部导航与核心页面", () => {
|
||||
buffer: Buffer.from("%PDF-1.4\n% R89"),
|
||||
});
|
||||
await expect(page.getByTestId("mobile-capture-upload")).toBeEnabled();
|
||||
await attachScreenshot(page, testInfo, "r96-mobile-capture.png");
|
||||
await attachScreenshot(page, testInfo, "r97-mobile-capture.png");
|
||||
|
||||
await page.getByTestId("mobile-nav-me").click();
|
||||
await expect(page).toHaveURL(/\/mobile\/me/);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expect, test, type Page, type Route } from "@playwright/test";
|
||||
import { expect, test, type Page, type Route, type TestInfo } from "@playwright/test";
|
||||
|
||||
import { ADMIN } from "./helpers/auth";
|
||||
import { TEST_IDS } from "./helpers/testIds";
|
||||
@@ -15,6 +15,13 @@ async function fulfillJson(route: Route, data: unknown) {
|
||||
});
|
||||
}
|
||||
|
||||
async function attachScreenshot(page: Page, testInfo: TestInfo, name: string) {
|
||||
await testInfo.attach(name, {
|
||||
body: await page.screenshot({ fullPage: true }),
|
||||
contentType: "image/png",
|
||||
});
|
||||
}
|
||||
|
||||
async function mockAuthenticatedShell(page: Page) {
|
||||
await page.route((url) => url.pathname === "/api/auth/sso/providers", (route) => fulfillJson(route, []));
|
||||
await page.route((url) => url.pathname === "/api/auth/wecom/config", (route) =>
|
||||
@@ -289,7 +296,7 @@ test.describe("R92 手机端票据与报销可用性", () => {
|
||||
await mockReimbursements(page);
|
||||
});
|
||||
|
||||
test("报销草稿在手机视口使用卡片并暴露继续编辑入口", async ({ page }) => {
|
||||
test("报销草稿在手机视口使用卡片并暴露继续编辑入口", async ({ page }, testInfo) => {
|
||||
await loginThenUseMobileViewport(page);
|
||||
await page.goto("/reimbursements?status=draft&scope=own");
|
||||
|
||||
@@ -302,9 +309,11 @@ test.describe("R92 手机端票据与报销可用性", () => {
|
||||
const box = await editLink.boundingBox();
|
||||
expect(box).not.toBeNull();
|
||||
expect((box?.x ?? 0) + (box?.width ?? 0)).toBeLessThanOrEqual(390);
|
||||
|
||||
await attachScreenshot(page, testInfo, "r97-reimbursements-mobile-draft.png");
|
||||
});
|
||||
|
||||
test("票据中心手机视口可批量选择并顺序上传", async ({ page }) => {
|
||||
test("票据中心手机视口可批量选择并顺序上传", async ({ page }, testInfo) => {
|
||||
const uploadCounter = { value: 0 };
|
||||
await mockInvoicePool(page, uploadCounter);
|
||||
await loginThenUseMobileViewport(page);
|
||||
@@ -312,6 +321,10 @@ test.describe("R92 手机端票据与报销可用性", () => {
|
||||
|
||||
await expect(page.getByTestId("invoice-mobile-row")).toBeVisible();
|
||||
await expect(page.getByTestId("invoice-pool-table")).toBeHidden();
|
||||
await page.getByTestId("invoice-mobile-detail-button").click();
|
||||
await expect(page.getByTestId("invoice-detail-panel")).toBeVisible();
|
||||
await expect(page.getByText("票据详情")).toBeVisible();
|
||||
await attachScreenshot(page, testInfo, "r97-invoice-pool-mobile.png");
|
||||
|
||||
await page.getByTestId("invoice-upload-file").setInputFiles([
|
||||
{ name: "invoice-a.pdf", mimeType: "application/pdf", buffer: Buffer.from("%PDF-a") },
|
||||
@@ -323,7 +336,7 @@ test.describe("R92 手机端票据与报销可用性", () => {
|
||||
await expect.poll(() => uploadCounter.value).toBe(2);
|
||||
});
|
||||
|
||||
test("手机票据上传页支持一次选择多张并逐张上传", async ({ page }) => {
|
||||
test("手机票据上传页支持一次选择多张并逐张上传", async ({ page }, testInfo) => {
|
||||
const uploadCounter = { value: 0 };
|
||||
await mockInvoicePool(page, uploadCounter);
|
||||
await loginThenUseMobileViewport(page);
|
||||
@@ -334,6 +347,7 @@ test.describe("R92 手机端票据与报销可用性", () => {
|
||||
{ name: "mobile-b.jpg", mimeType: "image/jpeg", buffer: Buffer.from("mobile-b") },
|
||||
]);
|
||||
await expect(page.getByText("已选择 2 个")).toBeVisible();
|
||||
await attachScreenshot(page, testInfo, "r97-mobile-capture-multi-upload.png");
|
||||
await page.getByTestId("mobile-capture-upload").click();
|
||||
|
||||
await expect.poll(() => uploadCounter.value).toBe(2);
|
||||
|
||||
@@ -22,7 +22,7 @@ const visualMe = {
|
||||
directManagerId: null,
|
||||
status: "active",
|
||||
notificationPushEnabled: true,
|
||||
roleCodes: ["employee"],
|
||||
roleCodes: ["manager"],
|
||||
profileCompleteness: {
|
||||
hasEmergencyContact: true,
|
||||
hasBankAccount: true,
|
||||
@@ -156,6 +156,7 @@ async function mockVisualApis(page: Page) {
|
||||
createdAt: "2026-05-06T09:00:00.000Z",
|
||||
completedAt: null,
|
||||
workflowInstance: { entityType: "reimbursement", entityId: "rb-r96" },
|
||||
allowedActions: ["approve"],
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
@@ -242,4 +243,20 @@ test.describe("visual shell", () => {
|
||||
|
||||
await attachScreenshot(page, testInfo, "mobile-today.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 page.goto("/workflow/tasks");
|
||||
|
||||
await expect(page.getByTestId("workflow-tasks-mobile-list")).toBeVisible();
|
||||
await expect(page.getByTestId("workflow-tasks-table")).toBeHidden();
|
||||
await expect(page.getByRole("link", { name: "详情" })).toBeVisible();
|
||||
|
||||
await attachScreenshot(page, testInfo, "r97-workflow-tasks-mobile.png");
|
||||
});
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户