Useful Cypress bits, recipes, and cheat sheets for end-to-end testing.

Miscellaneous

Official documentation: docs.cypress.io


Must read: Best practices in the official Cypress documentation. There is a great number of examples and useful do’s and don’ts. Some of them will pop up below.


Always use data-* attributes for selecting, e.g. <input data-test='password' />. I use data-test together with custom getBySel and functions.


Difference between Cypress commands:

  • .get() and .find(): get unless specifically constrained always searches for the selector in the whole document, find’s scope is limited by the scope of the preceding call:

    cy.get('#selector').find('input')
    
  • .parent() and .parents(): .parent() travels only one level up, i.e. returns the immediate parent, .parents() travels many levels up and can return multiple parents, depending on the selector.


Match made in heaven: first use getBySel and/or findBySel to limit the scope, then .contains(text) to check that the text in question exists.

cy.getBySel('sign-up-form').findBySel('submit-btn').contains('Sign up')

How to’s

Emulate global key presses

How to simulate a “global” key press, for example, to test shortcuts:

cy.get('body').type('{ctrl}q')

Browser confirm modal

How to work with browser confirm modal:

// Test contents of an alert/confirm modal
cy.on('window:alert', (str) => {
    expect(str).to.include('your text')
})
// Accept confirm
cy.on('window:confirm', () => true)

Environment variables and secrets

How to store secrets. Use environment variables in cypress.env.json file in the project root:

{
    "email": "...",
    "password": "..."
}

To access these variables in the tests:

Cypress.env('email')

More on environment variables in the documentation .

Custom commands

Cypress is easily extensibleCustom cypress commands I always use. They must be added to /cypress/support/commands.js file.

Commands can be either standalone (see getBySel) or chainable. To make a chainable command you need to:

  1. Add options object with prevSubject: true after the command name.

  2. Accept in the callable subject as the first argument: (subject, selector, ...args).

  3. Wrap the subject before chaining the desired action: cy.wrap(subject).[command()].

For full example see findBySel.

getBySel

To reduce boilerplate code when using get with data-test selectors:

Cypress.Commands.add('getBySel', (selector, ...args) => {
    return cy.get(`[data-test=${selector}]`, ...args)
})

// Example
cy.getBySel('sign-up-form')

findBySel

To reduce boilerplate code when using find with data-test selectors:

Cypress.Commands.add(
    'findBySel',
    { prevSubject: true },
    (subject, selector, ...args) => {
        return cy.wrap(subject).find(`[data-test=${selector}]`, ...args)
    }
)

// Example
cy.findBySel('submit-btn')

// Works well in combination with getBySel
cy.getBySel('sign-up-form').findBySel('submit-btn').click()

Click outside

Often you need to click anywhere outside an element, for example to close a modal or blur an element.

Cypress.Commands.add('clickOutside', () => {
    return cy.get('body').click(0, 0)
})

Global delay to fight test flakiness

Cypress end-to-end tests can be flaky. This may have nothing to do with the underlying system being tested. For example, some elements may not (dis)appear fast enough or clash due to slow hardware the tests are run on, general “heaviness” of the system, etc.

One way to overcome it is to introduce a global delay for certain Cypress actions. Some 200-400 milliseconds should be enough. This can be done in the commands.js file (preferably at the very end, since it overwrites the commands):

// The implementation from:
// https://github.com/cypress-io/cypress/issues/249

// Skip the delay if it is not set in the environment
// or set the default value other than 0
const COMMAND_DELAY = Cypress.env('COMMAND_DELAY') || 0
if (COMMAND_DELAY > 0) {
    // List of the commands to slow down
    for (const command of ['click', 'contains']) {
        Cypress.Commands.overwrite(command, (originalFn, ...args) => {
            const origVal = originalFn(...args)

            return new Promise((resolve) => {
                setTimeout(() => {
                    resolve(origVal)
                }, COMMAND_DELAY)
            })
        })
    }
}

This will make your tests running a bit longer, but we are not in a hurry, right?