| 版本 | 日期 | 描述 | 作者 |
|---|---|---|---|
| 1.0 | 2025-12-26 | 从测试策略文档拆分,专注Web UI包测试 | James (Claude Code) |
本文档定义了Web UI包的测试标准和最佳实践,包括:
@testing-library/react、@testing-library/user-event)@happy-dom/global-registrator)packages/*-ui/tests/**/*.test.tsx 或 web/tests/unit/client/**/*.test.tsxweb/tests/integration/**/*.test.tsxweb/tests/e2e/**/*.test.tspackages/user-management-ui/
├── src/
│ ├── components/
│ │ ├── UserTable.tsx
│ │ └── UserForm.tsx
│ └── index.ts
└── tests/
├── unit/
│ ├── UserTable.test.tsx
│ └── UserForm.test.tsx
└── integration/
└── UserManagementFlow.test.tsx
web/tests/
├── unit/
│ └── client/
│ ├── pages/
│ │ └── Users.test.tsx
│ └── components/
│ └── DataTablePagination.test.tsx
├── integration/
│ └── client/
│ └── user-management.test.tsx
└── e2e/
├── login.spec.ts
└── user-management.spec.ts
// ✅ 推荐:从用户角度测试
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('应该允许用户创建新用户', async () => {
const user = userEvent.setup();
render(<UserForm />);
// 填写表单
await user.type(screen.getByLabelText(/用户名/), 'testuser');
await user.type(screen.getByLabelText(/邮箱/), 'test@example.com');
// 提交表单
await user.click(screen.getByRole('button', { name: /创建/ }));
// 验证结果
expect(screen.getByText(/创建成功/)).toBeInTheDocument();
});
// ❌ 避免:测试实现细节
test('submitButton onClick 应该被调用', () => {
const mockOnClick = vi.fn();
render(<Button onClick={mockOnClick}>提交</Button>);
fireEvent.click(screen.getByText('提交'));
expect(mockOnClick).toHaveBeenCalled();
});
// 组件代码
<Button data-testid="submit-button" type="submit">提交</Button>
// 测试代码
const submitButton = screen.getByTestId('submit-button');
await user.click(submitButton);
import { vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import { UserForm } from './UserForm';
// 模拟RPC客户端
vi.mock('@d8d/shared-ui-components/utils/hc', () => ({
rpcClient: vi.fn(() => ({
users: {
create: {
$post: vi.fn(() => Promise.resolve({
status: 201,
json: async () => ({ id: 1, username: 'testuser' })
}))
}
}
}))
}));
test('应该显示加载状态', async () => {
render(<UserList />);
// 初始加载状态
expect(screen.getByText(/加载中/)).toBeInTheDocument();
// 等待数据加载完成
await waitFor(() => {
expect(screen.getByText(/用户列表/)).toBeInTheDocument();
});
});
// ✅ 推荐:使用真实组件,模拟API
vi.mock('@d8d/shared-ui-components/utils/hc', () => ({
rpcClient: vi.fn(() => ({
users: {
$get: vi.fn(() => Promise.resolve({
status: 200,
json: async () => ({ data: mockUsers })
}))
}
}))
}));
// ❌ 避免:模拟整个组件
vi.mock('@d8d/user-management-ui', () => ({
UserTable: () => <div data-testid="mock-user-table" />
}));
const mockUsers = [
{
id: 1,
username: 'testuser',
email: 'test@example.com',
role: 'user',
createdAt: '2025-01-01T00:00:00.000Z',
// 包含真实组件需要的所有字段
}
];
test('应该成功创建用户并刷新列表', async () => {
const user = userEvent.setup();
render(<UserManagementPage />);
// 打开创建表单
await user.click(screen.getByTestId('create-user-button'));
// 填写表单
await user.type(screen.getByLabelText(/用户名/), 'newuser');
await user.type(screen.getByLabelText(/邮箱/), 'new@example.com'));
// 提交表单
await user.click(screen.getByRole('button', { name: /创建/ }));
// 验证成功消息
await waitFor(() => {
expect(screen.getByText(/创建成功/)).toBeInTheDocument();
});
// 验证列表刷新
await waitFor(() => {
expect(screen.getByText('newuser')).toBeInTheDocument();
});
});
import { completeRadixSelectFlow } from '@d8d/shared-ui-components/tests/utils';
// 处理Radix UI Select组件的完整选择流程
await completeRadixSelectFlow('role-selector', 'admin', { useFireEvent: true });
// tests/e2e/pages/login.page.ts
export class LoginPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto('/login');
}
async login(username: string, password: string) {
await this.page.fill('input[name="username"]', username);
await this.page.fill('input[name="password"]', password);
await this.page.click('button[type="submit"]');
}
async expectWelcomeMessage() {
await expect(this.page.getByText(/欢迎/)).toBeVisible();
}
}
// 测试文件
test('用户登录流程', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('testuser', 'password');
await loginPage.expectWelcomeMessage();
});
// ✅ 推荐:使用语义化定位器
await page.click('button:has-text("提交")');
await page.fill('input[placeholder="请输入用户名"]', 'testuser');
await page.click('[data-testid="submit-button"]');
// ❌ 避免:使用不稳定的CSS选择器
await page.click('.btn-primary');
// 等待元素可见
await page.waitForSelector('[data-testid="user-list"]');
// 等待网络请求完成
await page.waitForLoadState('networkidle');
// 等待特定条件
await page.waitForURL('/dashboard');
[ComponentName].test.tsx[feature].integration.test.tsx[feature].spec.tsdescribe('UserForm', () => {
describe('表单验证', () => {
it('应该验证必填字段', async () => { });
it('应该验证邮箱格式', async () => { });
});
describe('表单提交', () => {
it('应该成功创建用户', async () => { });
it('应该处理网络错误', async () => { });
});
});
// 错误:测试useState调用
expect(useState).toHaveBeenCalledWith([]);
// 正确:测试渲染结果
expect(screen.getByText('用户列表')).toBeInTheDocument();
// 错误:模拟整个组件库
vi.mock('@d8d/shared-ui-components', () => ({
Button: () => <button data-testid="mock-button" />
}));
// 正确:使用真实组件,模拟其依赖
// 错误:不等待异步操作
fireEvent.click(submitButton);
expect(successMessage).toBeInTheDocument();
// 正确:等待异步操作完成
await user.click(submitButton);
await waitFor(() => {
expect(screen.getByText(/成功/)).toBeInTheDocument();
});
| 测试类型 | 最低要求 | 目标要求 |
|---|---|---|
| 组件测试 | 70% | 80% |
| 集成测试 | 50% | 60% |
| E2E测试 | 关键流程100% | 主要流程80% |
# 运行所有测试
pnpm test
# 运行组件测试
pnpm test:components
# 运行集成测试
pnpm test:integration
# 运行E2E测试
pnpm test:e2e:chromium
# 生成覆盖率报告
pnpm test:coverage
# 运行特定测试
pnpm test --testNamePattern="UserForm"
web-component-tests:
runs-on: ubuntu-latest
steps:
- run: cd web && pnpm test:components
web-integration-tests:
runs-on: ubuntu-latest
steps:
- run: cd web && pnpm test:integration
web-e2e-tests:
runs-on: ubuntu-latest
steps:
- run: cd web && pnpm test:e2e:chromium
# 运行特定测试并显示详细信息
pnpm test --testNamePattern="UserForm" --reporter=verbose
# 在浏览器中打开调试器
pnpm test:components --debug
// 在测试中打印DOM结构
screen.debug();
// 打印特定元素
screen.debug(screen.getByTestId('user-table'));
// 使用调试模式
test('调试模式', async ({ page }) => {
await page.goto('/users');
await page.pause(); // 暂停执行,打开Playwright Inspector
});
packages/user-management-uipackages/file-management-uiweb/tests/文档状态: 正式版 适用范围: Web UI包和Web应用