End-to-end tests — a real browser, a real backend, and why they're slow
An end-to-end test is the only kind of test that exercises the entire real system the way an actual user does — which is exactly what makes it both the most valuable single test in a suite and the most expensive one to run and maintain.
3 min read
What "end-to-end" actually means: nothing substituted, anywhere
// Playwright — drives a REAL browser against a REAL running app
test("a user can sign up and land on the dashboard", async ({ page }) => {
await page.goto("https://staging.example.com/signup");
await page.fill('[name="email"]', "ada@example.com");
await page.fill('[name="password"]', "correct-horse-battery-staple");
await page.click('button:has-text("Sign up")');
await expect(page).toHaveURL(/\/dashboard/);
await expect(page.getByText("Welcome, ada@example.com")).toBeVisible();
});Every earlier lesson in this domain has involved some substitution — a stub, a fake, a real-but-disposable test database instead of production infrastructure. An end-to-end (E2E) test substitutes nothing: a real browser (Chromium, Firefox, or WebKit, actually rendering pixels and running real JavaScript) drives real UI interactions against a genuinely running instance of the full application — real frontend, real backend, real database, often a real (sandboxed) version of every third-party integration. This is the only kind of test in this entire domain that can catch a bug in how all the pieces are actually deployed and connected together, not just how they're wired in code.
The real cost: slow, expensive to maintain, and the hardest to debug when it fails
A single E2E test can take seconds where a unit test takes milliseconds — booting (or reusing) real infrastructure, rendering a real page, waiting for real network round trips adds up fast, and a suite of hundreds of E2E tests can genuinely take longer to run than the rest of the entire test suite combined. They're also the most expensive to maintain: because they touch the real UI, a legitimate redesign (a button moves, a class name changes, a flow gets an extra confirmation step) can break dozens of E2E tests at once, even when no actual bug was introduced — a real, recurring maintenance cost that unit tests, testing behavior at a much smaller and more stable surface, mostly avoid. And when an E2E test fails, "something in this entire system, top to bottom, is wrong" is a far harder starting point to debug from than a focused unit test's specific failure.
Why the answer isn't "write E2E tests for everything" or "skip them entirely"
Because E2E tests are the only layer that catches real deployment and integration issues, skipping them entirely leaves a genuine blind spot — code that passes every unit and integration test can still be broken by a misconfigured environment variable, a CDN serving stale JavaScript, or a real third-party API behaving differently than its sandbox. But because they're slow and expensive to maintain, writing E2E tests for every single behavior a unit test could just as well cover means paying that cost for no real additional protection. The practical answer both extremes miss: a small number of E2E tests covering the handful of flows where a real, deployed break would be genuinely catastrophic (signup, checkout, login) — not comprehensive coverage of every edge case, which is what the unit and integration layers underneath are for.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What makes an end-to-end (E2E) test different from every other kind of test in this domain?
2. What's the real cost of E2E tests that makes 'just write E2E tests for everything' a bad strategy?
3. What's the practical answer to how many E2E tests a suite should have?