End-to-End Testing vs Integration Testing: Where Each Test Should Actually Live

End-to-end testing verifies that a real user can complete a real workflow through the actual interface, with real services connected. Integration testing verifies that two or more components communicate correctly through their interfaces. Both are part of a healthy functional testing strategy, and each catches a category of bug the other can’t.

The pain point sits in the space between those two definitions. A test case lands on the whiteboard, CI already takes 40 minutes, and half the team wants it in integration because it’s cheap, while the other half wants it in E2E because “it touches the UI”. Every article on end-to-end testing vs integration testing ends with the same shrug: use both, follow the 70/20/10 pyramid, good luck. That advice tells you nothing about the test in front of you.

This piece does. Four questions, five seconds per test case, real checkout examples. The choice is framed as a cost-of-feedback decision because that’s what it actually is.

Why The 70/20/10 Rule Leaves You Stuck

The pyramid is a distribution target. It says roughly 70% unit, 20% integration, 10% E2E, and stops there. Distribution is the outcome of good per-test placement decisions, not something you achieve by shuffling tests between layers until the ratio looks right. That’s why the E2E testing vs integration testing debate never gets resolved in most teams: everyone argues about the ratio, and nobody agrees on the rule.

Two failure modes show up when teams treat the pyramid as advice. The first is E2E bloat. Anything that touches the UI gets pushed up; the suite balloons to 45 minutes; developers stop running it locally; and CI becomes a rubber stamp. The second is integration over-mocking. Everything below the browser gets mocked out to keep tests fast, the suite stays green, and production breaks on a Stripe webhook shape change that no test ever exercised against the real payload.

Both failures come from the same root cause: nobody had a rule for deciding where a specific test belongs. That’s the gap we’re closing.

The Four-Question Placement Filter

Run every new test case through these four questions in order. Each one isolates a single decision. If the answers point to a real browser, real services, and a revenue path, the test earns an E2E slot. If not, it belongs at the integration layer, and your CI budget thanks you.

End-to-End Testing vs Integration Testing: Where Each Test Should Actually Live

Question 1: What Layer Is the Assertion on?

The assertion tells you where the test belongs. Take a coupon test: “applying a 15%-off code reduces the cart total.”

If the assertion is expect(response.discount).toBe(15), this is an integration test. You’re checking that the pricing service returned the correct number. The browser is irrelevant. This is the layer where our API testing work sits, and it’s where most business-logic verification should live.

If the assertion is expect(page.getByTestId(‘cart-total’)).toHaveText(‘$85.00’), this is an E2E test. You’re checking that the number reached the DOM, was rendered in the right element, and matched what a user would see.

Same business logic. Two different tests. Two different homes. The rule: the layer of the assertion decides the layer of the test. Integration tests that assert on rendered text are lying integration tests. They either need a real browser (in which case they’re E2E) or a different assertion (in which case they’re integration done right).

Question 2: Is the Third Party the Subject or the Plumbing?

Every checkout flow depends on external services. Stripe, Twilio, Auth0, SendGrid. The question is whether the third party is what you’re testing or just something the test happens to touch.

Take a declined-card test: “a declined Stripe card shows the retry screen”.

If you’re verifying that Stripe’s decline codes map correctly to your internal error taxonomy, Stripe is the subject. This is an integration test against Stripe’s test mode or a recorded fixture. What you care about is whether card_declined becomes PaymentFailedError.RetryableDecline on your side.

If you’re verifying that the user sees a retry button, can re-enter card details, and gets a fresh attempt, Stripe is plumbing. The retry UI is the subject. This is an E2E test.

The mistake teams make is pushing anything that touches a third party up to E2E because “it needs the real service.” It doesn’t. It needs the real service only when the service is what you’re asserting on. Everything else can mock the plumbing and test the actual subject at the cheaper level.

Question 3: Would a Contract Change Break This Test Even If Behavior Is Unchanged?

Take an email test: “The order confirmation email contains the order ID”.

If a downstream service renames orderId to order_id and this test breaks even though the user still receives the correct email with the correct ID, you have a contract test dressed as an E2E test. The failure log will say “expected field orderId, got order_id.” That’s an integration failure, and integration is where it should surface: fast, cheap, and pointing directly at the broken contract.

E2E tests should fail on behavior, meaning the user got the wrong outcome. When they fail on shape, meaning a payload field is renamed, but the user experience is identical, debugging cost multiplies by roughly 10x because you’re chasing a JSON diff through a browser trace, screenshots, and network logs. Contract failures belong at the integration layer and are verified using schema assertions or a contract-testing tool.

Question 4: Revenue-Critical Flow or Secondary Branch?

Not every real user journey earns an E2E test. Take a settings test: “user updates notification preferences from weekly to daily digest”.

Real workflow. Real database write. Real user impact. It still belongs at the integration layer. Every E2E test you add taxes every future pull request, and the tax compounds. Reserve E2E slots for flows where a failure is a revenue event: signup, login, checkout, payment, subscription upgrade, account recovery. Everything else earns integration coverage unless there’s a specific UI-rendering risk that only a browser can catch.

This is the framing the entire SERP misses. Integration testing vs end-to-end testing is a cost-of-feedback decision. The economics reinforce it: IBM’s Report puts the US average breach cost at a record $10.22 million, and while not every production incident is a breach, the same asymmetry applies to any revenue-path failure. Spend your E2E budget where a failure would cost you a customer.

