Skip to content
Cypress E2E Testing Framework — Practical Guide

Cypress E2E Testing Framework — Practical Guide

DodaTech Updated Jun 7, 2026 7 min read

Cypress is a developer-friendly end-to-end testing framework that runs tests in the browser alongside your application, providing real-time reloading, time-travel debugging, and automatic waiting for seamless test authoring.

What You’ll Learn

By the end of this tutorial, you’ll be able to set up Cypress, write reliable E2E tests using commands and custom assertions, manage test data with fixtures, intercept and stub network requests, and use the Cypress Dashboard for CI debugging.

Why Cypress Matters

Cypress reimagined E2E testing by running inside the browser — not in a separate process. This gives you access to the DOM, network, and application state in real time. At DodaTech, Cypress validates critical user flows in DodaZIP’s web interface, ensuring file operations work correctly before every deployment.

Cypress Learning Path

    flowchart LR
  A[Testing Basics] --> B[Jest]
  B --> C[Playwright]
  C --> D[Cypress]
  D --> E{You Are Here}
  style E fill:#f90,color:#fff
  
Prerequisites: Proficiency in JavaScript. Understanding of the testing pyramid and unit testing concepts from the Testing Basics tutorial is recommended.

Setting Up Cypress

Install Cypress in your project:

npm install --save-dev cypress
npx cypress open

This creates the default folder structure:

cypress/
  e2e/          # test files
  fixtures/     # test data (JSON)
  support/      # custom commands, global config
cypress.config.js

For headless CI runs:

npx cypress run

The configuration file lets you set base URL, viewport, timeouts, and environment variables:

// cypress.config.js
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    supportFile: 'cypress/support/e2e.js',
    specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
  },
  viewportWidth: 1280,
  viewportHeight: 720,
  defaultCommandTimeout: 4000,
});

Commands and Assertions

Cypress chains commands using a fluent API. Commands don’t execute immediately — they queue and run sequentially.

// login.cy.js
describe('Login Flow', () => {
  it('logs in with valid credentials', () => {
    cy.visit('/login');
    cy.get('[data-cy=email]').type('user@example.com');
    cy.get('[data-cy=password]').type('secret123');
    cy.get('[data-cy=submit]').click();

    cy.url().should('include', '/dashboard');
    cy.get('[data-cy=welcome]').should('contain', 'Welcome back');
  });

  it('shows error for invalid credentials', () => {
    cy.visit('/login');
    cy.get('[data-cy=email]').type('wrong@example.com');
    cy.get('[data-cy=password]').type('wrongpass');
    cy.get('[data-cy=submit]').click();

    cy.get('[data-cy=error]')
      .should('be.visible')
      .and('contain', 'Invalid email or password');
  });
});

Fixtures for Test Data

Fixtures let you load external test data instead of hardcoding values.

// cypress/fixtures/users.json
{
  "valid": {
    "email": "alice@example.com",
    "password": "password123",
    "name": "Alice"
  },
  "invalid": {
    "email": "bob@example.com",
    "password": "wrongpass"
  }
}
// user-profile.cy.js
describe('User Profile', () => {
  beforeEach(() => {
    cy.fixture('users').as('userData');
  });

  it('displays user name', function () {
    cy.visit('/profile');
    cy.get('[data-cy=email]').type(this.userData.valid.email);
    cy.get('[data-cy=name]').should('have.value', this.userData.valid.name);
  });
});

Network Interception

Cypress can intercept and stub HTTP requests to test how your app handles API responses.

// products.cy.js
describe('Product Listing', () => {
  it('shows products from API', () => {
    cy.intercept('GET', '/api/products', {
      fixture: 'products.json',
    }).as('getProducts');

    cy.visit('/products');
    cy.wait('@getProducts');

    cy.get('[data-cy=product-card]').should('have.length.at.least', 1);
  });

  it('handles API failure gracefully', () => {
    cy.intercept('GET', '/api/products', {
      statusCode: 500,
      body: { error: 'Server error' },
    }).as('getProductsFail');

    cy.visit('/products');
    cy.wait('@getProductsFail');

    cy.get('[data-cy=error-banner]')
      .should('be.visible')
      .and('contain', 'Failed to load products');
  });

  it('shows empty state when no products exist', () => {
    cy.intercept('GET', '/api/products', {
      body: [],
    });

    cy.visit('/products');
    cy.get('[data-cy=empty-state]').should('be.visible');
  });
});

Custom Commands

Reduce duplication by creating reusable custom commands:

// cypress/support/commands.js
Cypress.Commands.add('login', (email, password) => {
  cy.session([email, password], () => {
    cy.visit('/login');
    cy.get('[data-cy=email]').type(email);
    cy.get('[data-cy=password]').type(password);
    cy.get('[data-cy=submit]').click();
    cy.url().should('include', '/dashboard');
  });
});

// usage in tests
it('accesses settings page', () => {
  cy.login('user@example.com', 'secret123');
  cy.visit('/settings');
  cy.get('[data-cy=settings-title]').should('be.visible');
});

Cypress Dashboard

The Cypress Dashboard records test results, screenshots, and video recordings from CI runs. Connect your project:

npx cypress run --record --key YOUR_RECORD_KEY

The dashboard shows:

  • Pass/fail trends over time
  • Test duration breakdowns
  • Screenshots and video of failing tests
  • Flaky test detection

The Cypress Test Flow

    flowchart LR
  A[cy.visit] --> B[cy.get Element]
  B --> C[cy.type / click]
  C --> D[cy.should Assert]
  D --> E{Pass?}
  E -->|Yes| F[Next Command]
  E -->|No| G[Show Error + Screenshot]
  

