Learn how to build, test, deploy, and maintain robust software systems.
Unit tests prove that individual functions work in isolation, but real systems are made of many parts that must work together: a frontend, an API, a database, and the browser itself. Integration and end-to-end (E2E) tests verify those connections. This lesson explains the difference between unit, integration, and E2E testing, how browser automation works, how to write stable tests with good selectors and network stubbing, and how to avoid flaky tests. It builds directly on the Unit Testing lesson, moving from "does this function work" to "does the whole system work for a real user."
Unit tests check one piece in isolation, but they cannot catch problems where two pieces interact: the frontend sends the wrong shape, the API returns a status the UI does not handle, or a change breaks the login flow.
| Unit | Integration | End-to-End | |
|---|---|---|---|
| Scope | One function/class | Several real components | The whole system |
| Environment | Isolated, mocked | Controlled (test DB) | Real browser + services |
| Speed | Milliseconds | Seconds | Seconds to minutes |
| Confidence | Low | Medium | High |
| Typical failures | Logic errors | Wiring between parts | Real user flows |
Most tests should be fast unit tests; E2E tests are fewer because they are slower and more expensive to maintain.
Integration tests run several real components together — for example, a service and a real database, or an API and its data access layer.
An E2E test drives a real browser as a user would.
Tools such as Playwright and Cypress control a real browser programmatically: they locate elements, click, type, and read the page. They share the goal but differ in API and architecture.
Selectors that depend on fragile CSS or layout make tests break when the UI changes. Prefer stable, semantic identifiers such as data-testid attributes.
| Selector | Fragility |
|---|---|
| .nav > div:nth-child(2) > button | Fragile — breaks on any layout change |
| [data-testid="login-submit"] | Stable — survives restyling |
Stubbing replaces a real API response with a controlled one, so tests are predictable and do not depend on a live backend. For full confidence, some tests should still hit a real backend.
Visual regression compares screenshots to detect unintended visual changes. It is powerful but sensitive to fonts, environments, and rendering differences, which can cause false positives.
The test drives a real browser, uses stable data-testid selectors, and asserts on the result.
A stable selector survives restyling; a fragile one does not.
(the stable selector keeps working after UI changes)