Comprehensive Testing Strategies for Modern Web Applications
Testing is not optional in modern software development—it's a fundamental practice that ensures code reliability, prevents regressions, and enables confident refactoring. Let's explore a comprehensive testing strategy for full-stack applications.
The Testing Pyramid
The testing pyramid, introduced by Mike Cohn, provides a strategic framework for test distribution:
/\
/ \ E2E Tests(10%)
/____\
/ \ Integration Tests(20%)
/____\
/ \ Unit Tests(70%)
/____\
Principles:
- 70% Unit Tests - Fast, isolated, focused
- 20% Integration Tests - Component interactions
- 10% E2E Tests - Complete user flows
- Cost increases upward - Execution time and maintenance
- Confidence increases upward - Real-world scenarios
Unit Testing with Vitest
Vitest is a modern, blazing-fast test runner built on Vite.
Setup
npm install -D vitest @vitest/ui @testing-library/react @testing-library/jest-dom jsdom
class="keyword">class="comment">// vitest.config.ts
class="keyword">import { defineConfig } class="keyword">from class="keyword">class="string">"vitest/config";
class="keyword">import react class="keyword">from class="keyword">class="string">"@vitejs/plugin-react";
class="keyword">import path class="keyword">from class="keyword">class="string">"path";
class="keyword">export class="keyword">default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: class="keyword">class="string">"jsdom",
setupFiles: class="keyword">class="string">"./test/setup.ts",
coverage: {
provider: class="keyword">class="string">"v8",
reporter: [class="keyword">class="string">"text", class="keyword">class="string">"json", class="keyword">class="string">"html"],
exclude: [class="keyword">class="string">"node_modules/", class="keyword">class="string">"test/", class="keyword">class="string">"class="keyword">class="comment">/*.config.ts", class="keyword">class="string">"/*.d.ts"],
},
},
resolve: {
alias: {
class="keyword">class="string">"@": path.resolve(__dirname, class="keyword">class="string">"./src"),
},
},
});
class="keyword">class="comment">// test/setup.ts
class="keyword">import class="keyword">class="string">"@testing-library/jest-dom";
class="keyword">import { cleanup } class="keyword">from class="keyword">class="string">"@testing-library/react";
class="keyword">import { afterEach } class="keyword">from class="keyword">class="string">"vitest";
afterEach(() => {
cleanup();
});
Testing Pure Functions
class="keyword">class="comment">// lib/utils.ts
class="keyword">export class="keyword">class="keyword">function calculateDiscount(price: number, percentage: number): number {
class="keyword">class="keyword">if (price < 0 || percentage < 0 || percentage > 100) {
class="keyword">throw class="keyword">new Error(class="keyword">class="string">"Invalid input");
}
class="keyword">class="keyword">return price * (1 - percentage / 100);
}
class="keyword">export class="keyword">class="keyword">function formatCurrency(amount: number, currency = class="keyword">class="string">"USD"): string {
class="keyword">class="keyword">return class="keyword">new Intl.NumberFormat(class="keyword">class="string">"en-US", {
style: class="keyword">class="string">"currency",
currency,
}).format(amount);
}
class="keyword">export class="keyword">class="keyword">function debounceclass="keyword">extends (...args: any[]) => any>(
func: T,
delay: number
): (...args: Parameters) => class="keyword">void {
class="keyword">class="keyword">let timeoutId: NodeJS.Timeout;
class="keyword">class="keyword">return class="keyword">class="keyword">function (...args: Parameters) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), delay);
};
}
class="keyword">class="comment">// lib/utils.test.ts
class="keyword">import { describe, it, expect, vi } class="keyword">from class="keyword">class="string">"vitest";
class="keyword">import { calculateDiscount, formatCurrency, debounce } class="keyword">from class="keyword">class="string">"./utils";
describe(class="keyword">class="string">"calculateDiscount", () => {
it(class="keyword">class="string">"should calculate correct discount", () => {
expect(calculateDiscount(100, 10)).toBe(90);
expect(calculateDiscount(200, 25)).toBe(150);
expect(calculateDiscount(50, 50)).toBe(25);
});
it(class="keyword">class="string">"should handle zero discount", () => {
expect(calculateDiscount(100, 0)).toBe(100);
});
it(class="keyword">class="string">"should class="keyword">throw on negative price", () => {
expect(() => calculateDiscount(-100, 10)).toThrow(class="keyword">class="string">"Invalid input");
});
it(class="keyword">class="string">"should class="keyword">throw on invalid percentage", () => {
expect(() => calculateDiscount(100, -10)).toThrow(class="keyword">class="string">"Invalid input");
expect(() => calculateDiscount(100, 110)).toThrow(class="keyword">class="string">"Invalid input");
});
});
describe(class="keyword">class="string">"formatCurrency", () => {
it(class="keyword">class="string">"should format USD by class="keyword">default", () => {
expect(formatCurrency(1234.56)).toBe(class="keyword">class="string">"$1,234.56");
});
it(class="keyword">class="string">"should format other currencies", () => {
expect(formatCurrency(1234.56, class="keyword">class="string">"EUR")).toContain(class="keyword">class="string">"1,234.56");
});
it(class="keyword">class="string">"should handle zero", () => {
expect(formatCurrency(0)).toBe(class="keyword">class="string">"$0.00");
});
});
describe(class="keyword">class="string">"debounce", () => {
it(class="keyword">class="string">"should delay class="keyword">class="keyword">function execution", () => {
vi.useFakeTimers();
class="keyword">class="keyword">const func = vi.fn();
class="keyword">class="keyword">const debouncedFunc = debounce(func, 300);
debouncedFunc();
expect(func).not.toHaveBeenCalled();
vi.advanceTimersByTime(300);
expect(func).toHaveBeenCalledTimes(1);
vi.useRealTimers();
});
it(class="keyword">class="string">"should cancel previous calls", () => {
vi.useFakeTimers();
class="keyword">class="keyword">const func = vi.fn();
class="keyword">class="keyword">const debouncedFunc = debounce(func, 300);
debouncedFunc();
debouncedFunc();
debouncedFunc();
vi.advanceTimersByTime(300);
expect(func).toHaveBeenCalledTimes(1);
vi.useRealTimers();
});
});
Testing React Components
class="keyword">class="comment">// components/Button.tsx
class="keyword">import { ButtonHTMLAttributes, ReactNode } class="keyword">from class="keyword">class="string">"react";
class="keyword">interface ButtonProps class="keyword">extends ButtonHTMLAttributes {
variant?: class="keyword">class="string">"primary" | class="keyword">class="string">"secondary" | class="keyword">class="string">"danger";
size?: class="keyword">class="string">"sm" | class="keyword">class="string">"md" | class="keyword">class="string">"lg";
isLoading?: boolean;
children: ReactNode;
}
class="keyword">export class="keyword">class="keyword">function Button({
variant = class="keyword">class="string">"primary",
size = class="keyword">class="string">"md",
isLoading = false,
disabled,
children,
className = class="keyword">class="string">"",
...props
}: ButtonProps) {
class="keyword">class="keyword">const baseStyles = class="keyword">class="string">"rounded font-medium transition-colors";
class="keyword">class="keyword">const variantStyles = {
primary: class="keyword">class="string">"bg-blue-600 text-white hover:bg-blue-700",
secondary: class="keyword">class="string">"bg-gray-200 text-gray-900 hover:bg-gray-300",
danger: class="keyword">class="string">"bg-red-600 text-white hover:bg-red-700",
};
class="keyword">class="keyword">const sizeStyles = {
sm: class="keyword">class="string">"px-3 py-1.5 text-sm",
md: class="keyword">class="string">"px-4 py-2 text-base",
lg: class="keyword">class="string">"px-6 py-3 text-lg",
};
class="keyword">class="keyword">return (
);
}
class="keyword">class="comment">// components/Button.test.tsx
class="keyword">import { describe, it, expect, vi } class="keyword">from class="keyword">class="string">"vitest";
class="keyword">import { render, screen } class="keyword">from class="keyword">class="string">"@testing-library/react";
class="keyword">import userEvent class="keyword">from class="keyword">class="string">"@testing-library/user-event";
class="keyword">import { Button } class="keyword">from class="keyword">class="string">"./Button";
describe(class="keyword">class="string">"Button", () => {
it(class="keyword">class="string">"should render with class="keyword">default props", () => {
render();
class="keyword">class="keyword">const button = screen.getByRole(class="keyword">class="string">"button", { name: /click me/i });
expect(button).toBeInTheDocument();
});
it(class="keyword">class="string">"should apply variant styles", () => {
class="keyword">class="keyword">const { rerender } = render();
expect(screen.getByRole(class="keyword">class="string">"button")).toHaveClass(class="keyword">class="string">"bg-blue-600");
rerender();
expect(screen.getByRole(class="keyword">class="string">"button")).toHaveClass(class="keyword">class="string">"bg-red-600");
});
it(class="keyword">class="string">"should apply size styles", () => {
render();
expect(screen.getByRole(class="keyword">class="string">"button")).toHaveClass(class="keyword">class="string">"px-6", class="keyword">class="string">"py-3");
});
it(class="keyword">class="string">"should handle loading state", () => {
render();
expect(screen.getByRole(class="keyword">class="string">"button")).toHaveTextContent(class="keyword">class="string">"Loading...");
expect(screen.getByRole(class="keyword">class="string">"button")).toBeDisabled();
});
it(class="keyword">class="string">"should call onClick handler", class="keyword">class="keyword">async () => {
class="keyword">class="keyword">const handleClick = vi.fn();
render();
class="keyword">class="keyword">const user = userEvent.setup();
class="keyword">class="keyword">await user.click(screen.getByRole(class="keyword">class="string">"button"));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it(class="keyword">class="string">"should not call onClick when disabled", class="keyword">class="keyword">async () => {
class="keyword">class="keyword">const handleClick = vi.fn();
render(
);
class="keyword">class="keyword">const user = userEvent.setup();
class="keyword">class="keyword">await user.click(screen.getByRole(class="keyword">class="string">"button"));
expect(handleClick).not.toHaveBeenCalled();
});
});
Testing Custom Hooks
class="keyword">class="comment">// hooks/use-local-storage.ts
class="keyword">import { useState, useEffect } class="keyword">from class="keyword">class="string">"react";
class="keyword">export class="keyword">class="keyword">function useLocalStorage(
key: string,
initialValue: T
): [T, (value: T) => class="keyword">void] {
class="keyword">class="keyword">const [storedValue, setStoredValue] = useState(() => {
class="keyword">class="keyword">if (class="keyword">typeof window === class="keyword">class="string">"class="keyword">undefined") {
class="keyword">class="keyword">return initialValue;
}
class="keyword">try {
class="keyword">class="keyword">const item = window.localStorage.getItem(key);
class="keyword">class="keyword">return item ? JSON.parse(item) : initialValue;
} class="keyword">catch (error) {
console.error(error);
class="keyword">class="keyword">return initialValue;
}
});
class="keyword">class="keyword">const setValue = (value: T) => {
class="keyword">try {
setStoredValue(value);
class="keyword">class="keyword">if (class="keyword">typeof window !== class="keyword">class="string">"class="keyword">undefined") {
window.localStorage.setItem(key, JSON.stringify(value));
}
} class="keyword">catch (error) {
console.error(error);
}
};
class="keyword">class="keyword">return [storedValue, setValue];
}
class="keyword">class="comment">// hooks/use-local-storage.test.ts
class="keyword">import { describe, it, expect, beforeEach } class="keyword">from class="keyword">class="string">"vitest";
class="keyword">import { renderHook, act } class="keyword">from class="keyword">class="string">"@testing-library/react";
class="keyword">import { useLocalStorage } class="keyword">from class="keyword">class="string">"./use-local-storage";
describe(class="keyword">class="string">"useLocalStorage", () => {
beforeEach(() => {
localStorage.clear();
});
it(class="keyword">class="string">"should class="keyword">class="keyword">return initial value", () => {
class="keyword">class="keyword">const { result } = renderHook(() => useLocalStorage(class="keyword">class="string">"test", class="keyword">class="string">"initial"));
expect(result.current[0]).toBe(class="keyword">class="string">"initial");
});
it(class="keyword">class="string">"should update localStorage on setValue", () => {
class="keyword">class="keyword">const { result } = renderHook(() => useLocalStorage(class="keyword">class="string">"test", class="keyword">class="string">"initial"));
act(() => {
});
expect(result.current[0]).toBe(class="keyword">class="string">"updated");
expect(localStorage.getItem(class="keyword">class="string">"test")).toBe(JSON.stringify(class="keyword">class="string">"updated"));
});
it(class="keyword">class="string">"should retrieve existing value class="keyword">from localStorage", () => {
localStorage.setItem(class="keyword">class="string">"test", JSON.stringify(class="keyword">class="string">"existing"));
class="keyword">class="keyword">const { result } = renderHook(() => useLocalStorage(class="keyword">class="string">"test", class="keyword">class="string">"initial"));
expect(result.current[0]).toBe(class="keyword">class="string">"existing");
});
it(class="keyword">class="string">"should handle complex objects", () => {
class="keyword">class="keyword">const { result } = renderHook(() =>
useLocalStorage(class="keyword">class="string">"user", { name: class="keyword">class="string">"John", age: 30 })
);
act(() => {
});
expect(result.current[0]).toEqual({ name: class="keyword">class="string">"Jane", age: 25 });
});
});
Integration Testing
Test component interactions and data flow.
class="keyword">class="comment">// components/TodoApp.tsx
class="keyword">class="string">"use client";
class="keyword">import { useState } class="keyword">from class="keyword">class="string">"react";
class="keyword">interface Todo {
id: string;
title: string;
completed: boolean;
}
class="keyword">export class="keyword">class="keyword">function TodoApp() {
class="keyword">class="keyword">const [todos, setTodos] = useState([]);
class="keyword">class="keyword">const [input, setInput] = useState(class="keyword">class="string">"");
class="keyword">class="keyword">const addTodo = () => {
class="keyword">class="keyword">if (!input.trim()) class="keyword">class="keyword">return;
setTodos([
...todos,
{ id: crypto.randomUUID(), title: input, completed: false },
]);
setInput(class="keyword">class="string">"");
};
class="keyword">class="keyword">const toggleTodo = (id: string) => {
setTodos(
todos.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t))
);
};
class="keyword">class="keyword">const deleteTodo = (id: string) => {
setTodos(todos.filter((t) => t.id !== id));
};
class="keyword">class="keyword">return (
Todo List
class="keyword">type=class="keyword">class="string">"text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === class="keyword">class="string">"Enter" && addTodo()}
placeholder=class="keyword">class="string">"Add a todo..."
aria-label=class="keyword">class="string">"New todo"
/>
class="keyword">class="string">"Todo list">
{todos.map((todo) => (
-
class="keyword">type=class="keyword">class="string">"checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
aria-label={class="keyword">class="string">Toggle ${todo.title}}
/>
style={{
textDecoration: todo.completed ? class="keyword">class
="string">"line-through" : class="keyword">class="string">"none",
}}
>
{todo.title}
))}
Total: {todos.length} | Completed:{class="keyword">class="string">" "}
{todos.filter((t) => t.completed).length}
);
}
class="keyword">class="comment">// components/TodoApp.test.tsx
class="keyword">import { describe, it, expect } class="keyword">from class="keyword">class="string">"vitest";
class="keyword">import { render, screen } class="keyword">from class="keyword">class="string">"@testing-library/react";
class="keyword">import userEvent class="keyword">from class="keyword">class="string">"@testing-library/user-event";
class="keyword">import { TodoApp } class="keyword">from class="keyword">class="string">"./TodoApp";
describe(class="keyword">class="string">"TodoApp Integration", () => {
it(class="keyword">class="string">"should add a class="keyword">new todo", class="keyword">class="keyword">async () => {
render( );
class="keyword">class="keyword">const user = userEvent.setup();
class="keyword">class="keyword">const input = screen.getByLabelText(class="keyword">class="string">"New todo");
class="keyword">class="keyword">const addButton = screen.getByRole(class="keyword">class="string">"button", { name: /add/i });
class="keyword">class="keyword">await user.class="keyword">type(input, class="keyword">class="string">"Buy groceries");
class="keyword">class="keyword">await user.click(addButton);
expect(screen.getByText(class="keyword">class="string">"Buy groceries")).toBeInTheDocument();
expect(input).toHaveValue(class="keyword">class="string">"");
});
it(class="keyword">class="string">"should toggle todo completion", class="keyword">class="keyword">async () => {
render( );
class="keyword">class="keyword">const user = userEvent.setup();
class="keyword">class="comment">// Add a todo
class="keyword">class="keyword">await user.class="keyword">type(screen.getByLabelText(class="keyword">class="string">"New todo"), class="keyword">class="string">"Test task");
class="keyword">class="keyword">await user.click(screen.getByRole(class="keyword">class="string">"button", { name: /add/i }));
class="keyword">class="comment">// Toggle it
class="keyword">class="keyword">const checkbox = screen.getByLabelText(class="keyword">class="string">"Toggle Test task");
class="keyword">class="keyword">await user.click(checkbox);
expect(checkbox).toBeChecked();
expect(screen.getByText(class="keyword">class="string">"Test task")).toHaveStyle({
textDecoration: class="keyword">class="string">"line-through",
});
});
it(class="keyword">class="string">"should delete a todo", class="keyword">class="keyword">async () => {
render( );
class="keyword">class="keyword">const user = userEvent.setup();
class="keyword">class="comment">// Add a todo
class="keyword">class="keyword">await user.class="keyword">type(screen.getByLabelText(class="keyword">class="string">"New todo"), class="keyword">class="string">"Delete me");
class="keyword">class="keyword">await user.click(screen.getByRole(class="keyword">class="string">"button", { name: /add/i }));
class="keyword">class="comment">// Delete it
class="keyword">class="keyword">await user.click(screen.getByRole(class="keyword">class="string">"button", { name: /delete/i }));
expect(screen.queryByText(class="keyword">class="string">"Delete me")).not.toBeInTheDocument();
});
it(class="keyword">class="string">"should display correct counts", class="keyword">class="keyword">async () => {
render( );
class="keyword">class="keyword">const user = userEvent.setup();
class="keyword">class="comment">// Add todos
class="keyword">class="keyword">const input = screen.getByLabelText(class="keyword">class="string">"New todo");
class="keyword">class="keyword">await user.class="keyword">type(input, class="keyword">class="string">"Task 1");
class="keyword">class="keyword">await user.click(screen.getByRole(class="keyword">class="string">"button", { name: /add/i }));
class="keyword">class="keyword">await user.class="keyword">type(input, class="keyword">class="string">"Task 2");
class="keyword">class="keyword">await user.click(screen.getByRole(class="keyword">class="string">"button", { name: /add/i }));
expect(screen.getByText(/Total: 2/)).toBeInTheDocument();
expect(screen.getByText(/Completed: 0/)).toBeInTheDocument();
class="keyword">class="comment">// Complete one
class="keyword">class="keyword">await user.click(screen.getByLabelText(class="keyword">class="string">"Toggle Task 1"));
expect(screen.getByText(/Completed: 1/)).toBeInTheDocument();
});
});
E2E Testing with Playwright
Test complete user journeys in real browsers.
npm install -D @playwright/test
npx playwright install
class="keyword">class="comment">// playwright.config.ts
class="keyword">import { defineConfig, devices } class="keyword">from class="keyword">class="string">"@playwright/test";
class="keyword">export class="keyword">default defineConfig({
testDir: class="keyword">class="string">"./e2e",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : class="keyword">undefined,
reporter: class="keyword">class="string">"html",
use: {
baseURL: class="keyword">class="string">"http:class="keyword">class="comment">//localhost:3000",
trace: class="keyword">class="string">"on-first-retry",
screenshot: class="keyword">class="string">"only-on-failure",
},
projects: [
{
name: class="keyword">class="string">"chromium",
use: { ...devices[class="keyword">class="string">"Desktop Chrome"] },
},
{
name: class="keyword">class="string">"firefox",
use: { ...devices[class="keyword">class="string">"Desktop Firefox"] },
},
{
name: class="keyword">class="string">"webkit",
use: { ...devices[class="keyword">class="string">"Desktop Safari"] },
},
],
webServer: {
command: class="keyword">class="string">"npm run dev",
url: class="keyword">class="string">"http:class="keyword">class="comment">//localhost:3000",
reuseExistingServer: !process.env.CI,
},
});
class="keyword">class="comment">// e2e/auth.spec.ts
class="keyword">import { test, expect } class="keyword">from class="keyword">class="string">"@playwright/test";
test.describe(class="keyword">class="string">"Authentication Flow", () => {
test(class="keyword">class="string">"should register class="keyword">new user", class="keyword">class="keyword">async ({ page }) => {
class="keyword">class="keyword">await page.goto(class="keyword">class="string">"/auth/register");
class="keyword">class="keyword">await page.fill(class="keyword">class="string">'input[name="name"]', class="keyword">class="string">"John Doe");
class="keyword">class="keyword">await page.fill(class="keyword">class="string">'input[name="email"]', class="keyword">class="string">"john@example.com");
class="keyword">class="keyword">await page.fill(class="keyword">class="string">'input[name="password"]', class="keyword">class="string">"SecurePass123!");
class="keyword">class="keyword">await page.click(class="keyword">class="string">'button[class="keyword">type="submit"]');
class="keyword">class="keyword">await expect(page).toHaveURL(class="keyword">class="string">"/dashboard");
class="keyword">class="keyword">await expect(page.locator(class="keyword">class="string">"h1")).toContainText(class="keyword">class="string">"Welcome, John Doe");
});
test(class="keyword">class="string">"should login existing user", class="keyword">class="keyword">async ({ page }) => {
class="keyword">class="keyword">await page.goto(class="keyword">class="string">"/auth/login");
class="keyword">class="keyword">await page.fill(class="keyword">class="string">'input[name="email"]', class="keyword">class="string">"john@example.com");
class="keyword">class="keyword">await page.fill(class="keyword">class="string">'input[name="password"]', class="keyword">class="string">"SecurePass123!");
class="keyword">class="keyword">await page.click(class="keyword">class="string">'button[class="keyword">type="submit"]');
class="keyword">class="keyword">await expect(page).toHaveURL(class="keyword">class="string">"/dashboard");
});
test(class="keyword">class="string">"should show error on invalid credentials", class="keyword">class="keyword">async ({ page }) => {
class="keyword">class="keyword">await page.goto(class="keyword">class="string">"/auth/login");
class="keyword">class="keyword">await page.fill(class="keyword">class="string">'input[name="email"]', class="keyword">class="string">"wrong@example.com");
class="keyword">class="keyword">await page.fill(class="keyword">class="string">'input[name="password"]', class="keyword">class="string">"wrongpassword");
class="keyword">class="keyword">await page.click(class="keyword">class="string">'button[class="keyword">type="submit"]');
class="keyword">class="keyword">await expect(page.locator(class="keyword">class="string">'[role="alert"]')).toContainText(
class="keyword">class="string">"Invalid credentials"
);
});
test(class="keyword">class="string">"should logout user", class="keyword">class="keyword">async ({ page }) => {
class="keyword">class="comment">// Login first
class="keyword">class="keyword">await page.goto(class="keyword">class="string">"/auth/login");
class="keyword">class="keyword">await page.fill(class="keyword">class="string">'input[name="email"]', class="keyword">class="string">"john@example.com");
class="keyword">class="keyword">await page.fill(class="keyword">class="string">'input[name="password"]', class="keyword">class="string">"SecurePass123!");
class="keyword">class="keyword">await page.click(class="keyword">class="string">'button[class="keyword">type="submit"]');
class="keyword">class="comment">// Logout
class="keyword">class="keyword">await page.click(class="keyword">class="string">'button:has-text("Logout")');
class="keyword">class="keyword">await expect(page).toHaveURL(class="keyword">class="string">"/auth/login");
});
});
class="keyword">class="comment">// e2e/blog.spec.ts
test.describe(class="keyword">class="string">"Blog Functionality", () => {
test(class="keyword">class="string">"should create and publish blog post", class="keyword">class="keyword">async ({ page }) => {
class="keyword">class="comment">// Login as admin
class="keyword">class="keyword">await page.goto(class="keyword">class="string">"/auth/login");
class="keyword">class="keyword">await page.fill(class="keyword">class="string">'input[name="email"]', class="keyword">class="string">"admin@example.com");
class="keyword">class="keyword">await page.fill(class="keyword">class="string">'input[name="password"]', class="keyword">class="string">"admin123");
class="keyword">class="keyword">await page.click(class="keyword">class="string">'button[class="keyword">type="submit"]');
class="keyword">class="comment">// Navigate to create post
class="keyword">class="keyword">await page.goto(class="keyword">class="string">"/dashboard/posts/class="keyword">new");
class="keyword">class="keyword">await page.fill(class="keyword">class="string">'input[name="title"]', class="keyword">class="string">"Test Blog Post");
class="keyword">class="keyword">await page.fill(class="keyword">class="string">'textarea[name="content"]', class="keyword">class="string">"This is test content");
class="keyword">class="keyword">await page.selectOption(class="keyword">class="string">'select[name="category"]', class="keyword">class="string">"technology");
class="keyword">class="comment">// Save as draft
class="keyword">class="keyword">await page.click(class="keyword">class="string">'button:has-text("Save Draft")');
class="keyword">class="keyword">await expect(page.locator(class="keyword">class="string">".toast")).toContainText(class="keyword">class="string">"Draft saved");
class="keyword">class="comment">// Publish
class="keyword">class="keyword">await page.click(class="keyword">class="string">'button:has-text("Publish")');
class="keyword">class="keyword">await expect(page.locator(class="keyword">class="string">".toast")).toContainText(class="keyword">class="string">"Post published");
class="keyword">class="comment">// Verify on class="keyword">public page
class="keyword">class="keyword">await page.goto(class="keyword">class="string">"/blog");
class="keyword">class="keyword">await expect(page.locator(class="keyword">class="string">'h2:has-text("Test Blog Post")')).toBeVisible();
});
});
Test-Driven Development (TDD)
Write tests first, then implement.
class="keyword">class="comment">// Example: Implementing a shopping cart with TDD
class="keyword">class="comment">// Step 1: Write failing test
describe(class="keyword">class="string">"ShoppingCart", () => {
it(class="keyword">class="string">"should start empty", () => {
class="keyword">class="keyword">const cart = class="keyword">new ShoppingCart();
expect(cart.getItems()).toHaveLength(0);
expect(cart.getTotal()).toBe(0);
});
});
class="keyword">class="comment">// Step 2: Implement minimal code to pass
class="keyword">class ShoppingCart {
class="keyword">private items: CartItem[] = [];
getItems() {
class="keyword">class="keyword">return class="keyword">this.items;
}
getTotal() {
class="keyword">class="keyword">return 0;
}
}
class="keyword">class="comment">// Step 3: Write next test
it(class="keyword">class="string">"should add items", () => {
class="keyword">class="keyword">const cart = class="keyword">new ShoppingCart();
cart.addItem({ id: class="keyword">class="string">"1", name: class="keyword">class="string">"Product", price: 10, quantity: 2 });
expect(cart.getItems()).toHaveLength(1);
expect(cart.getTotal()).toBe(20);
});
class="keyword">class="comment">// Step 4: Implement feature
class="keyword">class ShoppingCart {
class="keyword">private items: CartItem[] = [];
addItem(item: CartItem) {
class="keyword">this.items.push(item);
}
getItems() {
class="keyword">class="keyword">return class="keyword">this.items;
}
getTotal() {
class="keyword">class="keyword">return class="keyword">this.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
}
}
class="keyword">class="comment">// Continue cycle...
Best Practices
1. Follow AAA Pattern - Arrange, Act, Assert
2. One assertion per test - Focus and clarity
3. Test behavior, not implementation - Refactor-safe
4. Use descriptive test names - Documentation
5. Mock external dependencies - Isolation
6. Avoid test interdependence - Run in any order
7. Maintain test code quality - Same standards as production
8. Run tests in CI/CD - Automated quality gates
9. Measure coverage - Aim for 80%+ critical paths
10. Test edge cases - Empty arrays, null, undefined, errors
Conclusion
Comprehensive testing isn't about achieving 100% coverage—it's about strategic confidence. Combine unit tests for logic, integration tests for interactions, and E2E tests for critical flows. Embrace TDD when appropriate, maintain test quality, and make testing a natural part of your development workflow.
Well-tested code is maintainable code, and maintainable code is sustainable code! ✅