# Epic 1 Retrospective: 测试工具包基础框架与 Select 支持 **会议日期:** 2026-01-09 **Epic 状态:** ✅ Done **参与人员:** Root (Project Lead), Bob (Scrum Master), Alice (Product Owner), Charlie (Senior Dev), Dana (QA Engineer), Elena (Junior Dev) ## 会议概览 **Epic 1 目标:** 测试开发者可以安装 `@d8d/e2e-test-utils` 包,立即使用 Select 工具测试 Radix UI Select 组件。 **交付成果:** - 完成故事: 6/6 (100%) - 单元测试: 37/37 通过 (100%) - 测试覆盖率: 93.65% Statements, 88.09% Branches - 类型检查: ✅ 通过 **会议目的:** 1. 学习 Epic 1 的执行经验 2. 为下一个 Epic 的成功做准备 ## 成功经验 ✅ ### 1. 代码审查流程有效 代码审查在 Epic 1 中发挥了关键作用: | 故事 | 审查问题 | HIGH | MEDIUM | LOW | 状态 | |------|---------|------|--------|-----|------| | 1.1 | 9 | 3 | 3 | 2 | 全部修复 | | 1.2 | 4 | 1 | 2 | 1 | 全部修复 | | 1.3 | 8 | 2 | 6 | 0 | 全部修复 | | 1.4 | 7 | 2 | 5 | 0 | 全部修复 | | 1.5 | 6 | 0 | 4 | 2 | 全部修复 | | 1.6 | 5 | 0 | 4 | 1 | 全部修复 | | **总计** | **39** | **8** | **24** | **7** | **100% 修复** | **关键发现:** - HIGH 级问题从 Story 1.1-1.4 的 8 个降至 Story 1.5-1.6 的 0 个 - 所有问题无一遗漏,全部得到修复 - 团队从审查中学习并应用到下一个故事 **Charlie (Senior Dev):** "Story 1.3 的审查发现的问题尤其关键。如果我们没有发现 `:has-text()` 会部分匹配的问题,测试可能会在错误选项上通过,导致严重的假阳性 bug。" ### 2. 持续改进模式 从前一个故事的经验传递到下一个故事: | 经验来源 | 应用故事 | 内容 | |---------|---------|------| | Story 1.3 审查 | Story 1.4 | DOM 类型问题处理、精确文本匹配 | | Story 1.4 实现 | Story 1.6 | 单元测试 Mock 策略、重试机制测试 | | Story 1.6 测试 | Bug 修复 | 发现并修复网络空闲等待超时配置 bug | **Elena (Junior Dev):** "Charlie 在 Story 1.3 的审查后让我把每个内部函数都加上完整的 JSDoc。虽然一开始觉得麻烦,但后来写 Story 1.4 的测试时发现这些文档真的很有帮助。" ### 3. 测试质量提升 测试覆盖率从 Story 1.1 的基础占位测试提升到 Story 1.6 的优秀覆盖率: - **Statements:** 93.65% (目标: ≥80%) - **Branches:** 88.09% (目标: ≥80%) - **Functions:** 100% (目标: ≥80%) - **Lines:** 93.65% (目标: ≥80%) **Dana (QA Engineer):** "Story 1.6 更有意思——我们在写单元测试的过程中发现了一个实际的 bug:`selectRadixOptionAsync` 的网络空闲等待超时配置不对。这证明代码审查和测试是互补的。" ### 4. 类型系统基础扎实 Epic 1 建立的类型系统为后续开发奠定了基础: - `BaseOptions` - 所有配置对象的基类 - `ErrorContext` - 结构化错误信息 - `E2ETestError` - 统一的错误处理 - `AsyncSelectOptions`, `FileUploadOptions`, `FormStepOptions`, `DialogOptions` - 特定选项类型 **Charlie (Senior Dev):** "我们在 Epic 1 建立的类型系统是一个很大的胜利。`BaseOptions` 作为所有配置对象的基类、`E2ETestError` 提供结构化错误信息,这些设计模式在后续故事中持续复用,减少了重复代码。" --- **[第一部分完 - 会议概览和成功经验]** ## 挑战和问题分析 ⚠️ ### 1. 重复出现的审查问题 代码审查发现的 39 个问题中,有相当一部分是重复出现的: | 问题类型 | 出现次数 | 可预防? | 预防方法 | |---------|---------|----------|----------| | 冗余 null 检查 | 5+ | ✅ 是 | ESLint 规则 | | 缺少 JSDoc | 8+ | ✅ 是 | ESLint 规则 | | 空 catch 块 | 4+ | ✅ 是 | ESLint 规则 | | DOM 类型问题 | 1 | ✅ 是 | 架构文档 | | 文本选择器精确性 | 1 | ✅ 是 | 编码规范 | **Dana (QA Engineer):** "这 18+ 个重复问题占总数的近一半。如果能通过工具或文档预防,就能节省约 8 小时的修复时间。" **成本分析:** - 每个问题平均修复时间:25 分钟 - 18 个可预防问题 × 25 分钟 = **7.5 小时** - **预防优于治疗:** 配置 ESLint 规则只需 2 小时,一次性解决问题 ### 2. TypeScript + Playwright DOM 类型问题 **Elena (Junior Dev):** "Story 1.3 的 DOM 类型问题真的让我很困惑。`page.evaluate()` 在 TypeScript 中总是报错,我花了很长时间才找到 `page.locator().allTextContents()` 这个解决方案。" **问题详情:** - 使用 `page.evaluate()` 获取文本内容会触发 TypeScript DOM 类型错误 - Playwright 的类型定义与浏览器 DOM 环境不完全兼容 **解决方案:** ```typescript // ❌ 不推荐 - TypeScript 类型问题 const text = await page.evaluate(el => el.textContent, element); // ✅ 推荐 - 使用 Playwright API const text = await element.textContent(); // 或 const texts = await page.locator(selector).allTextContents(); ``` **Charlie (Senior Dev):** "我应该在架构文档中注明这一点,而不是让你自己去摸索。" ### 3. 精确文本匹配问题 **问题:** 使用 `:has-text()` 选择器会进行部分匹配,可能导致误选错误选项。 **Story 1.3 发现的案例:** - 期望选择 "广东省" - 实际选择了 "广东省xx市"(因为包含 "广东省") **解决方案:** ```typescript // ❌ 部分匹配 - 可能误选 page.locator(`.option:has-text("广东省")`) // ✅ 精确匹配 - 只匹配完全相等的文本 page.locator(`.option:text-is("广东省")`) ``` **影响:** 如果没有在代码审查中发现,测试可能会在错误选项上通过,导致假阳性 bug。 ### 4. 网络空闲等待超时配置 Bug **Story 1.6 单元测试中发现:** ```typescript // ❌ Bug - 网络空闲等待使用了默认的 networkIdle 超时 await page.waitForLoadState('networkidle', { timeout: DEFAULT_TIMEOUTS.networkIdle }); // ✅ 修复 - 使用用户自定义的 timeout await page.waitForLoadState('networkidle', { timeout: options.timeout ?? DEFAULT_TIMEOUTS.async }); ``` **Dana (QA Engineer):** "这证明代码审查和测试是互补的。审查可以发现问题,但单元测试能在实际执行中暴露隐藏的 bug。" ### 5. Epic 规划问题(Root 发现) **问题 1: Epic 顺序错误** 原规划顺序: - Epic 1: ✅ Select 工具(已完成) - Epic 2: 扩展工具集(文件上传、表单等) - Epic 3: 在残疾人管理中验证工具包 - Epic 4: 完善文档 **Root (Project Lead):** "为什么先扩展更多工具,而不是先验证现有工具?如果我们现在构建的工具不能用,Epic 2 会浪费大量时间。" **正确顺序:** - Epic 1: ✅ Select 工具(已完成) - **Epic 2 (新):** **在现有 E2E 测试中验证 Select 工具** - Epic 3 (原 Epic 2): 扩展工具集 - Epic 4 (原 Epic 3): 全面验证 - Epic 5 (原 Epic 4): 完善文档 **问题 2: 重复建设而非复用** 原 Epic 3 Story 3.1 计划创建 `tests/test-app/` **独立测试应用**,而不是使用现有的 `web/tests/e2e/`。 **Root (Project Lead):** "现有的 web 目录已经有 E2E 测试了。为什么要另外创建一个 test-app?直接在现有测试中使用工具包更高效。" **Alice (Product Owner):** "这个修正更符合敏捷原则——利用现有资源,而不是重复建设。" **Charlie (Senior Dev):** "而且从测试角度看,在真实的残疾人管理测试中使用 Select 工具更能发现实际问题。" --- **[第二部分完 - 挑战和问题分析]** ## 行动项 📋 ### 优先级 HIGH #### 1. 配置 ESLint 规则捕获常见问题 **负责人:** Charlie **预计时间:** 2 小时 **需要配置的规则:** ```javascript // .eslintrc.js { rules: { // 捕获冗余的 null 检查 'no-constant-binary-expression': 'error', // 捕获未使用的变量 'no-unused-vars': 'error', // 捕获空 catch 块 'no-empty': ['error', { allowEmptyCatch: false }], // 首选 const 'prefer-const': 'error' } } ``` **预期收益:** 自动捕获约 50% 的重复审查问题 #### 2. 更新架构文档记录 TypeScript + Playwright 陷阱 **负责人:** Charlie **预计时间:** 1 小时 **文档位置:** `_bmad-output/planning-artifacts/architecture.md` **需要添加的内容:** - TypeScript DOM 类型问题及解决方案 - 精确文本匹配选择器规范 - 常见陷阱和最佳实践 #### 3. 创建新的 Epic 2 **负责人:** Bob (Scrum Master) **预计时间:** 1 小时 **Epic 2: 在现有 E2E 测试中验证 Select 工具** **包含的故事:** | 故事 | 内容 | 预计时间 | |------|------|----------| | 2.1 | 在 web 目录安装 `@d8d/e2e-test-utils` | 0.5h | | 2.2 | 使用 `selectRadixOption` 重写残疾类型选择测试 | 1h | | 2.3 | 使用 `selectRadixOptionAsync` 重写省份/城市选择测试 | 1.5h | | 2.4 | 运行测试并收集问题和改进建议 | 1h | | 2.5 | 修复发现的问题(如有) | 取决于发现 | | 2.6 | 稳定性验证(连续 10 次,100% 通过) | 0.5h | **预计总工作量:** 4-6 小时 + 修复时间 #### 4. 更新 Epic 编号 **负责人:** Bob (Scrum Master) **预计时间:** 0.5 小时 **变更:** - 原 Epic 2 → Epic 3 - 原 Epic 3 → Epic 4 - 原 Epic 4 → Epic 5 ### 优先级 MEDIUM #### 5. 创建开发者自查清单 **负责人:** Elena **预计时间:** 1 小时 **清单内容:** - [ ] 所有导出函数都有完整的 JSDoc - [ ] 内部函数使用 `@internal` 标记 - [ ] 错误处理使用 `E2ETestError` 而非原生 `Error` - [ ] 文本选择器使用 `:text-is()` 而非 `:has-text()` - [ ] 配置对象继承 `BaseOptions` - [ ] 超时值使用 `DEFAULT_TIMEOUTS` 常量 #### 6. 更新代码审查检查清单 **负责人:** Charlie **预计时间:** 0.5 小时 **添加检查点:** - 检查是否复用现有基础设施而非创建新应用 - 验证是否遵循"先验证再扩展"原则 #### 7. 更新 Epic 规划模板 **负责人:** Bob (Scrum Master) **预计时间:** 0.5 小时 **添加指导原则:** - 鼓励先验证 MVP 再扩展功能 - 优先复用现有资源而非重复建设 - 每个功能完成后应有验证环节 ### 优先级 LOW #### 8. 在 Epic 2 开始前完成技术改进 **前置条件:** 行动项 1-3 必须在启动新 Epic 2 之前完成 --- ## Epic 规划修正 🔄 ### 修正后的 Epic 顺序 | Epic | 内容 | 状态 | |------|------|------| | **Epic 1** | Select 工具基础框架 | ✅ Done | | **Epic 2 (新)** | **在现有 E2E 测试中验证 Select 工具** | 🆕 To Be Created | | **Epic 3** | 扩展工具集(文件上传、表单、列表、对话框)| ⏸️ Pending | | **Epic 4** | 全面验证工具包 | ⏸️ Pending | | **Epic 5** | 完善文档与开发者体验 | ⏸️ Pending | ### 新 Epic 2 详细规划 **Epic 2: 在现有 E2E 测试中验证 Select 工具** **目标:** - 在 `web/tests/e2e/` 的现有残疾人管理测试中使用 Select 工具 - 验证工具在真实场景中的可用性和稳定性 - 收集实际使用反馈,为后续工具设计提供指导 **范围:** - ✅ 使用现有 `web/tests/e2e/` 测试基础设施 - ✅ 使用现有的残疾人管理测试场景 - ❌ 不创建新的 test-app - ❌ 不添加新功能(仅验证现有功能) **验收标准:** 1. Select 工具在至少 2 个真实 E2E 测试场景中使用 2. 所有测试连续运行 10 次,100% 通过率 3. 发现的问题已记录并修复(或列入待办) 4. 收集的使用反馈已整理 --- ## 关键决策 🎯 ### 决策 1: 重新排列 Epic 顺序 **原顺序:** 构建 → 扩展 → 验证 **新顺序:** 构建 → **验证 → 扩展 → 全面验证 **理由:** 1. **更快反馈** - 不用等所有工具做完再验证 2. **降低风险** - 如果 Select 工具有问题,只修这一个工具 3. **更好决策** - 验证后的经验可以指导后续工具设计 4. **更小批次** - 每个 Epic 都是可独立交付的价值 ### 决策 2: 使用现有基础设施 **原计划:** 创建 `tests/test-app/` 独立测试应用 **新计划:** 使用现有 `web/tests/e2e/` 测试 **理由:** 1. **更快启动** - 无需搭建新应用 2. **更真实** - 在实际业务场景中测试 3. **更易维护** - 只有一个测试套件 4. **更早发现** - 直接在现有测试中发现问题 ### 决策 3: 技术改进先行 **计划:** 在启动新 Epic 2 之前完成技术改进(ESLint 配置、文档更新) **理由:** 1. **预防胜于治疗** - 节费 2 小时配置,节省 7.5 小时修复 2. **一次性投资,持续受益** - 改进将惠及所有后续 Epic 3. **建立标准** - 为团队建立清晰的编码规范 --- ## 总结 📝 **Epic 1 状态:** ✅ **Done** **关键成果:** - ✅ Select 工具函数完整实现 - ✅ 93.65% 测试覆盖率 - ✅ 37/37 单元测试通过 - ✅ 类型检查通过 - ✅ 所有代码审查问题已修复 **关键经验:** 1. 代码审查流程有效,但应与预防措施结合 2. 从前一个故事学习并应用到下一个故事效果显著 3. 在真实场景中验证比构建更多功能更优先 4. 利用现有基础设施比重复建设更高效 **下一步:** 1. ✅ Epic 1 完成并归档 2. 🆕 创建新 Epic 2(验证现有工具) 3. 🔧 完成技术改进(ESLint、文档) 4. ⏸️ 原 Epic 2-4 暂停,等待验证结果 **Bob (Scrum Master):** "这是一次非常成功的回顾会议。Root 的洞察帮助我们发现了 Epic 规划中的关键问题,避免在错误的方向上投入更多时间。感谢所有人的坦诚分享。" --- **[文档完]**