Cypress is a test automation tool that is free and open-source. It is easy to set up Cypress and write, run, and debug tests. Cypress tests run inside the browser, making it known for its speed. It provides cross-browser testing capabilities. It can also be integrated into the CI/CD pipeline.
The first step in using Cypress would be to create a simple React application that has an image, some buttons, and text. These elements are then interacted with in Cypress. We can explore topics like the components in the Cypress Test Runner, how to debug a failing test, and more.
To create a react app, run:
npx create-react-app react-cypress
To run the app, navigate into the folder and run the following command:
npm start
The simple application:
import { React, useState } from 'react'
import logo from './logo.svg';
import './App.css';
export default function App() {
const [counter, setCounter] = useState(0);
const resetCounter = () =>{
setCounter(0)
};
const increaseCounter = () => {
setCounter(count => count + 1);
};
const decreaseCounter = () => {
setCounter(count => count - 1);
};
return (
<div className="app">
<header className="header">
<img src={logo} data-cy="logo" className="logo" alt="logo" />
<h3 data-cy="title">React and Cypress</h3>
<p>Count: <span className="counter" data-cy="counter">{counter}</span> </p>
<div className="counter-buttons-wrapper">
<button className="counter-buttons" data-cy="increase" onClick={increaseCounter}>Increase Count</button>
<button className="counter-buttons" data-cy="decrease" onClick={decreaseCounter}>Decrease Count</button>
</div>
<div className="counter-buttons-wrapper reset-button-wrapper">
<button className="counter-buttons reset-button" data-cy="reset" onClick={resetCounter}>Reset</button>
</div>
</header>
</div>
);
}
To install Cypress, run:
npm install cypress --save-dev
Cypress can be started with the open command or by adding a script in package.json.
//terminal command
npx cypress open
// script in package.json
"scripts": {
"cytest": "cypress open"
}
npm run cytest
The first step in setting up Cypress is to choose between E2E and component testing. Choose E2E for this example.
The next window lists the configuration files that are generated automatically.
Tests can be run in Chrome, Electron, and Firefox. This selection is not set in stone. It can be changed later in the Test Runner screen.
Click ‘Create new spec’ to create a test file.
The next steps show the path where the new spec file is created and what the sample test looks like.
The automatically generated spec file can be run to see what the test runner looks like and what options it has.
The test status component shows a variety of information like:
As the tests are executed, Cypress shows a live preview of the app.
This field shows the current URL of the app shown in the live preview section.
The ‘print to console’ icon will print out the information of the selector, how many elements are available, and which element was found.
This dropdown box contains a list of the browsers available for testing. Choosing one will open a new Test Runner window.
describe('Simple React App', () => {
beforeEach(()=> {
cy.visit('http://localhost:3000/');
});
it('renders a logo', () => {
cy.get('[data-cy="logo"]').should('have.css', 'height', '250px');
});
it('renders three buttons', () => {
cy.get('[data-cy="increase"]').should('exist');
cy.get('[data-cy="decrease"]').should('exist');
cy.get('[data-cy="reset"]').should('exist');
});
it('increases and decreases the counter', () => {
const increaseNumberOfTimes = 10;
// Cypress has lodash support
// It can be used to click on a button multiple times
Cypress._.times(increaseNumberOfTimes, () => {
cy.get('[data-cy="increase"]').should('exist').click();
});
cy.get('[data-cy="counter"]').should('have.text', '10');
const decreaseNumberOfTimes = 3;
Cypress._.times(decreaseNumberOfTimes, () => {
cy.get('[data-cy="decrease"]').should('exist').click();
});
cy.get('[data-cy="counter"]').should('have.text', '7');
});
it('resets the counter', () => {
cy.get('[data-cy="reset"]').should('exist').click();
cy.get('[data-cy="counter"]').should('have.text', '0');
});
});
What does this test do?
It is a good practice to look for a DOM element with a custom attribute instead of looking for an element with a tag name, class, or ID. This test uses the data-cy attribute.
// bad
<button id="button1">Button 1</button>
<button id="button2">Button 2</button>
cy.get('button').click();
// okay
<button id="button1">Button 1</button>
<button id="button2">Button 2</button>
cy.get('#button1').click();
// best
<button data-cy="button1">Button 1</button>
<button data-cy="button2">Button 2</button>
cy.get('[data-cy="button1"]').click();
Consider the example of selecting an element by its tag name. There are three buttons with different text and different IDs.
<button id="button1">Button 1</button>
<button id="button2">Button 2</button>
<button id="button3">Button 3</button>
cy.get('button').click();
The get command uses the tag name ‘button’ to find the element and then click on it.
The test runner throws the following error:
The click command can only be called on a single element.
Cypress found three elements with the tag name ‘button,’ but the click command was not executed since only one element can be clicked. The cy.get command is not clear enough. It does not say which one of the three buttons to click. In such a situation, it is better to select the element by IDs.
If there is a use case for clicking all the buttons, then a key called multiple has to be passed to the click function. It takes in a boolean value.
cy.get('button').click({ multiple: true })
In this scenario, the test case will click all the buttons once, and the test will pass.
Cypress recommends using a data-* attribute so that the selector does not get affected when there are CSS changes to the code.
With the data-* attributes, the selectors will be very specific, and there will be no confusion about which element the ‘get’ command uses in the test.
Cypress tests can be run without the Test Runner GUI. It is less resource-intensive and, therefore, faster.
To start cypress in headless mode:
npx cypress run --headless
The default browser in this mode is Electron. To change the browser to Chrome or Firefox, use the browser flag in the command.
npx cypress run --headless --browser --firefox
When a test fails, the test status panel displays descriptive logs.
In this example, Cypress was not able to find an element with the [data-cy=“log”] selector. The logs show:
Screenshots will be saved for all failing tests. This is the default behavior in Cypress. If this feature has to be disabled, then it has to be mentioned in the config file.
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {},
},
screenshotOnRunFailure: false
});
If there is a use-case to save a screenshot from the test suite, then the cy.screenshot() command can be used.
it('renders three buttons', () => {
cy.get('[data-cy="increase"]').should('exist');
cy.get('[data-cy="decrease"]').should('exist');
cy.get('[data-cy="reset"]').should('exist');
cy.screenshot();
});
The test result will have information as to where the screenshot is saved. It is usually saved in the cypress folder inside the project by default.
Videos of failing tests will be saved only when it is specified in the cypress.config.js file.
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {},
},
video: true
});
Like the screenshots, the videos will be saved in the Cypress folder inside the project.
Cypress is an amazing tool for test automation. As someone who has tried and still tries to follow the TDD approach, Cypress has succeeded in making writing tests fun. It is very beginner-friendly, and it makes it easy to test user interactions, network requests, and much more for web applications.