R32-1 R32-4 R33-2: 收口工作台交互与报销链路
这个提交包含在:
@@ -0,0 +1,105 @@
|
||||
import type { Locator, Page } from "@playwright/test";
|
||||
|
||||
import { test, expect } from "./helpers/fixtures";
|
||||
import { EMPLOYEE, MANAGER, apiPostData, createAuthenticatedApiClient, login, logout } from "./helpers/auth";
|
||||
import { openNavLink } from "./helpers/navigation";
|
||||
import { TEST_IDS } from "./helpers/testIds";
|
||||
|
||||
function formatDate(offsetDays: number) {
|
||||
const value = new Date();
|
||||
value.setUTCDate(value.getUTCDate() + offsetDays);
|
||||
return value.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
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),
|
||||
);
|
||||
return optionValues[0] ?? "";
|
||||
})
|
||||
.not.toBe("");
|
||||
|
||||
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),
|
||||
);
|
||||
|
||||
await locator.selectOption(firstValue);
|
||||
}
|
||||
|
||||
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(),
|
||||
);
|
||||
|
||||
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();
|
||||
|
||||
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`, {});
|
||||
}
|
||||
|
||||
test("E2E 审批列表快捷通过:经理可在待办列表行内直接通过请假申请", async ({ page }) => {
|
||||
const employeeClient = await createAuthenticatedApiClient(EMPLOYEE);
|
||||
const startDate = formatDate(45 + Number(String(Date.now()).slice(-2)));
|
||||
const reason = `Playwright 快捷通过 ${Date.now()}`;
|
||||
|
||||
try {
|
||||
await login(page, EMPLOYEE);
|
||||
await (await openNavLink(page, "请假")).click();
|
||||
await expect(page.getByRole("heading", { name: "请假申请" })).toBeVisible();
|
||||
|
||||
const leaveRequestId = await createLeaveDraft(page, startDate, startDate, reason);
|
||||
const submitResult = await submitLeave(employeeClient.api, leaveRequestId);
|
||||
|
||||
await logout(page, EMPLOYEE.name);
|
||||
|
||||
await login(page, MANAGER);
|
||||
await (await openNavLink(page, "待我处理")).click();
|
||||
await expect(page.getByTestId(TEST_IDS.workflow.tasksTable)).toBeVisible();
|
||||
|
||||
const taskRows = page.getByTestId(TEST_IDS.workflow.taskRow).filter({ hasText: submitResult.currentTaskId });
|
||||
await expect(taskRows).toHaveCount(1, { timeout: 30_000 });
|
||||
const taskRow = taskRows.first();
|
||||
await expect(taskRow.getByTestId(TEST_IDS.workflow.quickApprove)).toBeVisible();
|
||||
await expect(taskRow.getByTestId(TEST_IDS.workflow.detailLink)).toBeVisible();
|
||||
|
||||
await taskRow.getByTestId(TEST_IDS.workflow.quickApprove).click();
|
||||
await expect(taskRows).toHaveCount(0, { timeout: 30_000 });
|
||||
|
||||
await logout(page, MANAGER.name);
|
||||
|
||||
await login(page, EMPLOYEE);
|
||||
await page.goto("/leave-requests");
|
||||
await expect(page.getByRole("row").filter({ hasText: startDate }).first()).toContainText("已通过", { timeout: 30_000 });
|
||||
} finally {
|
||||
await employeeClient.dispose();
|
||||
}
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户