The Testing Pyramid: Where to Start with Front-End Testing
Start with fast checks for isolated logic and component behavior, add integration tests at important seams, and use browser tests for critical user journeys.
The testing pyramid is a way to balance fast feedback with confidence in how your frontend works. Start with focused tests for isolated logic and component behavior, add integration tests where components or services meet, and reserve browser-driven end-to-end tests for critical user journeys. Treat the pyramid as a guide to choosing coverage, not a required test-count ratio.
Choose the lowest level that gives useful confidence about a behavior. Move up when the risk depends on a real interaction across boundaries or on the complete flow a user sees. The UK Home Office test pyramid guidance likewise says to adapt the model to the system’s complexity, risk, time, and resources.
1. What the testing pyramid means
The pyramid describes a portfolio of tests with different costs and kinds of feedback:
- Unit tests: Check isolated logic such as formatting, validation rules, reducers, or calculations. They are most useful when a failure should point to a small piece of code.
- Component tests: Render a component and exercise its user-visible behavior, such as entering text, choosing an option, or seeing an error. They help check interactions without requiring the entire application journey.
- Integration tests: Check that connected parts work together, such as a form and its validation, a component and an API boundary, or a route and its data-loading behavior.
- End-to-end (E2E) tests: Drive the application in a browser through a real user journey, covering the assembled frontend and the configured services it depends on.
The layers are not rigid categories. A test can render a component in a browser while still focusing on one component’s behavior. What matters is the confidence it provides, the boundaries it exercises, and its cost to run and maintain.
2. Where to start: a practical sequence
- List the user-visible failures that matter. Consider the main navigation, sign-in, a high-value form, data persistence across screens, or a purchase flow. Cypress identifies authentication, purchasing, and multi-screen persistence as common E2E scenarios in its testing-types guidance.
- Cover isolated rules with focused tests. Test calculations, data transformations, and validation logic at the smallest level that makes failures easy to diagnose.
- Exercise important component behavior. Render the component and interact with it as a user would. Check visible results, accessible names, errors, and state changes rather than private implementation details.
- Add integration coverage at risky seams. If behavior relies on two or more parts agreeing, test that boundary. Examples include submitting a form and showing the server response, or selecting a route and rendering the corresponding data.
- Automate a small set of critical journeys in a browser. Cover complete flows where wiring, routing, browser behavior, or service integration could break the outcome. Keep the set focused enough that failures remain actionable.
- Review the suite as the application changes. Remove redundant coverage, improve tests that fail without explaining why, and add coverage when a user-impacting gap appears.
For each behavior, ask: What could fail? Which boundary is involved? What is the narrowest test that would catch it reliably? Would a browser journey add meaningful confidence beyond existing checks?
3. Choose a test level by the risk
| Concern | Good starting level | Why | Move up when |
|---|---|---|---|
| Price, date, or formatting calculation | Unit | Fast feedback and a focused failure location | The calculation’s integration with displayed or persisted data is also risky |
| Field validation and error display | Component | Checks rendered behavior and interaction | Submission, server responses, or multiple fields must work together |
| Form submit and API response | Integration | Exercises the boundary between UI and service behavior | Routing, authentication, browser behavior, or a complete journey is at risk |
| Sign-in, purchase, or multi-screen persistence | End-to-end | Checks the user journey across assembled parts | Usually keep the flow focused; add lower-level tests for detailed cases |
| Keyboard navigation or accessible interaction | Component or browser, depending on scope | Checks what can be operated and perceived in rendered output | The concern spans routes, dialogs, or browser-level behavior |
These are starting points, not rules. The Cypress documentation covers end-to-end, component, API, and accessibility test types; select based on the application and the question the test needs to answer.
4. Test what users can observe
A test tied to internal implementation details can break during a harmless refactor while the page still works. Prefer assertions about rendered content, accessible roles and names, enabled or disabled controls, and the outcome of user actions. Playwright’s best-practices guidance says tests should typically interact with the same rendered output the end user sees.
For example, a sign-in component test can enter credentials, submit the form, and assert that a visible error appears for invalid input. It should not need to assert which private state variable changed. A browser test can then cover a critical successful sign-in journey if the route, session behavior, and navigation together are important risks.
5. How many tests belong in each layer?
There is no universal ratio. Google’s Testing Blog published a historical starting suggestion in 2015: “As a good first guess, Google often suggests a 70/20/10 split: 70% unit tests, 20% integration tests, and 10% end-to-end tests.” That is a heuristic from “Just Say No to More End-to-End Tests”, not a current universal standard or a quota for your repository.
Adjust the mix to the application’s shape. A complex integration or safety-sensitive flow may need more browser-level confidence. A short-lived prototype may justify a lighter automated suite. Track whether each test catches a meaningful class of failure and whether its feedback is fast and understandable.
6. Decide between unit, integration, component, and E2E checks
- Fidelity: Does the test exercise the behavior users actually see?
- Diagnosis: If it fails, can the team tell what likely broke?
- Feedback speed: How long does it take to get a useful result?
- Setup burden: Does it need a server, browser, test data, or external service?
- Maintenance: Is the check stable, or does it often need repair after unrelated changes?
- Boundary coverage: Which component, API, route, or browser behavior does it really exercise?
End-to-end tests provide valuable confidence in complete journeys, but they involve more setup, infrastructure, execution, and maintenance than focused lower-level checks. Cypress explains these tradeoffs in its testing types documentation. Use E2E for risks that justify that cost; use narrower tests for detailed cases and quick feedback.
7. A browser-level check for a critical journey
The precise test syntax depends on the chosen framework and application. This generic runnable browser-check outline shows the behavior to cover; replace the selectors, URL, and credentials with your test environment’s values. It assumes a Playwright project is installed and configured with a browser.
import { test, expect } from '@playwright/test';
test('a user can sign in and reach the dashboard', async ({ page }) => {
await page.goto('http://localhost:3000/sign-in');
await page.getByLabel('Email').fill('user@example.test');
await page.getByLabel('Password').fill('correct-test-password');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page).toHaveURL(/dashboard/);
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});
Run it against an isolated test environment with controlled accounts and data. Avoid depending on production users or mutable shared state. For framework-specific installation and configuration, use the official Playwright guidance and the documentation for the runner in your project.
8. Screenshot checks: use them for visual questions
A screenshot can help investigate layout changes, responsive behavior, or whether a page renders as expected at a chosen viewport. It is an artifact to inspect or compare; it does not replace assertions about interactions, accessibility, or business behavior. For stable visual checks, control the page state, viewport, fonts, data, and timing so irrelevant rendering differences do not obscure a real change.
To inspect a rendered page manually, capture a representative route at the relevant viewport. If the concern is whether a button submits correctly, write an interaction assertion as well; a static image cannot prove the behavior.
9. Or skip the browser setup
For a screenshot of a page without configuring browser automation, ScreenshotNeo provides a website screenshot API and MCP server. Its one-call API returns an image or PDF. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server lets Claude, Cursor, and other MCP clients use screenshot tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Those screenshots can help you inspect a page, but they do not replace automated behavioral tests for your application.
Sign up free for 1,000 screenshots a month, with no card required.
10. Troubleshooting a test suite
| Symptom | Likely cause | Fix |
|---|---|---|
| A small change breaks many tests | Tests depend on internal structure or shared setup | Assert on rendered behavior; isolate data and setup; keep each test focused |
| Browser tests fail intermittently | Uncontrolled timing, shared mutable data, or an external dependency | Wait for an observable condition, use isolated test data, and control the test environment |
| A test passes locally but fails in CI | Environment differences, race conditions, missing services, or resource constraints | Compare browser and service versions, make prerequisites explicit, and capture useful failure output |
| Many tests fail after one backend outage | Too many checks depend on the same live service | Use controlled boundaries for focused tests and reserve live integration journeys for cases where they provide needed confidence |
| The suite is slow and gives little guidance | Too much broad browser coverage or redundant checks | Move detailed cases to focused tests; keep browser tests for important cross-system risks |
| Visual snapshots change unexpectedly | Fonts, dynamic content, viewport, animations, or data differ | Fix the rendering inputs and state before accepting or rejecting the visual change |
11. Performance, reliability, and cost
Test cost includes more than runtime: consider setup, compute, debugging, maintenance, and the delay before a developer gets feedback. Keep isolated checks easy to run during development. Run broader integration and browser checks at appropriate points in the workflow, while preserving a small fast path for common changes.
Reliability improves when tests control their inputs and assert on observable outcomes instead of arbitrary delays. When a browser journey fails, capture enough context to diagnose it, such as the failing step and relevant browser output. Do not repeatedly retry a genuinely broken test until it appears green; investigate instability and fix its cause.
There is no sourced universal runtime or cost benchmark for the layers. Measure your own suite: feedback time, failure usefulness, maintenance effort, and the user risks covered. The Home Office guidance recommends adapting the pyramid to available time and resources rather than forcing one shape.
12. A checklist for a new frontend project
- List the user journeys where failure would cause real harm or block a key task.
- Cover calculations, transformations, and validation rules with focused tests.
- Test components through rendered output and meaningful interaction.
- Add integration checks at boundaries where parts must agree.
- Automate a concise set of critical browser journeys.
- Make test data and external dependencies predictable.
- Review slow, flaky, redundant, and hard-to-diagnose tests regularly.
- Change the layer mix when the application’s risks, lifetime, or architecture change.
13. Frequently asked questions
Do I need end-to-end tests for every page?
No. Cover representative critical journeys and use focused checks for detailed cases. A page-by-page browser script often adds maintenance without answering a distinct risk.
Should component tests use a real browser?
That depends on what you need to validate and the tooling. Cypress describes component testing as mounting components directly in a browser. A browser can be useful when browser rendering or interaction is part of the concern.
Is a screenshot test the same as an end-to-end test?
No. A screenshot records rendered pixels at a point in time. An end-to-end test exercises a user flow and asserts its outcomes; a flow may also produce screenshots for diagnosis.
Which testing framework should I choose?
Choose based on your stack, the test levels you need, and the team’s ability to run and maintain the suite. The cited guidance describes test types and tradeoffs but does not establish one framework as best for every frontend project.


