收敛企微主办 HR 考勤页面(05-30-wecom-hr-boundary-pages)
这个提交包含在:
+305
-187
@@ -1,206 +1,324 @@
|
||||
import { test, expect } from "./helpers/fixtures";
|
||||
import { EMPLOYEE, MANAGER, apiPostData, createAuthenticatedApiClient, login, logout } from "./helpers/auth";
|
||||
import type { Page, Route } from "@playwright/test";
|
||||
|
||||
import { ADMIN, EMPLOYEE, login, logout, type E2EUser } from "./helpers/auth";
|
||||
import { openNavLink } from "./helpers/navigation";
|
||||
import { TEST_IDS } from "./helpers/testIds";
|
||||
import type { Locator, Page } from "@playwright/test";
|
||||
|
||||
function formatDate(offsetDays: number) {
|
||||
const value = new Date();
|
||||
value.setUTCDate(value.getUTCDate() + offsetDays);
|
||||
return value.toISOString().slice(0, 10);
|
||||
test.use({ serviceWorkers: "block" });
|
||||
|
||||
function envelope(data: unknown) {
|
||||
return {
|
||||
requestId: "mock-leave-boundary",
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function uniqueFutureOffset(minOffsetDays: number) {
|
||||
return minOffsetDays + (Math.floor(Date.now() / 1000) % 2000);
|
||||
async function fulfillJson(route: Route, data: unknown, status = 200, headers: Record<string, string> = {}) {
|
||||
await route.fulfill({
|
||||
status,
|
||||
contentType: "application/json",
|
||||
headers,
|
||||
body: JSON.stringify(envelope(data)),
|
||||
});
|
||||
}
|
||||
|
||||
function buildMe(user: E2EUser) {
|
||||
const isAdmin = user.identifier === ADMIN.identifier;
|
||||
return {
|
||||
id: isAdmin ? "employee-admin" : "employee-user",
|
||||
employeeNo: isAdmin ? "A001" : "E001",
|
||||
name: user.name,
|
||||
phone: "13800000000",
|
||||
email: user.identifier,
|
||||
departmentId: "department-leave",
|
||||
directManagerId: null,
|
||||
status: "active",
|
||||
notificationPushEnabled: true,
|
||||
roleCodes: isAdmin ? ["admin"] : ["employee"],
|
||||
profileCompleteness: {
|
||||
hasEmergencyContact: true,
|
||||
hasBankAccount: true,
|
||||
hasAvatar: false,
|
||||
hasIdNumber: false,
|
||||
score: 50,
|
||||
},
|
||||
wecomBinding: null,
|
||||
};
|
||||
}
|
||||
|
||||
async function selectFirstEnabledOption(locator: Locator) {
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const optionValues = await locator.locator("option").evaluateAll((options) =>
|
||||
options
|
||||
.map((option) => ({
|
||||
value: (option as HTMLOptionElement).value,
|
||||
disabled: (option as HTMLOptionElement).disabled,
|
||||
}))
|
||||
.filter((option) => option.value && !option.disabled)
|
||||
.map((option) => option.value),
|
||||
function leaveRequestFor(user: E2EUser) {
|
||||
const me = buildMe(user);
|
||||
return {
|
||||
id: "leave-boundary-1",
|
||||
ownerId: me.id,
|
||||
departmentId: me.departmentId,
|
||||
status: "draft",
|
||||
startDate: "2026-05-31",
|
||||
endDate: "2026-05-31",
|
||||
startSession: "am",
|
||||
endSession: "pm",
|
||||
requestedDays: 1,
|
||||
reason: "企业微信同步记录",
|
||||
attachment: null,
|
||||
leaveType: {
|
||||
id: "leave-type-annual",
|
||||
code: "annual",
|
||||
name: "年假",
|
||||
requiresAttachment: false,
|
||||
attachmentMimeTypes: [],
|
||||
},
|
||||
owner: {
|
||||
id: me.id,
|
||||
employeeNo: me.employeeNo,
|
||||
name: me.name,
|
||||
},
|
||||
pendingWorkflow: null,
|
||||
approvalHistory: [],
|
||||
createdAt: "2026-05-30T09:00:00.000Z",
|
||||
updatedAt: "2026-05-30T09:30:00.000Z",
|
||||
};
|
||||
}
|
||||
|
||||
async function mockLeaveBoundaryApis(page: Page) {
|
||||
let activeUser = EMPLOYEE;
|
||||
|
||||
await page.route((url) => url.pathname.startsWith("/api/"), async (route) => {
|
||||
const request = route.request();
|
||||
const url = new URL(request.url());
|
||||
const path = url.pathname;
|
||||
|
||||
if (path === "/api/auth/capabilities") {
|
||||
await fulfillJson(route, {
|
||||
emailDelivery: { ready: true, statusReasons: [] },
|
||||
emailLoginCode: { enabled: true },
|
||||
passwordReset: { enabled: true },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/auth/sso/providers") {
|
||||
await fulfillJson(route, []);
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/auth/wecom/config") {
|
||||
await fulfillJson(route, {
|
||||
enabled: true,
|
||||
entryEnabled: true,
|
||||
oauthEnabled: true,
|
||||
corpId: "ww-leave-boundary",
|
||||
agentId: "1000002",
|
||||
loginUrl: "/api/auth/wecom/login?returnTo=%2Fportal",
|
||||
bindEnabled: true,
|
||||
defaultReturnTo: "/portal",
|
||||
status: "ready",
|
||||
statusReasons: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/auth/login") {
|
||||
const payload = request.postDataJSON() as { identifier?: string };
|
||||
activeUser = payload.identifier === ADMIN.identifier ? ADMIN : EMPLOYEE;
|
||||
await fulfillJson(
|
||||
route,
|
||||
{ accessToken: `mock-token-${activeUser.name}`, expiresIn: 900, mustChangePassword: false },
|
||||
200,
|
||||
{ "set-cookie": "has_session=1; Path=/; SameSite=Strict" },
|
||||
);
|
||||
return optionValues[0] ?? "";
|
||||
})
|
||||
.not.toBe("");
|
||||
return;
|
||||
}
|
||||
|
||||
const [firstValue] = await locator.locator("option").evaluateAll((options) =>
|
||||
options
|
||||
.map((option) => ({
|
||||
value: (option as HTMLOptionElement).value,
|
||||
disabled: (option as HTMLOptionElement).disabled,
|
||||
}))
|
||||
.filter((option) => option.value && !option.disabled)
|
||||
.map((option) => option.value),
|
||||
);
|
||||
if (path === "/api/auth/refresh") {
|
||||
await fulfillJson(route, { accessToken: `mock-token-${activeUser.name}`, expiresIn: 900 });
|
||||
return;
|
||||
}
|
||||
|
||||
await locator.selectOption(firstValue);
|
||||
if (path === "/api/auth/logout") {
|
||||
await fulfillJson(route, {}, 200, { "set-cookie": "has_session=; Path=/; Max-Age=0" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/me") {
|
||||
await fulfillJson(route, buildMe(activeUser));
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/system/navigation") {
|
||||
await fulfillJson(route, {
|
||||
workspace: {
|
||||
id: "workspace-leave-boundary",
|
||||
persisted: true,
|
||||
profile: "small_team_internal",
|
||||
orgMode: "single_company",
|
||||
primaryEntryMode: "wecom",
|
||||
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 },
|
||||
{ key: "expense", href: "/reimbursements", label: "报销", icon: "request_quote", placement: "main", permissionCodes: [], featureFlagKey: null },
|
||||
{ key: "leave", href: "/leave-requests", label: "请假记录", icon: "event_available", placement: "main", permissionCodes: [], featureFlagKey: null },
|
||||
],
|
||||
admin: [],
|
||||
backup: [],
|
||||
},
|
||||
recommendedRolePresets: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/portal/home") {
|
||||
await fulfillJson(route, {
|
||||
workspace: { id: "workspace-leave-boundary", persisted: true, profile: "small_team_internal" },
|
||||
greeting: { name: activeUser.name },
|
||||
cards: [],
|
||||
pendingTasks: { count: 0, href: "/workflow/tasks" },
|
||||
myApplications: [{ domain: "leave", count: 1, href: "/leave-requests" }],
|
||||
riskSummary: null,
|
||||
announcements: [],
|
||||
quickLinks: [],
|
||||
todayPresence: { date: "2026-05-30", status: "office", label: "在岗", note: null, href: "/team" },
|
||||
teamPresence: null,
|
||||
weeklyReport: null,
|
||||
weeklySummary: null,
|
||||
todoSummary: null,
|
||||
projectSummary: null,
|
||||
peopleLifecycle: null,
|
||||
adoptionSummary: null,
|
||||
recentActivities: [],
|
||||
teamStatusBoard: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/attendance/me/today") {
|
||||
await fulfillJson(route, {
|
||||
workDate: "2026-05-30",
|
||||
needClockIn: false,
|
||||
exemptionReason: "wecom_owned",
|
||||
item: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/notifications") {
|
||||
await fulfillJson(route, {
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
unreadOnly: false,
|
||||
unreadCount: 0,
|
||||
items: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/leave-types") {
|
||||
await fulfillJson(route, {
|
||||
items: [
|
||||
{
|
||||
id: "leave-type-annual",
|
||||
code: "annual",
|
||||
name: "年假",
|
||||
description: null,
|
||||
enabled: true,
|
||||
requiresAttachment: false,
|
||||
attachmentMimeTypes: [],
|
||||
sortOrder: 1,
|
||||
policy: {
|
||||
id: "leave-policy-annual",
|
||||
annualQuotaDays: 15,
|
||||
enabled: true,
|
||||
description: null,
|
||||
},
|
||||
createdAt: "2026-05-30T00:00:00.000Z",
|
||||
updatedAt: "2026-05-30T00:00:00.000Z",
|
||||
},
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/leave-balances") {
|
||||
await fulfillJson(route, {
|
||||
employeeId: buildMe(activeUser).id,
|
||||
items: [
|
||||
{
|
||||
leaveType: {
|
||||
id: "leave-type-annual",
|
||||
code: "annual",
|
||||
name: "年假",
|
||||
description: null,
|
||||
enabled: true,
|
||||
requiresAttachment: false,
|
||||
attachmentMimeTypes: [],
|
||||
sortOrder: 1,
|
||||
policy: null,
|
||||
createdAt: null,
|
||||
updatedAt: null,
|
||||
},
|
||||
policy: {
|
||||
id: "leave-policy-annual",
|
||||
annualQuotaDays: 15,
|
||||
enabled: true,
|
||||
description: null,
|
||||
},
|
||||
granted: 15,
|
||||
adjusted: 0,
|
||||
reserved: 1,
|
||||
used: 2,
|
||||
available: 12,
|
||||
periodStart: "2026-01-01",
|
||||
periodEnd: "2026-12-31",
|
||||
},
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "/api/leave-requests") {
|
||||
await fulfillJson(route, {
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 1,
|
||||
scope: "own",
|
||||
items: [leaveRequestFor(activeUser)],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await fulfillJson(route, {});
|
||||
});
|
||||
}
|
||||
|
||||
async function createLeaveDraft(page: Page, startDate: string, endDate: string, reason: string) {
|
||||
const createResponsePromise = page.waitForResponse(
|
||||
(response) => response.request().method() === "POST" && new URL(response.url()).pathname === "/api/leave-requests" && response.ok(),
|
||||
);
|
||||
test("E2E 请假边界:员工只看企微主办记录,不显示 OA 日常提交入口", async ({ page }) => {
|
||||
await mockLeaveBoundaryApis(page);
|
||||
await login(page, EMPLOYEE);
|
||||
await (await openNavLink(page, "请假记录")).click();
|
||||
|
||||
await selectFirstEnabledOption(page.locator("#leave-type"));
|
||||
await page.locator("#leave-start-date").fill(startDate);
|
||||
await page.locator("#leave-end-date").fill(endDate);
|
||||
await page.locator("#leave-start-session").selectOption("am");
|
||||
await page.locator("#leave-end-session").selectOption("pm");
|
||||
await page.locator("#leave-reason").fill(reason);
|
||||
await page.getByRole("button", { name: "创建草稿" }).click();
|
||||
await expect(page.locator("h1", { hasText: "请假记录" })).toBeVisible();
|
||||
await expect(page.getByText("企业微信主办请假、补卡、外出和加班")).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "日常请假入口" })).toBeVisible();
|
||||
await expect(page.locator("#leave-type")).toHaveCount(0);
|
||||
await expect(page.getByRole("button", { name: "创建应急草稿" })).toHaveCount(0);
|
||||
await expect(page.getByRole("button", { name: "提交应急审批" })).toHaveCount(0);
|
||||
|
||||
const createPayload = (await (await createResponsePromise).json()) as { data?: { id?: string } };
|
||||
const leaveRequestId = createPayload.data?.id;
|
||||
if (!leaveRequestId) {
|
||||
throw new Error(`创建请假草稿后未返回 id: ${JSON.stringify(createPayload)}`);
|
||||
}
|
||||
return leaveRequestId;
|
||||
}
|
||||
|
||||
async function submitLeave(api: Awaited<ReturnType<typeof createAuthenticatedApiClient>>["api"], leaveRequestId: string) {
|
||||
return apiPostData<{ leaveRequestId: string; currentTaskId: string }>(api, `/api/leave-requests/${leaveRequestId}/submit`, {});
|
||||
}
|
||||
|
||||
async function withdrawLeave(api: Awaited<ReturnType<typeof createAuthenticatedApiClient>>["api"], leaveRequestId: string) {
|
||||
return apiPostData<{ leaveRequestId: string; leaveStatus: string }>(api, `/api/leave-requests/${leaveRequestId}/withdraw`, {});
|
||||
}
|
||||
|
||||
test("E2E 请假编辑:草稿保存后保留原单并可继续提交", async ({ page }) => {
|
||||
const employeeClient = await createAuthenticatedApiClient(EMPLOYEE);
|
||||
const baseOffset = uniqueFutureOffset(420);
|
||||
const startDate = formatDate(baseOffset);
|
||||
const updatedEndDate = formatDate(baseOffset + 1);
|
||||
|
||||
try {
|
||||
await login(page, EMPLOYEE);
|
||||
await (await openNavLink(page, "请假记录")).click();
|
||||
await expect(page.getByRole("heading", { name: "请假申请" })).toBeVisible();
|
||||
|
||||
const leaveId = await createLeaveDraft(page, startDate, startDate, `Playwright 编辑请假 ${Date.now()}`);
|
||||
await page.goto("/leave-requests");
|
||||
|
||||
const draftRow = page.getByRole("row").filter({ hasText: startDate }).first();
|
||||
await expect(draftRow).toContainText("草稿", { timeout: 30_000 });
|
||||
await expect(draftRow).toContainText("草稿可继续编辑", { timeout: 30_000 });
|
||||
|
||||
const updateResponsePromise = page.waitForResponse((response) => {
|
||||
return response.request().method() === "PATCH" && new URL(response.url()).pathname === `/api/leave-requests/${leaveId}` && response.ok();
|
||||
});
|
||||
|
||||
await draftRow.getByRole("button", { name: "编辑" }).click();
|
||||
await expect(page.getByText("编辑请假单")).toBeVisible();
|
||||
await page.locator("#leave-end-date").fill(updatedEndDate);
|
||||
await page.locator("#leave-reason").fill(`更新后的请假说明 ${Date.now()}`);
|
||||
await page.getByRole("button", { name: "保存修改" }).click();
|
||||
|
||||
const updatePayload = (await (await updateResponsePromise).json()) as { data?: { id?: string } };
|
||||
expect(updatePayload.data?.id).toBe(leaveId);
|
||||
|
||||
await page.goto("/leave-requests");
|
||||
const updatedRow = page.getByRole("row").filter({ hasText: updatedEndDate }).first();
|
||||
await expect(updatedRow).toContainText("草稿", { timeout: 30_000 });
|
||||
|
||||
await submitLeave(employeeClient.api, leaveId);
|
||||
await page.goto("/leave-requests");
|
||||
await expect(updatedRow).toContainText("审批中", { timeout: 30_000 });
|
||||
await expect(updatedRow).toContainText("当前节点", { timeout: 30_000 });
|
||||
} finally {
|
||||
await employeeClient.dispose();
|
||||
}
|
||||
await logout(page, EMPLOYEE.name);
|
||||
});
|
||||
|
||||
test("E2E 请假主链路:草稿 → 提交 → 撤回 + 提交 → 审批", async ({ page, workflowDetail }) => {
|
||||
const employeeClient = await createAuthenticatedApiClient(EMPLOYEE);
|
||||
const baseOffset = uniqueFutureOffset(2600);
|
||||
const firstDate = formatDate(baseOffset);
|
||||
const secondDate = formatDate(baseOffset + 7);
|
||||
test("E2E 请假边界:管理员保留 OA 应急草稿入口", async ({ page }) => {
|
||||
await mockLeaveBoundaryApis(page);
|
||||
await login(page, ADMIN);
|
||||
await page.goto("/leave-requests");
|
||||
|
||||
try {
|
||||
await login(page, EMPLOYEE);
|
||||
await (await openNavLink(page, "请假记录")).click();
|
||||
await expect(page.getByRole("heading", { name: "请假申请" })).toBeVisible();
|
||||
|
||||
const firstLeaveId = await createLeaveDraft(page, firstDate, firstDate, `Playwright 撤回请假 ${Date.now()}`);
|
||||
await submitLeave(employeeClient.api, firstLeaveId);
|
||||
await page.goto("/leave-requests");
|
||||
const firstRow = page.getByRole("row").filter({ hasText: firstDate }).first();
|
||||
await expect(firstRow).toContainText("审批中", { timeout: 30_000 });
|
||||
|
||||
await withdrawLeave(employeeClient.api, firstLeaveId);
|
||||
await page.goto("/leave-requests");
|
||||
await expect(firstRow).toContainText("草稿", { timeout: 30_000 });
|
||||
|
||||
const secondLeaveId = await createLeaveDraft(page, secondDate, secondDate, `Playwright 审批请假 ${Date.now()}`);
|
||||
const secondSubmit = await submitLeave(employeeClient.api, secondLeaveId);
|
||||
await page.goto("/leave-requests");
|
||||
const secondRow = page.getByRole("row").filter({ hasText: secondDate }).first();
|
||||
await expect(secondRow).toContainText("审批中", { timeout: 30_000 });
|
||||
|
||||
await logout(page, EMPLOYEE.name);
|
||||
|
||||
await login(page, MANAGER);
|
||||
await (await openNavLink(page, "待我处理")).click();
|
||||
await expect(page.getByTestId(TEST_IDS.workflow.tasksTable)).toBeVisible();
|
||||
await expect(page.locator(`a[href="/workflow/tasks/${secondSubmit.currentTaskId}"]`).first()).toHaveCount(1, { timeout: 30_000 });
|
||||
await page.goto(`/workflow/tasks/${secondSubmit.currentTaskId}`);
|
||||
await workflowDetail.assertLoaded();
|
||||
await workflowDetail.approve();
|
||||
await expect(page.getByRole("main")).toContainText("已完成", { timeout: 30_000 });
|
||||
await expect(page.getByRole("main")).toContainText("业务状态:已通过", { timeout: 30_000 });
|
||||
|
||||
await logout(page, MANAGER.name);
|
||||
|
||||
await login(page, EMPLOYEE);
|
||||
await page.goto("/leave-requests");
|
||||
const approvedRow = page.getByRole("row").filter({ hasText: secondDate }).first();
|
||||
await expect(approvedRow).toContainText("已通过", { timeout: 30_000 });
|
||||
await expect(approvedRow).toContainText("已通过,系统会按请假结果更新额度和团队状态", { timeout: 30_000 });
|
||||
await expect(page.getByRole("row").filter({ hasText: firstDate }).first()).toContainText("草稿", { timeout: 30_000 });
|
||||
} finally {
|
||||
await employeeClient.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
test("E2E 请假退回说明:审批退回后员工可看到处理意见", async ({ page, workflowDetail }) => {
|
||||
const employeeClient = await createAuthenticatedApiClient(EMPLOYEE);
|
||||
const baseOffset = uniqueFutureOffset(5200);
|
||||
const leaveDate = formatDate(baseOffset);
|
||||
const returnComment = `请补充外出交接安排 ${Date.now()}`;
|
||||
|
||||
try {
|
||||
await login(page, EMPLOYEE);
|
||||
await (await openNavLink(page, "请假记录")).click();
|
||||
await expect(page.getByRole("heading", { name: "请假申请" })).toBeVisible();
|
||||
|
||||
const leaveId = await createLeaveDraft(page, leaveDate, leaveDate, `Playwright 退回请假 ${Date.now()}`);
|
||||
const submitResult = await submitLeave(employeeClient.api, leaveId);
|
||||
|
||||
await logout(page, EMPLOYEE.name);
|
||||
|
||||
await login(page, MANAGER);
|
||||
await page.goto(`/workflow/tasks/${submitResult.currentTaskId}`);
|
||||
await workflowDetail.assertLoaded();
|
||||
await workflowDetail.returnTask(returnComment);
|
||||
await expect(page.getByRole("main")).toContainText("已完成", { timeout: 30_000 });
|
||||
await expect(page.getByRole("main")).toContainText("业务状态:已终止", { timeout: 30_000 });
|
||||
|
||||
await logout(page, MANAGER.name);
|
||||
|
||||
await login(page, EMPLOYEE);
|
||||
await page.goto("/leave-requests");
|
||||
const returnedRow = page.getByRole("row").filter({ hasText: leaveDate }).first();
|
||||
await expect(returnedRow).toContainText("草稿", { timeout: 30_000 });
|
||||
await expect(returnedRow).toContainText("审批已退回", { timeout: 30_000 });
|
||||
await expect(returnedRow).toContainText(returnComment, { timeout: 30_000 });
|
||||
} finally {
|
||||
await employeeClient.dispose();
|
||||
}
|
||||
await expect(page.locator("h1", { hasText: "请假记录" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "OA 应急请假草稿" })).toBeVisible();
|
||||
await expect(page.locator("#leave-type")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "创建应急草稿" })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -206,7 +206,7 @@ test("E2E 请假团队日历:HR 可见全员周/月视图并显示截断提示
|
||||
await login(page, LEAVE_CALENDAR_HR);
|
||||
await page.goto("/leave-requests");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "请假申请" })).toBeVisible();
|
||||
await expect(page.locator("h1", { hasText: "请假记录" })).toBeVisible();
|
||||
await expect(page.getByTestId("leave-view-calendar")).toBeVisible();
|
||||
|
||||
const weekResponse = page.waitForResponse((response) => {
|
||||
@@ -244,7 +244,7 @@ test("E2E 请假团队日历:经理只拉本部门数据且导航会触发新
|
||||
await login(page, MANAGER);
|
||||
await page.goto("/leave-requests");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "请假申请" })).toBeVisible();
|
||||
await expect(page.locator("h1", { hasText: "请假记录" })).toBeVisible();
|
||||
await expect(page.getByTestId("leave-view-calendar")).toBeVisible();
|
||||
|
||||
const firstResponse = page.waitForResponse((response) => {
|
||||
@@ -319,7 +319,10 @@ test("E2E 请假团队日历:员工视角不显示团队日历 Tab", async ({
|
||||
await login(page, EMPLOYEE);
|
||||
await page.goto("/leave-requests");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "请假申请" })).toBeVisible();
|
||||
await expect(page.locator("h1", { hasText: "请假记录" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "日常请假入口" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "OA 应急请假草稿" })).toHaveCount(0);
|
||||
await expect(page.getByRole("button", { name: "创建应急草稿" })).toHaveCount(0);
|
||||
await expect(page.getByTestId("leave-view-calendar")).toHaveCount(0);
|
||||
await expect(page.getByTestId("leave-page-view-tabs")).toHaveCount(0);
|
||||
});
|
||||
|
||||
@@ -397,20 +397,18 @@ test.describe("R101 mobile WIP closeout", () => {
|
||||
await attachScreenshot(page, testInfo, "r101-mobile-reimbursements.png");
|
||||
});
|
||||
|
||||
test("移动请假页使用表单和卡片记录", async ({ page }, testInfo) => {
|
||||
test("移动请假页默认只看记录并提示企微主办", async ({ page }, testInfo) => {
|
||||
await page.goto("/mobile/leave");
|
||||
await expect(page.getByRole("heading", { name: "请假", exact: true })).toBeVisible();
|
||||
await expect(page.locator("h1", { hasText: "请假记录" })).toBeVisible();
|
||||
await expect(page.getByText("企业微信主办")).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-leave-records")).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-leave-record-card")).toBeVisible();
|
||||
|
||||
await page.getByTestId("mobile-leave-view-form").click();
|
||||
await expect(page.getByTestId("mobile-leave-summary")).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-leave-form")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "选择附件" })).toBeVisible();
|
||||
await page.getByTestId("mobile-leave-view-records").click();
|
||||
await expect(page.getByTestId("mobile-leave-view-form")).toHaveCount(0);
|
||||
await expect(page.getByTestId("mobile-leave-form")).toHaveCount(0);
|
||||
await expect(page.getByTestId("mobile-leave-record-card")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "编辑" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "提交" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "编辑" })).toHaveCount(0);
|
||||
await expect(page.getByRole("button", { name: "提交应急审批" })).toHaveCount(0);
|
||||
await assertNoMobileDesignRegression(page);
|
||||
await attachScreenshot(page, testInfo, "r101-mobile-leave.png");
|
||||
});
|
||||
|
||||
@@ -223,17 +223,22 @@ test.describe("R75 自动化补盲", () => {
|
||||
await login(page, EMPLOYEE);
|
||||
await page.goto("/attendance");
|
||||
await expect(page.getByRole("heading", { name: "考勤" })).toBeVisible();
|
||||
await expect(page.getByText("企业微信采集打卡")).toBeVisible();
|
||||
await expect(page.getByText("企业微信主办打卡")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: /点击打上班卡|点击下班|重新打下班卡/ })).toHaveCount(0);
|
||||
await expect(page.getByText("本月打卡记录")).toHaveCount(0);
|
||||
await page.getByRole("tab", { name: "修正申请" }).click();
|
||||
await expect(page.getByRole("heading", { name: "企微处理入口" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "提交应急修正" })).toHaveCount(0);
|
||||
|
||||
await logout(page, EMPLOYEE.name);
|
||||
|
||||
await login(page, ADMIN);
|
||||
await page.goto("/attendance");
|
||||
await expect(page.getByRole("heading", { name: "考勤" })).toBeVisible();
|
||||
await expect(page.getByText("OA 统一处理请假、补卡、外出、加班和月度解释")).toBeVisible();
|
||||
await expect(page.getByText("OA 展示月度摘要、异常解释、同步状态和审计记录")).toBeVisible();
|
||||
await expect(page.getByRole("tab", { name: "修正申请" })).toBeVisible();
|
||||
await page.getByRole("tab", { name: "修正申请" }).click();
|
||||
await expect(page.getByRole("heading", { name: "OA 应急修正" })).toBeVisible();
|
||||
await expect(page.getByRole("tab", { name: "团队考勤" })).toHaveCount(0);
|
||||
await expect(page.getByRole("button", { name: "保存补卡" })).toHaveCount(0);
|
||||
} finally {
|
||||
|
||||
在新工单中引用
屏蔽一个用户