Cypress Testing Complete Reference Guide

What is Cypress?

Cypress is a next-generation front-end testing tool built for the modern web. It enables developers to write end-to-end tests, integration tests, and unit tests with a focus on developer experience and debugging capabilities. Unlike Selenium, Cypress runs directly in the browser alongside your application, providing real-time reloads, time travel debugging, and automatic waiting.

Why Cypress Matters:

  • Fast, reliable, and flake-resistant tests
  • Real-time browser preview and debugging
  • Automatic waiting eliminates test flakiness
  • Excellent developer experience with detailed error messages
  • Built-in screenshots and video recording
  • No need for explicit waits or sleep statements

Core Concepts & Principles

Cypress Architecture

  • Test Runner: Interactive GUI for writing and debugging tests
  • Dashboard: Cloud-based service for CI/CD integration and analytics
  • Browser Control: Direct browser automation without WebDriver
  • Network Stubbing: Intercept and mock network requests
  • Time Travel: Debug tests by stepping through commands

Key Principles

  • Tests run in the same event loop as your application
  • Cypress automatically waits for elements and assertions
  • Tests are written in JavaScript/TypeScript
  • Each test runs in isolation with a clean slate
  • Cypress can test anything that runs in a browser

Installation & Setup

Quick Start Installation

# Install Cypress via npm
npm install cypress --save-dev

# Install Cypress via yarn
yarn add cypress --dev

# Open Cypress Test Runner
npx cypress open

# Run Cypress tests headlessly
npx cypress run

Project Structure

cypress/
├── e2e/              # End-to-end test files
├── fixtures/         # Test data files
├── support/          # Custom commands and utilities
└── cypress.config.js # Configuration file

Basic Configuration (cypress.config.js)

const { defineConfig } = require('cypress')

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    viewportWidth: 1280,
    viewportHeight: 720,
    defaultCommandTimeout: 10000,
    requestTimeout: 10000,
    responseTimeout: 10000,
  },
})

Essential Cypress Commands

Navigation & Page Interaction

CommandPurposeExample
cy.visit()Navigate to URLcy.visit('/login')
cy.go()Browser navigationcy.go('back'), cy.go('forward')
cy.reload()Refresh pagecy.reload()
cy.title()Get page titlecy.title().should('eq', 'My App')
cy.url()Get current URLcy.url().should('include', '/dashboard')

Element Selection & Interaction

CommandPurposeExample
cy.get()Select elementscy.get('[data-testid="submit-btn"]')
cy.contains()Find by text contentcy.contains('Login').click()
cy.click()Click elementcy.get('#button').click()
cy.type()Type textcy.get('#email').type('user@example.com')
cy.clear()Clear input fieldcy.get('#search').clear()
cy.check()Check checkbox/radiocy.get('#terms').check()
cy.uncheck()Uncheck checkboxcy.get('#newsletter').uncheck()
cy.select()Select from dropdowncy.get('#country').select('Canada')

Assertions & Validation

CommandPurposeExample
.should()Make assertionscy.get('#error').should('be.visible')
.and()Chain assertionscy.get('#input').should('have.value', 'test').and('be.visible')
.expect()BDD assertionsexpect(result).to.equal('success')

Common Assertion Patterns

// Visibility assertions
cy.get('#element').should('be.visible')
cy.get('#element').should('not.exist')
cy.get('#element').should('be.hidden')

// Text content assertions
cy.get('#title').should('contain.text', 'Welcome')
cy.get('#message').should('have.text', 'Success!')

// Attribute assertions
cy.get('#link').should('have.attr', 'href', '/home')
cy.get('#input').should('have.class', 'active')
cy.get('#button').should('be.disabled')

// Length assertions
cy.get('.list-item').should('have.length', 5)
cy.get('.error').should('have.length.greaterThan', 0)

Advanced Testing Techniques

Network Requests & API Testing

Intercepting Network Requests

// Intercept and mock API calls
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')
cy.visit('/users')
cy.wait('@getUsers')

// Intercept with dynamic responses
cy.intercept('POST', '/api/login', (req) => {
  if (req.body.email === 'admin@test.com') {
    req.reply({ statusCode: 200, body: { token: 'abc123' } })
  } else {
    req.reply({ statusCode: 401, body: { error: 'Invalid credentials' } })
  }
}).as('login')

Testing API Endpoints

// Direct API testing
cy.request({
  method: 'POST',
  url: '/api/users',
  body: { name: 'John Doe', email: 'john@example.com' }
}).then((response) => {
  expect(response.status).to.eq(201)
  expect(response.body).to.have.property('id')
})

File Handling

