|
|
@@ -41,8 +41,66 @@ pnpm add -D @d8d/e2e-test-utils
|
|
|
pnpm add -D @playwright/test
|
|
|
```
|
|
|
|
|
|
+### TypeScript 配置
|
|
|
+
|
|
|
+确保你的 `tsconfig.json` 包含以下配置以获得最佳类型支持:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "compilerOptions": {
|
|
|
+ "moduleResolution": "bundler",
|
|
|
+ "esModuleInterop": true,
|
|
|
+ "allowSyntheticDefaultImports": true
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+如果你使用 Node.js 16+,也可以使用 `node16` 或 `nodenext` 模块解析。
|
|
|
+
|
|
|
## 🚀 快速入门
|
|
|
|
|
|
+### Select 工具使用
|
|
|
+
|
|
|
+```typescript
|
|
|
+import { test, expect } from '@playwright/test';
|
|
|
+import { selectRadixOption, selectRadixOptionAsync } from '@d8d/e2e-test-utils';
|
|
|
+
|
|
|
+test('选择静态下拉框选项', async ({ page }) => {
|
|
|
+ await page.goto('/form');
|
|
|
+
|
|
|
+ // 选择静态下拉框(如残疾类型、性别等枚举类型)
|
|
|
+ await selectRadixOption(page, '残疾类型', '视力残疾');
|
|
|
+ await selectRadixOption(page, '性别', '男');
|
|
|
+});
|
|
|
+
|
|
|
+test('选择异步加载的下拉框选项', async ({ page }) => {
|
|
|
+ await page.goto('/form');
|
|
|
+
|
|
|
+ // 选择异步加载的下拉框(如省份、城市、银行等动态数据)
|
|
|
+ await selectRadixOptionAsync(page, '省份', '广东省');
|
|
|
+ await selectRadixOptionAsync(page, '城市', '深圳市');
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+### 选择器策略
|
|
|
+
|
|
|
+工具函数按以下优先级查找下拉框触发器:
|
|
|
+
|
|
|
+1. **`data-testid="标签-trigger"`** - 推荐,最稳定
|
|
|
+2. **`aria-label="标签"` + `role="combobox"`** - 无障碍属性
|
|
|
+3. **`text="标签"`** - 文本匹配(兜底)
|
|
|
+
|
|
|
+**推荐做法**:在 Radix Select 组件上添加 `data-testid` 属性以获得最佳稳定性。
|
|
|
+
|
|
|
+```tsx
|
|
|
+<RadixSelect.Root>
|
|
|
+ <RadixSelect.Trigger data-testid="省份-trigger">
|
|
|
+ <RadixSelect.Value placeholder="选择省份" />
|
|
|
+ </RadixSelect.Trigger>
|
|
|
+ {/* ... */}
|
|
|
+</RadixSelect.Root>
|
|
|
+```
|
|
|
+
|
|
|
### 基础使用
|
|
|
|
|
|
```typescript
|
|
|
@@ -100,14 +158,12 @@ test('错误处理示例', async ({ page }) => {
|
|
|
|
|
|
### 使用测试数据
|
|
|
|
|
|
-**注意**:测试数据文件(fixtures)不会随包发布,需要复制到您的项目中使用。
|
|
|
+**注意**:测试数据文件(fixtures)仅在开发环境可用,不会随 npm 包发布。
|
|
|
|
|
|
-```bash
|
|
|
-# 1. 将测试数据复制到您的项目中
|
|
|
-cp -r node_modules/@d8d/e2e-test-utils/tests/fixtures ./tests/
|
|
|
+**Monorepo 开发环境:**
|
|
|
|
|
|
-# 2. 在测试中使用相对路径导入
|
|
|
-import testUsers from './fixtures/data/test-users.json' assert { type: 'json' };
|
|
|
+```typescript
|
|
|
+import testUsers from '@d8d/e2e-test-utils/tests/fixtures/data/test-users.json' assert { type: 'json' };
|
|
|
|
|
|
test('用户登录', async ({ page }) => {
|
|
|
const user = testUsers.users[0];
|
|
|
@@ -121,10 +177,25 @@ test('用户登录', async ({ page }) => {
|
|
|
});
|
|
|
```
|
|
|
|
|
|
-**或者直接从源码引用(仅 Monorepo 开发环境):**
|
|
|
+**独立项目:**
|
|
|
+
|
|
|
+在您的项目中创建测试数据文件:
|
|
|
+
|
|
|
+```bash
|
|
|
+mkdir -p tests/fixtures/data
|
|
|
+cat > tests/fixtures/data/test-users.json << 'EOF'
|
|
|
+{
|
|
|
+ "users": [
|
|
|
+ { "name": "Test User", "email": "test@example.com" }
|
|
|
+ ]
|
|
|
+}
|
|
|
+EOF
|
|
|
+```
|
|
|
+
|
|
|
+然后导入使用:
|
|
|
|
|
|
```typescript
|
|
|
-import testUsers from '@d8d/e2e-test-utils/tests/fixtures/data/test-users.json' assert { type: 'json' };
|
|
|
+import testUsers from './fixtures/data/test-users.json' assert { type: 'json' };
|
|
|
```
|
|
|
|
|
|
## 📚 API 文档
|
|
|
@@ -240,6 +311,145 @@ const SELECTOR_STRATEGIES = [
|
|
|
2. `aria-label + role` - 遵循无障碍标准
|
|
|
3. `text content + role` - 兜底方案
|
|
|
|
|
|
+---
|
|
|
+
|
|
|
+### Radix UI Select 工具
|
|
|
+
|
|
|
+#### `selectRadixOption()`
|
|
|
+
|
|
|
+选择静态枚举型 Radix UI Select 选项。适用于选项在页面加载时已存在于 DOM 中的下拉框。
|
|
|
+
|
|
|
+**函数签名:**
|
|
|
+
|
|
|
+```typescript
|
|
|
+function selectRadixOption(
|
|
|
+ page: Page,
|
|
|
+ label: string,
|
|
|
+ value: string
|
|
|
+): Promise<void>
|
|
|
+```
|
|
|
+
|
|
|
+**参数:**
|
|
|
+
|
|
|
+| 参数 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| `page` | `Page` | ✅ | Playwright Page 对象 |
|
|
|
+| `label` | `string` | ✅ | 下拉框触发器的标签文本(data-testid/aria-label/文本内容) |
|
|
|
+| `value` | `string` | ✅ | 要选择的选项值 |
|
|
|
+
|
|
|
+**使用场景:**
|
|
|
+- 枚举类型选择(残疾类型、性别、婚姻状况等)
|
|
|
+- 静态配置选项
|
|
|
+- 选项在页面加载时已存在 DOM 中
|
|
|
+
|
|
|
+**示例:**
|
|
|
+
|
|
|
+```typescript
|
|
|
+import { selectRadixOption } from '@d8d/e2e-test-utils';
|
|
|
+
|
|
|
+test('填写残疾人信息', async ({ page }) => {
|
|
|
+ await page.goto('/disabled-person-form');
|
|
|
+
|
|
|
+ // 选择残疾类型
|
|
|
+ await selectRadixOption(page, '残疾类型', '视力残疾');
|
|
|
+
|
|
|
+ // 选择性别
|
|
|
+ await selectRadixOption(page, '性别', '男');
|
|
|
+
|
|
|
+ // 选择婚姻状况
|
|
|
+ await selectRadixOption(page, '婚姻状况', '未婚');
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+#### `selectRadixOptionAsync()`
|
|
|
+
|
|
|
+选择异步加载的 Radix UI Select 选项。适用于选项需要从 API 动态加载的下拉框。
|
|
|
+
|
|
|
+**函数签名:**
|
|
|
+
|
|
|
+```typescript
|
|
|
+function selectRadixOptionAsync(
|
|
|
+ page: Page,
|
|
|
+ label: string,
|
|
|
+ value: string,
|
|
|
+ options?: AsyncSelectOptions
|
|
|
+): Promise<void>
|
|
|
+```
|
|
|
+
|
|
|
+**参数:**
|
|
|
+
|
|
|
+| 参数 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| `page` | `Page` | ✅ | Playwright Page 对象 |
|
|
|
+| `label` | `string` | ✅ | 下拉框触发器的标签文本 |
|
|
|
+| `value` | `string` | ✅ | 要选择的选项值 |
|
|
|
+| `options` | `AsyncSelectOptions` | ❌ | 可选配置对象 |
|
|
|
+| `options.timeout` | `number` | ❌ | 超时时间(毫秒),默认 5000 |
|
|
|
+| `options.waitForOption` | `boolean` | ❌ | 是否等待选项加载,默认 true |
|
|
|
+| `options.waitForNetworkIdle` | `boolean` | ❌ | 是否等待网络空闲,默认 true |
|
|
|
+
|
|
|
+**使用场景:**
|
|
|
+- 动态数据选择(省份、城市、银行等)
|
|
|
+- 选项从 API 加载
|
|
|
+- 需要等待网络请求完成
|
|
|
+
|
|
|
+**示例:**
|
|
|
+
|
|
|
+```typescript
|
|
|
+import { selectRadixOptionAsync } from '@d8d/e2e-test-utils';
|
|
|
+
|
|
|
+test('填写地址信息', async ({ page }) => {
|
|
|
+ await page.goto('/address-form');
|
|
|
+
|
|
|
+ // 选择省份(使用默认配置)
|
|
|
+ await selectRadixOptionAsync(page, '省份', '广东省');
|
|
|
+
|
|
|
+ // 选择城市(自定义超时时间)
|
|
|
+ await selectRadixOptionAsync(page, '城市', '深圳市', {
|
|
|
+ timeout: 10000
|
|
|
+ });
|
|
|
+
|
|
|
+ // 选择银行(禁用网络空闲等待,适用于网络不稳定环境)
|
|
|
+ await selectRadixOptionAsync(page, '开户银行', '中国工商银行', {
|
|
|
+ waitForNetworkIdle: false
|
|
|
+ });
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 静态 Select vs 异步 Select
|
|
|
+
|
|
|
+| 特性 | 静态 Select (`selectRadixOption`) | 异步 Select (`selectRadixOptionAsync`) |
|
|
|
+|------|----------------------------------|----------------------------------------|
|
|
|
+| **选项加载时机** | 页面加载时已存在 DOM 中 | 点击触发器后 API 加载 |
|
|
|
+| **使用场景** | 枚举类型(残疾类型、性别等) | 动态数据(省份、城市、银行等) |
|
|
|
+| **等待策略** | 立即查找选项 | 等待网络请求 + 选项出现 |
|
|
|
+| **默认超时** | 2000ms | 5000ms |
|
|
|
+| **配置对象** | 无 | `AsyncSelectOptions` |
|
|
|
+| **网络空闲等待** | 不需要 | 默认启用 |
|
|
|
+| **函数签名** | `selectRadixOption(page, label, value)` | `selectRadixOptionAsync(page, label, value, options?)` |
|
|
|
+
|
|
|
+**选择建议:**
|
|
|
+
|
|
|
+- ✅ 如果下拉框选项在页面加载时已存在 → 使用 `selectRadixOption()`
|
|
|
+- ✅ 如果下拉框选项需要从 API 动态加载 → 使用 `selectRadixOptionAsync()`
|
|
|
+- 🤔 不确定时,可以使用异步版本(会有额外等待,但更稳定)
|
|
|
+
|
|
|
+**实际案例对比:**
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 静态 Select - 残疾类型(枚举)
|
|
|
+await selectRadixOption(page, '残疾类型', '视力残疾');
|
|
|
+// ↓ 点击 → 立即查找选项 → 选择
|
|
|
+
|
|
|
+// 异步 Select - 省份(API 加载)
|
|
|
+await selectRadixOptionAsync(page, '省份', '广东省');
|
|
|
+// ↓ 点击 → 等待网络请求 → 等待选项出现 → 选择
|
|
|
+```
|
|
|
+
|
|
|
## 🧪 测试
|
|
|
|
|
|
### 运行测试
|
|
|
@@ -294,7 +504,7 @@ packages/e2e-test-utils/
|
|
|
│ ├── types.ts # 共享类型定义
|
|
|
│ ├── errors.ts # 错误类和错误处理
|
|
|
│ ├── constants.ts # 常量定义
|
|
|
-│ ├── radix-select.ts # Radix UI Select 工具(开发中)
|
|
|
+│ ├── radix-select.ts # Radix UI Select 工具
|
|
|
│ ├── file-upload.ts # 文件上传工具(规划中)
|
|
|
│ ├── form-helper.ts # 表单辅助函数(规划中)
|
|
|
│ ├── dialog.ts # 对话框操作(规划中)
|
|
|
@@ -352,7 +562,6 @@ MIT
|
|
|
|
|
|
## 🔗 相关资源
|
|
|
|
|
|
-- [Radix UI](https://www.radix-ui.com/)
|
|
|
-- [Playwright](https://playwright.dev/)
|
|
|
-- [E2E Radix UI 测试标准](../../docs/standards/e2e-radix-testing.md)
|
|
|
-- [项目测试规范](../../docs/standards/testing-standards.md)
|
|
|
+- [Radix UI](https://www.radix-ui.com/) - 无障碍 UI 组件库
|
|
|
+- [Playwright](https://playwright.dev/) - E2E 测试框架
|
|
|
+- [TypeScript](https://www.typescriptlang.org/) - 类型安全
|