文件
hyb-oa/client/tests/e2e/user-onboarding.spec.ts
T
wangsli 7b86ffb3c9
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s
优化(07-28-all-account-login-readiness): 调整密码框Tab顺序
2026-08-23 19:10:34 +08:00

437 行
15 KiB
TypeScript

import { expect, test } from "@playwright/test";
function envelope(data: unknown) {
return {
requestId: "mock-request",
data,
};
}
function mockCommonAuth(page: import("@playwright/test").Page, options: {
mustChangePassword?: boolean;
meId?: string;
meName?: string;
meEmployeeNo?: string;
permissions?: string[];
roleCodes?: string[];
}) {
const mustChangePassword = options.mustChangePassword === true;
const meId = options.meId ?? "employee-admin-1";
const meName = options.meName ?? "Admin";
const meEmployeeNo = options.meEmployeeNo ?? "HYD000001";
const permissions = options.permissions ?? ["system.users", "system.accounts.manage", "system.roles.manage"];
const roleCodes = options.roleCodes ?? ["admin"];
return Promise.all([
page.route("**/api/auth/sso/providers", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(envelope([])),
});
}),
page.route("**/api/auth/wecom/config", async (route) => {
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: ["disabled"],
}),
),
});
}),
page.route("**/api/auth/login", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(
envelope({
accessToken: "test-access-token",
expiresIn: 900,
mustChangePassword,
wecomBindingStatus: null,
}),
),
});
}),
page.route("**/api/me", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(
envelope({
id: meId,
employeeNo: meEmployeeNo,
name: meName,
phone: "13800000000",
email: "[email protected]",
departmentId: "dept-1",
directManagerId: null,
status: "active",
notificationPushEnabled: true,
roleCodes,
permissions,
profileCompleteness: {
hasEmergencyContact: true,
hasBankAccount: true,
hasAvatar: false,
hasIdNumber: false,
score: 50,
},
wecomBinding: null,
}),
),
});
}),
page.route("**/api/system/navigation", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(
envelope({
workspace: {
id: "workspace-1",
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: [{ key: "portal", href: "/portal", label: "工作台", icon: "home_app_logo", placement: "main", permissionCodes: [], featureFlagKey: null }],
admin: [{ key: "settings", href: "/system/settings", label: "管理", icon: "settings", placement: "admin", permissionCodes: ["system.users"], featureFlagKey: null }],
backup: [],
},
recommendedRolePresets: [],
}),
),
});
}),
page.route((url) => url.pathname === "/api/notifications", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(
envelope({
page: 1,
pageSize: 10,
total: 0,
unreadOnly: true,
unreadCount: 0,
items: [],
}),
),
});
}),
]);
}
type MockUser = {
id: string;
employeeNo: string;
name: string;
phone: string;
email: string;
status: "active" | "archived";
departmentId: string;
title: string;
roleCodes: string[];
createdAt: string;
updatedAt: string;
};
function buildMockUser(id: string, name: string, roleCodes: string[]): MockUser {
return {
id,
employeeNo: `MOCK-${id}`,
name,
phone: "00000000000",
email: `${id}@example.test`,
status: "active",
departmentId: "dept-1",
title: "员工",
roleCodes,
createdAt: "2026-07-18T00:00:00.000Z",
updatedAt: "2026-07-18T00:00:00.000Z",
};
}
async function mockUserManagementData(
page: import("@playwright/test").Page,
items: MockUser[],
) {
await Promise.all([
page.route((url) => url.pathname === "/api/users", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(envelope({ page: 1, pageSize: 20, total: items.length, items })),
});
}),
page.route((url) => url.pathname === "/api/departments", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(envelope({ includeArchived: false, items: [], tree: [] })),
});
}),
page.route((url) => url.pathname === "/api/employees", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(envelope({ page: 1, pageSize: 200, total: 0, items: [] })),
});
}),
]);
}
async function loginToUserManagement(page: import("@playwright/test").Page) {
await page.goto("/login?redirect=%2Fsystem%2Fusers");
await page.getByTestId("login-identifier").fill("[email protected]");
await page.getByTestId("login-password").fill("SyntheticPassword1234");
await page.getByTestId("login-submit").click();
await expect(page).toHaveURL(/\/system\/users/);
}
test("E2E 用户开通:新建用户时由系统生成工号和一次性口令", async ({ page }) => {
let createPayload: Record<string, unknown> | null = null;
await mockCommonAuth(page, {});
await page.route("**/api/users?**", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(
envelope({
page: 1,
pageSize: 20,
total: 0,
items: [],
}),
),
});
});
await page.route("**/api/departments?includeArchived=0", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(
envelope({
includeArchived: false,
items: [{ id: "dept-1", name: "行政部", parentId: null, archivedAt: null, createdAt: null, updatedAt: null }],
tree: [],
}),
),
});
});
await page.route("**/api/employees?page=1&pageSize=200&status=active", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(
envelope({
page: 1,
pageSize: 200,
total: 0,
items: [],
}),
),
});
});
await page.route("**/api/users", async (route) => {
if (route.request().method() !== "POST") {
await route.fallback();
return;
}
createPayload = route.request().postDataJSON() as Record<string, unknown>;
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(
envelope({
id: "user-1",
employeeNo: "HYD000123",
name: "张三",
phone: "13800000001",
email: "[email protected]",
status: "active",
departmentId: "dept-1",
title: "员工",
roleCodes: ["employee"],
createdAt: "2026-05-04T12:00:00.000Z",
updatedAt: "2026-05-04T12:00:00.000Z",
oneTimePassword: "ResetA1abc123def456",
}),
),
});
});
await page.goto("/login?redirect=%2Fsystem%2Fusers");
await page.getByTestId("login-identifier").fill("[email protected]");
await page.getByTestId("login-password").fill("AdminPassword1234");
await page.getByTestId("login-submit").click();
await expect(page).toHaveURL(/\/system\/users/);
await page.getByTestId("users-create-open").click();
await expect(page.getByTestId("users-create-employeeNo")).toHaveCount(0);
await expect(page.getByTestId("users-create-password")).toHaveCount(0);
await page.getByTestId("users-create-name").fill("张三");
await page.getByTestId("users-create-phone").fill("13800000001");
await page.getByTestId("users-create-email").fill("[email protected]");
await page.getByTestId("users-create-submit").click();
expect(createPayload).toEqual({
name: "张三",
phone: "13800000001",
email: "[email protected]",
departmentId: undefined,
directManagerId: null,
roles: ["employee"],
});
await expect(page.getByTestId("users-reset-result")).toContainText("工号=HYD000123");
await expect(page.getByTestId("users-reset-result")).toContainText("ResetA1abc123def456");
});
test("E2E 账号保护:OA 系统管理员只能操作普通账号", async ({ page }) => {
const currentId = "system-admin-current";
const ordinary = buildMockUser("ordinary-user", "普通员工", ["employee"]);
const protectedAdmin = buildMockUser("protected-admin", "应急账号甲", ["employee", "admin"]);
const protectedSystemAdmin = buildMockUser("protected-system-admin", "运维账号乙", ["employee", "system_admin"]);
const currentSystemAdmin = buildMockUser(currentId, "当前运维账号", ["employee", "system_admin"]);
await mockCommonAuth(page, {
meId: currentId,
meName: "当前运维账号",
roleCodes: ["system_admin"],
permissions: ["system.accounts.manage", "system.roles.manage"],
});
await mockUserManagementData(page, [ordinary, protectedAdmin, protectedSystemAdmin, currentSystemAdmin]);
await loginToUserManagement(page);
const ordinaryRow = page.getByTestId("users-row").filter({ hasText: ordinary.name });
await expect(ordinaryRow.getByTestId("users-toggle")).toHaveCount(1);
await expect(ordinaryRow.getByTestId("users-reset-password")).toHaveCount(1);
await expect(ordinaryRow.getByTestId("users-edit-roles")).toHaveCount(1);
for (const user of [protectedAdmin, protectedSystemAdmin, currentSystemAdmin]) {
const row = page.getByTestId("users-row").filter({ hasText: user.name });
await expect(row.getByTestId("users-toggle")).toHaveCount(0);
await expect(row.getByTestId("users-reset-password")).toHaveCount(0);
await expect(row.getByTestId("users-edit-roles")).toHaveCount(0);
await expect(row.getByTestId("users-protected-account")).toHaveText("受保护账号");
}
});
test("E2E 账号保护:应急超级管理员可操作其他受保护账号但不能停用或改自己的角色", async ({ page }) => {
const currentId = "employee-admin-1";
const otherAdmin = buildMockUser("other-admin", "其他应急账号", ["employee", "admin"]);
const otherSystemAdmin = buildMockUser("other-system-admin", "其他运维账号", ["employee", "system_admin"]);
const currentAdmin = buildMockUser(currentId, "当前应急账号", ["employee", "admin"]);
await mockCommonAuth(page, { meId: currentId, meName: currentAdmin.name });
await mockUserManagementData(page, [otherAdmin, otherSystemAdmin, currentAdmin]);
await loginToUserManagement(page);
for (const user of [otherAdmin, otherSystemAdmin]) {
const row = page.getByTestId("users-row").filter({ hasText: user.name });
await expect(row.getByTestId("users-toggle")).toHaveCount(1);
await expect(row.getByTestId("users-reset-password")).toHaveCount(1);
await expect(row.getByTestId("users-edit-roles")).toHaveCount(1);
await expect(row.getByTestId("users-protected-account")).toHaveCount(0);
}
const currentRow = page.getByTestId("users-row").filter({ hasText: currentAdmin.name });
await expect(currentRow.getByTestId("users-toggle")).toHaveCount(0);
await expect(currentRow.getByTestId("users-edit-roles")).toHaveCount(0);
await expect(currentRow.getByTestId("users-reset-password")).toHaveCount(1);
});
test("E2E 首次登录:一次性口令登录后强制改密", async ({ page }) => {
let changePasswordPayload: Record<string, unknown> | null = null;
await mockCommonAuth(page, { mustChangePassword: true, meName: "新员工", meEmployeeNo: "HYD000123" });
await page.route("**/api/auth/change-password", async (route) => {
changePasswordPayload = route.request().postDataJSON() as Record<string, unknown>;
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(
envelope({
accessToken: "post-change-token",
expiresIn: 900,
mustChangePassword: false,
}),
),
});
});
await page.route("**/api/portal/home", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(
envelope({
workspace: { id: "workspace-1", persisted: true, profile: "small_team_internal" },
greeting: { name: "新员工" },
cards: [],
pendingTasks: { count: 0, href: "/workflow/tasks" },
myApplications: [],
riskSummary: null,
announcements: [],
quickLinks: [],
todayPresence: null,
teamPresence: null,
weeklyReport: null,
weeklySummary: null,
todoSummary: { openCount: 0, highPriorityCount: 0, href: "/todos" },
projectSummary: { myCount: 0, overdueMilestonesCount: 0, highPriorityTaskCount: 0, openRiskCount: 0, followUpDueTodayCount: 0, closurePendingProjectCount: 0, href: "/projects" },
peopleLifecycle: null,
adoptionSummary: null,
recentActivities: [],
teamStatusBoard: [],
}),
),
});
});
await page.goto("/login");
await page.getByTestId("login-identifier").fill("[email protected]");
await page.getByTestId("login-password").fill("ResetA1abc123def456");
await page.getByTestId("login-submit").click();
await expect(page).toHaveURL(/\/change-password/);
await page.getByTestId("change-password-old").fill("ResetA1abc123def456");
await page.getByTestId("change-password-new").fill("NewPassword1234");
await page.getByLabel("确认新密码").fill("NewPassword1234");
await page.getByTestId("change-password-old").focus();
await page.keyboard.press("Tab");
await expect(page.getByTestId("change-password-new")).toBeFocused();
await page.keyboard.press("Tab");
await expect(page.getByLabel("确认新密码")).toBeFocused();
await page.keyboard.press("Tab");
await expect(page.getByTestId("change-password-submit")).toBeFocused();
await page.getByTestId("change-password-submit").click();
expect(changePasswordPayload).toEqual({
oldPassword: "ResetA1abc123def456",
newPassword: "NewPassword1234",
});
await expect(page).toHaveURL(/\/portal/);
});