|
|
hace 2 días | |
|---|---|---|
| .. | ||
| docs | hace 5 días | |
| src | hace 2 días | |
| tests | hace 5 días | |
| PACKAGING.md | hace 5 días | |
| README.md | hace 5 días | |
| eslint.config.js | hace 5 días | |
| package.json | hace 5 días | |
| tsconfig.json | hace 5 días | |
| vitest.config.ts | hace 5 días | |
E2E 测试工具集 - 专门用于测试 Radix UI 组件的 Playwright 工具函数
@d8d/e2e-test-utils 是一个专为 Radix UI 组件设计的 E2E 测试工具集。它提供了一组强大且易于使用的函数,帮助你更高效地编写和维护端到端测试。
pnpm add -D @d8d/e2e-test-utils@workspace:*
pnpm add -D @d8d/e2e-test-utils
确保你的项目已安装 @playwright/test:
pnpm add -D @playwright/test
确保你的 tsconfig.json 包含以下配置以获得最佳类型支持:
{
"compilerOptions": {
"moduleResolution": "bundler",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}
如果你使用 Node.js 16+,也可以使用 node16 或 nodenext 模块解析。
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, '城市', '深圳市');
});
工具函数按以下优先级查找下拉框触发器:
data-testid="标签-trigger" - 推荐,最稳定aria-label="标签" + role="combobox" - 无障碍属性text="标签" - 文本匹配(兜底)推荐做法:在 Radix Select 组件上添加 data-testid 属性以获得最佳稳定性。
<RadixSelect.Root>
<RadixSelect.Trigger data-testid="省份-trigger">
<RadixSelect.Value placeholder="选择省份" />
</RadixSelect.Trigger>
{/* ... */}
</RadixSelect.Root>
import { test, expect } from '@playwright/test';
import { BaseOptions, E2ETestError, DEFAULT_TIMEOUTS } from '@d8d/e2e-test-utils';
test('示例测试', async ({ page }) => {
// 使用工具函数进行测试
const options: BaseOptions = {
timeout: DEFAULT_TIMEOUTS.static
};
// 测试逻辑...
});
import type { BaseOptions, ErrorContext } from '@d8d/e2e-test-utils';
// 定义自定义选项
const options: BaseOptions = {
timeout: 5000
};
// 构建错误上下文
const errorContext: ErrorContext = {
operation: 'selectOption',
target: 'dropdown',
expected: 'Option A',
actual: 'Option B',
available: ['Option A', 'Option B', 'Option C'],
suggestion: '检查选项值是否正确'
};
import { E2ETestError } from '@d8d/e2e-test-utils';
test('错误处理示例', async ({ page }) => {
const selectElement = await page.$('[data-testid="my-select"]');
if (!selectElement) {
throw new E2ETestError({
operation: 'findSelect',
target: '[data-testid="my-select"]',
suggestion: '确认 data-testid 属性是否正确设置'
});
}
});
注意:测试数据文件(fixtures)仅在开发环境可用,不会随 npm 包发布。
Monorepo 开发环境:
import testUsers from '@d8d/e2e-test-utils/tests/fixtures/data/test-users.json' assert { type: 'json' };
test('用户登录', async ({ page }) => {
const user = testUsers.users[0];
await page.goto('/login');
await page.fill('[name="email"]', user.email);
await page.fill('[name="password"]', 'test_password');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
});
独立项目:
在您的项目中创建测试数据文件:
mkdir -p tests/fixtures/data
cat > tests/fixtures/data/test-users.json << 'EOF'
{
"users": [
{ "name": "Test User", "email": "test@example.com" }
]
}
EOF
然后导入使用:
import testUsers from './fixtures/data/test-users.json' assert { type: 'json' };
BaseOptions基础配置选项,所有工具函数配置对象的基类。
interface BaseOptions {
/** 超时时间(毫秒)*/
timeout?: number;
}
ErrorContext错误上下文信息,用于结构化错误报告。
interface ErrorContext {
/** 操作类型(如 'selectRadixOption')*/
operation: string;
/** 目标(如下拉框标签)*/
target: string;
/** 期望值 */
expected?: string;
/** 实际值 */
actual?: string;
/** 可用选项列表 */
available?: string[];
/** 修复建议 */
suggestion?: string;
}
E2ETestErrorE2E 测试专用错误类,提供结构化的错误上下文信息。
class E2ETestError extends Error {
constructor(
public readonly context: ErrorContext,
message?: string
)
}
示例:
throw new E2ETestError({
operation: 'selectOption',
target: 'Role Selector',
expected: 'Admin',
actual: 'User',
available: ['Admin', 'User', 'Guest'],
suggestion: '确认选项值是否正确拼写'
});
// 输出:
// ❌ selectOption failed
// Target: Role Selector
// Expected: Admin
// Actual: User
// Available: Admin, User, Guest
// 💡 确认选项值是否正确拼写
DEFAULT_TIMEOUTS默认超时配置(毫秒)。
const DEFAULT_TIMEOUTS = {
/** 静态选项超时 */
static: 2000,
/** 异步选项超时 */
async: 5000,
/** 网络空闲超时 */
networkIdle: 10000
} as const;
使用示例:
import { DEFAULT_TIMEOUTS } from '@d8d/e2e-test-utils';
await page.waitForTimeout(DEFAULT_TIMEOUTS.static);
SELECTOR_STRATEGIES支持的选择器策略列表。
const SELECTOR_STRATEGIES = [
'data-testid',
'aria-label + role',
'text content + role'
] as const;
策略优先级:
data-testid - 最高优先级,最稳定aria-label + role - 遵循无障碍标准text content + role - 兜底方案selectRadixOption()选择静态枚举型 Radix UI Select 选项。适用于选项在页面加载时已存在于 DOM 中的下拉框。
函数签名:
function selectRadixOption(
page: Page,
label: string,
value: string
): Promise<void>
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page |
Page |
✅ | Playwright Page 对象 |
label |
string |
✅ | 下拉框触发器的标签文本(data-testid/aria-label/文本内容) |
value |
string |
✅ | 要选择的选项值 |
使用场景:
示例:
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 动态加载的下拉框。
函数签名:
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 |
使用场景:
示例:
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 (selectRadixOption) |
异步 Select (selectRadixOptionAsync) |
|---|---|---|
| 选项加载时机 | 页面加载时已存在 DOM 中 | 点击触发器后 API 加载 |
| 使用场景 | 枚举类型(残疾类型、性别等) | 动态数据(省份、城市、银行等) |
| 等待策略 | 立即查找选项 | 等待网络请求 + 选项出现 |
| 默认超时 | 2000ms | 5000ms |
| 配置对象 | 无 | AsyncSelectOptions |
| 网络空闲等待 | 不需要 | 默认启用 |
| 函数签名 | selectRadixOption(page, label, value) |
selectRadixOptionAsync(page, label, value, options?) |
选择建议:
selectRadixOption()selectRadixOptionAsync()实际案例对比:
// 静态 Select - 残疾类型(枚举)
await selectRadixOption(page, '残疾类型', '视力残疾');
// ↓ 点击 → 立即查找选项 → 选择
// 异步 Select - 省份(API 加载)
await selectRadixOptionAsync(page, '省份', '广东省');
// ↓ 点击 → 等待网络请求 → 等待选项出现 → 选择
# 运行所有单元测试
pnpm test:unit
# 运行测试并生成覆盖率报告
pnpm test:coverage
# 监听模式(开发时使用)
pnpm test
tests/
├── fixtures/ # 测试资源
│ ├── data/ # 测试数据(JSON)
│ └── images/ # 测试图片
├── unit/ # 单元测试(Vitest)
├── integration/ # 集成测试(Playwright)
└── stability/ # 稳定性测试
// 导入测试用户数据
import testUsers from '@d8d/e2e-test-utils/tests/fixtures/data/test-users.json' assert { type: 'json' };
const user = testUsers.users[0];
console.log(user.name, user.email);
图片文件用于测试文件上传功能。请参考 tests/fixtures/images/README.md 了解如何添加测试图片。
packages/e2e-test-utils/
├── src/
│ ├── index.ts # 主导出,tree-shakeable
│ ├── types.ts # 共享类型定义
│ ├── errors.ts # 错误类和错误处理
│ ├── constants.ts # 常量定义
│ ├── radix-select.ts # Radix UI Select 工具
│ ├── file-upload.ts # 文件上传工具(规划中)
│ ├── form-helper.ts # 表单辅助函数(规划中)
│ ├── dialog.ts # 对话框操作(规划中)
│ └── dynamic-list.ts # 动态列表管理(规划中)
├── tests/
│ ├── fixtures/ # 测试资源
│ ├── unit/ # Vitest 单元测试
│ ├── integration/ # Playwright 集成测试
│ └── stability/ # 稳定性测试
├── package.json
├── tsconfig.json
├── vitest.config.ts
└── README.md
# 类型检查
pnpm typecheck
# 构建包
pnpm build
# 开发模式(监听文件变化)
pnpm dev
# 运行测试
pnpm test:unit
# 生成覆盖率报告
pnpm test:coverage
欢迎贡献!请遵循以下步骤:
git checkout -b feature/amazing-feature)git commit -m 'Add some amazing feature')git push origin feature/amazing-feature)MIT