R31-1 R31-4: 收口详情页工作台化与 E2E 契约
这个提交包含在:
@@ -0,0 +1,45 @@
|
||||
import { expect, type Page } from "@playwright/test";
|
||||
|
||||
import { openBackupModulesIfNeeded, openNavLink, openProfileSettings } from "../navigation";
|
||||
import { TEST_IDS } from "../testIds";
|
||||
|
||||
export class AppShell {
|
||||
readonly page: Page;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
get sidebar() {
|
||||
return this.page.locator("nav").first();
|
||||
}
|
||||
|
||||
get topbarProfileTrigger() {
|
||||
return this.page.getByTestId(TEST_IDS.topbar.profileTrigger).first();
|
||||
}
|
||||
|
||||
async navigate(name: string | RegExp) {
|
||||
return openNavLink(this.page, name);
|
||||
}
|
||||
|
||||
async openBackupModules() {
|
||||
return openBackupModulesIfNeeded(this.page);
|
||||
}
|
||||
|
||||
async openProfile(userDisplayName?: string) {
|
||||
return openProfileSettings(this.page, userDisplayName);
|
||||
}
|
||||
|
||||
async assertMainNavVisible() {
|
||||
await expect(this.page.getByRole("link", { name: "工作台" }).or(this.page.getByRole("link", { name: "Portal" })).first()).toBeVisible();
|
||||
await expect(this.page.getByRole("link", { name: /项目/ }).first()).toBeVisible();
|
||||
}
|
||||
|
||||
async assertBackupModulesCollapsed() {
|
||||
const summary = this.page.locator("summary").filter({ hasText: "备用模块" }).first();
|
||||
if (await summary.count()) {
|
||||
const details = summary.locator("xpath=ancestor::details[1]");
|
||||
await expect(details).not.toHaveAttribute("open", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { expect, type Page } from "@playwright/test";
|
||||
|
||||
import { TEST_IDS } from "../testIds";
|
||||
|
||||
export class ReimbursementDetailPageObject {
|
||||
readonly page: Page;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
get overview() {
|
||||
return this.page.getByTestId(TEST_IDS.reimbursement.overview);
|
||||
}
|
||||
|
||||
get summary() {
|
||||
return this.page.getByTestId(TEST_IDS.reimbursement.summary);
|
||||
}
|
||||
|
||||
get itemsTable() {
|
||||
return this.page.getByTestId(TEST_IDS.reimbursement.items);
|
||||
}
|
||||
|
||||
get addItemBtn() {
|
||||
return this.page.getByTestId(TEST_IDS.reimbursement.itemAdd);
|
||||
}
|
||||
|
||||
get budgetHint() {
|
||||
return this.page.getByTestId(TEST_IDS.reimbursement.budgetHint);
|
||||
}
|
||||
|
||||
get erpExport() {
|
||||
return this.page.getByTestId(TEST_IDS.reimbursement.erpExport);
|
||||
}
|
||||
|
||||
get submitBtn() {
|
||||
return this.page.getByTestId(TEST_IDS.reimbursement.submit);
|
||||
}
|
||||
|
||||
get withdrawBtn() {
|
||||
return this.page.getByTestId(TEST_IDS.reimbursement.withdraw);
|
||||
}
|
||||
|
||||
get payBtn() {
|
||||
return this.page.getByTestId(TEST_IDS.reimbursement.pay);
|
||||
}
|
||||
|
||||
async assertLoaded() {
|
||||
await expect(this.overview).toBeVisible({ timeout: 15_000 });
|
||||
await expect(this.summary).toBeVisible({ timeout: 15_000 });
|
||||
}
|
||||
|
||||
async assertStatus(statusText: string) {
|
||||
await expect(this.summary).toContainText(statusText);
|
||||
}
|
||||
|
||||
// 当前 seed 保证 precheck 不触发 warn/manual_review;若 seed 变更,此处需相应调整。
|
||||
async submitReimbursement(acceptConfirm = true) {
|
||||
const dialogPromise = acceptConfirm
|
||||
? this.page.waitForEvent("dialog", { timeout: 2_000 }).then((dialog) => dialog.accept()).catch(() => undefined)
|
||||
: Promise.resolve();
|
||||
|
||||
await this.submitBtn.click();
|
||||
await dialogPromise;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { expect, type Page } from "@playwright/test";
|
||||
|
||||
import { TEST_IDS } from "../testIds";
|
||||
|
||||
export class WorkflowTaskDetailPageObject {
|
||||
readonly page: Page;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
get overview() {
|
||||
return this.page.getByTestId(TEST_IDS.workflow.overview);
|
||||
}
|
||||
|
||||
get summary() {
|
||||
return this.page.getByTestId(TEST_IDS.workflow.summary);
|
||||
}
|
||||
|
||||
get approveBtn() {
|
||||
return this.page.getByTestId(TEST_IDS.workflow.approve);
|
||||
}
|
||||
|
||||
get rejectCommentInput() {
|
||||
return this.page.getByTestId(TEST_IDS.workflow.rejectComment);
|
||||
}
|
||||
|
||||
get rejectBtn() {
|
||||
return this.page.getByTestId(TEST_IDS.workflow.reject);
|
||||
}
|
||||
|
||||
get returnCommentInput() {
|
||||
return this.page.getByTestId(TEST_IDS.workflow.returnComment);
|
||||
}
|
||||
|
||||
get returnBtn() {
|
||||
return this.page.getByTestId(TEST_IDS.workflow.returnBtn);
|
||||
}
|
||||
|
||||
get actionError() {
|
||||
return this.page.getByTestId(TEST_IDS.workflow.actionError);
|
||||
}
|
||||
|
||||
get transferApproverSelect() {
|
||||
return this.page.getByTestId(TEST_IDS.workflow.transferApprover);
|
||||
}
|
||||
|
||||
async assertLoaded() {
|
||||
await expect(this.overview).toBeVisible({ timeout: 15_000 });
|
||||
await expect(this.summary).toBeVisible({ timeout: 15_000 });
|
||||
}
|
||||
|
||||
async approve() {
|
||||
await this.approveBtn.click();
|
||||
}
|
||||
|
||||
async reject(comment: string) {
|
||||
await this.rejectCommentInput.fill(comment);
|
||||
await this.rejectBtn.click();
|
||||
}
|
||||
|
||||
async returnTask(comment: string) {
|
||||
await this.returnCommentInput.fill(comment);
|
||||
await this.returnBtn.click();
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户