Status: done
作为测试开发者, 我想要创建公司管理的 Page Object, 以便组织公司管理相关的页面元素和操作,为后续编写公司管理 E2E 测试提供基础。
AC1: 创建 CompanyManagementPage 文件
web/tests/e2e/pages/admin/company-management.page.tsAC2: 定义页面选择器
AC3: 实现导航和基础验证方法
goto() 方法:导航到 /admin/companies 页面expectToBeVisible() 方法:验证页面关键元素可见AC4: 实现对话框操作方法
openCreateDialog() 方法:打开创建公司对话框openEditDialog(companyName) 方法:打开编辑公司对话框openDeleteDialog(companyName) 方法:打开删除确认对话框cancelDialog() 方法:取消对话框waitForDialogClosed() 方法:等待对话框关闭AC5: 实现表单操作方法
fillCompanyForm(data) 方法:填写公司表单submitForm() 方法:提交表单并捕获网络响应AC6: 实现 CRUD 操作方法
createCompany(data) 方法:创建公司(完整流程)editCompany(companyName, data) 方法:编辑公司(完整流程)deleteCompany(companyName) 方法:删除公司(使用 API 直接删除)AC7: 实现搜索和验证方法
searchByName(name) 方法:按公司名称搜索companyExists(companyName) 方法:验证公司是否存在(使用精确匹配)AC8: 定义数据接口和类型
AC9: 代码质量标准
[x] 任务 1: 创建文件和基础结构 (AC: 1, 8, 9)
web/tests/e2e/pages/admin/company-management.page.tsexport class CompanyManagementPage[x] 任务 2: 定义页面选择器 (AC: 2, 9)
[x] 任务 3: 实现导航和基础验证方法 (AC: 3, 9)
goto() 方法expectToBeVisible() 方法[x] 任务 4: 实现对话框操作方法 (AC: 4, 9)
openCreateDialog() 方法openEditDialog(companyName) 方法openDeleteDialog(companyName) 方法cancelDialog() 和 waitForDialogClosed() 方法[x] 任务 5: 实现表单操作方法 (AC: 5, 9)
fillCompanyForm(data) 方法submitForm() 方法(包含网络响应捕获)[x] 任务 6: 实现 CRUD 操作方法 (AC: 6, 9)
createCompany(data) 方法editCompany(companyName, data) 方法deleteCompany(companyName) 方法(API 直接删除)[x] 任务 7: 实现搜索和验证方法 (AC: 7, 9)
searchByName(name) 方法companyExists(companyName) 方法[x] 任务 8: TypeScript 类型检查和代码质量验证 (AC: 9)
pnpm typecheck 检查类型Epic 11: 基础配置管理测试 (Epic F)
为平台、公司、渠道配置管理编写 E2E 测试,为后续用户管理和跨端测试提供必要的测试数据。
实体关系链:
Platform (平台)
↓ 1:N
Company (公司) - 必须 platformId
↓ 1:N
Order (订单) - 必须 companyId
Story 11.4 在 Epic 中的位置:
Company 实体关键特点:
参考 Story 11.1 的 PlatformManagementPage 模式:
选择器策略:
data-testid 定位元素网络响应捕获:
waitForResponse() 捕获特定 API 响应createCompany、updateCompany、getAllCompanies 等 APIPromise.race() 添加超时保护Toast 消息验证:
[data-sonner-toast][data-type="error"] 和 [data-type="success"]删除操作策略:
page.evaluate() 在浏览器上下文中执行 fetchCompany Entity (来自 company.entity.ts):
{
id: number; // 主键ID
platformId: number | null; // 平台ID(外键)
companyName: string; // 公司名称(必填,最大100字符)
contactPerson: string | null; // 联系人(可选,最大50字符)
contactPhone: string | null; // 联系电话(可选,最大20字符)
contactEmail: string | null; // 联系邮箱(可选,最大100字符)
address: string | null; // 地址(可选,最大200字符)
status: number; // 状态:1-正常,0-禁用(默认1)
createTime: Date; // 创建时间
updateTime: Date; // 更新时间
platform?: Platform; // 关联的平台信息(eager加载)
}
唯一约束:
idx_company_name_platform (companyName, platformId)页面路径: /admin/companies
UI 组件结构(来自 CompanyManagement.tsx):
页面结构:
创建/编辑对话框:
删除确认对话框:
data-testid 属性(关键):
搜索相关:
- search-company-input (搜索输入框)
- search-company-button (搜索按钮)
- create-company-button (创建公司按钮)
表单字段(创建):
- create-company-platform-selector (平台选择器)
- create-company-name-input (公司名称)
- create-company-contact-person-input (联系人)
- create-company-contact-phone-input (联系电话)
- create-company-contact-email-input (联系邮箱)
- create-company-address-input (地址)
- submit-create-company-button (提交按钮)
- cancel-company-button (取消按钮)
表单字段(编辑):
- edit-company-platform-selector
- edit-company-name-input
- edit-company-contact-person-input
- edit-company-contact-phone-input
- edit-company-contact-email-input
- edit-company-address-input
- submit-edit-company-button (提交按钮)
- cancel-edit-company-button (取消按钮)
删除对话框:
- cancel-delete-company-button (取消)
- confirm-delete-company-button (确认删除)
表格操作按钮:
- edit-company-button-{id} (编辑按钮,动态ID)
- delete-company-button-{id} (删除按钮,动态ID)
其他:
- company-modal-title (对话框标题)
PlatformSelector 集成:
@d8d/e2e-test-utils 的 selectRadixOption 或 selectRadixOptionAsync公司管理 API 端点:
// 获取所有公司
GET /api/v1/company/getAllCompanies?skip=0&take=10
// 搜索公司
GET /api/v1/company/searchCompanies?name=xxx&skip=0&take=10
// 创建公司
POST /api/v1/company/createCompany
Body: { platformId?: number, companyName: string, contactPerson?, contactPhone?, contactEmail?, address? }
// 更新公司
POST /api/v1/company/updateCompany
Body: { id: number, platformId?, companyName?, contactPerson?, contactPhone?, contactEmail?, address? }
// 删除公司
POST /api/v1/company/deleteCompany
Body: { id: number }
CompanyManagementPage 类结构:
import { Page, Locator } from '@playwright/test';
/**
* 公司数据接口
*/
export interface CompanyData {
/** 平台ID(可选) */
platformId?: number;
/** 公司名称(必填) */
companyName: string;
/** 联系人(可选) */
contactPerson?: string;
/** 联系电话(可选) */
contactPhone?: string;
/** 联系邮箱(可选) */
contactEmail?: string;
/** 地址(可选) */
address?: string;
}
/**
* 网络响应数据接口
*/
export interface NetworkResponse {
url: string;
method: string;
status: number;
ok: boolean;
responseHeaders: Record<string, string>;
responseBody: unknown;
}
/**
* 表单提交结果接口
*/
export interface FormSubmitResult {
success: boolean;
hasError: boolean;
hasSuccess: boolean;
errorMessage?: string;
successMessage?: string;
responses?: NetworkResponse[];
}
/**
* 公司管理 Page Object
*
* 用于公司管理功能的 E2E 测试
* 页面路径: /admin/companies
*/
export class CompanyManagementPage {
readonly page: Page;
// ===== 页面级选择器 =====
readonly pageTitle: Locator;
readonly createCompanyButton: Locator;
readonly searchInput: Locator;
readonly searchButton: Locator;
readonly companyTable: Locator;
// ===== 对话框选择器 =====
readonly createDialogTitle: Locator;
readonly editDialogTitle: Locator;
// ===== 表单字段选择器 =====
readonly platformSelector: Locator; // PlatformSelector 容器
readonly companyNameInput: Locator;
readonly contactPersonInput: Locator;
readonly contactPhoneInput: Locator;
readonly contactEmailInput: Locator;
readonly addressInput: Locator;
// ===== 按钮选择器 =====
readonly createSubmitButton: Locator;
readonly updateSubmitButton: Locator;
readonly cancelButton: Locator;
readonly confirmDeleteButton: Locator;
constructor(page: Page) {
this.page = page;
// 初始化所有选择器
}
// ===== 导航和基础验证 =====
async goto(): Promise<void>
async expectToBeVisible(): Promise<void>
// ===== 对话框操作 =====
async openCreateDialog(): Promise<void>
async openEditDialog(companyName: string): Promise<void>
async openDeleteDialog(companyName: string): Promise<void>
async cancelDialog(): Promise<void>
async waitForDialogClosed(): Promise<void>
// ===== 表单操作 =====
async fillCompanyForm(data: CompanyData): Promise<void>
async submitForm(): Promise<FormSubmitResult>
// ===== CRUD 操作 =====
async createCompany(data: CompanyData): Promise<FormSubmitResult>
async editCompany(companyName: string, data: CompanyData): Promise<FormSubmitResult>
async deleteCompany(companyName: string): Promise<boolean>
// ===== 搜索和验证 =====
async searchByName(name: string): Promise<boolean>
async companyExists(companyName: string): Promise<boolean>
}
关键注意事项:
使用 @d8d/e2e-test-utils 的 Select 工具:
import { selectRadixOption } from '@d8d/e2e-test-utils';
// 在 fillCompanyForm 中选择平台
if (data.platformId !== undefined) {
// 需要知道平台的名称来选择
// 选项格式:平台名称作为显示值,platformId 作为实际值
await selectRadixOption(this.page, '平台', platformName);
}
测试数据准备:
相同部分:
差异部分:
额外的表单字段:
address 字段(Platform 没有)platformName,Company 有 companyName外键关系:
表格列差异:
删除限制:
使用时间戳确保唯一性:
const timestamp = Date.now();
const uniqueId = `company_${timestamp}`;
const companyName = `测试公司_${uniqueId}`;
测试数据清理:
deleteCompany() 方法删除测试数据Epic 11 内部依赖:
外部依赖:
@d8d/e2e-test-utils 包(已存在)web/tests/e2e/utils/test-setup.ts: 需要添加 companyManagementPage fixture测试夹具配置(需要在 test-setup.ts 中添加):
export const test = test.extend<{
adminLoginPage: AdminLoginPage;
companyManagementPage: CompanyManagementPage;
}>({
adminLoginPage: async ({ page }, use) => {
await use(new AdminLoginPage(page));
},
companyManagementPage: async ({ page }, use) => {
await use(new CompanyManagementPage(page));
},
});
遵循项目测试标准:
docs/standards/testing-standards.mddocs/standards/web-ui-testing-standards.md关键测试原则:
从 Story 11.1-11.3 中学到的关键经验:
Toast 检测不可靠:
表格列顺序:
nth(0) 检查公司名称列测试数据要求:
页面刷新:
page.reload() 或 goto() 刷新选择器优先级:
Platform 异步加载:
selectRadixOptionAsync 可能更可靠公司名称唯一性:
删除限制:
状态徽章显示:
测试文件存放路径:
web/tests/e2e/
├── pages/admin/
│ ├── platform-management.page.ts # 平台管理 Page Object(已完成)
│ └── company-management.page.ts # 公司管理 Page Object(当前)
├── specs/admin/
│ ├── platform-create.spec.ts # 平台创建测试(已完成)
│ ├── platform-list.spec.ts # 平台列表测试(已完成)
│ └── company-*.spec.ts # 公司管理测试(后续 Story)
└── utils/
└── test-setup.ts # 测试夹具配置(需要添加 companyManagementPage)
Claude (d8d-model)
Story 创建完成:
Story 实施完成 (2026-01-12):
web/tests/e2e/pages/admin/company-management.page.ts 文件代码审查完成 (2026-01-12):
@d8d/e2e-test-utils 的 selectRadixOptionAsync 实现 PlatformSelector 选择fillCompanyForm、createCompany、editCompany 方法签名,添加 platformName 参数Story 文件位置:
_bmad-output/implementation-artifacts/11-4-company-page-object.story.md实现的关键特性:
新创建的文件(Story 创建):
_bmad-output/implementation-artifacts/11-4-company-page-object.story.md新创建的文件(Story 实施):
web/tests/e2e/pages/admin/company-management.page.ts修改的文件(Story 实施):
web/tests/e2e/utils/test-setup.ts - 添加 companyManagementPage fixture已有的依赖文件:
web/tests/e2e/pages/admin/platform-management.page.ts - Story 11.1 创建(参考模式)allin-packages/company-management-ui/src/components/CompanyManagement.tsx - UI 组件参考allin-packages/company-module/src/entities/company.entity.ts - 实体参考allin-packages/company-module/src/schemas/company.schema.ts - Schema 参考