Introduction

End-to-end testing has a reputation for being slow and flaky. Selenium-based tools run in a separate process from the browser, leading to timing issues and tests that fail randomly even when the application is fine. Cypress takes a different approach: it runs inside the browser itself, in the same event loop as the application. This eliminates a whole category of timing problems and makes tests faster to write and easier to debug.

What is Cypress?

Cypress is an open-source testing tool built for modern JavaScript applications. It runs directly in the browser and handles React, Angular, and Vue.js without extra configuration. It comes with a graphical test runner that shows you exactly what happened at each step, and an automatic waiting mechanism that removes the need for explicit sleeps or waits in your tests.

Key Features of Cypress

  • Time Travel: Cypress takes snapshots as tests run. Hover over any command in the log to see the application's state at that moment.
  • Debuggability: Readable error messages and stack traces. Failures tell you what happened, not just which assertion failed.
  • Automatic Waiting: Cypress waits for DOM elements, network requests, and animations before moving on. You don't sprinkle cy.wait(1000) throughout your tests.
  • Real-Time Reloads: Test files reload automatically when you save them.
  • Network Traffic Control: Intercept and stub HTTP requests to test error states and edge cases without a live backend.

Why Use Cypress?

Flaky tests are worse than no tests: they erode confidence until the team starts ignoring failures. Cypress's in-process model eliminates most of the race conditions that cause flakiness in other tools. The interactive GUI makes debugging failures fast, since you can see what the application looked like at the exact moment the test failed.

How to Implement Cypress

Installation

Ensure you have Node.js installed, then add Cypress to your project:

npm install cypress --save-dev

Alternatively, using yarn:

yarn add cypress --dev

Initial Setup

Open Cypress for the first time to generate the required folders and files:

npx cypress open

This creates a cypress folder with:

  • fixtures/ for static test data.
  • integration/ for test files (specs).
  • plugins/ to extend or modify Cypress behavior.
  • support/ for reusable custom commands and global configuration.

Configuration

Customize settings in cypress.json:

{
  "baseUrl": "http://localhost:3000",
  "viewportWidth": 1280,
  "viewportHeight": 720,
  "video": true
}

baseUrl is the URL where your app runs. viewportWidth and viewportHeight set the browser dimensions. video enables recording of test runs.

Creating an Example System

Setting Up a Sample Application

Create a basic Todo application using React:

Step 1: Create a New React App

npx create-react-app cypress-todo-app
cd cypress-todo-app

Step 2: Build the Todo Application

Implement a simple interface where users can add and remove todo items, then start the development server:

npm start

Writing Cypress Tests

Step 1: Create a New Test File

Inside cypress/integration/, create todo.spec.js.

Step 2: Write Tests

describe('Todo App', () => {
  beforeEach(() => {
    cy.visit('/');
  });

  it('Displays the app title', () => {
    cy.contains('h1', 'Todo App');
  });

  it('Can add a new todo item', () => {
    const newItem = 'Buy milk';
    cy.get('input[name="new-todo"]').type(`${newItem}{enter}`);
    cy.get('.todo-list').should('contain', newItem);
  });

  it('Can remove a todo item', () => {
    const itemToRemove = 'Buy milk';
    cy.get('.todo-list')
      .contains(itemToRemove)
      .parent()
      .find('.delete-button')
      .click();
    cy.get('.todo-list').should('not.contain', itemToRemove);
  });
});

Step 3: Run the Tests

npx cypress open

Select todo.spec.js to run the tests interactively.

Running Tests and Analyzing Results

The Cypress test runner shows each command as it executes. You can hover over any command in the log to see a snapshot of the application at that point. Failed tests show screenshots automatically, and video recording captures the full run.

Advanced Features

Fixtures

Fixtures load static data from files into your tests:

cy.fixture('user.json').then((user) => {
  // Use user data in your tests
});

Custom Commands

Repeated setup code belongs in custom commands, not copy-pasted across specs.

In cypress/support/commands.js:

Cypress.Commands.add('login', (email, password) => {
  cy.visit('/login');
  cy.get('input[name="email"]').type(email);
  cy.get('input[name="password"]').type(password);
  cy.get('form').submit();
});

Use the custom command in your tests:

cy.login('test@example.com', 'password123');

Continuous Integration with Cypress

Example with GitHub Actions

Create a .github/workflows/cypress.yml file:

name: Cypress Tests

on: [push]

jobs:
  cypress-run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Install Dependencies
        run: npm install
      - name: Run Cypress Tests
        run: npx cypress run

This runs Cypress in headless mode on every push.

Conclusion

Cypress is worth adopting if your team has been burned by flaky Selenium tests or by the slow feedback cycle of traditional E2E tools. The in-browser architecture solves the timing problem at its root. The interactive GUI makes debugging practical rather than painful. Start with a handful of tests covering your most-used user flows, get them passing reliably in CI, and expand from there.

Additional Resources