Cypress v10 Tips and Tricks

A few tips on getting the most out of E2E testing tool Cypress v10+

This blog post collects my tips for using Cypress v10+ which is a large step after the previous versions of Cypress. I plan to add more tips to this blog post as I use v10 more. See my post Cypress Tips and Tricks for more content; most of it still applies to Cypress v10+ tests.

Learn Cypress v10 Fundamentals

The best way to start learning E2E and component testing in Cypress v10 is to go through my FREE course "Cypress version 10 Fundamentals" hosted by BlazeMeter University.

  • go to the BlazeMeter University Login screen
  • create a free account and log in
  • find my course by name "Cypress ..." and enroll

Cypress v10 Fundamentals course

Tip: after learning the Cypress fundamentals, continue learning by taking my courses Cypress Network Testing Exercises and Cypress Plugins.

Register the plugins

Cypress v10+ has merged the cypress.json and the cypress/plugins/index.js files into a single cypress.config.js file. The plugins that you have registered before now should be registered in the e2e / setupNodeEvents method. For example, the cypress-high-resolution used to be registered like this:

1
2
3
4
5
// cypress/plugins/index.js
module.exports = (on, config) => {
// https://github.com/bahmutov/cypress-high-resolution
require('cypress-high-resolution')(on, config)
}

In Cypress v10 it should be registered as

1
2
3
4
5
6
7
8
9
10
// cypress.config.js
const { defineConfig } = require('cypress')

module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
},
})

Tip: the Cypress migration wizard moves the plugins file automatically and it worked pretty well for me.

The migration wizard moving the plugins file to the config file

Launch the test runner in the desired mode

When using cypress open command, we have to pick the testing type and then pick the browser before we can click on the spec to run. This requires extra clicks just to get to the desired list of specs. You can shorten the process via command line arguments (which you can always look up using cypress open --help), for example to open e2e tests using the Electron browser:

1
cypress open --e2e --browser electron

If you want to start component testing using the Chrome browser:

1
cypress open --component --browser chrome

Tip: you can still use start-server-and-test to launch the application server when doing e2e testing. For example, here are my NPM scripts from the package.json file

package.json
1
2
3
4
5
6
7
8
9
10
{
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"eject": "react-scripts eject",
"dev": "start-test 3000 cy:e2e",
"e2e": "cypress open --e2e --browser electron",
"comp": "cypress open --component --browser electron"
}
}

If I want to run just the component tests, I use npm run comp command. If I plan to run the E2E tests, I use npm run dev script which starts the application and opens Cypress in the E2E testing mode.

Quickly change the testing type

When running the tests, you can quickly switch from E2E to Component testing and back. Click on the test type icon in the top left corner.

Click on the testing type icon

This brings the testing type modal dialog where you can switch to E2E testing

Pick the other testing type

Run all specs

Cypress v10 has removed the "Run all specs" button. To learn how to get around it, read my blog post Run All Specs in Cypress v10.

Run E2E and component tests on CI

If you are using the Cypress GitHub Action, you need to upgrade to v4 to correctly install the dependencies and run the tests. For example, if you plan to run the component tests before running e2e tests (because it is faster), then do the following in your GitHub workflow

1
2
3
4
5
6
7
8
9
10
11
12
13
- name: Run E2E tests ๐Ÿงช
uses: cypress-io/github-action@v4
with:
# to run component tests we need to use "component: true"
component: true

- name: Run Component tests ๐Ÿงช
uses: cypress-io/github-action@v4
with:
# we have already installed everything
install: false
# start the application before running
start: npm start

Similarly, if you use Cypress CircleCI Orb, you would need to use v2

1
2
3
4
5
6
7
8
9
10
version: 2.1
orbs:
cypress: cypress-io/cypress@2
workflows:
build:
jobs:
# check out the code, install the dependencies
# and run just the component tests
- cypress/run:
component: true

Check the mode from the config file

If you are trying to decide if the user is running cypress using the cypress open or the cypress run command, you can look at the config.isTextTerminal property. It is set to true during the run non-interactive mode.

cypress.config.js
1
2
3
4
5
6
7
8
9
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
if (config.isTextTerminal) {
console.log('cypress run!)
}
},
}
})

Set the user values using the env block

Read the blog post Cypress v10 Environment Variables.

Overwrite cy.log to print to the terminal

You can overwrite the cy.log command to print the message both to the Command Log and to the terminal. See the code in the bahmutov/cypress-log-to-term repo.

spec.cy.js
1
2
3
4
5
6
7
8
9
10
11
12
13
Cypress.Commands.overwrite('log', (log, message, ...args) => {
// print the to Cypress Command Log
// to preserve the existing functionality
log(message, ...args)
// send the formatted message down to the Node
// callback in the cypress.config.js to be printed to the terminal
cy.task('print', [message, ...args].join(', '), { log: false })
})

it('prints log messages', function () {
cy.log('Hello')
cy.log('Hello', 'world')
})
cypress.config.js
1
2
3
4
5
6
7
8
setupNodeEvents(on, config) {
on('task', {
print(s) {
console.log(s)
return null
},
})
},

Logs the messages to the Command Log and the terminal

Stub an ES6 import

Read the blog post Stub an import from a Cypress v10 component test.

Collect the component code coverage

Read the blog post Component Code Coverage in Cypress v10.

Slow down Cypress tests

This tip works both with Cypress v9 and v10. You can slow down each Cypress command by X milliseconds using my plugin cypress-slow-down. Watch the video below:

Migrate your Angular tests from Protractor

Use the migrator.cypress.io tool from Cypress team.

Migrating an example Protractor test to Cypress

Access the file system

Cypress tests run in the browser, thus they cannot access the file system directly. You have two main choices

Run specs in a different order

By default, Cypress runs the specs in the order it finds them on disk. You can adjust this order by specifying your own spec pattern.

cypress.config.js
1
2
3
4
5
6
7
8
9
10
setupNodeEvents(on, config) {
config.specPattern = [
'cypress/e2e/spec2.cy.js',
'cypress/e2e/spec3.cy.js',
'cypress/e2e/spec1.cy.js',
]
// IMPORTANT: need to return the changed config
// so Cypress knows about your changes
return config
}

See the repo bahmutov/cypress-spec-order-example and the video Run Cypress Specs In The Order You Want.

Delete videos for passing specs

๐Ÿ“บ watch this tip in the video Delete Cypress Videos For Passing Specs.

If you store the videos from cypress run test runs, they might take a lot of space. My preferred way is to clean up all test artifacts older than N days / weeks (because if a test fails I want to compare its video to the last passing run video). If you cannot do this, you might want to only store videos for specs with test failures. To do this, use the after:spec hook in your config file. I will keep the video file if the spec has test failures or skipped tests.

cypress.config.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { defineConfig } from 'cypress'
import { unlinkSync } from 'fs'

export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
setupNodeEvents(on, config) {
if (config.video) {
on('after:spec', (spec, results) => {
if (results.video) {
// https://glebbahmutov.com/blog/cypress-test-statuses/
if (
results.stats.failures ||
results.stats.skipped
) {
console.log(
'keeping the video %s',
results.video,
)
} else {
console.log('deleting video for passing spec')
unlinkSync(results.video)
}
}
})
}
},
},
})

๐Ÿšจ Giant warning sign: Cypress has a broken event emitter for plugins. If several plugins register after:spec event handlers, only one will run, see issue #5240.

You can find this code in the branch delete-video of the repo bahmutov/cy-report-example. Two out of four specs are failing:

1
2
3
4
5
6
7
8
9
10
11
     Spec                                              Tests  Passing  Failing
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
โ”‚ โœ– broken.cy.ts 507ms 1 - 1
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
โ”‚ โœ” spec1.cy.ts 00:02 3 3 -
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
โ”‚ โœ” spec2.cy.ts 00:02 3 3 -
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
โ”‚ โœ– feature/viewports.cy.ts 00:03 3 2 1
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
โœ– 2 of 4 failed (50%) 00:09 10 8 2

The cypress/videos folder has only two video files

1
2
3
4
5
6
7
8
$ ls -lR cypress/videos
total 112
-rw-r--r-- 1 glebbahmutov staff 54387 Feb 1 07:37 broken.cy.ts.mp4
drwxr-xr-x 3 glebbahmutov staff 96 Feb 1 07:37 feature

cypress/videos/feature:
total 392
-rw-r--r-- 1 glebbahmutov staff 197254 Feb 1 07:37 viewports.cy.ts.mp4

Very nice.

Control React slider

If you have a React component that uses onInput to listen to <input type="range" /> slider, then you can send the events yourself. ๐Ÿ“บ Watch the video Testing React Input With Type Range By Dispatching Events

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Cypress.Commands.add(
'setRange',
{ prevSubject: 'element' },
(subject, value) => {
// set the input value
subject.val(value)
subject[0].dispatchEvent(new Event('input', { bubbles: true }))
Cypress.log({
name: 'setRange',
message: value,
})
},
)
// use the custom command to control the slider
it('test on slide position preventivatore', function () {
cy.getByData('test').setRange(30)
cy.getByData('slide30').should('exist')
cy.getByData('test').setRange(50)
cy.getByData('slide50').should('exist')
})

If the React component is using onChange handler, you need to be careful how you set the input's value. Because React overwrites the value set method, you need to grab the "real" method from the HTML element's prototype.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
'value',
).set

it('slides', () => {
cy.visit('/slider')
cy.get('#sliderContainer input[type=range]')
.should('have.value', 25)
.then(($slider) => {
// call the native HTML method
nativeInputValueSetter.call($slider[0], 84)
})
.trigger('change')
cy.get('#sliderContainer input[type=range]').should('have.value', 84)
})

Upgrade from Cypress v9 to v12

Read the blog post Upgrade Cypress From Version 9 to Version 12.

Dynamically control the retries

Read the blog post Retry Or Not.

Add TypeScript properties to the automation window object

๐Ÿ“บ You can watch this tip in the video Add Cypress AUTWindow Properties.

If you are passing custom properties using the application's window object, TypeScript might complain that these properties are not there on the DOM Window object.

1
2
3
4
5
6
// spec code
cy.visit('/', {
onBeforeLoad (win) {
win.user = 'Joe'
}
}

The TS will complain "Property user does not exist on type AUTWindow". The "AUTWindow" stands for "Application Under Test Window". It is defined in the cypress.d.ts types included with the Cypress NPM module.

cypress.d.ts
1
2
3
4
5
6
declare namespace Cypress {
/**
* Window type for Application Under Test(AUT)
*/
type AUTWindow = Window & typeof globalThis & ApplicationWindow
}

You can extend this type in your project's index.d.ts file.

index.d.ts
1
2
3
4
5
6
/// <reference types="cypress" />
declare namespace Cypress {
interface ApplicationWindow {
user?: string
}
}

That's it. Now your Cypress specs can set the window.user property, but only to a string value.

See all Chrome browser command line switches

When Cypress launches the browser, it sets a lot of command line flags (switches) to make sure the browser can be controlled during testing. You can see all of them by opening a Chrome tab at chrome://version/ URL.

All browser command line switches set by Cypress

๐Ÿ“บ You can watch me explain this page in this short video See All Chrome Browser Command Line Flags Cypress Uses To Launch The Browser.

Tip: you can see all internal Chrome pages by opening the special URL chrome://about/

Set the subject type for an aliased value

To tell TypeScript compiler what is the aliased value you are getting, add the generic type

1
2
3
4
// assumes the subject is a jQuery<Element>
cy.get('@aliasName')
// will know the subject is a string
cy.get<string>('@aliasName')

Use the browser name in the config

Let's say you want to run tests using different browsers:

1
2
3
$ npx cypress run --browser chrome
$ npx cypress run --browser firefox
$ npx cypress run --browser electron

You want to produce a JUnit report

cypress.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const { defineConfig } = require('cypress')

// try running with "cypress run --browser chrome"
// can we get the browser name here?
module.exports = defineConfig({
reporter: 'junit',
reporterOptions: {
mochaFile: 'results/my-test-output.xml',
},
e2e: {
// baseUrl, etc
supportFile: false,
fixturesFolder: false,
setupNodeEvents(on, config) {},
},
})

Can you save the report that includes the browser name like results/my-test-output-chrome.xml? How can you determine the browser name inside the config file?

Unfortunately, there is no Cypress.browser object in the config Node.js process. You can look into the config object passed into setupNodeEvents callback

1
2
3
setupNodeEvents(on, config) {
console.log(config)
},

This outputs a lot of values, but the browser argument is not included (just like the --spec is not included, see 26032)

1
2
3
4
5
6
7
8
9
10
11
12
{
additionalIgnorePattern: [],
animationDistanceThreshold: 5,
arch: 'x64',
autoOpen: false,
baseUrl: null,
blockHosts: null,
browsers: [
... all detected browsers
]
// lots of other config options
}

The only way workaround I found is to pass the browser name in the command again via --env argument:

1
$ npx cypress run --browser chrome --env browser=chrome

We can see all env values in the config.env object and use them to return modified config.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const { defineConfig } = require('cypress')

// try running with "cypress run --browser chrome"
// can we get the browser name here?
module.exports = defineConfig({
reporter: 'junit',
reporterOptions: {
mochaFile: 'results/my-test-output.xml',
},
e2e: {
// baseUrl, etc
supportFile: false,
fixturesFolder: false,
setupNodeEvents(on, config) {
// pass the browser name via Cypress env argument
const browser = config.env.browser
console.log('browser', browser)
// change the the reporter output filename
if (browser) {
config.reporterOptions.mochaFile = `results/my-test-output-${browser}.xml`
}
// important: return the config file
return config
},
},
})

Now we get the report with the browser name in its filename:

results/my-test-output-chrome.xml
1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="Mocha Tests" time="0.068" tests="1" failures="0">
<testsuite name="Root Suite" timestamp="2024-01-05T15:00:32" tests="1" file="cypress/e2e/spec.cy.js" time="0.067" failures="0">
<testcase name="works" time="0.017" classname="works">
</testcase>
</testsuite>
</testsuites>

See the source code in the repo bahmutov/cypress-browser-name.

Use a different baseUrl for a test

Imagine you are testing the customer site. You set the baseUrl in the config file:

cypress.config.js
1
2
3
4
5
6
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
baseUrl: 'https://mysite.com'
}
})

But there is a second admin site and you need to test a few features there. How do you change the baseUrl? Set it in the single test or in a suite of tests!

cypress/e2e/admin.cy.js
1
2
3
4
5
6
7
8
9
it('works', { baseUrl: 'https://admin.mysite.com' }, () => {
// test the admin site
})
// or set it for all the tests inside the suite
describe('Admin site', { baseUrl: 'https://admin.mysite.com' }, () => {
it('works 1', () => { ... })

it('works 2', () => { ... })
})

See Configuration page to see all parameters you can set in the config object.

Name the specs and the tests

If a test fails, you can find its source code using the spec filename and the full name of the test. For example,

cypress/e2e/spec-a.cy.js
1
2
3
4
5
describe('Parent suite', () => {
context('Inner suite', () => {
it('works', () => { ... })
})
})

If the test "works" fails, Cypress outputs the name of the spec cypress/e2e/spec-a.cy.js and the full name of the test which is the concatenation of the parent suite names and the test's own title: Parent suite Inner suite works. At Mercari US we use TestRail test management system via cypress-testrail-simple plugin. Each test title starts with C1234... test case ID, for example.

cypress/e2e/spec-a.cy.js
1
2
3
4
5
describe('Parent suite', () => {
context('Inner suite', () => {
it('C12340004 works', () => { ... })
})
})

Often when the test fails, we simply search for the string C12340004 to quickly go to that test. But in general, I would use the spec filename plus the test title combination without adding any additional unique ids.