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.