stackademic

The leading education platform for anyone with an interest in software development.

End-to-End Testing

Driving the whole application like a real user to verify complete flows

Overview

End-to-end (E2E) tests exercise your application the way a real user would: opening a page, clicking buttons, and checking what appears on screen. They cover the full stack — UI, server, and database — so they catch problems no isolated test can. E2E tests are the slowest and fewest in a suite, reserved for critical user journeys like sign-up and checkout.

Syntax / Usage

Tools like Playwright launch a real browser and interact with the page. You navigate, act, and assert on visible results.

const { test, expect } = require("@playwright/test");

test("user can log in and see the dashboard", async ({ page }) => {
  await page.goto("https://localhost:3000/login");

  await page.getByLabel("Email").fill("ada@example.com");
  await page.getByLabel("Password").fill("correct-horse");
  await page.getByRole("button", { name: "Sign in" }).click();

  await expect(page).toHaveURL(/.*dashboard/);
  await expect(page.getByRole("heading", { name: "Welcome, Ada" })).toBeVisible();
});

Run with npx playwright test. The browser can run headless in CI.

Examples

Asserting a validation error appears for bad input:

test("shows an error for wrong password", async ({ page }) => {
  await page.goto("https://localhost:3000/login");
  await page.getByLabel("Email").fill("ada@example.com");
  await page.getByLabel("Password").fill("wrong");
  await page.getByRole("button", { name: "Sign in" }).click();

  await expect(page.getByText("Invalid credentials")).toBeVisible();
});

Checking that a new item persists after being created:

test("adding a todo shows it in the list", async ({ page }) => {
  await page.goto("https://localhost:3000/todos");
  await page.getByPlaceholder("New todo").fill("Write E2E tests");
  await page.getByRole("button", { name: "Add" }).click();

  await expect(page.getByText("Write E2E tests")).toBeVisible();
});

Common Mistakes

  • Writing E2E tests for every case instead of only the critical flows
  • Using fixed sleep delays instead of waiting for elements, causing flakiness
  • Selecting elements by fragile CSS classes rather than roles or labels
  • Sharing state between tests so one run pollutes the next
  • Running against a shared live environment instead of a clean test instance

See Also

testing-integration-tests testing-fundamentals testing-code-coverage