用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/echoVic/boss-skill --skill frontend-testing-guide命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | frontend/testing-guide |
| description | 前端测试编写指南,包括单元测试、集成测试和E2E测试的编写方法和最佳实践 |
| type | methodology |
| agent | boss-frontend |
职责边界:Frontend Agent 是测试的编写者,QA Agent 是测试的验证者。
| 测试类型 | 占比 | 要求 |
|---|---|---|
| 单元测试 | ~70% | 每个组件/Hook 必须有测试 |
| 集成测试 | ~20% | 组件交互、状态管理测试 |
| E2E 测试 | ~10% | 必须编写,覆盖用户流程 |
// Button.test.tsx
import { render, screen } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('renders with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click</Button>);
screen.getByText('Click').click();
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('is disabled when disabled prop is true', () => {
render(<Button disabled>Click</Button>);
expect(screen.getByText('Click')).toBeDisabled();
});
});
// useCounter.test.ts
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';
describe('useCounter', () => {
it('initializes with default value', () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
});
it('increments count', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
});
// LoginForm.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from './LoginForm';
describe('LoginForm', () => {
it('shows validation error for invalid email', async () => {
render(<LoginForm />);
const emailInput = screen.getByLabelText('Email');
await userEvent.type(emailInput, 'invalid-email');
await userEvent.tab(); // Trigger blur
await waitFor(() => {
expect(screen.getByText('Invalid email format')).toBeInTheDocument();
});
});
it('submits form with valid data', async () => {
const onSubmit = jest.fn();
render(<LoginForm onSubmit={onSubmit} />);
await userEvent.(screen.(), );
userEvent.(screen.(), );
userEvent.(screen.(, { : }));
( {
(onSubmit).({
: ,
: ,
});
});
});
});
// UserList.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserList } from './UserList';
import { UserProvider } from './UserContext';
describe('UserList integration', () => {
it('adds new user to list', async () => {
render(
<UserProvider>
<UserList />
</UserProvider>
);
// 打开添加用户表单
await userEvent.click(screen.getByText('Add User'));
// 填写表单
await userEvent.type(screen.getByLabelText('Name'), 'John Doe');
await userEvent.type(screen.getByLabelText('Email'), 'john@example.com');
// 提交
await userEvent.click(screen.getByRole('button', { : }));
( {
(screen.()).();
(screen.()).();
});
});
});
// store.test.ts
import { renderHook, act } from '@testing-library/react';
import { useStore } from './store';
describe('Store integration', () => {
it('updates user state across components', () => {
const { result } = renderHook(() => useStore());
act(() => {
result.current.setUser({ id: '1', name: 'Alice' });
});
expect(result.current.user).toEqual({ id: '1', name: 'Alice' });
act(() => {
result.current.updateUserName('Bob');
});
expect(result.current.user?.name).toBe('Bob');
});
});
完整 Playwright 方法论:详见
Skill(skill: "qa/e2e-playwright"),包含项目初始化、Page Object Model、认证复用、API Mock、视觉回归、多浏览器测试、CI 集成和调试技巧。
// e2e/pages/user-list.page.ts
import { type Page, type Locator } from '@playwright/test';
export class UserListPage {
private readonly addButton: Locator;
private readonly table: Locator;
constructor(private readonly page: Page) {
this.addButton = page.getByRole('button', { name: '添加用户' });
this.table = page.getByRole('table');
}
async goto() { await this.page.goto('/users'); }
async clickAddUser() { await this.addButton.click(); }
getTable() { return this.; }
() {
..(, { name }).(, { : }).();
}
() {
..(, { name }).(, { : }).();
}
() {
..(, { : }).();
}
}
// e2e/specs/crud/user-management.spec.ts
import { test, expect } from '@playwright/test';
import { UserListPage } from '../../pages/user-list.page';
test.describe('用户管理 CRUD', () => {
let userList: UserListPage;
test.beforeEach(async ({ page }) => {
userList = new UserListPage(page);
await userList.goto();
});
test('创建 → 编辑 → 删除完整流程', async ({ page }) => {
// 创建
await userList.clickAddUser();
await page.getByLabel('姓名').fill('测试用户');
await page.getByLabel('邮箱').fill('test@example.com');
await page.getByRole('button', { name: '提交' }).click();
await expect(page.getByText('测试用户')).toBeVisible();
// 编辑
await userList.();
page.().();
page.(, { : }).();
(page.()).();
userList.();
userList.();
(page.())..();
});
(, ({ page }) => {
(userList.()).();
page.(, { : }).();
(page).();
});
});
| 优先级 | 方法 | 说明 |
|---|---|---|
| 1 | getByRole | 无障碍语义,最稳定 |
| 2 | getByLabel | 表单元素首选 |
| 3 | getByText | 静态文本 |
| 4 | getByTestId | 无语义标记时兜底 |
| 5 | CSS/XPath | 尽量避免 |
it('should [expected behavior] when [condition]')it('should show error message when email is invalid')// mocks/handlers.ts
import { rest } from 'msw';
export const handlers = [
rest.get('/api/users', (req, res, ctx) => {
return res(
ctx.json([
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Bob' },
])
);
}),
];
it('is accessible', async () => {
const { container } = render(<Button>Click</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
运行覆盖率报告:
npm test -- --coverage
实现完成后,在输出中包含:
测试添加:
| 类型 | 文件 | 描述 |
|---|---|---|
| 单元测试 | src/components/Button.test.tsx | Button 组件渲染和交互测试 |
| 集成测试 | src/features/users/UserList.test.tsx | 用户列表增删改查集成测试 |
| E2E 测试 | e2e/user-management.spec.ts | 用户管理完整流程 E2E 测试 |
测试结果: