An end-to-end test checks the system the way a user sees it: it runs the application in an environment close to production, interacts with it through the browser and observes the result at public boundaries. Usually one such test passes through the whole stack - UI, frontend, HTTP, backend and database - and asserts not about a single function but about all the parts being connected and working together. This is its unique strength: nothing else proves the connectivity of the whole system.
E2E's place is easiest to understand through the pyramid of levels. A unit test checks a precise rule and does it fast, but says nothing about whether the parts connect. An integration test checks the boundary between modules or with the database, but not the whole user path. E2E goes through the critical path in full - and that is exactly why it cannot and should not check all combinations of business logic: that is the work of the lower levels, where each check is cheaper and localizes the break more precisely.
| Level | Strength | Does not prove |
|---|---|---|
| Unit | A precise rule, fast | Connectivity of the parts |
| Integration | The module and database boundary | The whole user path |
| E2E | The critical path in full | All business-logic combinations |
From this follows the main economy principle. E2E tests are expensive in everything: they take longer to write, longer to run, are harder to maintain, and each one is a liability someone will fix at the next interface change. So you must not port all unit scenarios into E2E: the suite bloats, slows CI and falls apart at any edit. E2E takes on little, but the most valuable.
What exactly is valuable is decided by the business, not by coverage. Worth checking are a few of the system's most expensive promises: signing in, purchasing, publishing, checking access rights, recovering from a failure. These are paths whose breakage costs the company money or reputation directly. Half a dozen such scenarios working reliably give more confidence than a hundred small E2E tests duplicating unit logic through the browser.
The caveat about "everything real" matters. E2E is valued for real connectivity, but that does not mean the test must spend something real. A payment provider, sending an SMS, an external OAuth or a dangerous integration are replaced with a sandbox or a controlled fake - but this is done at the system's external boundary, not inside it. The point of E2E is to prove that your system is connected and works, not to make a real payment on every run.
The boundary of "real" runs along ownership. Everything you own - the frontend, your API, your database - must be real in E2E: it is their connectivity you are checking. Everything outside, unstable or dangerous - someone else's payment, a weather service, an SMS gateway - is replaced with a controlled boundary. Confusing these two sides is a typical mistake: by mocking your own API, the test stops proving anything about the integration it was written for.
It helps to lay out the levels and what each one proves once, so you do not argue about it in every code review. Below is a short map you return to when deciding at which level to catch a given risk: a rule with a unit, a boundary with integration, an expensive end-to-end path with E2E.
The typical failures at this level grow from misunderstanding its role. Porting all unit cases into E2E gives a slow and brittle suite that checks what is cheaper to catch lower down. Mocking your own system for the sake of stability gives a green test that does not prove connectivity. And chasing coverage of business-logic combinations through the browser spends hours of run time on what E2E is not meant for. Keep little at this level, and the most expensive.