Common Cypress Mistakes

1. Using CSS Classes for Selectors

Classes change frequently with CSS frameworks or refactoring. Tests break for non-functional reasons.

Fix: Use data-cy attributes for dedicated test selectors. Cypress recommends this as the most resilient approach.

2. Chaining Too Many Commands

A chain of 10 commands makes failures hard to debug — you can’t tell which step failed.

Fix: Break long chains into named steps with descriptive cy.then callbacks or separate test cases.

3. Forgetting to Use cy.wait for Intercepted Requests

Assertions after an intercepted response run immediately, before the response arrives.

Fix: Use cy.wait('@aliasName') to pause until the intercept fires. Then assert on the UI.

4. Testing Third-Party Sites

Cypress cannot navigate to a different origin within a single test (it throws a cross-origin error).

Fix: Use cy.origin() (available in Cypress 12+) for cross-origin tests, or mock the third-party interaction.

5. Not Using cy.session for Authentication

Logging in before every test is slow and creates unnecessary API calls.

Fix: Use cy.session() to cache authentication state. The login only runs once per unique set of credentials.

6. Ignoring Video and Screenshot Artifacts

When a test fails in CI, Cypress captures video and screenshots automatically. Ignoring these means debugging blindly.

Fix: Configure the project to upload cypress/videos/ and cypress/screenshots/ as CI artifacts.

7. Running Tests Sequentially by Default

Cypress runs tests serially within a file by default, which is slow for large suites.

Fix: Split test files by feature and run them in parallel across CI machines using --parallel.

Practice Questions

1. What is the difference between cy.get and cy.find?

cy.get queries from the root of the document. cy.find queries from the previously yielded element. Use find to scope searches within a container.

2. How do you stub a network request in Cypress?

Use cy.intercept(method, url, response) to intercept matching requests. The response can be a fixture, inline data, or an error status.

3. What is cy.session used for?

It caches browser state (cookies, localStorage, sessionStorage) between tests, avoiding repeated login steps. Sessions are keyed by an array of identifiers.

4. How do you run Cypress in headless mode?

cypress run runs headless by default. cypress open launches the interactive Test Runner with a visible browser.

5. Challenge: Write a test suite for a form with validation.

Create tests for: successful form submission, empty field validation, invalid email format, server error response, and form reset after submission.

Mini Project: E2E Test Suite for a Search Feature

// search.cy.js
describe('Search Feature', () => {
  beforeEach(() => {
    cy.visit('/search');
  });

  it('shows search input and button', () => {
    cy.get('[data-cy=search-input]').should('be.visible');
    cy.get('[data-cy=search-button]').should('be.visible');
  });

  it('displays search results', () => {
    cy.intercept('GET', '/api/search?q=cypress', {
      fixture: 'search-results.json',
    }).as('search');

    cy.get('[data-cy=search-input]').type('cypress');
    cy.get('[data-cy=search-button]').click();
    cy.wait('@search');

    cy.get('[data-cy=result-item]').should('have.length.at.least', 1);
    cy.get('[data-cy=result-count]').should('contain', 'results');
  });

  it('shows no results message', () => {
    cy.intercept('GET', '/api/search?q=zzzzzz', {
      body: { results: [], total: 0 },
    }).as('emptySearch');

    cy.get('[data-cy=search-input]').type('zzzzzz');
    cy.get('[data-cy=search-button]').click();
    cy.wait('@emptySearch');

    cy.get('[data-cy=no-results]').should('be.visible');
  });

  it('shows loading state', () => {
    cy.intercept('GET', '/api/search?q=slow', (req) => {
      req.on('response', (res) => {
        res.setDelay(2000);
      });
    }).as('slowSearch');

    cy.get('[data-cy=search-input]').type('slow');
    cy.get('[data-cy=search-button]').click();
    cy.get('[data-cy=loading]').should('be.visible');
    cy.wait('@slowSearch');
    cy.get('[data-cy=loading]').should('not.exist');
  });

  it('clears results when input is cleared', () => {
    cy.intercept('GET', '/api/search?q=test', {
      body: { results: [{ title: 'Test Result' }], total: 1 },
    }).as('search');

    cy.get('[data-cy=search-input]').type('test');
    cy.get('[data-cy=search-button]').click();
    cy.wait('@search');
    cy.get('[data-cy=result-item]').should('be.visible');

    cy.get('[data-cy=search-input]').clear();
    cy.get('[data-cy=result-item]').should('not.exist');
  });
});

FAQ

What’s the difference between Cypress and Playwright?
Cypress runs inside the browser with real-time reloading and time-travel debugging. Playwright runs outside the browser and supports more browsers (including WebKit). Cypress is known for developer experience; Playwright is known for cross-browser coverage.
Can Cypress test mobile views?
Yes. Set viewportWidth and viewportHeight in the config or use cy.viewport('iphone-x') per test.
Does Cypress support component testing?
Yes. Cypress Component Testing renders individual React, Vue, or Angular components in the Cypress runner without a full application build.
How do I run Cypress in Docker?
Use the official cypress/included Docker images which come with Cypress pre-installed and all browser dependencies configured.
What is cy.intercept and when should I use it?
cy.intercept lets you spy on or stub network requests. Use it to verify API calls were made, return mock data, or simulate network errors.

Try It Yourself

  1. Install Cypress in a project and open the Test Runner with npx cypress open
  2. Write a test that visits a page and checks the page title
  3. Add a fixture file and use it in a test
  4. Intercept an API call and return mock data
  5. Run all tests headlessly with npx cypress run

What’s Next

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro