# E2E 测试工具包 - 问题与改进建议 生成日期: 2026-01-09 测试文件: `web/tests/e2e/specs/admin/disability-person-complete.spec.ts` 工具版本: @d8d/e2e-test-utils (from Story 2.1-2.3) ## 执行摘要 - **测试总数**: 6 - **通过**: 0 - **失败**: 1(首个测试失败,其余未运行) - **跳过**: 5 - **执行时间**: ~10秒 - **失败原因**: `selectRadixOption` 工具无法找到 `[role="listbox"]` 元素 ## 问题清单 ### 1. [Bug] selectRadixOption 工具无法找到 listbox 元素 **类别**: 工具 bug **严重程度**: **高** - 阻塞所有使用静态 Select 的测试 **状态**: 待修复 **问题描述**: `selectRadixOption` 工具在点击 Radix UI Select 触发器后,等待 `[role="listbox"]` 元素出现,但该元素从未出现,导致测试超时失败。 **复现步骤**: 1. 运行测试: `pnpm test:e2e:chromium disability-person-complete` 2. 测试尝试使用 `selectRadixOption(page, "残疾类型 *", "视力残疾")` 选择残疾类型 3. 触发器被成功找到并点击 4. 工具等待 `[role="listbox"]` 出现(超时 2000ms) 5. 超时失败 **错误信息**: ``` TimeoutError: page.waitForSelector: Timeout 2000ms exceeded. Call log: - waiting for locator('[role=listbox]') to be visible at ../../../../packages/e2e-test-utils/src/radix-select.ts:37 ``` **预期行为**: 点击触发器后,Radix UI Select 应该渲染带有 `[role="listbox"]` 属性的内容容器。 **实际行为**: 从 Playwright 错误上下文快照可以看到,点击后选项出现在 `[role="combobox"]` 内部,而非独立的 `[role="listbox"]`: ```yaml - combobox "残疾类型 *" [active] [ref=e26]: - option "请选择残疾类型" [selected] - option "视力残疾" - option "听力残疾" - option "言语残疾" - option "肢体残疾" - option "智力残疾" - option "精神残疾" - option "多重残疾" ``` **根本原因分析**: 有两种可能: 1. **Radix UI Select v2.2.5 渲染方式不同**: 该版本的 Radix UI Select 可能不使用 `[role="listbox"]` 角色,或者将其应用在不同的元素上。 2. **工具假设错误**: 工具期望的 DOM 结构与实际 Radix UI Select 的实现不匹配。 **建议解决方案**: 1. **验证 Radix UI Select 的实际角色**: ```bash # 在浏览器中手动测试,检查打开 Select 时的 DOM 结构 # 或者使用 Playwright 的 page.content() 查看完整 HTML ``` 2. **修复工具以支持实际的 DOM 结构**: - 移除对 `[role="listbox"]` 的依赖,或改为可选 - 直接等待 `[role="option"]` 元素出现 - 或者使用更灵活的选择器 3. **修复建议代码**: ```typescript // packages/e2e-test-utils/src/radix-select.ts export async function selectRadixOption(page: Page, label: string, value: string): Promise { console.debug(`[selectRadixOption] 开始选择: label="${label}", value="${value}"`); const trigger = await findTrigger(page, label, value); console.debug(`[selectRadixOption] 找到触发器,准备点击`); await trigger.click(); // 修复: 不等待 listbox,直接等待选项出现 console.debug(`[selectRadixOption] 已点击触发器,等待选项`); await page.waitForSelector("[role=option]", { timeout: DEFAULT_TIMEOUTS.static, state: "visible" }); console.debug(`[selectRadixOption] 选项已出现`); const availableOptions = await page.locator("[role=option]").allTextContents(); console.debug(`[selectRadixOption] 可用选项:`, availableOptions); await findAndClickOption(page, value, availableOptions); console.debug(`[selectRadixOption] 选择完成`); } ``` **相关文件**: - `packages/e2e-test-utils/src/radix-select.ts:37` - `packages/e2e-test-utils/src/radix-select.ts:230` (async version) --- ### 2. [Bug] selectRadixOptionAsync 存在相同的 listbox 依赖问题 **类别**: 工具 bug **严重程度**: **高** - 阻塞所有使用异步 Select 的测试 **状态**: 待验证(未运行到相关代码) **问题描述**: `selectRadixOptionAsync` 工具在第 230 行也使用 `page.waitForSelector('[role="listbox"]')`,预计会遇到与静态 Select 相同的问题。 **相关代码**: ```typescript // packages/e2e-test-utils/src/radix-select.ts:230 await page.waitForSelector('[role="listbox"]', { timeout: DEFAULT_TIMEOUTS.static, state: 'visible' }); ``` **建议解决方案**: 应用与问题 #1 相同的修复方案。 --- ## 改进建议汇总 ### API 设计建议 1. **简化选择器策略**: - 当前的 `findTrigger` 函数有 4 种策略,过于复杂 - 建议: 简化为 2-3 种最常用的策略 2. **更好的错误消息**: - 当前错误消息可以更清晰地指出问题 - 建议: 在错误消息中显示实际的 DOM 结构片段 ### 文档改进建议 1. **更新 README 中的 Radix UI 版本说明**: - 当前文档未明确说明支持的 Radix UI Select 版本 - 建议添加版本兼容性说明 2. **添加常见问题排查指南**: - 当 listbox 未找到时的排查步骤 - 如何使用 browser devtools 检查 DOM 结构 ### 性能优化建议 1. **减少硬编码超时**: - 当前 `DEFAULT_TIMEOUTS.static = 2000` 可能不够 - 建议使用动态等待或配置化超时时间 2. **并行等待优化**: - 可以同时等待多个选择器,第一个成功即可 --- ## 测试环境信息 - **Node.js**: 20.19.2 - **Playwright**: 1.55.0 - **@radix-ui/react-select**: ^2.2.5 - **@d8d/e2e-test-utils**: 版本待确认 --- ## 下一步行动 1. **高优先级**: 修复问题 #1(listbox 依赖),使测试能够运行 2. **高优先级**: 应用相同修复到问题 #2(async version) 3. **中优先级**: 验证修复后所有 6 个测试是否通过 4. **中优先级**: 收集使用体验反馈(API 易用性、错误消息等) 5. **低优先级**: 根据反馈更新文档和实现改进建议 --- ## 附录:完整测试输出 ``` Running 6 tests using 1 worker ========== 开始完整功能测试 ========== [步骤1] 打开新增残疾人对话框... ✓ 对话框已打开 [步骤2] 填写基本信息... [selectRadixOption] 开始选择: label="残疾类型 *", value="视力残疾" 选择器策略1失败: [data-testid="残疾类型 *-trigger"] 选择器策略2失败: [aria-label="残疾类型 *"][role="combobox"] 选择器策略3: 尝试 getByRole(combobox, { name: "残疾类型 *" }) 选择器策略3成功: 找到 combobox "残疾类型 *" [selectRadixOption] 找到触发器,准备点击 [selectRadixOption] 已点击触发器,等待 listbox ✘ 1 [chromium] › 残疾人管理 - 完整功能测试 › 完整流程:新增残疾人 TimeoutError: page.waitForSelector: Timeout 2000ms exceeded. Call log: - waiting for locator('[role=listbox]') to be visible 1 failed 5 did not run ```