# 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<void> {
     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
```
