# Story 11.7: Channel 管理 Page Object(可选) Status: done ## Story 作为测试开发者, 我想要创建渠道管理的 Page Object(可选), 以便为渠道管理功能的 E2E 测试封装页面元素和操作方法(如需要)。 ## Acceptance Criteria 1. **AC1: 创建 ChannelManagementPage 文件(可选)** - 文件路径: `web/tests/e2e/pages/admin/channel-management.page.ts` - 使用 TypeScript 编写 - 导入 Playwright 的 Page、Locator、Response 等类型 - 定义完整的类结构和方法签名 2. **AC2: 定义页面选择器** - 定义页面级选择器:pageTitle, createChannelButton, searchInput, searchButton, channelTable - 定义对话框选择器:createDialogTitle, editDialogTitle - 定义表单字段选择器:channelNameInput, channelTypeInput, contactPersonInput, contactPhoneInput, descriptionInput - 定义按钮选择器:createSubmitButton, updateSubmitButton, cancelButton, confirmDeleteButton - 所有选择器使用 data-testid 定位(遵循测试标准) 3. **AC3: 实现导航和基础验证方法** - 实现 `goto()` 方法:导航到渠道管理页面 - 实现 `expectToBeVisible()` 方法:验证页面关键元素可见 - 等待页面标题和表格数据加载完成 4. **AC4: 实现对话框操作方法** - 实现 `openCreateDialog()` 方法:打开创建渠道对话框 - 实现 `openEditDialog(channelName)` 方法:打开编辑渠道对话框 - 实现 `openDeleteDialog(channelName)` 方法:打开删除确认对话框 - 实现 `cancelDialog()` 方法:取消对话框 - 实现 `waitForDialogClosed()` 方法:等待对话框关闭 5. **AC5: 实现表单操作方法** - 实现 `fillChannelForm(data)` 方法:填写渠道表单 - 实现 `submitForm()` 方法:提交表单并捕获网络响应 - 表单数据接口包含:channelName, channelType?, contactPerson?, contactPhone?, description? - 支持创建和编辑两种表单模式 6. **AC6: 实现 CRUD 操作方法** - 实现 `createChannel(data)` 方法:创建渠道(完整流程) - 实现 `editChannel(channelName, data)` 方法:编辑渠道(完整流程) - 实现 `deleteChannel(channelName)` 方法:删除渠道(使用 API 直接删除) - 删除成功后刷新页面确保列表更新 7. **AC7: 实现搜索和验证方法** - 实现 `searchByName(name)` 方法:按渠道名称搜索 - 实现 `channelExists(channelName)` 方法:验证渠道是否存在(使用精确匹配) - 搜索后验证结果 8. **AC8: 定义数据接口和类型** - 定义 ChannelData 接口(包含所有渠道字段) - 定义 NetworkResponse 接口(捕获网络响应) - 定义 FormSubmitResult 接口(表单提交结果) - 所有接口使用 JSDoc 注释 9. **AC9: 代码质量标准** - TypeScript 类型检查通过(无类型错误) - 所有公共方法有完整的 JSDoc 注释 - 代码结构与 PlatformManagementPage 和 CompanyManagementPage 保持一致 ## Tasks / Subtasks - [x] 任务 1: 创建文件和基础结构 (AC: 1, 8, 9) - [x] 创建文件 `web/tests/e2e/pages/admin/channel-management.page.ts` - [x] 导入 Playwright 类型和依赖 - [x] 定义类结构:`export class ChannelManagementPage` - [x] 定义接口:ChannelData, NetworkResponse, FormSubmitResult - [x] 任务 2: 定义页面选择器 (AC: 2, 9) - [x] 定义页面级选择器(pageTitle, createChannelButton, searchInput, searchButton, channelTable) - [x] 定义对话框选择器(createDialogTitle, editDialogTitle) - [x] 定义表单字段选择器(使用 data-testid) - [x] 定义按钮选择器(createSubmitButton, updateSubmitButton, cancelButton, confirmDeleteButton) - [x] 任务 3: 实现导航和基础验证方法 (AC: 3, 9) - [x] 实现 `goto()` 方法 - [x] 实现 `expectToBeVisible()` 方法 - [x] 添加 JSDoc 注释 - [x] 任务 4: 实现对话框操作方法 (AC: 4, 9) - [x] 实现 `openCreateDialog()` 方法 - [x] 实现 `openEditDialog(channelName)` 方法 - [x] 实现 `openDeleteDialog(channelName)` 方法 - [x] 实现 `cancelDialog()` 和 `waitForDialogClosed()` 方法 - [x] 添加 JSDoc 注释 - [x] 任务 5: 实现表单操作方法 (AC: 5, 9) - [x] 实现 `fillChannelForm(data)` 方法 - [x] 实现 `submitForm()` 方法(包含网络响应捕获) - [x] 处理 Toast 消息验证 - [x] 添加 JSDoc 注释 - [x] 任务 6: 实现 CRUD 操作方法 (AC: 6, 9) - [x] 实现 `createChannel(data)` 方法 - [x] 实现 `editChannel(channelName, data)` 方法 - [x] 实现 `deleteChannel(channelName)` 方法(API 直接删除) - [x] 添加 JSDoc 注释 - [x] 任务 7: 实现搜索和验证方法 (AC: 7, 9) - [x] 实现 `searchByName(name)` 方法 - [x] 实现 `channelExists(channelName)` 方法 - [x] 添加 JSDoc 注释 - [x] 任务 8: TypeScript 类型检查和代码质量验证 (AC: 9) - [x] 运行 `pnpm typecheck` 检查类型 - [x] 确保无类型错误 - [x] 验证代码风格符合项目标准 ## Dev Notes ### Epic 11 背景和目标 **Epic 11: 基础配置管理测试 (Epic F)** 为平台、公司、渠道配置管理编写 E2E 测试,为后续用户管理和跨端测试提供必要的测试数据。 **实体关系链:** ``` Platform (平台) ↓ 1:N Company (公司) - 必须 platformId ↓ 1:N Order (订单) - 必须 companyId ↓ Channel (渠道) - 订单的可选条件 ``` **Story 11.7 可选性的原因:** 1. **Channel 是订单的可选字段** - 在订单创建表单中,`channelId` 是可选的(`optional()`) - 订单可以不选择渠道直接创建 - 不像 Platform 和 Company,它们是创建订单的必要条件 2. **优先级较低** - Story 11.1-11.6 已经覆盖了 Platform 和 Company 的完整测试 - 这些是阻塞后续 Epic(用户管理、跨端测试)的核心功能 - Channel 管理测试可以延后实现 3. **实现时机** - 如果订单管理测试(Epic 10)发现需要渠道选择功能验证,再实现此 Story - 或者当有额外时间时补充渠道管理测试 - 当前 Epic 11 的核心目标(Platform + Company)已经完成 **Channel 实体关键特点:** - 渠道名称全局唯一(唯一索引:`idx_channel_name`) - 包含渠道类型(channelType)- 区分不同渠道来源 - 联系人信息(contactPerson, contactPhone) - 有状态字段(status: 1-启用,0-禁用) - 删除已禁用的渠道才能删除 - 独立实体,不依赖其他表 ### 架构模式和约束 **参考 Story 11.1 (Platform) 和 Story 11.4 (Company) 的 Page Object 模式:** 1. **选择器策略:** - 优先使用 `data-testid` 定位元素 - 使用 role + name 组合作为备用(如按钮) - 避免使用不稳定的 CSS 类选择器 2. **网络响应捕获:** - 使用 `waitForResponse()` 捕获特定 API 响应 - 监听 `createChannel`、`updateChannel`、`getAllChannels` 等 API - 使用 `Promise.race()` 添加超时保护 3. **Toast 消息验证:** - Toast 使用 Sonner UI 库:`[data-sonner-toast][data-type="error"]` 和 `[data-type="success"]` - Toast 可能出现得很快,需要主动等待 - 同时验证 API 响应作为主要验证方式 4. **删除操作策略:** - 使用 API 直接删除,绕过 UI 的不可靠性 - 使用 `page.evaluate()` 在浏览器上下文中执行 fetch - 删除成功后刷新页面确保列表更新 ### Channel 实体结构 **Channel Entity (来自 channel.entity.ts):** ```typescript { id: number; // 主键ID (channel_id) channelName: string; // 渠道名称(必填,最大100字符,全局唯一) channelType: string; // 渠道类型(必填,最大50字符,默认空字符串) contactPerson: string; // 联系人(必填,最大50字符,默认空字符串) contactPhone: string; // 联系电话(必填,最大20字符,默认空字符串) description?: string; // 描述(可选,text类型) status: number; // 状态:1-正常,0-禁用(默认1) createTime: Date; // 创建时间 updateTime: Date; // 更新时间 } ``` **唯一约束:** - 渠道名称全局唯一:`idx_channel_name (channelName)` **与 Company/Platform 的差异:** - Channel 没有 parentId 或外键关系 - Channel 是独立的顶级实体 - 渠道名称是全局唯一,而非在某个父级下唯一 ### ChannelManagement UI 组件分析 **页面路径:** 待确认(可能是 `/admin/channels`) **UI 组件结构(来自 ChannelManagement.tsx):** 1. **页面结构:** - 页面标题:"渠道管理" - 搜索栏:搜索输入框 + 搜索按钮 + 创建渠道按钮 - 渠道列表表格(7列):渠道ID、渠道名称、渠道类型、联系人、联系电话、创建时间、操作 - 分页组件(DataTablePagination) 2. **创建/编辑对话框:** - 对话框标题:"创建渠道" / "编辑渠道" - 表单字段: - channelName: Input(必填) - channelType: Input(可选) - contactPerson: Input(可选) - contactPhone: Input(可选) - description: Input(可选) - 提交按钮:"创建" / "更新" - 取消按钮:"取消" 3. **删除确认对话框:** - 标题:"确认删除" - 描述:"确定要删除这个渠道吗?此操作不可撤销。" - 确认按钮:"确认删除"(红色 destructive 样式) - 取消按钮:"取消" 4. **data-testid 属性(关键):** ``` 搜索相关: - search-input (搜索输入框) - search-button (搜索按钮) - create-channel-button (创建渠道按钮) 表单字段(创建): - create-channel-modal-title (对话框标题) 表单字段(编辑): - (编辑表单使用 react-hook-form,未显式设置 data-testid) 删除对话框: - delete-confirm-dialog-title (删除确认对话框标题) 表格操作按钮: - edit-channel-{id} (编辑按钮,动态ID) - delete-channel-{id} (删除按钮,动态ID) ``` **注意事项:** - ChannelManagement UI 组件中的 data-testid 设置不够完整 - 编辑表单的字段缺少显式的 data-testid - 可能需要通过 role + name 组合来定位元素 ### API 端点参考 **渠道管理 API 端点:** ```typescript // 获取所有渠道 GET /api/v1/channel/getAllChannels?skip=0&take=10 // 搜索渠道 GET /api/v1/channel/searchChannels?name=xxx&skip=0&take=10 // 创建渠道 POST /api/v1/channel/createChannel Body: { channelName, channelType?, contactPerson?, contactPhone?, description? } // 更新渠道 POST /api/v1/channel/updateChannel Body: { id, channelName?, channelType?, contactPerson?, contactPhone?, description? } // 删除渠道 POST /api/v1/channel/deleteChannel Body: { id: number } ``` ### Page Object 设计参考 **ChannelManagementPage 类结构:** ```typescript import { Page, Locator } from '@playwright/test'; /** * 渠道数据接口 */ export interface ChannelData { /** 渠道名称(必填) */ channelName: string; /** 渠道类型(可选) */ channelType?: string; /** 联系人(可选) */ contactPerson?: string; /** 联系电话(可选) */ contactPhone?: string; /** 描述(可选) */ description?: string; } /** * 网络响应数据接口 */ export interface NetworkResponse { url: string; method: string; status: number; ok: boolean; responseHeaders: Record; responseBody: unknown; } /** * 表单提交结果接口 */ export interface FormSubmitResult { success: boolean; hasError: boolean; hasSuccess: boolean; errorMessage?: string; successMessage?: string; responses?: NetworkResponse[]; } /** * 渠道管理 Page Object * * 用于渠道管理功能的 E2E 测试 * 页面路径: /admin/channels(待确认) */ export class ChannelManagementPage { readonly page: Page; // ===== 页面级选择器 ===== readonly pageTitle: Locator; readonly createChannelButton: Locator; readonly searchInput: Locator; readonly searchButton: Locator; readonly channelTable: Locator; // ===== 对话框选择器 ===== readonly createDialogTitle: Locator; readonly editDialogTitle: Locator; // ===== 表单字段选择器 ===== readonly channelNameInput: Locator; readonly channelTypeInput: Locator; readonly contactPersonInput: Locator; readonly contactPhoneInput: Locator; readonly descriptionInput: Locator; // ===== 按钮选择器 ===== readonly createSubmitButton: Locator; readonly updateSubmitButton: Locator; readonly cancelButton: Locator; readonly confirmDeleteButton: Locator; constructor(page: Page) { this.page = page; // 初始化所有选择器 } // ===== 导航和基础验证 ===== async goto(): Promise async expectToBeVisible(): Promise // ===== 对话框操作 ===== async openCreateDialog(): Promise async openEditDialog(channelName: string): Promise async openDeleteDialog(channelName: string): Promise async cancelDialog(): Promise async waitForDialogClosed(): Promise async confirmDelete(): Promise async cancelDelete(): Promise // ===== 表单操作 ===== async fillChannelForm(data: ChannelData): Promise async submitForm(): Promise // ===== CRUD 操作 ===== async createChannel(data: ChannelData): Promise async editChannel(channelName: string, data: ChannelData): Promise async deleteChannel(channelName: string): Promise // ===== 搜索和验证 ===== async searchByName(name: string): Promise async channelExists(channelName: string): Promise } ``` ### 与 PlatformManagementPage 和 CompanyManagementPage 的对比 **相同部分:** - 整体结构和模式完全一致 - 选择器命名规则一致 - 对话框操作逻辑一致 - 表单提交流程一致 - Toast 验证方式一致 - API 删除策略一致 **差异部分:** | 特性 | Platform | Company | Channel | |------|----------|---------|---------| | **外键依赖** | 无 | 需要 platformId | 无 | | **名称唯一性** | 全局 | 同平台下唯一 | 全局 | | **表格列数** | 7列 | 7列 | 7列 | | **状态显示** | 无 | 有(状态徽章) | 有(状态字段) | | **必填字段** | platformName | companyName | channelName | | **可选字段** | 联系人信息 | 地址 + platformId | channelType, description | **Channel 特有的特点:** - 有 channelType 字段区分不同渠道类型(如"小程序"、"网站"等) - 联系人字段都是必填的(但有默认值空字符串) - 与其他配置实体无关联,独立存在 ### 测试数据唯一性策略 **使用时间戳确保唯一性:** ```typescript const timestamp = Date.now(); const uniqueId = `channel_${timestamp}`; const channelName = `测试渠道_${uniqueId}`; ``` **测试数据清理:** - 每个测试用例结束后清理自己创建的渠道数据 - 使用 `deleteChannel()` 方法删除测试数据 - 验证删除成功后再结束测试 ### 依赖关系 **Epic 11 内部依赖:** - Story 11.1: ✅ 已完成(PlatformManagementPage 作为参考) - Story 11.2: ✅ 已完成(平台创建测试参考) - Story 11.3: ✅ 已完成(平台列表测试参考) - Story 11.4: ✅ 已完成(CompanyManagementPage 作为参考) - Story 11.5: Backlog(创建测试公司) - Story 11.6: ✅ 已完成(公司列表测试) - Story 11.7: Channel 管理 Page Object(当前,可选) **外部依赖:** - Epic 1, 2: `@d8d/e2e-test-utils` 包(已存在) - `web/tests/e2e/utils/test-setup.ts`: 需要添加 channelManagementPage fixture **测试夹具配置(需要在 test-setup.ts 中添加):** ```typescript export const test = test.extend<{ adminLoginPage: AdminLoginPage; channelManagementPage: ChannelManagementPage; }>({ adminLoginPage: async ({ page }, use) => { await use(new AdminLoginPage(page)); }, channelManagementPage: async ({ page }, use) => { await use(new ChannelManagementPage(page)); }, }); ``` ### 测试标准和规范 遵循项目测试标准: - `docs/standards/testing-standards.md` - `docs/standards/web-ui-testing-standards.md` **关键测试原则:** 1. 测试独立性:每个测试用例独立运行 2. 数据清理:每个测试结束后清理自己创建的数据 3. 清晰断言:使用 expect() 明确断言预期结果 4. 等待策略:使用 Playwright 的 auto-waiting ### 前序 Story (11.1-11.6) 关键经验 从 Story 11.1-11.6 中学到的关键经验: 1. **Toast 检测不可靠:** - Toast 消息有时出现得很快 - 已改用 API 响应验证作为主要验证方式 2. **表格列顺序:** - Channel 表格列顺序:渠道ID(0), 渠道名称(1), 渠道类型(2), 联系人(3), 联系电话(4), 创建时间(5), 操作(6) - 使用 `nth(1)` 检查渠道名称列 3. **测试数据要求:** - 后端 Zod schema 要求字段类型正确 - 空字符串在某些字段中是有效值(有默认值) - 测试不填的可选字段应该设为 undefined 4. **页面刷新:** - 创建/删除数据后需要刷新页面或重新导航 - 使用 `page.reload()` 或 `goto()` 刷新 5. **选择器优先级:** - 优先使用 data-testid(最稳定) - 其次使用 role + name(较稳定) - 避免使用文本选择器(可能变化) 6. **编辑表单 data-testid 缺失:** - 从 CompanyManagementPage 的经验,编辑表单可能缺少 data-testid - 需要使用 `getByRole('form')` + `getByLabel()` 组合定位 ### 已知问题和注意事项 1. **页面路由待确认:** - 渠道管理页面的实际路由需要确认 - 可能是 `/admin/channels` 或其他 - 需要通过实际访问验证 2. **data-testid 不完整:** - ChannelManagement UI 组件的 data-testid 设置不完整 - 编辑表单字段缺少显式的 data-testid - 需要使用 role + label 组合定位 3. **渠道名称全局唯一:** - 渠道名称全局唯一,不是在某个父级下唯一 - 测试使用时间戳确保唯一性 4. **删除限制:** - 只有禁用状态的渠道才能删除 - 测试删除功能需要先禁用渠道 - 或者使用 API 直接删除 5. **状态显示:** - 状态字段存在,但表格中未显示状态列 - 测试中不需要验证状态显示 ### 开发顺序建议 1. 创建文件和基础结构 2. 定义所有选择器(注意编辑表单可能需要使用 role + label) 3. 实现导航和基础验证方法 4. 实现对话框操作方法 5. 实现表单填写方法 6. 实现表单提交方法(网络响应捕获) 7. 实现 CRUD 操作方法 8. 实现搜索和验证方法 9. TypeScript 类型检查 10. 在 test-setup.ts 中添加 fixture ### 实现决策点 **由于此 Story 是可选的,实现前需要确认:** 1. **是否需要渠道管理测试?** - 订单管理测试(Epic 10)是否需要验证渠道选择功能? - 如果订单创建流程中渠道选择是关键步骤,则需要实现 2. **优先级评估:** - Epic 11 的核心目标(Platform + Company)已完成 - Channel 管理测试优先级低于其他 Epic - 可以在有空余时间时实现 3. **实现范围:** - 如果实现,建议实现完整的 CRUD 测试 - 如果不实现,可以在后续有需要时补充 ### 可选性说明 **标记为"可选"的原因总结:** 1. **业务优先级低** - Channel 是订单的可选字段,不影响核心业务流程 2. **依赖关系弱** - 不阻塞其他 Epic 的开发 3. **时间资源有限** - Epic 11 的核心目标(Platform + Company)已实现 4. **可延后实现** - 当发现确实需要渠道管理测试时,再实现此 Story **何时应该实现此 Story:** - 订单管理测试(Epic 10)中发现需要验证渠道选择功能 - 产品团队明确要求渠道管理测试覆盖 - 有额外开发时间资源 ### Dev Agent Record #### Implementation Plan **技术方案:** - 创建 `ChannelManagementPage` 类,遵循 `PlatformManagementPage` 和 `CompanyManagementPage` 的相同模式 - 使用 `data-testid` 定位元素(UI 组件中已设置的部分) - 对于未设置 `data-testid` 的编辑表单字段,使用 `role + label` 组合定位 **实现要点:** 1. 页面路由:`/admin/channels`(已从 routes.tsx 确认) 2. 表格列顺序:渠道ID(0), 渠道名称(1), 渠道类型(2), 联系人(3), 联系电话(4), 创建时间(5), 操作(6) 3. API 端点使用与 Platform/Company 相同的模式 4. 删除操作使用 API 直接删除,避免 UI 不可靠性 **选择器策略:** - 创建按钮:`data-testid="create-channel-button"` - 搜索输入:`data-testid="search-input"` - 搜索按钮:`data-testid="search-button"` - 创建对话框标题:`data-testid="create-channel-modal-title"` - 编辑对话框标题:使用文本定位 `getByText('编辑渠道')` - 表单字段:使用 `getByLabel()` 定位(编辑表单未设置 data-testid) - 删除按钮:`data-testid="delete-channel-{id}"`(动态ID) #### Debug Log 无特殊问题需要记录。实现过程顺利,参考了 PlatformManagementPage 和 CompanyManagementPage 的成熟模式。 #### Completion Notes **已完成的工作:** 1. ✅ 创建 `web/tests/e2e/pages/admin/channel-management.page.ts` 文件 2. ✅ 定义所有必要的接口:`ChannelData`, `NetworkResponse`, `FormSubmitResult` 3. ✅ 定义所有页面选择器(页面级、对话框、表单字段、按钮) 4. ✅ 实现导航和基础验证方法:`goto()`, `expectToBeVisible()` 5. ✅ 实现对话框操作方法:`openCreateDialog()`, `openEditDialog()`, `openDeleteDialog()`, `cancelDialog()`, `waitForDialogClosed()` 6. ✅ 实现表单操作方法:`fillChannelForm()`, `submitForm()`(包含网络响应捕获和 Toast 验证) 7. ✅ 实现 CRUD 操作方法:`createChannel()`, `editChannel()`, `deleteChannel()`(API 直接删除) 8. ✅ 实现搜索和验证方法:`searchByName()`, `channelExists()` 9. ✅ TypeScript 类型检查通过 10. ✅ 所有公共方法包含完整的 JSDoc 注释 **代码质量验证:** - TypeScript 类型检查:通过 ✅ - 代码风格:符合项目标准 ✅ - 与参考 Page Object 一致性:完全一致 ✅ **注意事项:** - 此 Story 是可选的,根据 Story 文档中的说明,渠道管理测试优先级较低 - UI 组件中编辑表单字段未设置 `data-testid`,已使用 `role + label` 组合定位 - 删除确认对话框的确认按钮使用了 `locator('..').getByRole()` 组合定位,因为按钮不在直接子级中 **代码审查修复(2026-01-12):** - ✅ 修复 M1: 更新 File List,添加了 test-setup.ts 修改记录和其他文件的说明 - ✅ 修复 M2: 在 test-setup.ts 中添加了 channelManagementPage fixture - ✅ 修复 M4: 更新 Page Object 设计参考,补充 confirmDelete/cancelDelete 方法文档 - ✅ 更新 Story 状态为 done - ✅ 同步 sprint-status.yaml ### File List **新增文件:** - `web/tests/e2e/pages/admin/channel-management.page.ts` **修改文件:** - `_bmad-output/implementation-artifacts/sprint-status.yaml` - 更新 Story 11.7 状态为 review - `_bmad-output/implementation-artifacts/11-7-channel-page-object.md` - 本 story 文件 - `web/tests/e2e/utils/test-setup.ts` - 添加 channelManagementPage fixture **注意:** 工作目录中还有其他已修改的文件(如 `8-9-region-stability-test.md`, `DisabledPersonSelector.tsx` 等),这些属于其他 Story 的更改,不在本 Story 的变更范围内。 ### Change Log **2026-01-12** - 创建 ChannelManagementPage Page Object - 实现所有选择器、导航、对话框操作、表单操作、CRUD 操作、搜索和验证方法 - TypeScript 类型检查通过 ### References - [Epic 11 基础配置管理测试](../planning-artifacts/epics.md#epic-11-基础配置管理测试-epic-f) - [Story 11.1 Platform Page Object](./11-1-platform-page-object.story.md) - [Story 11.4 Company Page Object](./11-4-company-page-object.story.md) - [Channel Entity](../../allin-packages/channel-module/src/entities/channel.entity.ts) - [Channel Schema](../../allin-packages/channel-module/src/schemas/channel.schema.ts) - [ChannelManagement UI](../../allin-packages/channel-management-ui/src/components/ChannelManagement.tsx) - [PlatformManagementPage](../../web/tests/e2e/pages/admin/platform-management.page.ts) - 参考模式 - [CompanyManagementPage](../../web/tests/e2e/pages/admin/company-management.page.ts) - 参考模式 - [测试标准](../../docs/standards/testing-standards.md) - [Web UI 测试标准](../../docs/standards/web-ui-testing-standards.md) - [Architecture Decision - TypeScript + Playwright 陷阱](../planning-artifacts/architecture.md#typescript--playwright-常见陷阱) ### Project Structure Notes **测试文件存放路径:** ``` web/tests/e2e/ ├── pages/admin/ │ ├── platform-management.page.ts # 平台管理 Page Object(已完成) │ ├── company-management.page.ts # 公司管理 Page Object(已完成) │ └── channel-management.page.ts # 渠道管理 Page Object(当前,可选) ├── specs/admin/ │ ├── platform-create.spec.ts # 平台创建测试(已完成) │ ├── platform-list.spec.ts # 平台列表测试(已完成) │ ├── company-create.spec.ts # 公司创建测试(待实现) │ ├── company-list.spec.ts # 公司列表测试(已完成) │ └── channel-*.spec.ts # 渠道管理测试(如需要,后续实现) └── utils/ └── test-setup.ts # 测试夹具配置(需要添加 channelManagementPage) ```