Tests That Got Promoted to E2e and Shouldn't Have Been

This is the single most common waste category in a bloated E2E suite. Each of these has a specific failure signature: the test fails, and the fix is a one-line change to code that never needed a browser to verify.

  • Form validation tests. “Email field rejects strings without @”. This belongs at the integration layer and should be tested against the validator. If it lives in E2E, you’re paying the browser-launch cost to test a regex.
  • API error-mapping tests. “A 500 from the payment service shows ‘try again later.’” Integration test at the error handler. The browser is not what maps the error.
  • Database persistence tests. “A submitted contact form appears in the admin panel.” Two integration tests, one for the write and one for the read, beat one flaky E2E that spans both and takes 30 seconds per run.
  • Feature-flag tests. “Users with flag X see variant B.” Integration test at the flag-resolution layer. E2E only if the visual variant itself carries risk.
  • Permission tests. “A non-admin user can’t hit /admin.” Integration test at the auth middleware. Every restricted route wrapped in an E2E is a suite you’ll be pruning in six months.

Diagnostic you can run on your own team this week: pull the last five E2E failures. If each were fixable by looking at a network response rather than a screenshot, your suite has drifted downward. Half of it belongs at the integration layer, and the CI clock is the receipt.

Tests That Got Demoted to Integration and Broke Production

The reverse mistake is rarer, more expensive, and usually goes unnoticed until a customer files a ticket. A test moved down to integration for speed; mocks fill in for real services; everything stays green; production breaks on the exact thing the mocks concealed.

Multi-step flows with state held in the browser are the classic case. Cart persistence across pages, multi-step wizards, session-based checkout. When you mock the session layer, you also mock away the bug that ships. Third-party redirects are another. OAuth callbacks, Stripe Checkout, PayPal handoff. The redirect round-trip is where things break, and it doesn’t exist in an integration test. CSS-dependent behavior belongs in E2E too: a submit button hidden behind a modal because a z-index changed on Tuesday’s release will not surface at any layer below a real browser. Cross-origin cookie behavior, SameSite changes, and iframe authentication all fall into the same bucket.

The rule: if the bug would only appear in a real browser talking to real services, integration can’t own it, regardless of how comprehensive the mocks are. Distributed systems intensify this pattern, which is why we cover it separately in our piece on microservices performance testing.

The Clean Split

Two ownership statements distill the whole piece. Integration owns:

  • Service-to-service communication and the shape of what crosses the boundary
  • Contract checks: field names, types, error codes, response schemas
  • Business logic below the browser: validation, error mapping, permissions, feature-flag resolution, database reads and writes
  • Any test where the third party is the subject of the assertion
  • Any test where a failure would surface as “wrong service response” rather than “wrong thing on screen”

E2E owns:

  • Rendered output: the right thing on the screen, in the right place, at the right time
  • Multi-step flows with state carried in the browser
  • Third-party redirects and round-trips (OAuth callbacks, Stripe Checkout, PayPal handoff)
  • CSS- and DOM-dependent behavior (modals, z-index, hidden buttons, cross-origin cookies)
  • Revenue-critical journeys where a failure means a lost customer

One-line summary: integration proves the pieces talk correctly; E2E proves the user gets through. If a test tries to do both, split it into two.

Now, the placement rule is a working tool. Run any test through the four questions. If Q1 answers “rendered output,” Q2 answers “subject,” and Q4 answers “revenue-critical,” the test earns its E2E slot. Otherwise, default to integration. Framework choice comes second and matters less than most teams think. Whether your team runs Cypress, Playwright, or Selenium, the discipline is in place, and our page on automated testing covers how we operationalize it end-to-end.

Where Each Test Earns Its Place

The difference between integration and E2E testing is not a taxonomy question. It’s a per-test decision that shapes your CI clock, your production incident rate, and your team’s trust in the suite. Four questions, applied consistently, replace the argument-per-test with a rule the whole team can share.

If your suite has drifted, if CI takes 40 minutes when it used to take 8, or if production keeps breaking on things your integration tests said were fine, contact us, and we’ll audit where your tests actually live versus where they should.

FAQ

What is the difference between integration testing and end-to-end testing?

Integration testing verifies that two or more components communicate correctly through their interfaces. End-to-end testing verifies that a user can complete a workflow through the actual interface with real services connected. The practical difference lies in where the assertion sits: in a service response or in what a user sees.

How do I decide if a test case belongs in integration or E2E?

Ask what the assertion is checking. If it’s a service response, a contract, or a data shape, it belongs at the integration layer; if it’s a rendered element or a multi-step user flow, it belongs at E2E. Any test that would still pass with a broken UI is not an E2E test.

When is integration testing enough, and when do you need E2E?

Integration is enough when the third party is the subject of the test, when a failure would surface as a wrong service response, or when the flow lives off the revenue path. You need E2E when the browser carries state across steps, when third-party redirects are involved, or when a failure means a lost customer on signup, checkout, or payment.

Why is my E2E test suite too slow?

Tests that belong at the integration layer got pushed up: form validation, error mapping, permission checks, and feature-flag resolution rarely need a real browser. Audit the last five E2E failures on your project, and if each was fixable with a network response rather than a screenshot, most of your suite is in the wrong place.

See how QAwerk stabilized checkout and payment integrations and improved regression coverage for Kazidomi before its expansion across Europe.

Please enter your business email isn′t a business email