--- stepsCompleted: ['step-01-validate-prerequisites', 'step-02-design-epics', 'step-03-create-stories'] inputDocuments: - name: PRD - E2E测试工具包 path: _bmad-output/planning-artifacts/prd.md type: prd - name: Architecture - E2E测试工具包 path: _bmad-output/planning-artifacts/architecture.md type: architecture - name: E2E Radix UI 测试标准 path: docs/standards/e2e-radix-testing.md type: testing-standard --- # 188-179-template-6 - Epic Breakdown ## Overview This document provides the complete epic and story breakdown for 188-179-template-6,从残疾人管理 E2E 测试实践中提取可复用的测试工具包。 ## Requirements Inventory ### Functional Requirements **Radix UI Select 组件测试支持 (FR1-FR6):** - FR1: 测试开发者可以使用工具函数选择静态枚举型 Radix UI Select 下拉框的选项 - FR2: 测试开发者可以使用工具函数选择异步加载型 Radix UI Select 下拉框的选项 - FR3: 工具函数可以自动处理 Radix UI Select 的 DOM 结构和交互流程 - FR4: 工具函数可以等待异步加载的选项出现在下拉列表中 - FR5: 工具函数可以提供清晰的错误提示,包含标签、期望值等上下文信息 - FR6: 工具函数可以区分"元素未找到"和"超时"错误类型 **文件上传测试支持 (FR7-FR10):** - FR7: 测试开发者可以使用工具函数上传文件到指定的文件输入字段 - FR8: 工具函数可以从 fixtures 目录加载测试文件 - FR9: 工具函数可以支持多文件上传场景 - FR10: 工具函数可以验证文件上传是否成功完成 **表单交互测试支持 (FR11-FR15):** - FR11: 测试开发者可以使用工具函数填写多步骤表单 - FR12: 工具函数可以滚动页面到特定的表单区域 - FR13: 工具函数可以处理表单验证错误场景 - FR14: 测试开发者可以使用工具函数提交表单并等待响应 - FR15: 工具函数可以支持常见的表单字段类型(文本、选择器、日期等) **动态列表测试支持 (FR16-FR20):** - FR16: 测试开发者可以使用工具函数向动态列表中添加新项 - FR17: 测试开发者可以使用工具函数从动态列表中删除项 - FR18: 工具函数可以支持不同类型的动态列表项(银行卡、备注等) - FR19: 工具函数可以验证动态列表项添加或删除后的状态 - FR20: 工具函数可以处理动态列表的异步更新场景 **对话框操作测试支持 (FR21-FR24):** - FR21: 测试开发者可以使用工具函数统一操作对话框(确认、取消、关闭) - FR22: 工具函数可以等待对话框完全关闭后再继续执行 - FR23: 工具函数可以处理对话框内的表单填写和提交 - FR24: 工具函数可以验证对话框是否按预期打开或关闭 **测试工具包基础设施 (FR25-FR32):** - FR25: 测试开发者可以通过 npm workspace 协议安装测试工具包 - FR26: 工具包可以作为 peer dependency 依赖 Playwright,不增加运行时依赖 - FR27: 工具包提供完整的 TypeScript 类型定义和类型提示 - FR28: 工具包的所有导出函数都有完整的 JSDoc 注释 - FR29: 工具包使用严格类型检查,不使用 any 类型 - FR30: 工具包支持目标 ES2020+ 的 JavaScript 环境 - FR31: 工具包的每个工具函数都可以独立导入和使用 - FR32: 工具包可以与其他测试工具和库兼容使用 **文档和开发者支持 (FR33-FR40):** - FR33: 测试开发者可以通过 README 快速了解工具包的用途和安装方法 - FR34: 文档提供每个工具函数的详细使用示例 - FR35: 文档提供静态 Select 和异步 Select 的区别说明 - FR36: 文档提供从现有测试代码迁移到工具函数的指南 - FR37: 文档提供常见问题和解决方案 - FR38: 文档提供残疾人管理测试作为完整的使用示例 - FR39: 测试开发者可以在 30 分钟内使用工具函数编写第一个测试 - FR40: 工具包的使用示例覆盖所有 6 个核心工具函数 **测试质量和稳定性保障 (FR41-FR45):** - FR41: 工具函数使用 Playwright 的 auto-waiting 机制防止时序问题 - FR42: 工具函数为静态选项设置合理的默认超时配置 - FR43: 工具函数为异步选项提供可配置的超时参数 - FR44: 工具函数可以支持重试机制处理不稳定的网络请求 - FR45: 工具函数可以在测试连续运行 20 次时保持 100% 通过率 **可扩展性和维护性 (FR46-FR50):** - FR46: 工具函数支持配置对象参数,允许自定义行为 - FR47: 工具函数设计支持未来的 Radix UI 版本升级 - FR48: 工具函数使用稳定的选择器策略(role、data-testid) - FR49: 工具包预留扩展接口,支持新增其他 Radix 组件测试模式 - FR50: 工具包的代码结构清晰,便于团队贡献和维护 ### NonFunctional Requirements **可靠性 (NFR1-NFR7):** - NFR1: 工具函数在相同条件下连续运行 20 次,必须保持 100% 通过率,无 flaky 失败 - NFR2: 工具函数能够正确处理异步加载场景,避免时序问题导致的测试失败 - NFR3: 当 DOM 元素暂时不可用时,工具函数提供清晰的错误消息,而不是超时无响应 - NFR4: 工具函数能够区分产品 bug 和测试代码问题,帮助开发者快速定位问题根源 - NFR5: 当选择操作失败时,错误消息包含:下拉框标签名称、期望选择的值、实际可用的选项列表、失败原因 - NFR6: 当文件上传失败时,错误消息包含文件路径、选择器、失败原因 - NFR7: 错误消息格式统一,便于日志分析和问题定位 **性能 (NFR8-NFR14):** - NFR8: 单个 Radix UI Select 选择操作(静态)应在 2 秒内完成 - NFR9: 单个 Radix UI Select 选择操作(异步)应在 5 秒内完成(默认超时) - NFR10: 工具函数本身的开销不超过 100ms(不包括 Playwright 操作时间) - NFR11: 使用工具函数的测试比手动编写 DOM 操作的测试执行时间差异 < 10% - NFR12: 静态选项使用合理的默认超时(2 秒),避免不必要的等待 - NFR13: 异步选项提供可配置的超时参数,默认值为 5 秒 - NFR14: 工具函数使用 Playwright 的 auto-waiting 机制,减少显式等待的需要 **集成性 (NFR15-NFR24):** - NFR15: 工具包兼容 Playwright 最新稳定版本和上一个 LTS 版本 - NFR16: 工具包可以作为 peer dependency 引用,不增加运行时依赖 - NFR17: 工具函数接受标准 Playwright Page 对象作为参数 - NFR18: 工具包不依赖特定版本的 Playwright,使用灵活的版本范围 - NFR19: 工具函数可以在任何使用 Playwright 的测试框架中运行(Vitest、Jest 等) - NFR20: 工具包不修改全局配置,不需要额外的测试框架配置 - NFR21: 工具函数可以与现有的 Page Object 模式无缝集成 - NFR22: 工具包通过 pnpm workspace 协议安装,支持本地开发 - NFR23: 工具包的构建产物与项目的 TypeScript 配置兼容 - NFR24: 工具包的类型定义可以自动被 IDE 识别和提示 **代码质量 (NFR25-NFR40):** - NFR25: 工具包使用 TypeScript 严格模式,无 `any` 类型 - NFR26: 所有导出函数都有完整的参数类型和返回值类型 - NFR27: 类型定义支持 IDE 自动补全和类型检查 - NFR28: 类型错误在编译时被捕获,不在运行时暴露 - NFR29: 工具包的代码覆盖率 ≥ 80% - NFR30: 每个工具函数都有对应的单元测试 - NFR31: 代码遵循项目的 ESLint 和 Prettier 配置 - NFR32: 函数复杂度保持在低水平(圈复杂度 < 10) - NFR33: 每个导出函数都有完整的 JSDoc 注释 - NFR34: README 包含快速入门、安装说明、基本用法 - NFR35: 每个工具函数至少有 1 个实际使用示例 - NFR36: 文档说明静态 Select 和异步 Select 的区别和使用场景 - NFR37: 新测试开发者可以在 30 分钟内使用工具函数编写第一个测试 - NFR38: 工具函数的命名清晰直观,不需要频繁查看文档 - NFR39: 函数参数设计简洁,必需参数 ≤ 3 个 - NFR40: 错误消息对新手友好,包含问题诊断和建议修复步骤 **兼容性 (NFR41-NFR46):** - NFR41: 工具函数在 Playwright 支持的所有浏览器中正常工作(Chromium、Firefox、WebKit) - NFR42: 工具函数在 headless 和 headed 模式下都能正常工作 - NFR43: 工具函数在 CI/CD 环境中稳定运行 - NFR44: 工具包支持 Node.js 当前 LTS 版本和上一个 LTS 版本 - NFR45: 工具包的设计考虑未来 Radix UI 版本升级,使用稳定的选择器策略 - NFR46: 重大版本变更时提供迁移指南 ### Additional Requirements **从 Architecture 文档提取的技术需求:** **包结构需求:** - 创建独立包 `packages/e2e-test-utils`,与现有的 `@d8d/shared-test-util`(后端集成测试)分离 - 按功能分组文件结构:`src/` 分为 `radix-select.ts`, `file-upload.ts`, `form-helper.ts`, `dialog.ts`, `dynamic-list.ts` - 共享代码集中在 `types.ts`, `errors.ts`, `constants.ts` - 主导出使用 `index.ts`,支持 tree-shaking **API 设计需求:** - 必需参数 ≤ 3 个,复杂配置通过可选对象传递 - 函数命名:动词+名词,camelCase(如 `selectRadixOption`) - 异步函数添加 `Async` 后缀(如 `selectRadixOptionAsync`) - 类型命名:接口 PascalCase + `Options` 后缀(如 `AsyncSelectOptions`) **类型系统需求:** - 分层类型:`types.ts` 存放共享类型(`BaseOptions`),各模块文件存放特定类型 - 所有配置对象继承 `BaseOptions` - TypeScript 严格模式,无 `any` 类型 **选择器策略需求:** - 混合策略优先级:data-testid → aria-label + role → text content + role - 推荐在 Radix 组件上添加 `data-testid` **错误处理需求:** - 使用结构化错误类 `E2ETestError` + `ErrorContext` - 错误消息格式:❌ 操作失败、上下文信息、💡 修复建议 **测试策略需求:** - 三层测试:单元测试(Vitest)、集成测试(Playwright)、稳定性测试(20次连续运行) - 创建独立测试应用 `tests/test-app/` 使用 Vite + React - 测试应用提供真实的 `@d8d/shared-ui-components` 组件 - Playwright 配置自动启动测试应用服务器 - 单元测试覆盖率 ≥ 80% **常量定义需求:** - 超时常量:`DEFAULT_TIMEOUTS.static = 2000`, `async = 5000`, `networkIdle = 10000` - 选择器策略常量:`SELECTOR_STRATEGIES` 数组 **文档需求:** - 完整的 JSDoc 注释格式 - README 包含快速入门、安装说明、基本用法 - 每个工具函数至少有 1 个实际使用示例 - 迁移指南:从现有测试代码迁移到工具函数 **遵循项目标准:** - 遵循 `docs/standards/testing-standards.md` 中的测试规范 - 遵循 `docs/standards/web-ui-testing-standards.md` 中的 Web UI 测试规范 - **遵循 `docs/standards/e2e-radix-testing.md` 中的 Radix UI E2E 测试标准**(核心标准文档) **从 E2E Radix UI 测试标准提取的详细需求:** **核心工具函数签名(必须按此实现):** ```typescript // Radix UI Select 工具 selectRadixOption(page: Page, label: string, value: string): Promise selectRadixOptionAsync(page: Page, label: string, value: string, options?: AsyncSelectOptions): Promise // 文件上传工具 uploadFileToField(page: Page, selector: string, fileName: string): Promise // 表单辅助工具 fillMultiStepForm(page: Page, steps: FormStep[]): Promise scrollToSection(page: Page, sectionName: string): Promise // 动态列表工具 addDynamicListItem(page: Page, itemType: string, data: Record): Promise deleteDynamicListItem(page: Page, itemType: string, index: number): Promise // 对话框工具 handleDialog(page: Page, action: 'confirm' | 'cancel' | 'close'): Promise waitForDialogClosed(page: Page): Promise cancelDialog(page: Page): Promise ``` **DOM 结构理解需求:** - 理解 Radix UI Select 的典型 DOM 结构:`data-radix-select-trigger` 触发器,`role="listbox"` 选项列表,`role="option"` 单个选项 - 理解 Radix UI Dialog 的 DOM 结构:`role="dialog"` 对话框容器 - 理解数据属性:`data-value` 用于选项值,`data-state` 用于状态跟踪 **选择器策略实现细节:** ```typescript // 触发器选择器优先级 const TRIGGER_SELECTORS = [ `[data-testid="${label}-trigger"]`, // 最高优先级 `text="${label}"`, // 文本匹配 `[role="combobox"]` // 兜底 ]; // 选项选择器优先级 const OPTION_SELECTORS = [ `[role="option"][data-value="${value}"]`, // data-testid + data-value `[role="option"]:has-text("${value}")` // 文本匹配兜底 ]; ``` **性能标准(来自测试标准文档):** | 操作 | 目标时间 | 最大可接受时间 | |------|---------|---------------| | 静态 Select 选择 | < 1s | 2s | | 异步 Select 选择 | < 3s | 5s | | 文件上传 | < 2s | 5s | | 表单提交 | < 2s | 5s | **Fixtures 目录结构标准:** ``` web/tests/ └── fixtures/ ├── images/ # 测试图片(身份证照片、残疾证照片等) └── documents/ # 测试文档(PDF 等) ``` **错误消息格式标准:** ``` Error: Radix Select 选项 "xxx" 未找到 标签: 残疾类型 期望值: xxx 可用选项: 视力残疾, 听力残疾, 肢体残疾, 智力残疾 Error: Radix Select 等待超时 标签: 省份 期望值: 广东省 超时时间: 5000ms 可能原因: 网络请求过慢或选项未加载 ``` **测试稳定性最佳实践:** - 使用 Playwright 的 auto-waiting 机制,不使用 `waitForTimeout` - 优先使用 `data-testid` 选择器,谨慎使用文本选择器,避免使用不稳定的 CSS 类 - 测试隔离:每个测试前清理数据,失败时截图 - 明确的错误断言:使用具体的选择器和期望的文本内容 **完整示例需求:** - 提供残疾人管理 E2E 测试作为完整示例 - 演示静态 Select、异步 Select、文件上传、动态列表、对话框的综合使用 - 展示与 Page Object 模式的集成方式 ### FR Coverage Map | FR 范围 | Epic | 描述 | |--------|------|------| | FR1-FR6 | Epic 1 | Radix UI Select 测试支持(静态和异步) | | FR25-FR32 | Epic 1 | 包基础设施(package.json、类型定义、配置) | | FR7-FR24, FR46-FR50 | Epic 3 | 扩展工具集(文件上传、表单、列表、对话框、可扩展性) | | FR41-FR45 | Epic 2 & 4 | 质量与稳定性(Select 验证、全面验证、稳定性测试) | | FR33-FR40 | Epic 5 | 文档与开发者体验(README、示例、迁移指南) | **Epic 规划变更说明(2026-01-09):** - Epic 1: ✅ 已完成(Select 工具基础框架) - Epic 2: 🆕 新增(在现有 E2E 测试中验证 Select 工具) - Epic 3: 原 Epic 2(扩展工具集) - Epic 4: 原 Epic 3(全面验证工具包) - Epic 5: 原 Epic 4(完善文档与开发者体验) ## Epic List ### Epic 1: 测试工具包基础框架与 Select 支持 **目标:** 测试开发者可以安装 `@d8d/e2e-test-utils` 包,立即使用 Select 工具测试 Radix UI Select 组件(最常用、最关键的测试场景)。 **交付物:** - 完整的包结构和配置(package.json, tsconfig.json, vitest.config.ts) - `selectRadixOption()` 和 `selectRadixOptionAsync()` 函数 - 类型定义和错误处理(E2ETestError, ErrorContext) - 选择器策略实现(testid → ARIA → text) - 基础文档(README 安装、快速入门) - 单元测试(Vitest) **FRs covered:** FR1-FR6, FR25-FR32 **用户价值:** - 可以安装并立即使用 Select 工具 - 无需深入了解 Radix UI DOM 结构 - 自动处理时序问题 - 清晰的错误提示 --- ### Epic 2: 扩展工具集(文件上传、表单、列表、对话框) **目标:** 测试开发者可以使用完整的 6 个核心工具函数,覆盖所有常见 E2E 测试场景。 **交付物:** - `uploadFileToField()` - 文件上传工具 - `fillMultiStepForm()`, `scrollToSection()` - 表单辅助工具 - `addDynamicListItem()`, `deleteDynamicListItem()` - 动态列表工具 - `handleDialog()`, `waitForDialogClosed()`, `cancelDialog()` - 对话框工具 - Fixtures 目录结构示例 - 单元测试 **FRs covered:** FR7-FR24, FR46-FR50 **用户价值:** - 完整的工具集覆盖所有常见测试场景 - 统一的 API 设计和错误处理 - 支持可配置和扩展 --- ### Epic 3: 在残疾人管理中验证工具包 **目标:** 工具包在真实的残疾人管理 E2E 测试中验证,证明工具函数可用且稳定,提供完整的参考示例。 **交付物:** - 残疾人管理 E2E 测试(使用工具函数) - 照片上传功能测试 - 银行卡管理功能测试 - 备注功能测试 - 回访功能测试 - 完整流程测试 - 稳定性验证(20次连续运行 100% 通过) **FRs covered:** FR38, FR41-FR45 **用户价值:** - 验证工具包的可用性和稳定性 - 提供完整的参考示例 - 证明工具包符合性能标准 --- ### Epic 4: 完善文档与开发者体验 **目标:** 测试开发者可以在 30 分钟内上手使用工具包,有完整的文档、示例和迁移指南。 **交付物:** - 完整的 README(安装、快速入门、API 文档) - 每个工具函数的详细使用示例 - 迁移指南(从现有测试代码到工具函数) - 常见问题和解决方案 - 残疾人管理测试作为完整示例 - JSDoc 完整注释 **FRs covered:** FR33-FR40 **用户价值:** - 快速上手(30 分钟内编写第一个测试) - 清晰的文档和示例 - 降低学习曲线 - 便于团队采用 --- ## Epic 1: 测试工具包基础框架与 Select 支持 **目标:** 测试开发者可以安装 `@d8d/e2e-test-utils` 包,立即使用 Select 工具测试 Radix UI Select 组件(最常用、最关键的测试场景)。 --- ### Story 1.1: 创建包基础结构和配置 作为测试开发者, 我想要可以安装 `@d8d/e2e-test-utils` 包, 以便在项目中使用测试工具函数。 **验收标准:** **Given** 项目是 Monorepo 结构 **When** 创建 `packages/e2e-test-utils/` 目录 **Then** 目录结构包含 `src/`, `tests/`, `package.json`, `tsconfig.json`, `vitest.config.ts` **And** `package.json` 包含正确的包名、版本、peer dependencies(`@playwright/test`) **And** `tsconfig.json` 启用严格模式,目标 ES2020+ **And** 可以通过 `pnpm add -D @d8d/e2e-test-utils@workspace:*` 安装 --- ### Story 1.2: 实现类型定义和错误处理 作为测试开发者, 我想要工具包有完整的类型支持和统一的错误处理, 以便获得类型安全和清晰的错误提示。 **验收标准:** **Given** 包结构已创建 **When** 实现 `src/types.ts`, `src/errors.ts`, `src/constants.ts` **Then** `types.ts` 导出 `BaseOptions`, `AsyncSelectOptions`, `FileUploadOptions` 等 **And** `errors.ts` 导出 `E2ETestError` 类和 `ErrorContext` 接口 **And** `constants.ts` 定义 `DEFAULT_TIMEOUTS`(static: 2000, async: 5000) **And** 所有类型使用 TypeScript 严格模式,无 `any` 类型 --- ### Story 1.3: 实现静态 Select 工具函数 作为测试开发者, 我想要使用 `selectRadixOption()` 函数选择静态枚举型下拉框, 以便无需理解 Radix UI DOM 结构就能编写测试。 **验收标准:** **Given** 类型定义已创建 **When** 实现 `src/radix-select.ts` 中的 `selectRadixOption()` 函数 **Then** 函数签名:`selectRadixOption(page: Page, label: string, value: string): Promise` **And** 选择器策略:`data-testid` → aria-label + role → text content **And** 自动处理点击触发器、等待选项列表、点击选项 **And** 错误时抛出 `E2ETestError`,包含标签、期望值、可用选项 **And** 操作在 2 秒内完成(NFR8) --- ### Story 1.4: 实现异步 Select 工具函数 作为测试开发者, 我想要使用 `selectRadixOptionAsync()` 函数选择异步加载的下拉框, 以便测试省份、城市等动态加载的选项。 **验收标准:** **Given** 静态 Select 函数已实现 **When** 实现 `selectRadixOptionAsync(page, label, value, options?)` 函数 **Then** 支持 `AsyncSelectOptions` 配置(timeout, waitForOption) **And** 使用 `waitForLoadState('networkidle')` 等待异步加载 **And** 默认超时 5 秒,可配置 **And** 超时时提供清晰错误消息(标签、期望值、超时时间、可能原因) --- ### Story 1.5: 创建主导出和基础文档 作为测试开发者, 我想要可以导入工具函数并查看快速入门文档, 以便快速开始使用工具包。 **验收标准:** **Given** Select 工具函数已实现 **When** 创建 `src/index.ts` 和 `README.md` **Then** `index.ts` 导出所有公共函数和类型(tree-shakeable) **And** README 包含:安装说明、快速入门、Select 使用示例 **And** 所有导出函数有完整的 JSDoc 注释 **And** 可以 `import { selectRadixOption } from '@d8d/e2e-test-utils'` --- ### Story 1.6: Select 工具函数单元测试 作为测试开发者, 我想要 Select 工具函数有充分的单元测试, 以便确保函数的正确性和稳定性。 **验收标准:** **Given** Select 工具函数已实现 **When** 创建 `tests/unit/radix-select.test.ts` **Then** 测试覆盖率 ≥ 80%(NFR29) **And** 测试用例包括:成功选择、选项不存在、超时、错误处理 **And** 使用 Vitest 运行测试 **And** 所有测试通过 --- ## Epic 2: 在现有 E2E 测试中验证 Select 工具 **目标:** 在 `web/tests/e2e/` 的现有残疾人管理测试中使用 Select 工具,验证工具在真实场景中的可用性和稳定性,收集实际使用反馈,为后续工具设计提供指导。 **背景:** Epic 1 已完成 Select 工具的开发和单元测试,但尚未在真实 E2E 测试场景中验证。通过在现有测试中使用这些工具,我们可以: 1. 验证工具在实际业务场景中的可用性 2. 发现并修复潜在问题 3. 收集使用体验反馈,改进 API 设计 4. 为后续工具开发建立信心和经验 **范围:** - ✅ 使用现有 `web/tests/e2e/` 测试基础设施 - ✅ 使用现有的残疾人管理测试场景 - ✅ 替换 Page Object 中现有的 Select 操作 - ❌ 不创建新的测试应用 - ❌ 不添加新功能(仅验证现有功能) **依赖:** - Epic 1: ✅ 已完成(Select 工具已开发) **验收标准:** 1. Select 工具在至少 2 个真实 E2E 测试场景中使用 2. 所有测试连续运行 10 次,100% 通过率 3. 发现的问题已记录并修复(或列入待办) 4. 收集的使用反馈已整理 --- ### Story 2.1: 在 web 目录安装 @d8d/e2e-test-utils 作为测试开发者, 我想要在 web 目录安装 `@d8d/e2e-test-utils` 包, 以便在 E2E 测试中使用 Select 工具。 **验收标准:** **Given** Epic 1 已完成,`@d8d/e2e-test-utils` 包已构建 **When** 在 `web/package.json` 中添加 workspace 依赖 **Then** 可以在 `web/tests/e2e/` 中导入 Select 工具 **And** TypeScript 类型检查通过 **And** 运行时无依赖错误 **实现要点:** - 使用 `pnpm add -D @d8d/e2e-test-utils@workspace:*` 安装 - 验证 `web/tests/e2e/` 中可以导入:`import { selectRadixOption } from '@d8d/e2e-test-utils'` --- ### Story 2.2: 使用 selectRadixOption 重写残疾类型选择 作为测试开发者, 我想要使用 `selectRadixOption()` 替换 Page Object 中的 Select 操作, 以便验证工具在静态 Select 场景中的可用性。 **验收标准:** **Given** @d8d/e2e-test-utils 已安装 **When** 修改 `web/tests/e2e/pages/admin/disability-person.page.ts` **Then** `fillBasicForm()` 中的残疾类型选择使用 `selectRadixOption()` **And** `fillBasicForm()` 中的残疾等级选择使用 `selectRadixOption()` **And** 移除原有的 `selectRadixOption()` 方法 **And** 测试通过,功能正常 **验证场景:** - 残疾类型:视力残疾、听力残疾、肢体残疾、言语残疾等(静态选项) - 残疾等级:一级、二级、三级、四级(静态选项) --- ### Story 2.3: 使用 selectRadixOptionAsync 重写省份/城市选择 作为测试开发者, 我想要使用 `selectRadixOptionAsync()` 处理异步加载的 Select, 以便验证工具在异步 Select 场景中的可用性。 **验收标准:** **Given** @d8d/e2e-test-utils 已安装 **When** 修改 `web/tests/e2e/pages/admin/disability-person.page.ts` **Then** `fillBasicForm()` 中的省份选择使用 `selectRadixOptionAsync()` **And** `fillBasicForm()` 中的城市选择使用 `selectRadixOptionAsync()` **And** 移除 `waitForTimeout(500)` 等待城市加载的 hack **And** 测试通过,功能正常 **验证场景:** - 省份选择(异步加载选项) - 城市选择(根据省份动态加载) **配置要点:** - 使用 `waitForOption: true` 等待选项加载 - 使用合理的超时配置(5-10 秒) --- ### Story 2.4: 运行测试并收集问题和改进建议 作为测试开发者, 我想要运行使用新工具的测试并收集反馈, 以便发现潜在问题并改进工具。 **验收标准:** 1. 运行 `web/tests/e2e/specs/admin/disability-person-complete.spec.ts` 2. 记录所有问题(包括失败的测试、错误消息、使用体验) 3. 分类问题:工具 bug vs 使用错误 vs 改进建议 4. 整理成问题清单 **关注点:** - 工具是否按预期工作? - 错误消息是否清晰? - API 是否简洁易用? - 是否有性能问题? --- ### Story 2.5: 修复发现的问题 作为测试开发者, 我想要修复 Story 2.4 中发现的问题, 以便工具可以正常使用。 **验收标准:** - 所有标记为"工具 bug"的问题已修复 - 所有测试通过 - 修复已记录到 story 文件 **优先级:** - HIGH: 影响测试结果的问题(如选择失败、超时) - MEDIUM: 影响开发体验的问题(如错误消息不清晰) - LOW: 优化建议(如性能改进) --- ### Story 2.6: 稳定性验证 作为测试开发者, 我想要验证测试的稳定性, 以便确保工具可以可靠地使用。 **验收标准:** **Given** 所有问题已修复 **When** 连续运行测试 10 次 **Then** 所有测试 100% 通过 **And** 无 flaky 失败 **And** 平均执行时间 < 5 分钟 **测试场景:** - `pnpm test:e2e:chromium disability-person-complete.spec.ts` 运行 10 次 **成功标准:** - 10/10 次通过 = 100% 稳定性 ✅ - 9/10 次通过 = 90% 稳定性,需要分析失败原因 ⚠️ - < 9/10 次通过 = 稳定性不足,需要修复 ❌ --- ## Epic 3: 扩展工具集(文件上传、表单、列表、对话框) **目标:** 测试开发者可以使用完整的 6 个核心工具函数,覆盖所有常见 E2E 测试场景。 **说明:** 本 Epic 原 Epic 2,已在 Epic 2 完成后重新编号。 --- ### Story 3.1: 实现文件上传工具 作为测试开发者, 我想要使用 `uploadFileToField()` 函数上传文件, 以便测试照片上传、文档上传等功能。 **验收标准:** **Given** Epic 1 的类型定义已存在 **When** 实现 `src/file-upload.ts` 中的 `uploadFileToField(page, selector, fileName)` 函数 **Then** 函数从 fixtures 目录加载测试文件 **And** 使用 Playwright 的 `setInputFiles()` API **And** 支持相对路径(相对于 fixtures 目录) **And** 错误时提供清晰消息(文件路径、选择器、失败原因) **And** 操作在 5 秒内完成(NFR9) --- ### Story 3.2: 实现表单辅助工具 作为测试开发者, 我想要使用 `fillMultiStepForm()` 和 `scrollToSection()` 函数, 以便测试多步骤表单和滚动操作。 **验收标准:** **Given** 类型定义已存在 **When** 实现 `src/form-helper.ts` 中的表单辅助函数 **Then** `fillMultiStepForm(page, steps)` 支持分步填写表单 **And** `scrollToSection(page, sectionName)` 滚动到特定区域 **And** 支持常见字段类型(文本、选择器、日期等) **And** 自动处理表单验证错误场景 --- ### Story 3.3: 实现动态列表工具 作为测试开发者, 我想要使用 `addDynamicListItem()` 和 `deleteDynamicListItem()` 函数, 以便测试银行卡管理、备注管理等动态列表功能。 **验收标准:** **Given** 类型定义已存在 **When** 实现 `src/dynamic-list.ts` 中的动态列表函数 **Then** `addDynamicListItem(page, itemType, data)` 添加新列表项 **And** `deleteDynamicListItem(page, itemType, index)` 删除指定索引的项 **And** 支持不同类型列表项(银行卡、备注等) **And** 验证列表状态变化 **And** 处理异步更新场景 --- ### Story 3.4: 实现对话框操作工具 作为测试开发者, 我想要使用 `handleDialog()`, `waitForDialogClosed()`, `cancelDialog()` 函数, 以便统一操作 Radix UI Dialog。 **验收标准:** **Given** 类型定义已存在 **When** 实现 `src/dialog.ts` 中的对话框操作函数 **Then** `handleDialog(page, action)` 支持 confirm/cancel/close 操作 **And** `waitForDialogClosed(page)` 等待对话框完全关闭 **And** `cancelDialog(page)` 提供取消操作的快捷方式 **And** 理解 Radix UI Dialog 的 DOM 结构(`role="dialog"`) **And** 等待对话框关闭后再继续后续操作 --- ### Story 3.5: 更新主导出和 Fixtures 示例 作为测试开发者, 我想要可以导入所有新增的工具函数, 并参考 Fixtures 目录结构示例。 **验收标准:** **Given** 所有扩展工具函数已实现 **When** 更新 `src/index.ts` 和创建 Fixtures 目录示例 **Then** `index.ts` 导出所有新函数(uploadFileToField, fillMultiStepForm 等) **And** 创建 `tests/fixtures/` 目录结构示例 **And** Fixtures 包含 `images/` 和 `documents/` 子目录 **And** 提供示例测试文件(sample-id-card.jpg 等) **And** 所有函数有完整的 JSDoc 注释 --- ### Story 3.6: 扩展工具集单元测试 作为测试开发者, 我想要所有扩展工具函数有充分的单元测试, 以便确保函数的正确性和稳定性。 **验收标准:** **Given** 扩展工具函数已实现 **When** 创建 `tests/unit/` 下的测试文件 **Then** 测试覆盖率 ≥ 80%(NFR29) **And** 测试文件包括:file-upload.test.ts, form-helper.test.ts, dynamic-list.test.ts, dialog.test.ts **And** 每个函数的成功和失败场景都有测试 **And** 使用 Vitest 运行,所有测试通过 --- ## Epic 4: 在残疾人管理中验证工具包 **目标:** 工具包在真实的残疾人管理 E2E 测试中验证,证明工具函数可用且稳定,提供完整的参考示例。 **说明:** 本 Epic 原 Epic 3,已在 Epic 2 完成后重新编号。 **注意:** 原 Epic 3 的范围需要调整,因为 Epic 2 已涵盖 Select 工具的验证。 --- ### Story 4.2: 照片上传功能测试(使用现有测试) 作为测试开发者, 我想要在现有的残疾人管理 E2E 测试中使用文件上传工具, 以便验证 `uploadFileToField()` 的可用性。 **验收标准:** **Given** Epic 3 已完成,文件上传工具已实现 **When** 在 `web/tests/e2e/specs/admin/disability-person-complete.spec.ts` 中使用 `uploadFileToField()` **Then** 上传身份证照片(正面、反面) **And** 上传残疾证照片 **And** 验证文件上传成功 **And** 测试使用 `web/tests/e2e/fixtures/images/` 中的示例文件 --- ### Story 4.3: 银行卡管理功能测试(使用现有测试) 作为测试开发者, 我想要在现有的残疾人管理 E2E 测试中使用动态列表和对话框工具, 以便验证这些工具的可用性。 **验收标准:** **Given** Epic 3 已完成,对话框和动态列表工具已实现 **When** 在现有测试中使用 `handleDialog()`, `addDynamicListItem()`, `deleteDynamicListItem()` **Then** 打开添加银行卡对话框 **And** 填写银行卡信息(银行名称、卡号、持卡人) **And** 添加银行卡到列表 **And** 验证列表状态变化 --- ### Story 4.4: 备注和回访功能测试(使用现有测试) 作为测试开发者, 我想要在现有的残疾人管理 E2E 测试中使用表单辅助工具, 以便验证表单工具的综合使用。 **验收标准:** **Given** Epic 3 已完成,表单辅助工具已实现 **When** 在现有测试中使用 `fillMultiStepForm()`, `scrollToSection()` **Then** 填写备注表单 **And** 滚动到回访区域 **And** 添加备注到列表 **And** 验证表单提交和列表更新 --- ### Story 4.5: 完整流程验证 作为测试开发者, 我想要在现有的完整流程测试中使用所有工具, 以便演示所有工具函数的综合使用。 **验收标准:** **Given** Epic 2-4 的所有工具已在部分测试中验证 **When** 确保 `disability-person-complete.spec.ts` 使用所有工具 **Then** 基本信息:使用 `selectRadixOption` 和 `selectRadixOptionAsync` **And** 照片上传:使用 `uploadFileToField` **And** 银行卡管理:使用 `handleDialog`, `addDynamicListItem`, `deleteDynamicListItem` **And** 备注添加:使用 `fillMultiStepForm`, `scrollToSection`, `addDynamicListItem` **And** 所有测试通过 --- ### Story 4.6: 稳定性测试(使用现有测试) 作为测试开发者, 我想要有稳定性测试验证工具包的可靠性, 以便确保工具函数连续运行 20 次 100% 通过。 **验收标准:** **Given** 完整流程测试已编写 **When** 创建 `tests/stability/repeat-run.spec.ts` **Then** 测试连续运行 20 次完整流程 **And** 验证 100% 通过率(NFR1) **And** 测试执行时间符合性能标准(NFR8-NFR11) **And** 无 flaky 失败 **And** 使用 Playwright 运行稳定性测试 --- ## Epic 5: 完善文档与开发者体验 **目标:** 测试开发者可以在 30 分钟内上手使用工具包,有完整的文档、示例和迁移指南。 **说明:** 本 Epic 原 Epic 4,已在 Epic 2 完成后重新编号。 --- ### Story 5.1: 完善 README、API 文档和示例 作为测试开发者, 我想要有完整的 README 和 API 文档, 以便快速上手使用工具包。 **验收标准:** **Given** Epic 1-4 的所有功能已实现 **When** 完善 `README.md` 和 API 文档 **Then** README 包含:项目简介、安装说明、快速入门、API 文档 **And** 每个工具函数都有完整的使用示例 **And** 说明静态 Select 和异步 Select 的区别和使用场景 **And** 包含迁移指南(从现有测试代码到工具函数) **And** 包含常见问题和解决方案 **And** 残疾人管理测试作为完整示例 **And** 所有函数有完整的 JSDoc 注释