Tests de composants React
Testez les composants React avec React Testing Library et Playwright. Couvrez les tests unitaires, les interactions utilisateur et les tests E2E.
Spar Skills Guide Bot
TestingIntermédiaire1 vues0 installations04/03/2026Claude CodeCursorCopilot
reacttesting-libraryplaywrightunit-testinge2e-testing
name: component-testing description: Test React components with React Testing Library and Playwright. Use when building UI components or verifying frontend functionality. allowed-tools: Read, Write, Bash, Glob
You help test React components and pages for the QA Team Portal frontend using React Testing Library and Playwright.
When to Use This Skill
- Testing React components after creation
- Writing unit tests for component logic
- Testing user interactions (clicks, typing, form submission)
- E2E testing of complete user flows
- Accessibility testing
- Visual regression testing
Testing Approaches
1. Unit Tests with React Testing Library
Basic Component Test
// tests/unit/components/TeamMemberCard.test.tsx
import { render, screen } from '@testing-library/react'
import { TeamMemberCard } from '@/components/public/TeamIntro/TeamMemberCard'
const mockMember = {
id: '123',
name: 'John Doe',
role: 'QA Lead',
email: 'john@example.com',
profilePhotoUrl: '/path/to/photo.jpg'
}
describe('TeamMemberCard', () => {
it('renders member name and role', () => {
render(<TeamMemberCard member={mockMember} />)
expect(screen.getByText('John Doe')).toBeInTheDocument()
expect(screen.getByText('QA Lead')).toBeInTheDocument()
})
it('displays profile photo with alt text', () => {
render(<TeamMemberCard member={mockMember} />)
const img = screen.getByRole('img', { name: /john doe/i })
expect(img).toHaveAttribute('src', mockMember.profilePhotoUrl)
})
it('shows email link when provided', () => {
render(<TeamMemberCard member={mockMember} />)
const emailLink = screen.getByRole('link', { name: /email/i })
expect(emailLink).toHaveAttribute('href', 'mailto:john@example.com')
})
})
Testing User Interactions
import { render, screen, fireEvent } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { UpdatesModal } from '@/components/public/Updates/UpdateModal'
describe('UpdatesModal', () => {
it('closes modal when close button clicked', async () => {
const onClose = vi.fn()
render(<UpdatesModal isOpen={true} onClose={onClose} update={mockUpdate} />)
const closeButton = screen.getByRole('button', { name: /close/i })
await userEvent.click(closeButton)
expect(onClose).toHaveBeenCalledTimes(1)
})
it('closes modal on escape key press', async () => {
const onClose = vi.fn()
render(<UpdatesModal isOpen={true} onClose={onClose} update={mockUpdate} />)
fireEvent.keyDown(document, { key: 'Escape' })
expect(onClose).toHaveBeenCalled()
})
})
Testing Forms
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { LoginForm } from '@/components/admin/auth/LoginForm'
describe('LoginForm', () => {
it('submits form with valid data', async () => {
const onSubmit = vi.fn()
render(<LoginForm onSubmit={onSubmit} />)
await userEvent.type(
screen.getByLabelText(/email/i),
'admin@test.com'
)
await userEvent.type(
screen.getByLabelText(/password/i),
'password123'
)
await userEvent.click(screen.getByRole('button', { name: /login/i }))
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
email: 'admin@test.com',
password: 'password123'
})
})
})
it('shows validation errors for invalid email', async () => {
render(<LoginForm onSubmit={vi.fn()} />)
await userEvent.type(
screen.getByLabelText(/email/i),
'invalid-email'
)
await userEvent.click(screen.getByRole('button', { name: /login/i }))
await waitFor(() => {
expect(screen.getByText(/invalid email/i)).toBeInTheDocument()
})
})
})
Testing API Integration
import { render, screen, waitFor } from '@testing-library/react'
import { TeamList } from '@/components/public/TeamIntro/TeamList'
import { rest } from 'msw'
import { setupServer } from 'msw/node'
const mockTeamMembers = [
{ id: '1', name: 'John Doe', role: 'QA Lead' },
{ id: '2', name: 'Jane Smith', role: 'QA Engineer' }
]
const server = setupServer(
rest.get('/api/v1/team-members', (req, res, ctx) => {
return res(ctx.json(mockTeamMembers))
})
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
describe('TeamList', () => {
it('displays loading state initially', () => {
render(<TeamList />)
expect(screen.getByRole('status')).toBeInTheDocument()
})
it('displays team members after loading', async () => {
render(<TeamList />)
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument()
expect(screen.getByText('Jane Smith')).toBeInTheDocument()
})
})
it('displays error message on API failure', async () => {
server.use(
rest.get('/api/v1/team-members', (req, res, ctx) => {
return res(ctx.status(500))
})
)
render(<TeamList />)
await waitFor(() => {
expect(screen.getByText(/error loading/i)).toBeInTheDocument()
})
})
})
2. E2E Tests with Playwright
Setup Playwright
// playwright.config.ts
import { defineConfig } from '@playwright/test'
export default defineConfig({
testDir: './tests/e2e',
use: {
baseURL: 'http://localhost:5173',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
webServer: {
command: 'npm run dev',
port: 5173,
reuseExistingServer: !process.env.CI,
},
})
Basic E2E Test
// tests/e2e/landing-page.spec.ts
import { test, expect } from '@playwright/test'
test.describe('Landing Page', () => {
test('displays all sections', async ({ page }) => {
await page.goto('/')
// Check all sections are visible
await expect(page.getByRole('heading', { name: /team introduction/i })).toBeVisible()
await expect(page.getByRole('heading', { name: /latest updates/i })).toBeVisible()
await expect(page.getByRole('heading', { name: /tools/i })).toBeVisible()
await expect(page.getByRole('heading', { name: /resources/i })).toBeVisible()
await expect(page.getByRole('heading', { name: /research/i })).toBeVisible()
})
test('navigation links scroll to sections', async ({ page }) => {
await page.goto('/')
// Click tools nav link
await page.getByRole('link', { name: /tools/i }).click()
// Check tools section is in view
const toolsSection = page.getByRole('heading', { name: /tools/i })
await expect(toolsSection).toBeInViewport()
})
})
Testing User Flows
// tests/e2e/admin-login.spec.ts
import { test, expect } from '@playwright/test'
test.describe('Admin Login', () => {
test('admin can login and access dashboard', async ({ page }) => {
// Navigate to login page
await page.goto('/admin/login')
// Fill login form
await page.getByLabel(/email/i).fill('admin@test.com')
await page.getByLabel(/password/i).fill('testpass123')
// Submit form
await page.getByRole('button', { name: /login/i }).click()
// Wait for redirect to dashboard
await expect(page).toHaveURL('/admin/dashboard')
// Check dashboard loads
await expect(page.getByRole('heading', { name: /dashboard/i })).toBeVisible()
})
test('shows error for invalid credentials', async ({ page }) => {
await page.goto('/admin/login')
await page.getByLabel(/email/i).fill('wrong@test.com')
await page.getByLabel(/password/i).fill('wrongpass')
await page.getByRole('button', { name: /login/i }).click()
// Check error message appears
await expect(page.getByText(/invalid credentials/i)).toBeVisible()
})
})
Testing CRUD Operations
// tests/e2e/team-management.spec.ts
import { test, expect } from '@playwright/test'
test.describe('Team Management', () => {
test.beforeEach(async ({ page }) => {
// Login as admin
await page.goto('/admin/login')
await page.getByLabel(/email/i).fill('admin@test.com')
await page.getByLabel(/password/i).fill('testpass123')
await page.getByRole('button', { name: /login/i }).click()
await page.waitForURL('/admin/dashboard')
// Navigate to team management
await page.getByRole('link', { name: /team members/i }).click()
})
test('can create new team member', async ({ page }) => {
await page.getByRole('button', { name: /add member/i }).click()
// Fill form
await page.getByLabel(/name/i).fill('New Member')
await page.getByLabel(/role/i).fill('QA Engineer')
await page.getByLabel(/email/i).fill('new@test.com')
// Upload photo
await page.getByLabel(/photo/i).setInputFiles('./tests/fixtures/profile.jpg')
// Submit
await page.getByRole('button', { name: /save/i }).click()
// Verify success message
await expect(page.getByText(/member created successfully/i)).toBeVisible()
// Verify appears in list
await expect(page.getByText('New Member')).toBeVisible()
})
test('can edit existing team member', async ({ page }) => {
// Click edit button for first member
await page.getByRole('row').first().getByRole('button', { name: /edit/i }).click()
// Update name
await page.getByLabel(/name/i).clear()
await page.getByLabel(/name/i).fill('Updated Name')
// Save
await page.getByRole('button', { name: /save/i }).click()
// Verify updated
await expect(page.getByText('Updated Name')).toBeVisible()
})
test('can delete team member', async ({ page }) => {
// Get initial count
const initialCount = await page.getByRole('row').count()
// Delete first member
await page.getByRole('row').first().getByRole('button', { name: /delete/i }).click()
// Confirm deletion
await page.getByRole('button', { name: /confirm/i }).click()
// Verify count decreased
const newCount = await page.getByRole('row').count()
expect(newCount).toBe(initialCount - 1)
})
})
3. Accessibility Testing
// tests/e2e/accessibility.spec.ts
import { test, expect } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'
test.describe('Accessibility', () => {
test('landing page has no accessibility violations', async ({ page }) => {
await page.goto('/')
const accessibilityScanResults = await new AxeBuilder({ page }).analyze()
expect(accessibilityScanResults.violations).toEqual([])
})
test('admin dashboard has no accessibility violations', async ({ page }) => {
// Login first
await page.goto('/admin/login')
await page.getByLabel(/email/i).fill('admin@test.com')
await page.getByLabel(/password/i).fill('testpass123')
await page.getByRole('button', { name: /login/i }).click()
await page.waitForURL('/admin/dashboard')
const accessibilityScanResults = await new AxeBuilder({ page }).analyze()
expect(accessibilityScanResults.violations).toEqual([])
})
})
Running Tests
Vitest (Unit Tests)
cd frontend
# Run all unit tests
npm run test
# Run in watch mode
npm run test:watch
# Run with coverage
npm run test:coverage
# Run specific file
npm run test -- TeamMemberCard.test.tsx
# Run with UI
npm run test:ui
Playwright (E2E Tests)
cd frontend
# Install browsers (first time)
npx playwright install
# Run all E2E tests
npx playwright test
# Run in UI mode
npx playwright test --ui
# Run specific test file
npx playwright test tests/e2e/landing-page.spec.ts
# Run in headed mode (see browser)
npx playwright test --headed
# Run in debug mode
npx playwright test --debug
# Run on specific browser
npx playwright test --project=chromium
# Generate test code
npx playwright codegen http://localhost:5173
Test Configuration
Vitest Setup (vitest.config.ts)
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './tests/setup.ts',
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})
Test Setup File (tests/setup.ts)
import '@testing-library/jest-dom'
import { cleanup } from '@testing-library/react'
import { afterEach, vi } from 'vitest'
// Cleanup after each test
afterEach(() => {
cleanup()
})
// Mock window.matchMedia
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
})
Test Checklist
For each component, verify:
- [ ] Rendering - Component renders without errors
- [ ] Props - Handles different prop combinations
- [ ] User interactions - Clicks, typing, form submission work
- [ ] Loading states - Shows loading indicators
- [ ] Error states - Shows error messages
- [ ] Empty states - Handles no data gracefully
- [ ] Accessibility - ARIA labels, keyboard navigation, screen readers
- [ ] Responsiveness - Works on mobile/tablet/desktop
- [ ] Edge cases - Null values, long text, special characters
Common Testing Patterns
Testing Hooks
import { renderHook, waitFor } from '@testing-library/react'
import { useTeamMembers } from '@/hooks/useTeamMembers'
test('useTeamMembers fetches data', async () => {
const { result } = renderHook(() => useTeamMembers())
expect(result.current.loading).toBe(true)
await waitFor(() => {
expect(result.current.loading).toBe(false)
expect(result.current.data).toHaveLength(2)
})
})
Testing Context
import { render, screen } from '@testing-library/react'
import { AuthProvider } from '@/contexts/AuthContext'
import { ProtectedComponent } from '@/components/ProtectedComponent'
test('shows content when authenticated', () => {
render(
<AuthProvider value={{ user: mockUser, isAuthenticated: true }}>
<ProtectedComponent />
</AuthProvider>
)
expect(screen.getByText(/protected content/i)).toBeInTheDocument()
})
Output Format
After testing, report:
- Tests Run: X passed, Y failed
- Coverage: X% of components/lines covered
- Failed Tests: List with error messages
- Accessibility Issues: WCAG violations found
- Performance: Slow-rendering components
- Recommendations: Suggested improvements
Best Practices
- Test user behavior, not implementation details
- Use semantic queries (getByRole, getByLabel, getByText)
- Avoid testing IDs or classes when possible
- Test accessibility (keyboard navigation, screen readers)
- Mock external dependencies (API calls, localStorage)
- Keep tests independent - no shared state
- Use descriptive test names - what you're testing and expected outcome
- Test error scenarios - not just happy path
Skills similaires
TDD Red-Green-Refactor
100
Skill qui guide Claude a travers le cycle TDD complet.
Claude CodeWindsurfintermediate
6501891882Admin
Audit d'Accessibilité Web
100
Réalise un audit d'accessibilité web complet selon les normes WCAG.
claudeCursorWindsurfintermediate
12937312Admin
Générateur de Tests UAT
100
Génère des cas de test d'acceptation utilisateur structurés et complets.
claudechatgptintermediate
8724226Admin