Frontend Testing Toolkit
A complete testing infrastructure for React and Next.js applications that covers every layer of the testing pyramid. Includes pre-configured Vitest for lightning-fast unit tests, Testing Library patterns for component integration tests, Playwright scripts for cross-browser E2E testing, and Storybook interaction tests for visual regression. Ships with 50+ test templates, custom matchers, mock factories, and CI/CD workflows — so you can go from zero test coverage to 80%+ in a weekend, not a quarter.
Key Features
- Vitest Configuration — Optimized config with path aliases, coverage thresholds, watch mode, and React/JSX transform via SWC
-
Testing Library Patterns — 30+ component test templates using user-centric queries (
getByRole,findByText) instead of implementation details - Playwright E2E Suite — Cross-browser test scripts for authentication flows, form submissions, navigation, and responsive behavior
- Storybook Integration — Interaction tests that run inside stories, combining visual review with automated assertions
- Mock Factory System — Type-safe factories for generating test data with overrides, relations, and sequences
-
Custom Test Matchers — Domain-specific matchers like
toBeAccessible(),toHaveFormError(), andtoMatchRouteSnapshot() - CI/CD Workflows — GitHub Actions pipelines for unit tests, E2E tests, visual regression, and coverage reporting
- MSW Request Mocking — Mock Service Worker handlers for API mocking in tests and Storybook without changing application code
Quick Start
- Install the testing stack:
npm install -D vitest @testing-library/react @testing-library/jest-dom \
@testing-library/user-event @playwright/test msw jsdom
- Copy
testing/into your project and add the Vitest config:
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react-swc';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./testing/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'lcov', 'html'],
thresholds: { statements: 80, branches: 75, functions: 80, lines: 80 },
},
include: ['src/**/*.test.{ts,tsx}'],
},
});
- Run your first test:
npx vitest run # single run
npx vitest --watch # watch mode
npx vitest --coverage # with coverage report
Architecture / How It Works
frontend-testing-toolkit/
├── setup/ # setup.ts, msw-handlers.ts, test-utils.tsx
├── unit/ # Hook, utility, and store test patterns
├── integration/ # Form, navigation, and data-fetching tests
├── e2e/ # Playwright config, auth/checkout specs, page objects
├── factories/ # Type-safe test data factories (user, product)
├── matchers/ # Custom matchers (accessibility, form)
└── ci/ # GitHub Actions workflows (test.yml, e2e.yml)
Usage Examples
Custom Render with Providers
import { render, RenderOptions } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
export function renderWithProviders(ui: React.ReactElement, options?: Omit<RenderOptions, 'wrapper'>) {
const qc = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 } } });
function Wrapper({ children }: { children: React.ReactNode }) {
return <QueryClientProvider client={qc}>{children}</QueryClientProvider>;
}
return { ...render(ui, { wrapper: Wrapper, ...options }), queryClient: qc };
}
Playwright E2E Test
// e2e/auth.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Authentication', () => {
test('user can log in and see dashboard', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('SecurePass123');
await page.getByRole('button', { name: 'Sign In' }).click();
await expect(page).toHaveURL('/dashboard');
await expect(page.getByRole('heading', { name: 'Welcome back' })).toBeVisible();
});
});
Type-Safe Test Data Factory
let sequence = 0;
interface User { id: string; name: string; email: string; role: 'admin' | 'user'; }
export function createUser(overrides?: Partial<User>): User {
sequence++;
return {
id: `user-${sequence}`, name: `Test User ${sequence}`,
email: `user${sequence}@example.com`, role: 'user', ...overrides,
};
}
Configuration
Playwright Multi-Browser Config
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
webServer: {
command: 'npm run dev',
port: 3000,
reuseExistingServer: !process.env.CI,
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'mobile', use: { ...devices['iPhone 14'] } },
],
});
Best Practices
-
Query by role, not by test ID —
getByRole('button', { name: 'Submit' })tests what users see;getByTestIdtests implementation -
Use
userEventoverfireEvent—userEvent.type()simulates real keystrokes including focus, keydown, input, and keyup events - Create one factory per entity — Consistent test data across your entire test suite eliminates "where did this mock come from?" questions
-
Mock at the network layer — MSW intercepts
fetchcalls, so your components use the same code paths they do in production - Run Playwright against a preview build — Test the production bundle, not dev mode, to catch build-time issues early
- Set coverage thresholds in CI — Prevent coverage from silently decreasing by failing the build when thresholds aren't met
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
document is not defined in Vitest |
Missing environment: 'jsdom' in config |
Add environment: 'jsdom' to vitest.config.ts
|
| Tests pass locally but fail in CI | Timing-sensitive assertions without await
|
Use findBy* queries (they wait) instead of getBy* for async content |
| Playwright can't find elements | Page not fully loaded before assertion | Add await page.waitForLoadState('networkidle') before assertions |
| MSW handlers not intercepting requests | MSW server not started in test setup | Add server.listen() in beforeAll and server.close() in afterAll
|
This is 1 of 11 resources in the Frontend Developer Pro toolkit. Get the complete [Frontend Testing Toolkit] with all files, templates, and documentation for $29.
Or grab the entire Frontend Developer Pro bundle (11 products) for $129 — save 30%.
Top comments (0)