An end-to-end test drives the whole system through the browser or a real API, which gives the highest confidence and the slowest, most fragile feedback. Because each one is expensive, they should cover the few journeys that matter most, not every variation.
Before you start
You should be comfortable with test levels and selectors. This article covers what end-to-end tests should and should not cover.
Step-by-step walkthrough
Step 1: Cover critical journeys only
Choose the paths that must work for the product to function: sign in, checkout, the primary workflow. A handful of these catch integration failures across the stack. Everything else is cheaper to cover at a lower level.
Step 2: Use stable, accessible selectors
Select by role and accessible name rather than CSS classes or DOM paths, which change with styling and refactors. A selector tied to data-testid or an accessible role survives layout changes and doubles as an accessibility signal.
Step 3: Do not duplicate lower coverage
Every branch, boundary and error message does not belong in an end-to-end test, because that duplicates unit and integration coverage at far higher cost. Keep end-to-end tests to the integration of the journey, and push detailed cases down.
Worked scenario
A stable selector targets the button by its role.
await page.getByRole('button', { name: 'Place order' }).click();
await expect(page.getByRole('heading', { name: 'Order confirmed' })).toBeVisible();Walk through the example
The test drives the checkout journey and asserts the confirmation, without touching every validation rule. Selecting by role and name keeps it stable across styling changes and reflects what a user sees. The detailed rules are covered by faster tests.
Common mistake
Writing hundreds of end-to-end tests that duplicate unit coverage, so the suite is slow and flaky and a failure does not indicate real risk. Another is selecting by brittle CSS classes or XPaths, so the tests break on unrelated styling changes.
Verify the behavior
Measure end-to-end suite duration and reduce it by moving duplicated cases down. Refactor a component’s markup and confirm role-based selectors still pass. Introduce a real integration bug and confirm a journey test catches it.
Interview exercise
Why should end-to-end tests be few?
Answer and reasoning
Because they are slow, run the whole stack and are more prone to flakiness and environmental issues, so each one carries high cost and lower reliability. Their value is confidence in the integration between layers, which a small number of critical journeys provides. Detailed variations are cheaper and more stable at the unit and integration levels.
Continue learning
Compare the levels in Integration vs unit and flakiness in Flaky test diagnosis. Read the Martin Fowler on the test pyramid and try the Testing interview questions.