CommandPurposeExample
cy.fixture()Load test datacy.fixture('users.json').then((users) => {})
cy.readFile()Read file contentcy.readFile('cypress/downloads/report.pdf')
cy.writeFile()Write to filecy.writeFile('path/to/file.txt', 'content')
cy.selectFile()Upload filescy.get('#file-input').selectFile('path/to/image.jpg')

Custom Commands

Create reusable commands in cypress/support/commands.js:

// Login command
Cypress.Commands.add('login', (email, password) => {
  cy.visit('/login')
  cy.get('#email').type(email)
  cy.get('#password').type(password)
  cy.get('#login-btn').click()
  cy.url().should('include', '/dashboard')
})

// Database seeding command
Cypress.Commands.add('seedDatabase', (fixture) => {
  cy.task('db:seed', fixture)
})

// Usage in tests
cy.login('user@example.com', 'password123')

Testing Strategies & Best Practices

Test Organization Patterns

Page Object Model

// pages/LoginPage.js
class LoginPage {
  visit() {
    cy.visit('/login')
  }
  
  fillEmail(email) {
    cy.get('#email').type(email)
    return this
  }
  
  fillPassword(password) {
    cy.get('#password').type(password)
    return this
  }
  
  submit() {
    cy.get('#login-btn').click()
    return this
  }
}

export default new LoginPage()

// Usage in tests
import LoginPage from '../pages/LoginPage'

it('should login successfully', () => {
  LoginPage.visit()
    .fillEmail('user@test.com')
    .fillPassword('password')
    .submit()
})

Data-Driven Testing

// Using fixtures for test data
describe('User Registration', () => {
  beforeEach(() => {
    cy.fixture('users').as('userData')
  })
  
  it('should register multiple users', function() {
    this.userData.forEach((user) => {
      cy.visit('/register')
      cy.get('#name').type(user.name)
      cy.get('#email').type(user.email)
      cy.get('#password').type(user.password)
      cy.get('#register-btn').click()
    })
  })
})

Element Selection Best Practices

Selector Priority (Recommended Order)

PrioritySelector TypeExampleWhy Use
1data-testid[data-testid="submit-button"]Test-specific, stable
2data-cy[data-cy="user-menu"]Cypress-specific
3ID#login-formUnique identifier
4Class.btn-primarySemantic meaning
5Text contentcy.contains('Save')User-facing text
6XPath/CSSinput[type="email"]Last resort

Selector Examples

// ✅ Good selectors
cy.get('[data-testid="user-profile"]')
cy.get('[data-cy="navigation-menu"]')
cy.get('#search-input')
cy.contains('button', 'Submit')

// ❌ Avoid these selectors
cy.get('.css-1234abc')  // Generated classes
cy.get('div > span:nth-child(3)')  // Brittle structure
cy.get('button:contains("Submit")')  // Non-standard syntax

Testing Scenarios & Examples

Form Testing

describe('Contact Form', () => {
  it('should submit form with valid data', () => {
    cy.visit('/contact')
    cy.get('#name').type('John Doe')
    cy.get('#email').type('john@example.com')
    cy.get('#message').type('Hello, this is a test message.')
    cy.get('#submit').click()
    
    cy.get('.success-message')
      .should('be.visible')
      .and('contain', 'Message sent successfully')
  })
  
  it('should show validation errors', () => {
    cy.visit('/contact')
    cy.get('#submit').click()
    
    cy.get('#name-error').should('contain', 'Name is required')
    cy.get('#email-error').should('contain', 'Email is required')
  })
})

Authentication Testing

describe('Authentication', () => {
  it('should login with valid credentials', () => {
    cy.visit('/login')
    cy.get('#email').type('user@example.com')
    cy.get('#password').type('password123')
    cy.get('#login-btn').click()
    
    cy.url().should('include', '/dashboard')
    cy.get('[data-testid="user-menu"]').should('be.visible')
    cy.getCookie('auth-token').should('exist')
  })
  
  it('should logout successfully', () => {
    cy.login('user@example.com', 'password123')  // Custom command
    cy.get('[data-testid="logout-btn"]').click()
    
    cy.url().should('include', '/login')
    cy.getCookie('auth-token').should('not.exist')
  })
})

E-commerce Testing

describe('Shopping Cart', () => {
  it('should add items to cart', () => {
    cy.visit('/products')
    cy.get('[data-testid="product-1"] .add-to-cart').click()
    cy.get('[data-testid="product-2"] .add-to-cart').click()
    
    cy.get('[data-testid="cart-counter"]').should('contain', '2')
    cy.get('[data-testid="cart-icon"]').click()
    
    cy.get('.cart-items').should('have.length', 2)
    cy.get('.cart-total').should('contain', '$39.98')
  })
})

Common Challenges & Solutions

Challenge: Flaky Tests

Problem: Tests that randomly pass or fail

Solutions:

