We had a signup flow that passed its E2E suite every single time a backend bug shipped that made new accounts default to the wrong pricing tier. The UI never noticed, because the UI didn't care what tier got assigned, it just cared that signup completed and redirected somewhere that looked like a dashboard. It took a support ticket from an annoyed customer to catch what an API-level assertion would have flagged in about four seconds.
That's the case for testing the same flow twice, on purpose, at two different layers. Not out of paranoia, and not because either layer is unreliable on its own, but because each one is structurally blind to a different category of bug.
Two layers, two different questions
An E2E test asks: does this work the way a person experiences it? Click, type, submit, does the right thing show up on screen, in the right state, without anything visibly breaking. It's the closest thing to ground truth for "does this feature actually work," and nothing else really substitutes for it.
An API test asks a narrower, sharper question: given this exact request, is the response correct? Right status code, right shape, right data, right side effects in whatever it touched. It doesn't care what any of that looks like rendered. It just cares whether the backend did the right thing.
The pricing-tier bug above is a clean example of the gap. The UI layer genuinely couldn't have caught it, because the UI never displayed the tier during signup at all — that information only showed up three screens later, in a part of the flow the E2E test didn't walk through. The API response, on the other hand, included the tier on every signup call. An assertion there would have caught it same-day.
Why one flow, tested twice, isn't redundant
The instinct to avoid this is understandable — testing the same user journey twice feels like doubling maintenance for no reason. In practice it's closer to doubling your blind-spot coverage than doubling your effort, because the two layers rarely fail for the same reason.
A flow can be API-correct and UI-broken: the backend does exactly the right thing, and a frontend bug displays it wrong, loses it in a bad render state, or fails to trigger the next step. Only the E2E test catches that.
A flow can be UI-correct and API-wrong: the frontend renders a perfectly reasonable-looking success state built on a response that's subtly incorrect underneath — wrong tier, wrong currency, a field that's silently null where it shouldn't be. Only the API test catches that, and usually catches it faster, because it's checking data directly instead of waiting for a person (or an E2E assertion) to notice something looks off on screen.
What actually changes when you build both, deliberately
This isn't "run every test twice." It's picking the handful of flows where the consequences of being wrong are actually expensive — signup, checkout, anything involving money or account state — and being intentional about covering each one from both directions instead of assuming one layer implies the other.
In practice this usually means the API-level version of the test goes deeper into the specific values and edge cases (what happens with an invalid promo code, what happens if a webhook fires twice, what the response looks like for every plan tier) while the E2E version stays focused on the handful of states that actually matter visually — does the confirmation show up, does the redirect happen, is there any state where the user gets stuck or sees something clearly broken.
Playwright api testing makes this pairing easier to actually maintain than it would be with two separate tools, since request-level API calls and full browser interaction live in the same test runner, the same fixtures, sometimes even the same file — which matters, because the version of this that never gets written is the one that requires switching tools and mental models every time.
The part that's easy to skip
The temptation, once both layers exist, is to let the API test quietly become the E2E test's setup step — using API calls to fast-forward through login or account creation so the E2E test can jump straight to the interesting part. That's a legitimate and common pattern for speed, but it's worth being honest that it's a different use of the API layer than testing the API layer as its own subject. A signup API call used to skip ahead in an E2E test isn't verifying the signup response is correct. It's assuming it is, so the UI test can move faster.
Both are useful. They're just not interchangeable, and conflating them is how a team ends up with heavy API usage across their suite and still no actual API-level assertions checking that the backend does the right thing on its own terms.
Where this earns its keep
The signup bug at the start of this piece is the shape of thing this pattern catches early instead of catching from a support ticket: something correct enough to pass the layer someone happened to test, and wrong enough to matter in the layer nobody checked. Testing a flow once tells you it works from one angle. Testing it deliberately from two tells you which angle, if either, is lying to you when something eventually breaks.
Top comments (0)