优化工作台与报销任务优先页面(portal-task-first-redesign)

这个提交包含在:
2026-05-29 22:31:59 +08:00
父节点 b75fa86e4e
当前提交 a9d91d3259
共修改 15 个文件,包含 988 行新增和 1149 行删除
@@ -36,11 +36,9 @@ test("E2E 公告与团队状态:公告可见 + 今日状态更新 + Portal 汇
await page.getByRole("button", { name: "保存状态" }).click();
await expect(page.getByText("今日状态已更新")).toBeVisible({ timeout: 30_000 });
await page.goto("/portal");
await page.locator("summary").filter({ hasText: "团队与通知" }).click();
await page.getByRole("tab", { name: "公告" }).click();
await expect(page.getByText(announcementTitle)).toBeVisible();
await page.locator("summary").filter({ hasText: "更多入口" }).click();
await expect(page.locator('[aria-label="portal-card-today_presence"]')).toBeVisible();
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
await expect(page.locator("summary").filter({ hasText: "团队与通知" })).not.toBeVisible();
await expect(page.getByText(announcementTitle)).not.toBeVisible();
await logout(page, EMPLOYEE.name);
@@ -51,8 +49,8 @@ test("E2E 公告与团队状态:公告可见 + 今日状态更新 + Portal 汇
await expect(page.getByRole("button", { name: "不在岗" })).toBeVisible();
await page.goto("/portal");
await page.getByRole("tab", { name: "公告" }).click();
await expect(page.getByText(announcementTitle)).toBeVisible();
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
await expect(page.getByText(announcementTitle)).not.toBeVisible();
await expect(page.getByTestId(TEST_IDS.topbar.profileTrigger)).toBeVisible();
} finally {
await adminClient.dispose();
+1 -1
查看文件
@@ -24,7 +24,7 @@ type Envelope<T> = {
export async function assertPortalLoaded(page: Page) {
await expect(page).toHaveURL(/\/portal/);
await expect(page.getByRole("link", { name: "工作台", exact: true }).first()).toBeVisible();
await expect(page.getByRole("heading", { name: "今日任务" })).toBeVisible();
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
}
export async function assertForbiddenPage(page: Page) {
+26 -54
查看文件
@@ -508,30 +508,17 @@ test("Portal 员工视角优先显示今日动作顺序", async ({ page }) => {
await login(page, EMPLOYEE);
await expect(page.getByRole("heading", { name: "今日任务" })).toBeVisible();
await expect(page.getByText("当前焦点")).toBeVisible();
await expect(page.getByText("下一步")).toBeVisible();
await expect(page.getByRole("link", { name: "新建报销" })).toBeVisible();
await page.locator("summary").filter({ hasText: "更多入口" }).click();
const labels = await page.locator('[aria-label^="portal-card-"]').evaluateAll((nodes) =>
nodes.map((node) => node.getAttribute("aria-label")),
);
expect(labels.slice(0, 4)).toEqual([
"portal-card-my_todos",
"portal-card-today_presence",
"portal-card-my_weekly_report",
"portal-card-my_projects",
]);
await expect(page.locator("summary").filter({ hasText: "团队与通知" })).toBeVisible();
await page.locator("summary").filter({ hasText: "团队与通知" }).click();
await expect(page.getByRole("tab", { name: "公告" })).toBeVisible();
await expect(page.getByRole("heading", { name: "团队状态" })).toBeVisible();
await expect(page.getByRole("heading", { name: "通知" })).toBeVisible();
await expect(page.getByTestId("portal-work-summary")).toContainText("今天有 4 件待处理");
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
await expect(page.getByRole("link", { name: /待办事项 4 条.*查看待办/ })).toBeVisible();
await expect(page.getByRole("link", { name: /本周周报待填写.*填写周报/ })).toBeVisible();
await expect(page.getByText("当前焦点")).not.toBeVisible();
await expect(page.getByText("下一步", { exact: true })).not.toBeVisible();
await expect(page.locator("summary").filter({ hasText: "团队与通知" })).not.toBeVisible();
await expect(page.locator("summary").filter({ hasText: "更多入口" })).not.toBeVisible();
});
test("Portal 管理视角重排正确,更多入口默认折叠", async ({ page }) => {
test("Portal 管理视角按角色分发待处理和团队卡点", async ({ page }) => {
await mockPasswordLogin(page);
await mockNavigation(page, "manager");
await mockPortalHome(page, "manager");
@@ -541,26 +528,13 @@ test("Portal 管理视角重排正确,更多入口默认折叠", async ({ page
await login(page, MANAGER);
await expect(page.getByRole("heading", { name: "今日任务" })).toBeVisible();
await expect(page.getByRole("link", { name: "处理待办" })).toBeVisible();
await page.locator("summary").filter({ hasText: "角色重点" }).click();
const labels = await page.locator('[aria-label^="portal-card-"]').evaluateAll((nodes) =>
nodes.map((node) => node.getAttribute("aria-label")),
);
expect(labels.slice(0, 4)).toEqual([
"portal-card-pending_approvals",
"portal-card-team_presence",
"portal-card-weekly_missing",
"portal-card-weekly_help",
]);
await expect(page.locator("summary").filter({ hasText: "团队与通知" })).toBeVisible();
await expect(page.getByText("试点观测摘要")).toBeVisible();
await expect(page.getByRole("link", { name: "采购申请" })).toBeVisible();
await expect(page.getByRole("link", { name: "差旅申请" })).not.toBeVisible();
await page.locator("summary").filter({ hasText: "更多" }).click();
await expect(page.getByRole("link", { name: "差旅申请" })).toBeVisible();
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
await expect(page.getByRole("link", { name: /待我处理 3 条.*去处理/ })).toBeVisible();
await expect(page.getByRole("heading", { name: "团队卡点" })).toBeVisible();
await expect(page.getByRole("link", { name: /项目风险未关闭.*处理风险/ })).toBeVisible();
await expect(page.locator("summary").filter({ hasText: "团队与通知" })).not.toBeVisible();
await expect(page.locator("summary").filter({ hasText: "角色重点" })).not.toBeVisible();
await expect(page.getByText("试点观测摘要")).not.toBeVisible();
});
test("Portal 新员工只显示轻量欢迎入口,不再显示快速上手清单", async ({ page }) => {
@@ -598,7 +572,7 @@ test("Portal 新员工只显示轻量欢迎入口,不再显示快速上手清
await expect(page.getByText("完善个人资料")).not.toBeVisible();
await expect(page.getByText("了解请假流程")).not.toBeVisible();
await expect(page.getByText("查看通知中心")).not.toBeVisible();
await expect(page.getByRole("heading", { name: "今日任务" })).toBeVisible();
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
});
test("个人设置默认只展示账号通知和企业微信,不展示权限 code", async ({ page }) => {
@@ -668,11 +642,11 @@ test("Portal bootstrap refresh 首次瞬时失败后重试成功仍保留登录
await page.goto("/portal");
await expect(page).toHaveURL(/\/portal/);
await expect(page.getByRole("heading", { name: "今日任务" })).toBeVisible();
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
expect(refreshAttempts).toBeGreaterThanOrEqual(2);
});
test("Portal 域管理角色也进入经理视图", async ({ page }) => {
test("Portal 域管理角色也能看到团队卡点", async ({ page }) => {
await mockPasswordLogin(page);
await mockNavigation(page, "manager");
await mockPortalHome(page, "manager");
@@ -682,14 +656,12 @@ test("Portal 域管理角色也进入经理视图", async ({ page }) => {
await login(page, EMPLOYEE);
await expect(page.getByRole("heading", { name: "今日任务" })).toBeVisible();
await expect(page.locator("summary").filter({ hasText: "角色重点" })).toBeVisible();
await page.locator("summary").filter({ hasText: "角色重点" }).click();
await expect(page.locator('[aria-label="portal-card-pending_approvals"]')).toBeVisible();
await expect(page.locator('[aria-label="portal-card-team_presence"]')).toBeVisible();
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
await expect(page.getByRole("heading", { name: "团队卡点" })).toBeVisible();
await expect(page.getByRole("link", { name: /未交周报/ })).toBeVisible();
});
test("Portal 系统摘要审计日志入口指向实际路由", async ({ page }) => {
test("Portal 管理提醒正常时不显示空壳", async ({ page }) => {
await mockPasswordLogin(page);
await mockNavigation(page, "manager");
await mockPortalHome(page, "manager");
@@ -699,9 +671,9 @@ test("Portal 系统摘要审计日志入口指向实际路由", async ({ page })
await login(page, ADMIN);
await page.locator("summary").filter({ hasText: "角色重点" }).click();
await expect(page.getByRole("heading", { name: "系统管理" })).toBeVisible();
await expect(page.getByRole("link", { name: /审计日志.*查看/ })).toHaveAttribute("href", "/audit-logs");
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
await expect(page.getByRole("heading", { name: "管理提醒" })).not.toBeVisible();
await expect(page.getByText("管理员按需查看")).not.toBeVisible();
});
test("企微 WebView 下默认进入移动工作台", async ({ page }) => {
@@ -194,11 +194,9 @@ test("R70 深层自动辅助试运行:周报待办、公告团队状态、真
await expect(page.getByText("今日状态已更新")).toBeVisible({ timeout: 30_000 });
await page.goto("/portal");
await page.locator("summary").filter({ hasText: "团队与通知" }).click();
await page.getByRole("tab", { name: "公告" }).click();
await expect(page.getByText(announcementTitle)).toBeVisible({ timeout: 30_000 });
await page.locator("summary").filter({ hasText: "更多入口" }).click();
await expect(page.locator('[aria-label="portal-card-today_presence"]')).toBeVisible();
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
await expect(page.locator("summary").filter({ hasText: "团队与通知" })).not.toBeVisible();
await expect(page.getByText(announcementTitle)).not.toBeVisible();
return announcementTitle;
});
@@ -316,8 +316,8 @@ test("PC 核心工作流 Wave2 视觉截图", async ({ page }) => {
await page.getByTestId("login-password").fill(visualUser.password);
await page.getByTestId("login-submit").click();
await expect(page).toHaveURL(/\/portal/);
await expect(page.getByRole("heading", { name: "今日任务" })).toBeVisible();
await expect(page.getByRole("link", { name: "处理待办" })).toBeVisible();
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
await expect(page.getByRole("link", { name: /待我处理.*去处理/ })).toBeVisible();
await capture(page, "02-portal");
await page.goto("/reimbursements?status=reviewing&scope=all");
@@ -320,6 +320,8 @@ test.describe("R102 reimbursement main flow", () => {
await page.setViewportSize({ width: 1440, height: 960 });
await page.goto("/reimbursements?status=draft");
await expect(page.getByTestId("reimbursements-task-card-draft")).toBeVisible();
await expect(page.getByRole("button", { name: "台账工具" })).toHaveCount(0);
await expect(page.getByTestId("reimbursement-row").getByText(formNumber)).toBeVisible();
await expect(page.getByText(reimbursementId)).toHaveCount(0);
@@ -328,7 +330,7 @@ test.describe("R102 reimbursement main flow", () => {
await expect(page.getByTestId("action-confirm-dialog")).toContainText(formNumber);
await page.getByTestId("action-confirm-submit").click();
await expect(page.getByTestId("reimbursement-row")).toHaveCount(0);
await expect(page.getByRole("cell", { name: "暂无数据" })).toBeVisible();
await expect(page.getByTestId("reimbursements-table").getByText("还没有报销草稿")).toBeVisible();
});
test("详情页发票按当前简化流程添加并隐藏原始码值", async ({ page }) => {
@@ -120,7 +120,7 @@ test.describe("R105 pretrial visual acceptance", () => {
await loginLiveUser(page, "employee");
await page.goto("/portal");
await expect(page.getByRole("heading", { name: "今日任务" })).toBeVisible();
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
await expectNoLegacyDesignCopy(page);
await page.screenshot({ path: screenshotPath("desktop-portal.png"), fullPage: true });
@@ -290,7 +290,7 @@ test.describe("R7E 全站高风险操作二次确认", () => {
await page.getByTestId("action-confirm-submit").click();
await expect.poll(() => calls.deleteReimbursement).toBe(1);
await page.getByRole("button", { name: "高级 / 台账" }).click();
await page.getByRole("button", { name: "台账工具" }).click();
await page.getByTestId("reimbursements-export").click();
await page.getByTestId("reimbursements-export-csv").click();
await expect(page.getByTestId("action-confirm-dialog")).toContainText("导出文件可能包含业务或个人信息");
+185 -17
查看文件
@@ -1,58 +1,226 @@
import { mkdirSync } from "node:fs";
import path from "node:path";
import { expect, test, type Page, type TestInfo } from "@playwright/test";
import { EMPLOYEE, FINANCE, login, logout, type E2EUser } from "./helpers/auth";
import { EMPLOYEE, FINANCE, login, type E2EUser } from "./helpers/auth";
const ADMIN: E2EUser = {
identifier: "[email protected]",
password: "AdminPassword1234",
name: "Admin",
};
const SCREENSHOT_DIR = path.resolve(process.cwd(), "../output/playwright/portal-task-first-redesign");
async function attachScreenshot(page: Page, testInfo: TestInfo, name: string) {
mkdirSync(SCREENSHOT_DIR, { recursive: true });
const body = await page.screenshot({ path: path.join(SCREENSHOT_DIR, name), fullPage: true });
await testInfo.attach(name, {
body: await page.screenshot({ fullPage: true }),
body,
contentType: "image/png",
});
}
function envelope<T>(data: T) {
return { requestId: "visual-portal", data };
}
function portalPayload(mode: "employee" | "admin" | "finance") {
const base = {
workspace: { id: "workspace-visual", persisted: true, profile: "small_team_internal" },
greeting: { name: mode === "employee" ? "Employee" : mode === "finance" ? "Finance" : "Admin" },
myApplications: [{ domain: "reimbursement", count: 1, href: "/reimbursements" }],
riskSummary: null,
announcements: [],
quickLinks: [],
todayPresence: null,
weeklyReport: null,
recentActivities: [],
teamStatusBoard: [],
};
if (mode === "finance") {
return {
...base,
cards: [],
pendingTasks: { count: 1, href: "/workflow/tasks" },
teamPresence: null,
weeklySummary: null,
todoSummary: { openCount: 0, highPriorityCount: 0, href: "/todos" },
projectSummary: null,
peopleLifecycle: null,
roleCapabilities: {
canSubmitApplications: true,
canApproveTasks: true,
canUseFinance: true,
canManageSystem: false,
canViewTeam: true,
isAdminOnly: false,
},
financeSummary: { pendingBatchCount: 2, failedItemCount: 1, riskCount: 1, paymentBatchEnabled: true, href: "/ap-operations" },
adoptionSummary: null,
};
}
if (mode === "admin") {
return {
...base,
cards: [],
pendingTasks: { count: 0, href: "/workflow/tasks" },
teamPresence: { date: "2026-05-29", href: "/team", office: 6, remote: 1, out: 0, travel: 0, leave: 0, unspecified: 1, total: 8 },
weeklySummary: { weekStart: "2026-05-25", submittedCount: 6, missingCount: 2, needsHelpCount: 0, href: "/weekly-reports" },
todoSummary: { openCount: 0, highPriorityCount: 0, href: "/todos" },
projectSummary: { myCount: 3, overdueMilestonesCount: 1, highPriorityTaskCount: 0, openRiskCount: 0, followUpDueTodayCount: 0, closurePendingProjectCount: 0, href: "/projects" },
peopleLifecycle: { onboardingCount: 0, offboardingCount: 0, assetReturnPendingCount: 0, href: "/people" },
roleCapabilities: {
canSubmitApplications: false,
canApproveTasks: false,
canUseFinance: false,
canManageSystem: true,
canViewTeam: true,
isAdminOnly: true,
},
financeSummary: null,
adoptionSummary: { activeEmployeeCount: 8, login7dCount: 8, weeklySubmittedCount: 6, weeklyMissingCount: 2, presenceUpdatedCount: 7, presenceMissingCount: 1, announcementCount: 0, login7dRate: 100, weeklySubmissionRate: 75, presenceCoverageRate: 87.5, hints: ["本周仍有 2 人未提交周报"] },
};
}
return {
...base,
cards: [],
pendingTasks: { count: 0, href: "/workflow/tasks" },
teamPresence: null,
weeklySummary: null,
todoSummary: { openCount: 2, highPriorityCount: 1, href: "/todos" },
projectSummary: { myCount: 2, overdueMilestonesCount: 0, highPriorityTaskCount: 1, openRiskCount: 0, followUpDueTodayCount: 0, closurePendingProjectCount: 0, href: "/projects" },
peopleLifecycle: null,
roleCapabilities: {
canSubmitApplications: true,
canApproveTasks: false,
canUseFinance: false,
canManageSystem: false,
canViewTeam: false,
isAdminOnly: false,
},
financeSummary: null,
adoptionSummary: null,
weeklyReport: { weekStart: "2026-05-25", status: "draft", needsHelp: false, href: "/weekly-reports" },
};
}
async function mockVisualPortal(page: Page, mode: "employee" | "admin" | "finance") {
await page.addInitScript(() => {
window.localStorage.removeItem("hyb_oa_show_welcome");
window.localStorage.removeItem("hyb_oa_onboarding_active");
window.localStorage.removeItem("hyb_oa_getting_started_dismissed");
});
await page.route((url) => url.pathname.startsWith("/api/"), async (route) => {
const pathName = new URL(route.request().url()).pathname;
if (pathName === "/api/auth/capabilities") {
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(envelope({ emailDelivery: { ready: true, statusReasons: [] }, emailLoginCode: { enabled: true }, passwordReset: { enabled: true } })) });
return;
}
if (pathName === "/api/auth/sso/providers") {
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(envelope([])) });
return;
}
if (pathName === "/api/auth/wecom/config") {
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(envelope({ enabled: false, entryEnabled: false, oauthEnabled: false, corpId: "", agentId: "", loginUrl: null, bindEnabled: false, defaultReturnTo: "/portal", status: "disabled", statusReasons: [] })) });
return;
}
if (pathName === "/api/auth/login") {
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(envelope({ accessToken: "visual-token", expiresIn: 900 })) });
return;
}
if (pathName === "/api/me") {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(envelope({
id: `visual-${mode}`,
employeeNo: "VISUAL",
name: mode === "employee" ? "Employee" : mode === "finance" ? "Finance" : "Admin",
email: `${mode}@hyinda.com`,
roleCodes: mode === "employee" ? ["employee"] : mode === "finance" ? ["finance"] : ["admin"],
notificationPushEnabled: true,
profileCompleteness: { hasEmergencyContact: true, hasBankAccount: true, hasAvatar: false, hasIdNumber: false, score: 50 },
wecomBinding: null,
})),
});
return;
}
if (pathName === "/api/system/navigation") {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(envelope({
workspace: { id: "workspace-visual", persisted: true, profile: "small_team_internal" },
sections: {
main: [
{ key: "portal", href: "/portal", label: "工作台", icon: "home_app_logo", placement: "main", permissionCodes: [], featureFlagKey: null },
{ key: "expense", href: "/reimbursements", label: "报销", icon: "request_quote", placement: "main", permissionCodes: [], featureFlagKey: null },
],
admin: [],
backup: [],
},
recommendedRolePresets: [],
})),
});
return;
}
if (pathName === "/api/notifications") {
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(envelope({ page: 1, pageSize: 1, total: 0, unreadOnly: true, unreadCount: 0, items: [] })) });
return;
}
if (pathName === "/api/portal/home") {
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(envelope(portalPayload(mode))) });
return;
}
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(envelope({})) });
});
}
test.describe("visual portal", () => {
test("员工 Portal 视觉基线", async ({ page }, testInfo) => {
await mockVisualPortal(page, "employee");
await page.setViewportSize({ width: 1440, height: 1200 });
await login(page, EMPLOYEE);
await expect(page.getByRole("heading", { name: "今日任务" })).toBeVisible();
await expect(page.getByText("当前焦点")).toBeVisible();
await expect(page.getByText("下一步")).toBeVisible();
await expect(page.locator("summary").filter({ hasText: "团队与通知" })).toBeVisible();
await expect(page.locator("summary").filter({ hasText: "更多入口" })).toBeVisible();
await expect(page.getByTestId("portal-work-summary")).toBeVisible();
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
await expect(page.getByText("当前焦点")).not.toBeVisible();
await expect(page.getByText("下一步", { exact: true })).not.toBeVisible();
await expect(page.locator("summary").filter({ hasText: "团队与通知" })).not.toBeVisible();
await attachScreenshot(page, testInfo, "portal-employee.png");
});
test("管理员 Portal 视觉基线", async ({ page }, testInfo) => {
await mockVisualPortal(page, "admin");
await page.setViewportSize({ width: 1440, height: 1200 });
await login(page, ADMIN);
await expect(page.locator('[aria-label="portal-card-pending_approvals"]')).toHaveCount(0);
await expect(page.getByRole("heading", { name: "今日任务" })).toBeVisible();
await expect(page.getByText("下一步")).toBeVisible();
await expect(page.locator("summary").filter({ hasText: "团队与通知" })).toBeVisible();
await expect(page.locator("summary").filter({ hasText: "角色重点" })).toBeVisible();
await expect(page.getByText("试点观测摘要")).toBeVisible();
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
await expect(page.getByText("下一步", { exact: true })).not.toBeVisible();
await expect(page.locator("summary").filter({ hasText: "团队与通知" })).not.toBeVisible();
await expect(page.locator("summary").filter({ hasText: "角色重点" })).not.toBeVisible();
await expect(page.getByText("试点观测摘要")).not.toBeVisible();
await attachScreenshot(page, testInfo, "portal-admin.png");
await logout(page, ADMIN.name);
});
test("财务 Portal 显示财务待处理入口", async ({ page }, testInfo) => {
await mockVisualPortal(page, "finance");
await page.setViewportSize({ width: 1440, height: 1200 });
await login(page, FINANCE);
await expect(page.getByText("财务工作台")).toBeVisible();
await expect(page.getByRole("heading", { name: "今日任务" })).toBeVisible();
await expect(page.locator("summary").filter({ hasText: "角色重点" })).toBeVisible();
await expect(page.getByTestId("portal-work-summary")).toBeVisible();
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
await expect(page.locator("summary").filter({ hasText: "角色重点" })).not.toBeVisible();
await attachScreenshot(page, testInfo, "portal-finance.png");
await logout(page, FINANCE.name);
});
});
+5 -5
查看文件
@@ -333,16 +333,16 @@ test.describe("visual shell", () => {
await page.setViewportSize({ width: 1440, height: 1024 });
await loginVisualUser(page);
await expect(page.getByRole("heading", { name: "今日任务" })).toBeVisible();
await expect(page.getByText("下一步")).toBeVisible();
await expect(page.locator("summary").filter({ hasText: "团队与通知" })).toBeVisible();
await expect(page.locator("summary").filter({ hasText: /角色重点|更多入口/ })).toBeVisible();
await expect(page.getByRole("heading", { name: "今日待处理" })).toBeVisible();
await expect(page.getByText("下一步", { exact: true })).not.toBeVisible();
await expect(page.locator("summary").filter({ hasText: "团队与通知" })).not.toBeVisible();
await expect(page.locator("summary").filter({ hasText: /角色重点|更多入口/ })).not.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.getByRole("button", { name: "高级 / 台账" })).toBeVisible();
await expect(page.getByRole("button", { name: "台账工具" })).toBeVisible();
await expect(page.getByTestId("reimbursements-export")).not.toBeVisible();
await expect(page.getByTestId("reimbursements-status-tabs")).toBeVisible();
await expect(page.getByTestId("reimbursements-table")).toBeVisible();