How-To2026-09-0810 min read

How to Set Up End-to-End Testing in Cypress

End-to-end tests verify that your application works correctly from the user's perspective — clicking buttons, filling forms, navigating between pages, and seeing the right results. Without E2E tests, every deployment is a gamble: unit tests might pass while a broken API integration, a misrouted page, or a CSS regression ruins the actual user experience.

Cypress is the most widely adopted E2E testing framework for web applications because it runs directly in the browser, provides time-travel debugging, and uses a developer-friendly API that reads like plain English. This guide walks you through setting up Cypress from scratch with patterns that scale as your test suite grows.

Step-by-step guide

01

Install and Configure Cypress

Install Cypress as a dev dependency in your project: 'npm install -D cypress'. Run 'npx cypress open' to launch the interactive test runner, which creates the default folder structure: cypress/e2e for test files, cypress/fixtures for test data, and cypress/support for custom commands and global configuration. Configure your base URL in cypress.config.ts so tests use relative paths rather than hardcoded URLs.

  • Set 'baseUrl' in cypress.config.ts to your local development server URL (e.g., http://localhost:3000)
  • Add cypress.config.ts, cypress/screenshots, and cypress/videos to your .gitignore to avoid committing test artifacts
02

Identify Critical User Flows to Test

Don't try to test everything with E2E tests — they're slow and expensive to maintain. Focus on the critical paths that, if broken, would prevent users from getting value. For most products, these include: sign-up/login, the core value action (creating a project, making a purchase, sending a message), and key conversion flows. List five to seven flows that must work for your product to be usable and start there.

  • Rank flows by business impact — test the ones where a regression would cause the most damage first
  • Map each flow as a sequence of steps before writing code: 'Visit login → Enter credentials → Click submit → See dashboard'
03

Write Your First Test Suite

Create a test file in cypress/e2e for your most critical flow. Use 'describe' blocks to group related tests and 'it' blocks for individual scenarios. Use Cypress commands like cy.visit(), cy.get(), cy.type(), cy.click(), and cy.should() to interact with and assert on the page. Write tests from the user's perspective — select elements by visible text, labels, or data-testid attributes rather than CSS classes or implementation details.

  • Add 'data-testid' attributes to key UI elements in your application code for stable test selectors
  • Use cy.intercept() to stub API responses for tests that shouldn't depend on a live backend
  • Add meaningful assertion messages so test failures clearly describe what went wrong
04

Set Up Test Data and Fixtures

Create fixture files in cypress/fixtures for test data like user credentials, form inputs, and expected API responses. Use cy.fixture() to load this data in tests rather than hardcoding values. For database-dependent tests, create a seed script that resets the test database to a known state before each test run. Deterministic test data is the foundation of reliable tests — flaky tests almost always trace back to unpredictable data.

  • Create a 'test-user' fixture with credentials for a pre-seeded test account
  • Use cy.intercept() with fixture files to mock API responses for isolated frontend testing
05

Add Custom Commands for Common Actions

Create reusable custom commands in cypress/support/commands.ts for actions you repeat across multiple tests. The most common custom command is login: 'Cypress.Commands.add("login", (email, password) => { ... })'. Other useful commands include navigating to a specific page state, dismissing modals, and setting up test preconditions. Custom commands reduce duplication and make tests more readable.

  • Create a cy.login() command that authenticates via the API rather than the UI to speed up test setup
  • Add a cy.resetDatabase() command if your tests need a clean state between runs
06

Integrate Cypress into CI/CD

Add Cypress to your CI pipeline so tests run automatically on every pull request. Use the 'cypress run' command for headless execution in CI. Configure your CI to start the development server, wait for it to be healthy, then run Cypress tests against it. Store video recordings and screenshots of failures as CI artifacts so developers can debug failing tests without reproducing them locally.

  • Use the 'start-server-and-test' npm package to automatically start your dev server and run Cypress when it's ready
  • Configure GitHub Actions or your CI provider to upload cypress/screenshots and cypress/videos as artifacts on failure
  • Set a reasonable timeout (e.g., 120 seconds per test) to prevent hanging tests from blocking your pipeline

Common mistakes

Using Fragile Selectors

Selecting elements by CSS class names or DOM hierarchy means tests break whenever the styling or structure changes, even if the functionality is fine. Use data-testid attributes for test selectors — they're explicit, stable, and signal to developers that tests depend on this element.

Writing Tests That Depend on Other Tests

If Test B relies on Test A having run first, a failure in Test A cascades into a false failure in Test B. Each test should set up its own preconditions and be runnable in isolation. Use beforeEach hooks to establish the state each test needs rather than relying on test execution order.

Testing Too Many Things in One Test

A test that verifies login, navigation, form submission, and data display is hard to debug when it fails because you don't know which part broke. Keep each test focused on one user flow or assertion. It's better to have five clear, fast tests than one long, fragile test.

Not Handling Asynchronous Behavior

Cypress commands are asynchronous and Cypress handles waiting automatically, but you still need to assert on the right thing at the right time. Use cy.should() with retries rather than arbitrary waits. Never use cy.wait() with a fixed timeout — instead, wait for specific network requests or DOM conditions.

Tips

Use Cypress's interactive test runner during development for time-travel debugging — you can hover over each command to see the DOM state at that moment.

Parallelize test execution in CI using Cypress Cloud or by splitting test files across multiple CI jobs to reduce pipeline time.

Create a 'smoke test' suite with your five most critical tests that runs in under two minutes — use this for post-deployment verification.

Tag tests with grep-compatible names so you can run subsets (e.g., 'npx cypress run --spec "**/auth/**"') during focused development.

How Vantage helps

Vantage generates tickets with detailed acceptance criteria that map directly to test scenarios. When your engineering team writes E2E tests, they can reference Vantage ticket descriptions for exact user flows to test, reducing the gap between product requirements and test coverage.

Frequently asked questions

Spend less time on setup, more on decisions

Vantage connects your tools and generates specs grounded in real data. Free to start.

Free to start. No credit card required.

Related reading