End-to-End Testing with Cypress

Loading

End-to-end (E2E) testing is essential to ensure that your web application works as expected by simulating real user interactions. Cypress is one of the most popular tools for writing and running end-to-end tests for web applications. It provides an easy-to-use and fast way to automate browser interactions and validate the behavior of your app from a user’s perspective.

1. What is Cypress?

Cypress is a JavaScript-based testing framework that is specifically designed for end-to-end testing. Unlike traditional testing tools that interact with the browser in the background, Cypress runs directly in the browser, enabling you to write tests that simulate real user actions such as clicking buttons, typing in forms, navigating pages, and more.

2. Setting Up Cypress

Installation

To get started with Cypress, you need to install it as a development dependency in your project:

npm install --save-dev cypress

Once installed, you can open Cypress with the following command:

npx cypress open

This will launch the Cypress Test Runner, which allows you to interactively run tests and view results in real-time.

Folder Structure

By default, Cypress creates the following directory structure when you run npx cypress open for the first time:

/cypress
  /fixtures      # Mock data for tests
  /integration   # Test files go here
  /plugins       # Cypress plugins (e.g., extending functionality)
  /support       # Custom commands and setup

Test files are placed in the /integration folder.

3. Writing Your First Test

Cypress tests are typically written in JavaScript. Here’s an example of how to write a simple end-to-end test for a login form.

Example: Testing a Login Form

Consider a simple login form with a username, password field, and a login button:

<form id="login-form">
  <input type="text" id="username" />
  <input type="password" id="password" />
  <button type="submit" id="login-button">Login</button>
</form>

Now, let’s write a test to simulate entering credentials and submitting the form.

Test: login.spec.js

describe('Login Form', () => {
  it('successfully logs in with valid credentials', () => {
    // Visit the page with the login form
    cy.visit('http://localhost:3000/login');

    // Type into username and password fields
    cy.get('#username').type('user123');
    cy.get('#password').type('password123');

    // Click the login button
    cy.get('#login-button').click();

    // Check if redirected to the dashboard
    cy.url().should('include', '/dashboard');
    cy.get('h1').should('contain', 'Welcome to your Dashboard');
  });

  it('shows error with invalid credentials', () => {
    // Visit the page with the login form
    cy.visit('http://localhost:3000/login');

    // Type invalid credentials
    cy.get('#username').type('invalidUser');
    cy.get('#password').type('wrongPassword');

    // Click the login button
    cy.get('#login-button').click();

    // Check for error message
    cy.get('.error').should('be.visible').and('contain', 'Invalid credentials');
  });
});

4. Understanding the Test

  • cy.visit(): Opens the URL where the application is hosted (in this case, http://localhost:3000/login).
  • cy.get(): Selects elements by CSS selectors (e.g., #username or #login-button).
  • cy.type(): Simulates typing into an input field.
  • cy.click(): Simulates a click on an element (like a button).
  • cy.url(): Asserts that the URL has changed after a successful login.
  • cy.get().should(): Asserts that the selected element is in the correct state (e.g., checking if an error message is visible).

5. Running the Tests

To run Cypress tests, you can use the Test Runner UI that opens when you run npx cypress open or run the tests headlessly using:

npx cypress run

This will run all tests without opening the browser. Cypress will automatically capture a video and generate a screenshot for each failed test.

6. Best Practices for E2E Testing with Cypress

Here are some best practices for writing robust and maintainable E2E tests with Cypress:

a) Use Fixtures for Mock Data

You can mock data to test your application without relying on real API calls. Cypress provides a fixtures folder where you can store mock JSON data. Here’s how to use it in tests.

Example: Using a mock user for login:

it('logs in with mocked credentials', () => {
  cy.fixture('user.json').then((user) => {
    cy.get('#username').type(user.username);
    cy.get('#password').type(user.password);
    cy.get('#login-button').click();
    cy.url().should('include', '/dashboard');
  });
});

user.json could look like:

{
  "username": "user123",
  "password": "password123"
}

b) Keep Tests Isolated

Each test should be independent. Ensure that one test’s success or failure doesn’t affect another test. This can be achieved by resetting the state before each test using beforeEach().

beforeEach(() => {
  cy.visit('http://localhost:3000/login');
});

c) Use Custom Commands

Cypress allows you to define custom commands for repetitive tasks, such as logging in. This helps keep your tests clean and maintainable.

// In support/commands.js
Cypress.Commands.add('login', (username, password) => {
  cy.get('#username').type(username);
  cy.get('#password').type(password);
  cy.get('#login-button').click();
});

// In your tests
it('logs in with valid credentials', () => {
  cy.login('user123', 'password123');
  cy.url().should('include', '/dashboard');
});

d) Avoid Using Time-Based Assertions

Avoid relying on hard-coded cy.wait() or setTimeout() for waiting. Instead, use Cypress commands like cy.get() with .should() that wait automatically for elements to appear.

cy.get('.welcome-message').should('be.visible');

This ensures that the test will wait for the element to appear before continuing.

e) Test on Multiple Browsers

Cypress supports multiple browsers (Chrome, Firefox, Electron). You can run tests on different browsers to ensure compatibility.

npx cypress run --browser firefox

f) Headless Testing for CI/CD

For continuous integration (CI) pipelines, it’s essential to run tests in headless mode (without opening the browser window). This can be done using the --headless flag:

npx cypress run --headless

7. Mocking Network Requests

Cypress can also intercept and mock network requests, which is particularly useful when you want to isolate your tests from external dependencies like APIs.

Example: Mocking API Response

it('loads user data from API', () => {
  cy.intercept('GET', '/api/user', { fixture: 'user.json' }).as('getUser');

  cy.visit('/profile');

  cy.wait('@getUser');
  cy.get('.username').should('contain', 'user123');
});

8. Running Tests in CI/CD Pipelines

Cypress can be easily integrated into continuous integration and deployment (CI/CD) pipelines like GitHub Actions, CircleCI, or Jenkins. To run Cypress tests in CI, use the cypress command within your pipeline configuration.

Example GitHub Action:

name: Cypress Tests

on:
  push:
    branches:
      - main

jobs:
  cypress:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2
      - name: Install dependencies
        run: npm install
      - name: Run Cypress tests
        run: npx cypress run --headless

Leave a Reply

Your email address will not be published. Required fields are marked *