End-to-End Testing with Cypress
In this page:
What Makes a Test 'End-to-End'
Unlike unit or integration tests that check isolated pieces, an end-to-end (E2E) test drives your ACTUAL running app in a real browser, simulating a complete user journey from start to finish — like visiting a signup page, filling the form, submitting, and confirming a welcome message appears.
Note: E2E tests give the highest confidence that a real feature works, since they exercise the whole stack, but they're also the slowest kind of test to run.
Warning: Because E2E tests are slow and can be flaky, reserve them for your most critical flows (signup, checkout, login) rather than every small feature.
Example: What Makes a Test 'End-to-End'
// Run in your local React project (npm install required)
// cypress/e2e/login.cy.js
describe('Login flow', () => {
it('logs in successfully', () => {
cy.visit('/login');
cy.get('input[name=email]').type('[email protected]');
cy.get('button').contains('Log In').click();
cy.contains('Welcome back!').should('be.visible');
});
});
Core Cypress Commands
cy.visit(url) navigates to a page, cy.get(selector) finds elements (similar in spirit to Testing Library's queries, but CSS-selector-based), and commands like .click() or .type() interact with them, all chained together to describe a full user flow.
Note: Cypress commands are chainable, so you can write a fluent sequence like cy.get(input).type(hello).should('have.value', hello).
Warning: Cypress commands are asynchronous under the hood even though they don't need await in your code — Cypress manages the queueing and retrying automatically.
Example: Core Cypress Commands
// Run in your local React project (npm install required)
describe('Search feature', () => {
it('shows results after searching', () => {
cy.visit('/search');
cy.get('input[type=search]').type('react{enter}');
cy.get('.result-item').should('have.length.greaterThan', 0);
});
});
Automatic Retrying and Avoiding Flaky Tests
Cypress automatically retries most assertions for a few seconds before failing, which handles a lot of async timing (like waiting for an API response to render) without manual sleeps or arbitrary waits. This built-in retrying reduces, though doesn't fully eliminate, timing-related test flakiness.
Note: Avoid cy.wait(1000)-style fixed delays — let Cypress's built-in retrying handle timing, asserting on the eventual state instead.
Warning: Relying on fixed-duration waits instead of Cypress's retry-based assertions makes tests slower on fast runs and still flaky on genuinely slow ones.
Example: Automatic Retrying and Avoiding Flaky Tests
// Run in your local React project (npm install required)
// Avoid this (fixed, unreliable wait):
// cy.wait(2000);
// cy.get('.result').should('exist');
// Prefer this (Cypress retries automatically until it appears or times out):
cy.get('.result', { timeout: 10000 }).should('exist');
- Writing E2E tests for every small detail instead of reserving them for critical, full user journeys.
- Making E2E tests depend on real, external, unpredictable data instead of a controlled test environment.
- Not waiting properly for async content, causing tests to fail intermittently ('flaky tests') depending on timing.
- Cypress is a popular end-to-end testing tool that drives a real browser to simulate complete user flows.
- cy.visit(), cy.get(), and cy.click() are core commands for navigating and interacting with a real page.
- Cypress automatically retries assertions for a short time, reducing (but not eliminating) timing-related flakiness.
- E2E tests are slower than unit tests, so they're reserved for critical, high-value user flows.
Requires npm install cypress — runs a real browser via Cypress's test runner, not this preview sandbox.
Chapter Quiz — Complete all 9 topics to unlock
0/9 topics done
Complete these topics first: