End-to-End Test Automation with Cypress for Modern Web Applications
End-to-end (E2E) testing is a critical aspect of ensuring modern web applications function as expected from the user’s perspective, while unit tests validate individual components and integration tests check their interactions, E2E tests simulate real user scenarios to verify the entire system’s workflow.
Cypress is one of the most popular tools for E2E testing, unlike traditional automation frameworks like Selenium, Cypress is built specifically for modern JavaScript frameworks like React, Angular, and Vue. It runs directly in the browser, giving you faster test execution and immediate feedback. Moreover, Cypress provides an intuitive interface, time-travel debugging, and automatic waits, making it a favorite among developers and QA teams.
In this tutorial, we’ll cover the following;
- Setting up Cypress in a project.
- Writing and running your first E2E test.
- Handling common testing scenarios, such as authentication, form submission, and API interaction.
Setting Up Cypress
Before you start writing tests, you need to install and configure Cypress in your project, follow these steps to get everything up and running.
Step 1: Install Cypress
Cypress is available as an npm package, which makes installation simple. In your project’s root directory, run the following
npm install cypress --save-dev
This installs Cypress as a development dependency in your project. The --save-dev
flag ensures that Cypress is only used during development and not in production.
Step 2: Initialize Cypress
After installation, you need to initialize Cypress and set up the default directory structure. Open Cypress using the following command;
npx cypress open
This command launches the Cypress Test Runner for the first time and creates the following directory structure, additionally you don’t have to initially modify these but it’s helpful to know where these are.
/cypress
├── integration/ # Test files go here
├── fixtures/ # Static data for tests (e.g., JSON files)
├── support/ # Custom commands and reusable logic
└── plugins/ # Plugin configuration
Writing Your First E2E Test
Now that Cypress is set up, let’s write a simple test to verify that the homepage of your application loads correctly and displays the expected content.
Step 1: Create a Test File
Inside the cypress/integration/
directory, create a new file called home.spec.js
. Cypress uses the .spec.js
extension to identify test files.
Step 2: Write the Test
Open home.spec.js
and add the following code
describe('Homepage Load Test', () => {
it('Should load the homepage and display the correct title', () => {
// Visit the homepage
cy.visit('http://localhost:3000'); // Update with your app's URL
// Check if the page contains the expected H1 element
cy.get('h1')
.should('contain', 'Welcome to My App'); // Update with the actual text on your homepage
});
});
Let’s break this down
describe()-
Defines a test suite, here, it groups all tests related to the homepage.it()-
Represents an individual test case, in this example, the test checks if the homepage loads and displays the correct title.cy.visit()-
Navigates to the specified URL.cy.get()-
Selects an element on the page, similar to a CSS selector..should()-
Asserts that the selected element contains the expected text.
Step 3: Run the Test
Open the Cypress Test Runner (if it’s not already open) by running
npx cypress open
In the Test Runner, you’ll see the home.spec.js
file listed. Click on it to run the test, Cypress will open a browser window, navigate to the specified URL, and execute the test. If everything is set up correctly, you’ll see the test pass.
Handling Common Testing Scenarios
Cypress is designed to handle a wide range of E2E testing scenarios, let’s explore a few common ones.
a) Testing User Authentication
Many web applications require users to log in, testing this functionality ensures that the login form works as expected and users can access protected pages.
Here’s a sample test for a login form;
describe('User Login Test', () => {
it('Logs in a user with valid credentials', () => {
cy.visit('/login'); // Navigate to the login page
// Enter email and password
cy.get('input[name="email"]').type('test@example.com');
cy.get('input[name="password"]').type('password123');
// Submit the form
cy.get('button[type="submit"]').click();
// Verify that the user is redirected to the dashboard
cy.url().should('include', '/dashboard');
});
});
This test simulates the following actions;
- Navigates to the login page.
- Fills in the email and password fields.
- Submits the login form.
- Verifies that the user is redirected to the dashboard.
b) Testing Form Submissions
Forms are a crucial part of most web applications, Cypress makes it easy to test form submissions and validate form behavior.
Here’s a test for a contact form;
describe('Contact Form Test', () => {
it('Submits the contact form successfully', () => {
cy.visit('/contact');
cy.get('input[name="name"]').type('John Doe');
cy.get('input[name="email"]').type('john.doe@example.com');
cy.get('textarea[name="message"]').type('This is a test message.');
cy.get('button[type="submit"]').click();
// Verify success message is displayed
cy.get('.success-message').should('be.visible').and('contain', 'Thank you for your message!');
});
});
c) Testing API Interactions
Modern web applications often interact with APIs to fetch or send data, Cypress allows you to intercept and mock API requests using cy.intercept()
.
Here’s an example;
describe('API Test', () => {
it('Intercepts and mocks an API request', () => {
// Intercept the API request and mock the response
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers');
cy.visit('/users'); // Visit the page that triggers the API request
cy.wait('@getUsers') // Wait for the intercepted request
.its('response.statusCode').should('eq', 200); // Verify status code
});
});
In this test;
cy.intercept()
intercepts the API request to/api/users
and returns mock data from theusers.json
file in thefixtures/
directory.cy.wait()
waits for the intercepted request to complete before verifying the response.
Best Practices for Cypress Testing
To make your E2E tests more reliable and maintainable, follow these best practices
- Use Data Attributes for Element Selectors
Instead of selecting elements by class or ID, use custom data attributes (data-cy
,data-test
) to ensure selectors are stable and less likely to break
<button data-cy="submit-button">Submit</button>
2. Avoid Hardcoding Test Data
Store test data in the fixtures/
directory and load it using cy.fixture()
. Doing this improves test readability and reusability.
3. Leverage Cypress Commands
Use custom commands to simplify repetitive actions. Define them in cypress/support/commands.js
.
4. Keep Tests Independent
Each test should be able to run independently without relying on the state or outcome of another test.
Cypress is a powerful tool for E2E testing, offering a seamless experience for testing modern web applications. By following the steps in this tutorial, you’ve set up Cypress, written basic tests, and explored common testing scenarios. As you continue to build your test suite, remember to keep your tests concise, maintainable, and focused on real user workflows.
All the best with testing!