Back to blog

Comprehensive Testing Strategies for Modern Web Applications

10 min readBy Mustafa Akkaya
#Testing#Jest#Vitest#Playwright#Cypress#TDD#Quality Assurance

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:

file.txt

/\

/ \ 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

script.sh

npm install -D vitest @vitest/ui @testing-library/react @testing-library/jest-dom jsdom

app.ts

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"),

},

},

});

app.ts

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

app.ts

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

app.ts

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

app.ts

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(() => {

result.currentclass="keyword">class="string">""updatedclass="keyword">class="string">"" target=class="keyword">class="string">"_blank" rel=class="keyword">class="string">"noopener noreferrer" class="keyword">class=class="keyword">class="string">"font-semibold text-indigo-600 transition-colors hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300">1;

});

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(() => {

result.currentclass="keyword">class="string">"{ name: "Janeclass="keyword">class="string">", age: 25 }" target=class="keyword">class="string">"_blank" rel=class="keyword">class="string">"noopener noreferrer" class="keyword">class=class="keyword">class="string">"font-semibold text-indigo-600 transition-colors hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300">1;

});

expect(result.current[0]).toEqual({ name: class="keyword">class="string">"Jane", age: 25 });

});

});

Integration Testing

Test component interactions and data flow.

app.ts

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.

script.sh

npm install -D @playwright/test

npx playwright install

app.ts

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,

},

});

app.ts

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.

app.ts

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! ✅