Browser testing using Cypress

Last updated on
29 July 2024

This documentation needs work. See "Help improve this page" in the sidebar.

With Cypress, you can easily create tests for Drupal, debug them visually, and automatically run them in your continuous integration builds. Cypress's simple, yet powerful API runs as fast as your browser can render content.

It's very easy to set up compared to most other test software, and you might run your first test already after a few minutes.

Requirements

Make sure you have Node.js installed on your system.

Install Cypress in your project

It is recommended to install Cypress in a sub-folder in the root of your Drupal project, since a lot of files and folders will be generated. In this example the folder is called "test", but you can call it anything. It is assumed that all commands are run after creating and entering the test folder:

$ mkdir test
$ cd test

... for a structure like this:

├── composer.json
├── composer.lock
├── test
├── vendor
└── web

Run the following commands in your newly created "test" folder to initialize a Node project package.json file with default values, install Cypress as a dependency, and open Cypress.

Install using NPX

$ npm init -y
$ npm install cypress --save-dev
$ npx cypress open

Install using Yarn

$ npm init -y
$ yarn add cypress
$ yarn run cypress open

Tip: You can add parameters to bypass selecting e2e and browser:

$ npx cypress open --e2e --browser firefox

Setting up Cypress

Cypress should now be open.

  • Click "E2E testing"
  • Select your preferred browser, and click the "Start E2E testing ..." button

A browser instance of Cypress will now open, but you need to do a few more steps, to finalize the set up.

Configure Cypress

Open the file test/cypress.config.js and add baseUrl: "https://example.org", substituting example.org with your local URL, so it looks like this:

const { defineConfig } = require("cypress");

module.exports = defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      // implement node event listeners here
    },
    baseUrl: "https://example.org",
  },
});

Create a folder called "e2e" here: /test/cypress/e2e
Create a file called succesful-login.cy.js here: /test/cypress/e2e/succesful-login.cy.js

Edit succesful-login.cy.js to look like this:

describe('Loads the front page', () => {
  it('Loads the front page', () => {
    cy.visit('/')
    cy.get('.site-branding__name')
      .should('exist')
  });
});

describe('Tests logging in using a correct password', () => {
  it('Succeeds authentication using correct login credentials', () => {
    cy.visit('/user/login')
    cy.get('#edit-name')
      .type('cypress')
    cy.get('#edit-pass')
      .type('cypress')
    cy.get('input#edit-submit')
      .contains('Log in')
      .click()
    cy.contains('Member for')
  });
});

The first bit checks if the class site-branding__name is present in the source on the front page, and then continues to attempt logging in at /user/login, and checks if eventually the text string "Member for" is present.

After creating the file succesful-login.cy.js in the /test/cypress/e2e folder, the browser instance of Cypress should now have a clickable succesful-login.cy.js test.

Run it and see that it fails.

Create a user with user name "cypress" and password "cypress". Click the "Specs" icon in the left side, run the test again, and (if all goes well) see that it succeeds.

Congratulations, you have just run your first Cypress test!

Run tests from the command line

Before deploying new configuration or code to your web site, you can test it locally with a copy of the target database with the code to be deployed, to ensure the functionality is working correctly.

If the tests pass, the code can be pushed to the server, following the standard deployment procedures - updating the database, and importing the new configuration.

Luckily, running Cypress tests from the command line is super easy. Just enter the "test" folder, and run this command to run a single test:

Run with NPX

$ npx cypress run --spec "cypress/e2e/succesful-login.cy.js"

Run with Yarn

$ yarn run cypress run --spec "cypress/e2e/succesful-login.cy.js"

The result will be printed to the screen, giving feedback whether the test failed or passed, and will even record a video to the "videos" folder.

To run all tests, simply run this command from the "test" folder:

Run all tests with NPX

$ npx cypress run

Run all tests with Yarn

$ yarn run cypress run

All tests found in the "e2e" folder are run, and the result is shown on the screen.

Run tests in the background via crontab

you can run Cypress automatically in the background, by adding a line like this in crontab:

*/15 * * * * cd /home/user/drupal10/test && /usr/bin/npx cypress run --spec "cypress/e2e/**/*" >>/tmp/crontab.log 2>&1

The >>/tmp/crontab.log 2>&1 bit at the end allows you to see errors with tail -f /tmp/crontab.log in another terminal. In Ubuntu, you might see "Error: spawn Xvfb ENOENT". Install it with sudo apt install xvfb and you may see the tests finally work.

You can find npx location on your machine with whereis npx.

For more, see Not able to use Node.js and Crontab for more info.

.gitignore file for Git repositories

Cypress generates a lot of files, and many files will be committed to Git. To only commit the relevant files, download cypress.gitignore.txt (original from
cypress-io/cypress-example-todomvc), rename it to .gitignore and place in the "test" folder.

Cypress, Drush, and Faker.js

General testing logic

The idea behind a Cypress test is as follows:

  1. Set up the (Drupal) system to a state in which it can be tested
  2. Run the Cypress tests, and confirm the tests have passed

Good tests use randomized data every time they are run, thereby testing a wide range of possible data possibilities over time. Setting up a Drupal system in a state ready to be tested can be accomplished using a combination of Drush and Faker.js.

Example scenario

Consider a scenario where a system has a custom login form, separate to the default Drupal login form. In this case, to test the form functionality, the following things must be tested/ensured:

  1. Unblocked users are able to log in using the form
  2. When a user logs in, they are redirected to the correct page
  3. Non-existent users cannot log in using the form
  4. Blocked users cannot log in using the form
  5. Valid users cannot log in with an invalid password

Creating random data with Faker.js and Drush in Cypress tests

Cypress tests can execute commands from the command line, which means Cypress can execute Drush commands. As Cypress tests require setting up the system to be in a state that is ready to be tested, a Cypress test can use Drush to set up the system before running the test on that system.

When using Drush to set up data, it is always preferable to use some kind of randomized data, to try to bring out bugs that you may not have considered. For the example scenario outlined above, a user needs to be created to test that the login form works correctly. Rather than hard-coding the name and password of the user into the test, which would test the same name every time, instead Faker.js can be used to generate a random username and password each time the test is run, then Drush can be used to create an account with the generated username and password. This ensures the system is tested by a wide range of data, rather than hard-coding a value.

Testing the example scenario

The following explains how to bring together Cypress, Drush, and Faker.js to test the example scenario given above.

  1. Create a new user in the system. Use Faker.js to create a random username, and then have Cypress use Drush to create an unblocked user with that username.
  2. Have Cypress access the custom login form
  3. Cypress test: Does the page form at the expected path, and have the login form on it?
  4. Have Cypress try logging in with the new user
  5. Cypress test: Was the user logged in? Were they redirected to the correct path?
  6. Have Cypress block the user with Drush. Have Cypress try logging in with the blocked user
  7. Cypress test: Was the user correctly blocked?
  8. Have Cypress try logging in with a non-existent account
  9. Cypress test: Was the non-existent account correctly blocked from logging in?
  10. Have Cypress try logging in with the user that was created, but an invalid password
  11. Cypress test: Was the valid account blocked from logging in with an invalid password?

Setting up a system with Drush

Consider a site using the Groups module. A new group will be required every time a test is run that tests group functionality. Using Cypress, there are two ways to create a group:

  1. Have Cypress log in with an admin user, visit the group creation page in the browser, and create the group, still in the browser, or:
  2. Have Cypress execute a Drush command to create the group

With option number one, Cypress, though a browser, will have to load a log-in page, login the user, load the page redirected to after login, load the group create page, create the group, and load the page the group creation form redirects to. This is a lot of page loads, making for a slow/inefficient test. Drush on the other hand can create entities/data in PHP using the Drupal API. With this method no pages are ever loaded, and therefore is significantly faster than running this process through a browser. Over time, as tests are added to a system, a few seconds different in a given test can add up to hours of cumulative execution time, so the more efficient the test, faster the tests will run, and the happier a developer you will be. As such, it is preferable to have the test create a group through the CLI using Drush, over doing so through a browser.

Custom Drush Commands

Often, there will not be an existing Drush command to provide the required functionality. For example, the Groups module does not provide a Drush command to create a group. As such, it is best to create a custom Drush command, and in that command, use the Drupal API to create a group. For example a Drush command, group:create, could be created allowing for Cypress to create a group with drush group:create "GroupName", with GroupName being generated by Faker.js to ensure it's random.

Custom Cypress commands for custom Drush commands

After creating a custom Drush command, it is advisable to create a custom Cypress command that matches the Drush command, so that tests can use the new functionality provided by the custom Drush command. For the above example of the custom Drush command group:create, a custom Cypress command cy.createGroup() can be created, by adding the following to cypress/commands/e2e.js:

Cypress.Commands.add('createGroup', function(groupName) {
  cy.log('Create the group');
  cy.exec(Cypress.env('drushCommand') + ' group:create "' + groupName + '"')
    .its('stderr')
    .should('match', /^\[success\]/);
});

This now allows for setting up a new Group from any Cypress test as follows:

// User Faker.js to create a random string for the group name.
// Note that depending on the system, something like company
// name (https://fakerjs.dev/api/company.html#name) that provides
// data appropriate to the real-world data type that the group
// represents.
const groupName = faker.random.alphaNumeric(10) + '-' + faker.random.alphaNumeric(5);
cy.createGroup(groupName);

How to Integrate Cypress Tests into DevOps Pipelines

[NEEDS WORK]

Resources

There is much more to learn about Cypress, please check out these resources. Thanks to Aten Design Group for inspiration for this documentation.

Help improve this page

Page status: Needs work

You can: