11-7-channel-page-object.md 26 KB

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)

    • 创建文件 web/tests/e2e/pages/admin/channel-management.page.ts
    • 导入 Playwright 类型和依赖
    • 定义类结构:export class ChannelManagementPage
    • 定义接口:ChannelData, NetworkResponse, FormSubmitResult
  • [x] 任务 2: 定义页面选择器 (AC: 2, 9)

    • 定义页面级选择器(pageTitle, createChannelButton, searchInput, searchButton, channelTable)
    • 定义对话框选择器(createDialogTitle, editDialogTitle)
    • 定义表单字段选择器(使用 data-testid)
    • 定义按钮选择器(createSubmitButton, updateSubmitButton, cancelButton, confirmDeleteButton)
  • [x] 任务 3: 实现导航和基础验证方法 (AC: 3, 9)

    • 实现 goto() 方法
    • 实现 expectToBeVisible() 方法
    • 添加 JSDoc 注释
  • [x] 任务 4: 实现对话框操作方法 (AC: 4, 9)

    • 实现 openCreateDialog() 方法
    • 实现 openEditDialog(channelName) 方法
    • 实现 openDeleteDialog(channelName) 方法
    • 实现 cancelDialog()waitForDialogClosed() 方法
    • 添加 JSDoc 注释
  • [x] 任务 5: 实现表单操作方法 (AC: 5, 9)

    • 实现 fillChannelForm(data) 方法
    • 实现 submitForm() 方法(包含网络响应捕获)
    • 处理 Toast 消息验证
    • 添加 JSDoc 注释
  • [x] 任务 6: 实现 CRUD 操作方法 (AC: 6, 9)

    • 实现 createChannel(data) 方法
    • 实现 editChannel(channelName, data) 方法
    • 实现 deleteChannel(channelName) 方法(API 直接删除)
    • 添加 JSDoc 注释
  • [x] 任务 7: 实现搜索和验证方法 (AC: 7, 9)

    • 实现 searchByName(name) 方法
    • 实现 channelExists(channelName) 方法
    • 添加 JSDoc 注释
  • [x] 任务 8: TypeScript 类型检查和代码质量验证 (AC: 9)

    • 运行 pnpm typecheck 检查类型
    • 确保无类型错误
    • 验证代码风格符合项目标准

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 响应
    • 监听 createChannelupdateChannelgetAllChannels 等 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):

{
  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 端点:

// 获取所有渠道
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 类结构:

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<string, string>;
  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<void>
  async expectToBeVisible(): Promise<void>

  // ===== 对话框操作 =====
  async openCreateDialog(): Promise<void>
  async openEditDialog(channelName: string): Promise<void>
  async openDeleteDialog(channelName: string): Promise<void>
  async cancelDialog(): Promise<void>
  async waitForDialogClosed(): Promise<void>
  async confirmDelete(): Promise<void>
  async cancelDelete(): Promise<void>

  // ===== 表单操作 =====
  async fillChannelForm(data: ChannelData): Promise<void>
  async submitForm(): Promise<FormSubmitResult>

  // ===== CRUD 操作 =====
  async createChannel(data: ChannelData): Promise<FormSubmitResult>
  async editChannel(channelName: string, data: ChannelData): Promise<FormSubmitResult>
  async deleteChannel(channelName: string): Promise<boolean>

  // ===== 搜索和验证 =====
  async searchByName(name: string): Promise<boolean>
  async channelExists(channelName: string): Promise<boolean>
}

与 PlatformManagementPage 和 CompanyManagementPage 的对比

相同部分:

  • 整体结构和模式完全一致
  • 选择器命名规则一致
  • 对话框操作逻辑一致
  • 表单提交流程一致
  • Toast 验证方式一致
  • API 删除策略一致

差异部分:

特性 Platform Company Channel
外键依赖 需要 platformId
名称唯一性 全局 同平台下唯一 全局
表格列数 7列 7列 7列
状态显示 有(状态徽章) 有(状态字段)
必填字段 platformName companyName channelName
可选字段 联系人信息 地址 + platformId channelType, description

Channel 特有的特点:

  • 有 channelType 字段区分不同渠道类型(如"小程序"、"网站"等)
  • 联系人字段都是必填的(但有默认值空字符串)
  • 与其他配置实体无关联,独立存在

测试数据唯一性策略

使用时间戳确保唯一性:

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 中添加):

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 类,遵循 PlatformManagementPageCompanyManagementPage 的相同模式
  • 使用 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

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)