frontend-testing
About
This skill helps developers implement comprehensive frontend testing using tools like Jest, Vitest, React Testing Library, and Cypress. It is used for building robust test suites covering unit, integration, and end-to-end testing for UI components. The skill enables regression prevention and supports test-driven development for quality assurance.
Documentation
Frontend Testing
Overview
Build comprehensive test suites for frontend applications including unit tests, integration tests, and end-to-end tests with proper coverage and assertions.
When to Use
- Component testing
- Integration testing
- End-to-end testing
- Regression prevention
- Quality assurance
- Test-driven development
Implementation Examples
1. Jest Unit Testing (React)
// Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { Button } from './Button';
describe('Button Component', () => {
it('renders button with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
it('calls onClick handler when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click</Button>);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('disables button when disabled prop is true', () => {
render(<Button disabled>Click me</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
it('applies variant styles correctly', () => {
const { container } = render(<Button variant="primary">Click</Button>);
const button = container.querySelector('button');
expect(button).toHaveClass('bg-blue-500');
});
it('applies size classes correctly', () => {
const { container } = render(<Button size="lg">Click</Button>);
const button = container.querySelector('button');
expect(button).toHaveClass('px-6 py-3 text-lg');
});
});
// hooks.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);
});
it('decrements count', () => {
const { result } = renderHook(() => useCounter(5));
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(4);
});
it('resets count', () => {
const { result } = renderHook(() => useCounter(5));
act(() => {
result.current.increment();
result.current.reset();
});
expect(result.current.count).toBe(5);
});
});
2. React Testing Library Integration Tests
// UserForm.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserForm } from './UserForm';
describe('UserForm Integration', () => {
beforeEach(() => {
// Clear mocks before each test
jest.clearAllMocks();
});
it('submits form with valid data', async () => {
const handleSubmit = jest.fn();
render(<UserForm onSubmit={handleSubmit} />);
await userEvent.type(screen.getByLabelText(/name/i), 'John Doe');
await userEvent.type(screen.getByLabelText(/email/i), '[email protected]');
await userEvent.type(screen.getByLabelText(/password/i), 'password123');
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(handleSubmit).toHaveBeenCalledWith({
name: 'John Doe',
email: '[email protected]',
password: 'password123'
});
});
});
it('displays validation errors for empty fields', async () => {
render(<UserForm onSubmit={jest.fn()} />);
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(screen.getByText(/name is required/i)).toBeInTheDocument();
expect(screen.getByText(/email is required/i)).toBeInTheDocument();
});
});
it('displays validation error for invalid email', async () => {
render(<UserForm onSubmit={jest.fn()} />);
await userEvent.type(screen.getByLabelText(/email/i), 'invalid-email');
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(screen.getByText(/invalid email/i)).toBeInTheDocument();
});
});
});
// UserList.test.tsx with data fetching
import { render, screen, waitFor } from '@testing-library/react';
import { UserList } from './UserList';
describe('UserList with API', () => {
beforeEach(() => {
jest.spyOn(global, 'fetch').mockClear();
});
it('displays loading state initially', () => {
(global.fetch as jest.Mock).mockImplementation(
() => new Promise(() => {}) // Never resolves
);
render(<UserList />);
expect(screen.getByText(/loading/i)).toBeInTheDocument();
});
it('fetches and displays users', async () => {
const mockUsers = [
{ id: 1, name: 'User 1', email: '[email protected]' },
{ id: 2, name: 'User 2', email: '[email protected]' }
];
(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => mockUsers
});
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('User 1')).toBeInTheDocument();
expect(screen.getByText('User 2')).toBeInTheDocument();
});
});
it('displays error message on fetch failure', async () => {
(global.fetch as jest.Mock).mockRejectedValueOnce(new Error('Network error'));
render(<UserList />);
await waitFor(() => {
expect(screen.getByText(/error/i)).toBeInTheDocument();
});
});
});
3. Vitest for Vue Testing
// Button.spec.ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import Button from './Button.vue';
describe('Button.vue', () => {
it('renders slot content', () => {
const wrapper = mount(Button, {
slots: {
default: 'Click me'
}
});
expect(wrapper.text()).toContain('Click me');
});
it('emits click event', async () => {
const wrapper = mount(Button);
await wrapper.trigger('click');
expect(wrapper.emitted('click')).toHaveLength(1);
});
it('disables button when disabled prop is true', () => {
const wrapper = mount(Button, {
props: { disabled: true }
});
expect(wrapper.attributes('disabled')).toBeDefined();
});
it('applies variant class', () => {
const wrapper = mount(Button, {
props: { variant: 'primary' }
});
expect(wrapper.classes()).toContain('bg-blue-500');
});
});
// composable.spec.ts
import { describe, it, expect } from 'vitest';
import { useCounter } from './useCounter';
describe('useCounter', () => {
it('initializes with default value', () => {
const { count } = useCounter();
expect(count.value).toBe(0);
});
it('increments count', () => {
const { count, increment } = useCounter();
increment();
expect(count.value).toBe(1);
});
});
4. Cypress E2E Testing
// cypress/e2e/login.cy.ts
describe('Login Flow', () => {
beforeEach(() => {
cy.visit('http://localhost:3000/login');
});
it('logs in with valid credentials', () => {
cy.get('input[name="email"]').type('[email protected]');
cy.get('input[name="password"]').type('password123');
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard');
cy.get('h1').should('contain', 'Welcome');
});
it('displays error for invalid credentials', () => {
cy.get('input[name="email"]').type('[email protected]');
cy.get('input[name="password"]').type('wrongpassword');
cy.get('button[type="submit"]').click();
cy.get('.error-message').should('contain', 'Invalid credentials');
});
it('validates email field', () => {
cy.get('input[name="email"]').type('invalid-email');
cy.get('input[name="password"]').type('password123');
cy.get('button[type="submit"]').click();
cy.get('.error-message').should('contain', 'Invalid email');
});
});
// cypress/e2e/user-management.cy.ts
describe('User Management', () => {
beforeEach(() => {
cy.login('[email protected]', 'password123');
cy.visit('http://localhost:3000/users');
});
it('creates a new user', () => {
cy.get('button:contains("Add User")').click();
cy.get('input[name="name"]').type('New User');
cy.get('input[name="email"]').type('[email protected]');
cy.get('button[type="submit"]').click();
cy.get('.success-message').should('contain', 'User created');
cy.get('table tbody').should('contain', 'New User');
});
it('edits an existing user', () => {
cy.get('table tbody tr').first().contains('button', 'Edit').click();
cy.get('input[name="name"]').clear().type('Updated Name');
cy.get('button[type="submit"]').click();
cy.get('.success-message').should('contain', 'User updated');
});
it('deletes a user with confirmation', () => {
cy.get('table tbody tr').first().contains('button', 'Delete').click();
cy.get('.modal button:contains("Confirm")').click();
cy.get('.success-message').should('contain', 'User deleted');
});
});
// cypress/support/commands.ts
Cypress.Commands.add('login', (email: string, password: string) => {
cy.visit('http://localhost:3000/login');
cy.get('input[name="email"]').type(email);
cy.get('input[name="password"]').type(password);
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard');
});
5. Test Coverage Configuration
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'],
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/index.tsx',
'!src/reportWebVitals.ts'
],
coverageThreshold: {
global: {
branches: 70,
functions: 70,
lines: 70,
statements: 70
}
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
transform: {
'^.+\\.tsx?$': ['ts-jest', {
tsconfig: {
jsx: 'react-jsx'
}
}]
}
};
// package.json scripts
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"cypress": "cypress open",
"cypress:headless": "cypress run"
}
}
Best Practices
- Write tests alongside code (TDD)
- Test behavior, not implementation
- Use descriptive test names
- Keep tests focused and independent
- Mock external dependencies
- Aim for high coverage (>80%)
- Use semantic queries in React Testing Library
- Implement E2E tests for critical paths
- Test error scenarios
- Use CI/CD for automated testing
Resources
Quick Install
/plugin add https://github.com/aj-geddes/useful-ai-prompts/tree/main/frontend-testingCopy and paste this command in Claude Code to install this skill
GitHub 仓库
Related Skills
evaluating-llms-harness
TestingThis Claude Skill runs the lm-evaluation-harness to benchmark LLMs across 60+ standardized academic tasks like MMLU and GSM8K. It's designed for developers to compare model quality, track training progress, or report academic results. The tool supports various backends including HuggingFace and vLLM models.
langchain
MetaLangChain is a framework for building LLM applications using agents, chains, and RAG pipelines. It supports multiple LLM providers, offers 500+ integrations, and includes features like tool calling and memory management. Use it for rapid prototyping and deploying production systems like chatbots, autonomous agents, and question-answering services.
Algorithmic Art Generation
MetaThis skill helps developers create algorithmic art using p5.js, focusing on generative art, computational aesthetics, and interactive visualizations. It automatically activates for topics like "generative art" or "p5.js visualization" and guides you through creating unique algorithms with features like seeded randomness, flow fields, and particle systems. Use it when you need to build reproducible, code-driven artistic patterns.
webapp-testing
TestingThis Claude Skill provides a Playwright-based toolkit for testing local web applications through Python scripts. It enables frontend verification, UI debugging, screenshot capture, and log viewing while managing server lifecycles. Use it for browser automation tasks but run scripts directly rather than reading their source code to avoid context pollution.