// ❌ Bad: Using fixed waits
cy.wait(5000)

// ✅ Good: Wait for specific conditions
cy.get('#loading').should('not.exist')
cy.get('#data-table').should('be.visible')

// ✅ Good: Increase command timeout for slow elements
cy.get('#slow-element', { timeout: 10000 }).should('be.visible')

// ✅ Good: Retry assertions
cy.get('#dynamic-content').should('contain', 'Updated', { timeout: 5000 })

Challenge: Handling Async Operations

Problem: Dealing with dynamic content and AJAX calls

Solutions:

// Wait for network requests
cy.intercept('GET', '/api/data').as('getData')
cy.get('#refresh-btn').click()
cy.wait('@getData')

// Wait for DOM changes
cy.get('#counter').should('contain', '0')
cy.get('#increment').click()
cy.get('#counter').should('contain', '1')

// Wait for element to appear
cy.get('#modal').should('be.visible')

Challenge: Testing Third-party Integrations

Problem: External dependencies in tests

Solutions:

// Mock external APIs
cy.intercept('GET', 'https://api.external.com/**', {
  statusCode: 200,
  body: { data: 'mocked response' }
})

// Skip tests conditionally
it('should work with external service', () => {
  cy.task('checkExternalService').then((isAvailable) => {
    if (!isAvailable) {
      cy.skip('External service unavailable')
    }
    // Test logic here
  })
})

CI/CD Integration

GitHub Actions Example

name: Cypress Tests
on: [push, pull_request]

jobs:
  cypress-run:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      
      - name: Cypress run
        uses: cypress-io/github-action@v5
        with:
          build: npm run build
          start: npm start
          wait-on: 'http://localhost:3000'
          wait-on-timeout: 120
          browser: chrome
          record: true
        env:
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}

Docker Configuration

FROM cypress/included:12.9.0

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .

RUN npx cypress verify
CMD ["npx", "cypress", "run"]

Performance Optimization

Speed Up Test Execution

TechniqueImplementationImpact
Parallel Executioncypress run --record --parallel50-70% faster
Skip Unnecessary Testsit.skip() or describe.skip()Variable
Optimize SelectorsUse data attributes10-20% faster
Mock Network Callscy.intercept()30-50% faster
Reduce Test IsolationShare state between tests20-30% faster

Configuration Optimizations

// cypress.config.js
module.exports = defineConfig({
  e2e: {
    // Reduce default timeouts for faster failures
    defaultCommandTimeout: 4000,
    requestTimeout: 5000,
    responseTimeout: 5000,
    
    // Disable video recording in CI
    video: false,
    
    // Only capture screenshots on failure
    screenshotOnRunFailure: true,
    
    // Optimize viewport for faster rendering
    viewportWidth: 1000,
    viewportHeight: 600,
  },
})

Debugging & Troubleshooting

Debug Commands

CommandPurposeUsage
cy.debug()Pause test executioncy.get('#element').debug()
cy.pause()Stop and allow interactioncy.pause()
cy.log()Add custom log messagecy.log('Testing login flow')
.then()Access element/valuecy.get('#input').then(($el) => {})

Common Error Patterns

// Element not found
// ❌ cy.get('#nonexistent').click()
// ✅ cy.get('#element').should('exist').click()

// Timing issues
// ❌ cy.get('#button').click().get('#result')
// ✅ cy.get('#button').click().then(() => {
//     cy.get('#result').should('be.visible')
// })

// Assertion failures
// ❌ cy.get('#element').should('have.text', 'exact text')
// ✅ cy.get('#element').should('contain.text', 'partial text')

Best Practices Checklist

Test Structure

  • ✅ Use descriptive test names that explain the expected behavior
  • ✅ Follow AAA pattern (Arrange, Act, Assert)
  • ✅ Keep tests independent and isolated
  • ✅ Use beforeEach() for common setup
  • ✅ Clean up data after tests when necessary

Selectors & Elements

  • ✅ Prefer data-testid attributes for test-specific selectors
  • ✅ Avoid CSS selectors that depend on styling
  • ✅ Use semantic selectors when possible
  • ✅ Create page objects for complex applications

Assertions

  • ✅ Use explicit assertions instead of implicit ones
  • ✅ Assert on user-visible behavior
  • ✅ Combine multiple assertions with .and()
  • ✅ Wait for conditions rather than using fixed waits

Test Data

  • ✅ Use fixtures for static test data
  • ✅ Generate dynamic data when needed
  • ✅ Clean up test data to avoid interference
  • ✅ Use meaningful test data that reflects real scenarios

Essential Resources

Official Documentation

Community Resources

Video Tutorials

Tools & Plugins


This cheat sheet covers Cypress 12+ features and is updated for modern web testing practices in 2025.

Scroll to Top