← All posts
Concepts

How much end-to-end testing do you actually need?

Every team eventually argues about the same thing: too many end-to-end tests and CI takes twenty minutes and flakes; too few and a broken checkout ships on a Friday. The old testing pyramid gave a rule of thumb, but it was drawn when writing an E2E test cost an afternoon — and that cost is changing. Here's how to think about the split in 2026.

What the pyramid still gets right

The classic shape — many unit tests, fewer integration tests, a thin cap of end-to-end tests — encodes one durable truth: the cost of a test and the cost of its failure both go up as you climb. A unit test runs in a millisecond, fails for exactly one reason, and points at one function. An E2E test boots a browser, walks a real user journey, and when it goes red you still have to figure out whether the bug is in your code, the test, the environment, or a third-party script that timed out.

That gradient hasn't gone away. What has changed is the authoring cost at the top of the pyramid, and that changes where the sweet spot sits. More on that below.

What E2E is uniquely good at

An end-to-end test is the only kind that answers the question your users actually ask: can I do the thing? It exercises the real stack — routing, auth, data layer, rendered DOM, network — in one pass. That makes it uniquely good at catching a specific class of bug that unit and integration tests structurally cannot:

A single E2E test that covers checkout:

import { test, expect } from '@playwright/test';

test('customer can check out with a saved card', async ({ page }) => {
  await page.goto('/cart');
  await page.getByRole('button', { name: 'Checkout' }).click();
  await page.getByLabel('Saved card ending 4242').check();
  await page.getByRole('button', { name: 'Place order' }).click();
  await expect(page.getByText('Order confirmed')).toBeVisible();
});

That one test crosses more system boundaries than a whole folder of units. That's the point — and also why you don't want a hundred of them.

Where unit tests win

E2E is a blunt instrument for logic. The moment a behavior has more than two or three meaningful branches, testing it through the UI is slow, fragile, and a poor description of intent. Business rules — pricing, permissions, validation, state machines, anything with edge cases — belong in unit tests, close to the code, where a failure names the exact input that broke.

import { describe, it, expect } from 'vitest';
import { applyDiscount } from './pricing';

describe('applyDiscount', () => {
  it('never lets a percentage discount push the total below zero', () => {
    expect(applyDiscount(50, { type: 'percent', value: 200 })).toBe(0);
  });
});

Proving that rule through a browser would mean constructing a cart, applying a coupon, and reading a rendered total — for one of a dozen cases. As a unit test it's three lines and runs instantly. The rule of thumb: if you can express the case as an input and an expected output, it's a unit test. Save E2E for "does the journey hold together."

A sane ratio

Ratios are heuristics, not laws, and the honest answer is "it depends on your blast radius." But teams tend to over-index on one extreme, so a starting point helps:

LayerRough shareWhat it covers
Unit~70%Logic, branches, edge cases, pure functions
Integration~20%Module seams, real DB/queries, API contracts
End-to-end~10%Critical user journeys, cross-layer wiring

The number that matters isn't the percentage — it's the absolute count of E2E tests. Most products have somewhere between five and thirty journeys that actually matter. Enumerate them: the flows that, if broken, would generate support tickets or lose money. That list is your E2E suite. Resist the urge to E2E-test the sixth variation of a settings toggle; that's a unit or integration concern wearing a browser costume.

Two anti-patterns to watch for:

Why generation changes the cost side

Here's the part the original pyramid couldn't account for. The pyramid is fundamentally an economic argument: E2E sits at the top because each one is expensive to write and maintain. Lower that cost and the optimal shape shifts — not toward an ice cream cone, but toward covering journeys you previously skipped because the authoring budget ran out.

That's the shift worth planning for. When a real end-to-end test for a journey can be produced from a plain-language description instead of an afternoon of getByRole archaeology, the marginal journey becomes worth covering. The five-flow suite grows to the fifteen flows that genuinely matter, without the maintenance tax that used to make teams cap it at five.

This is where a tool like TestVibe fits the strategy rather than replacing it. You describe a behavior in natural language; it generates the Playwright test by working through your live app in an isolated cloud session, and you run that test to confirm it does what you meant. The generated output is ordinary Playwright — readable locators, real assertions — so it lives in the same suite, under the same review, as anything you'd hand-write. Generation lowers the cost of the top of the pyramid; it doesn't change what belongs there.

The takeaway

The pyramid isn't obsolete — its economics just moved. Keep logic in fast unit tests where a failure names the cause. Reserve end-to-end tests for the journeys that lose you customers, and count them deliberately rather than letting them sprawl. Then, because authoring an E2E test no longer costs an afternoon, cover the handful of important flows you used to leave uncovered.

Want to put a real journey under test in minutes instead of an afternoon? Get early access, or read how AI generation works first.

Early access

Ready for tests that write themselves?