Migration Guide
Migrating to Cypress 13.0β
This guide details the changes and how to change your code to migrate to Cypress version 13. See the full changelog for version v13.0.
Cypress Cloud Test Replayβ
Test Replay is enabled by default in v13
of the Cypress App.
You may need to allowlist capture.cypress.io
if you work with a strict VPN. See our FAQ section about VPN subdomain allowlisting.
With Test Replay enabled, the Cypress Runner UI is hidden by default when recording a run to the Cloud. If the Runner UI is needed during the run, you can enable it by passing --runner-ui
to the cypress run
command.
You can opt-out of this feature in Cloud project-level settings.
Video updatesβ
video
is set to false
by defaultβ
You can continue recording video by setting video
to true
either in your Cypress configuration or via overriding options. This can be useful if you want video locally or want video for some other reason, like in non-Chromium browsers where Test Replay is not available.
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
video: true,
})
import { defineConfig } from 'cypress'
export default defineConfig({
video: true,
})
videoUploadOnPasses
configuration option has been removedβ
Most users used videoUploadOnPasses
as a way to skip the time to compress and upload videos to the Cloud. Since we're turning off videoCompression
by default, this configuration option does not offer the time saving value that it once would.
If you want to prevent a passing test from uploading to the Cloud, we recommend deleting the video using our guide with code examples to discard captured video of passing tests.
videoCompression
is set to false
by defaultβ
Cypress has the capability to compress recorded videos after a run to reduce the video file size. By default, compression is now turned off. This results in a reduced run time by removing the time to compress the video, a larger video file size and better video quality.
You can enable this with the videoCompression
configuration option if you'd like to reduce the video file size for any reason. This will also reduce the video quality and take slightly longer to process and complete the run.
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
// value can be true/false -or- an integer between 0 and 51
videoCompression: true,
})
import { defineConfig } from 'cypress'
export default defineConfig({
// value can be true/false -or- an integer between 0 and 51
videoCompression: true,
})
cy.readFile()
is now a query commandβ
In Cypress v13
, the .readFile()
command is now a query.
Tests written using it should continue to operate exactly as before; no changes
are necessary.
readFile()
will re-read the file from disk if any upcoming command in the same
chain fails. Assertions no longer have to be directly attached.
cy.readFile(`users.json`).its('users.123.fullName').should('eq', 'John Doe')
Beginning with Cypress v13
, the above test will re-read the file until the file
exists, it has the requested property, and it passes the assertion.
In previous versions of Cypress, the above command would retry until the file existed, but would not re-read it from disk if the file didn't have the requested property or the contents didn't match.
.readFile()
can no longer be overwritten with Cypress.Commands.overwrite()
β
Queries must be overwritten using Cypress.Commands.overwriteQuery()
. If you
were previously overwriting cy.readFile()
, you will need to update your code
to use Cypress.Commands.overwriteQuery('readFile', function() { ... })
rather
than Cypress.Commands.overwrite('readFile', () => { ... })
. For more details
on overwriting queries, see the
Overwriting Existing Queries.
Migrating to Cypress 12.0β
This guide details the changes and how to change your code to migrate to Cypress version 12.0. See the full changelog for version 12.0.
The Session and Origin experiment has been released as General Availability
(GA), meaning that we have deemed this experiment to be feature complete and
free of issues in the majority of use cases. With releasing this as GA, the
experimentalSessionAndOrigin
flag has been removed, the
cy.origin()
and
cy.session()
commands are generally available and
Test Isolation
is enabled by default.
Node.js 14+ supportβ
Cypress comes bundled with its own
Node.js version.
However, installing the cypress
npm package uses the Node.js version installed
on your system.
Node.js 12 reached its end of life on April 30, 2022. See Node's release schedule. This Node.js version will no longer be supported when installing Cypress. The minimum Node.js version supported to install Cypress is Node.js 14+.
Test Isolationβ
The
testIsolation
config option is enabled by default. This means Cypress resets the browser
context before each test by:
- clearing the dom state by visiting
about:blank
- clearing cookies in all domains
- clearing
localStorage
in all domains - clearing
sessionStorage
in all domains
Test suites that relied on the application to persist between tests may have to be updated to revisit their application and rebuild the browser state for each test that needs it.
Before this change, it was possible to write tests such that you could rely on the application (i.e. DOM state) to persist between tests. For example you could log in to a CMS in the first test, change some content in the second test, verify the new version is displayed on a different URL in the third, and log out in the fourth.
Here's a simplified example of such a test strategy.
Before Multiple small tests against different origins
it('logs in', () => {
cy.visit('https://example.cypress.io')
cy.get('input#password').type('Password123!')
cy.get('button#submit').click()
})
it('updates the content', () => {
// already on page redirect from clicking button#submit
cy.get('#current-user').contains('logged in')
cy.get('button#edit-1').click()
cy.get('input#title').type('Updated title')
cy.get('button#submit').click()
cy.get('.toast').contains('Changes saved!')
})
it('validates the change', () => {
cy.visit('/items/1')
cy.get('h1').contains('Updated title')
})
After migrating, when testIsolation=true
by default, this flow would need to
be contained within a single test. While the above practice has always been
discouraged
we know some users have historically written tests this way, often to get around
the same-origin
restrictions. But with cy.origin()
you no longer need these kind of brittle hacks, as your multi-origin logic can
all reside in a single test, like the following.
After One big test using cy.origin()
it('securely edits content', () => {
cy.origin('cypress.io', () => {
cy.visit('https://example.cypress.io')
cy.get('input#password').type('Password123!')
cy.get('button#submit').click()
})
cy.origin('cypress-dx.com', () => {
cy.url().should('contain', 'cms')
cy.get('#current-user').contains('logged in')
cy.get('button#edit-1').click()
cy.get('input#title').type('Updated title')
cy.get('button#submit').click()
cy.get('.toast').contains('Changes saved!')
})
cy.visit('/items/1')
cy.get('h1').contains('Updated title')
})
The just-released cy.session()
command can be used to setup and cache cookies,
local storage and session storage between tests to easily re-establish the
previous (or common) browser contexts needed in a suite. This command will run
setup on its initial execution and will restore the saved browser state on each
sequential command execution. This command reduces the need for repeated
application logins, while users also benefit from the test isolation guardrails
to write independent, reliable and deterministic tests from the start.
If for whatever reason you still need to persist the dom and browser context
between tests, you can disable test isolation by setting testIsolation=false
on the root configuration or at the suite-level. For example:
describe('workflow', { testIsolation: false }, () => {
...
})
It is important to note that while disabling test isolation may improve the overall performance of end-to-end tests, it can cause state to "leak" between tests. This can make later tests dependent on the results of earlier tests, and potentially cause misleading test failures. It is important to be extremely mindful of how tests are written when using this mode, and ensure that tests continue to run independently of one another.
For example the following tests are not independent nor deterministic:
describe('workflow', { testIsolation: false }, () => {
it('logs in', () => {
cy.visit('https://example.cypress.io/log-in')
cy.get('username').type('User1')
cy.get('password').type(Cypress.env('User1_password'))
cy.get('button#login').click()
cy.contains('User1')
})
it('clicks user profile', () => {
cy.get('User1').find('#profile_avatar').click()
cy.contains('Email Preferences')
})
it('updates profile', () => {
cy.get('button#edit')
cy.get('email').type('[email protected]')
cy.get('button#save').click()
})
})
In the above example, each test is relying on the previous test to be successful to correctly execute. If at any point, the first or second test fails, the sequential test(s) will automatically fail and provide unreliable debugging errors since the errors are representative of the previous test.
The best way to ensure your tests are independent is to add a .only()
to your
test and verify it can run successfully without the test before it.
Simulating Pre-Test Isolation Behaviorβ
Test isolation did not truly exist pre-12. Pre-Cypress 12, the behavior was a
hybrid of both testIsolation
enabled and disabled. All local storage and
cookies on the current domain were cleared, but Cypress did not clear session
storage and the page always persisted.
In Cypress 12+ when testIsolation
is enabled, local storage, session storage
and cookies in all domains are cleared and the page is cleared. When
testIsolation
is disabled, nothing is cleared before the next test so all
local storage, session storage and cookies & the page persists.
If you wanted to match pre-Cypress 12 behavior, you need to disable
testIsolation
, then run cy.clearLocalStorage()
and cy.clearCookies()
in a
beforeEach hook to clear the local storage and cookies in the current domain.
describe('match pre-12 behavior', { testIsolation: false }, () => {
beforeEach(() => {
cy.clearLocalStorage()
cy.clearCookies()
// other beforeEach logic to restore the expected local storage or cookies needed on the client.
})
})
Many of the issues test isolation solved were around cookie management with tests trying to save and persist cookies because the page was still available, but the cookies on the domain were unexpectedly cleared which broke interactions with the application. It wasnβt obvious Cypress was doing a partial browser clean up. Explicitly setting test isolation to enabled or disabled allows you to choose what is right for your tests.
Behavior Changes in Alias Resolutionβ
Cypress always re-queries aliases when they are referenced. This can result in certain tests that used to pass could start to fail. For example,
cy.findByTestId('popover')
.findByRole('button', { expanded: true })
.as('button')
.click()
cy.get('@button').should('have.attr', 'aria-expanded', 'false')
previously passed, because the initial button was collapsed when first queried, and then later expanded. However, in Cypress 12, this test fails because the alias is always re-queried from the DOM, effectively resulting in the following execution:
cy.findByTestId('popover').findByRole('button', { expanded: true }).click()
cy.findByTestId('popover')
.findByRole('button', { expanded: true }) // A button which matches here (is expanded)...
.should('have.attr', 'aria-expanded', 'false') // ...will never pass this assertion.
You can rewrite tests like this to be more specific; in our case, we changed the alias to be the first button rather than the unexpanded button.
cy.findByTestId('popover').findAllByRole('button').first().as('button')
If you want to alias a static value, such that it is never re-queried, you will
need Cypress 12.3.0 or later, which
introduced the type
option for .as()
to opt into the old
behavior.
cy.get('.username').invoke('val').as('username', { type: 'static' })
See .as()
for more details.
Command / Cypress API Changesβ
Cypress.Cookies.defaults
and Cypress.Cookies.preserveOnce
β
The Cypress.Cookies.defaults
and Cypress.Cookies.preserveOnce
APIs been
removed. Use the cy.session()
command to preserve
cookies (and local and session storage) between tests.
If you were using Cypress.Cookies.preserveOnce
to preserve a specific cookie
within a single spec, this might look like the following:
describe('Dashboard', () => {
beforeEach(() => {
- cy.login()
- Cypress.Cookies.preserveOnce('session_id', 'remember_token')
+ cy.session('unique_identifier', cy.login, {
+ validate () {
+ cy.getCookies().should('have.length', 2)
+ },
+ })
})
If you were using Cypress.Cookies.defaults
to preserve a cookie or set of
cookies across test, this might look like the following:
describe('Dashboard', () => {
beforeEach(() => {
- cy.login()
- Cypress.Cookies.defaults({
- preserve: ['session_id', 'remember_token']
- })
+ cy.session('unique_identifier', cy.login, {
+ validate () {
+ cy.getCookies().should('have.length', 2)
+ },
+ cacheAcrossSpecs: true
+ })
})
cy.server()
, cy.route()
and Cypress.Server.defaults
β
The cy.server()
and cy.route()
commands and the Cypress.server.defaults
API has been removed. Use the cy.intercept()
command instead.
it('can encode + decode headers', () => {
- Cypress.Server.defaults({
- delay: 500,
- method: 'GET',
- })
- cy.server()
- cy.route(/api/, () => {
- return {
- 'test': 'Weβll',
- }
- }).as('getApi')
+ cy.intercept('GET', /api/, (req) => {
+ req.on('response', (res) => {
+ res.setDelay(500)
+ })
+ req.body.'test': 'Weβll'
+ }).as('getApi')
cy.visit('/index.html')
cy.window().then((win) => {
const xhr = new win.XMLHttpRequest
xhr.open('GET', '/api/v1/foo/bar?a=42')
xhr.send()
})
cy.wait('@getApi')
- .its('url').should('include', 'api/v1')
+ .its('request.url').should('include', 'api/v1')
})
.invoke()
β
The .invoke()
command now throws an error if the
function returns a promise. If you wish to call a method that returns a promise
and wait for it to resolve, use .then()
instead of
.invoke()
.
cy.wrap(myAPI)
- .invoke('makeARequest', 'http://example.com')
+ .then(api => api.makeARequest('http://example.com'))
.then(res => { ...handle response... })
If .invoke()
is followed by additional commands or assertions, it will call
the named function multiple times. This has the benefit that the chained
assertions can more reliably use the function's return value.
If this behavior is undesirable because you expect the function to be invoked only once, break the command chain and move the chained commands and/or assertions to their own chain. For example, rewrite
- cy.get('input').invoke('val', 'text').type('newText')
+ cy.get('input').invoke('val', 'text')
+ cy.get('input').type('newText')
.should()
β
The .should()
assertion now throws an error if Cypress
commands are invoked from inside a .should()
callback. This previously
resulted in unusual and undefined behavior. If you wish to execute a series of
commands on the yielded value, use.then()
instead.
cy.get('button')
- .should(($button) => {
})
+ .then(api => api.makeARequest('http://example.com'))
.then(res => { ...handle response... })
.within()
β
The .within()
command now throws an error if it is
passed multiple elements as the subject. This previously resulted in
inconsistent behavior, where some commands would use all passed in elements,
some would use only the first and ignore the rest, and
.screenshot()
would throw an error if used inside
a .within()
block with multiple elements.
If you were relying on the old behavior, you have several options depending on the desired result.
The simplest option is to reduce the subject to a single element.
cy.get('tr')
+ .first() // Limit the subject to a single element before calling .within()
.within(() => {
cy.contains('Edit').click()
})
If you have multiple subjects and wish to run commands over the collection as a
whole, you can alias the subject rather than use .within()
.
cy.get('tr')
- .within(() => {
- cy.get('td').should('have.class', 'foo')
- cy.get('td').should('have.class', 'bar')
- })
+ .as('rows') // Store multiple elements as an alias
+cy.get('@rows').find('td').should('have.class', 'foo')
+cy.get('@rows').find('td').should('have.class', 'bar')
Or if you have a collection and want to run commands over every element, use
.each()
in conjunction with .within()
.
cy.get('tr')
- .within(() => {
- cy.contains('Edit').should('have.attr', 'disabled')
- })
+ .each($tr => {
+ cy.wrap($tr).within(() => {
+ cy.contains('Edit').should('have.attr', 'disabled')
+ })
+ })
Cypress.Commands.overwrite()
β
In Cypress 12.0.0, we introduced a new command type, called queries. A query is a small and fast command for getting data from the window or DOM. This distinction is important because Cypress can retry chains of queries, keeping the yielded subject up-to-date as a page rerenders.
With the introduction of query commands, the following commands have been
re-categorized and can no longer be overwritten with
Cypress.Commands.overwrite()
:
.as()
.children()
.closest()
.contains()
cy.debug()
cy.document()
.eq()
.filter()
.find()
.first()
.focused()
.get()
.hash()
.its()
.last()
cy.location()
.next()
.nextAll()
.not()
.parent()
.parents()
.parentsUntil()
.prev()
.prevUntil()
cy.root()
.shadow()
.siblings()
cy.title()
cy.url()
cy.window()
If you were previously overwriting one of the above commands, try adding your
version as a new command using
Cypress.Commands.add()
under a different
name.
Migrating to Cypress 11.0β
This guide details the changes and how to change your code to migrate to Cypress version 11.0. See the full changelog for version 11.0.
Component Testing Updatesβ
As of Cypress 11, Component Testing is now generally available. There are some minor breaking changes. Most projects should be able to migrate without any code modifications.
Changes to Mounting Optionsβ
Each major library we support has a mount
function with two arguments:
- The component
- Mounting Options
Mounting options previously had several properties that are now removed:
- cssFile, cssFiles
- style, styles
- stylesheet, stylesheets
Read more about the rationale
here.
We recommend writing test-specific styles in a separate css
file you import in
your test, or in your supportFile
.
Before (Cypress 10)β
import { mount } from 'cypress/react'
import { Card } from './Card'
it('renders some content', () => {
cy.mount(<Card title="title" />, {
styles: `
.card { width: 100px; }
`,
stylesheets: [
'https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css',
],
})
})
After (Cypress 11)β
/** style.css */
@import "https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css";
.card { width: 100px }
/** Card.cy.jsx */
import { mount } from 'cypress/react'
import { Card } from './Card'
import './styles.css' // contains CDN link and custom styling.
it('renders some content', () => {
cy.mount(<Card title="title" />)
})
React - mountHook
Removedβ
mountHook
from cypress/react
has been removed. Read more about the rationale
here.
We recommend simply replacing it with mount
and a component.
Consider the following useCounter
hook:
import { useState, useCallback } from 'react'
function useCounter() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount((x) => x + 1), [])
return { count, increment }
}
Before - Cypress 10 and mountHook
β
import { mountHook } from 'cypress/react'
import { useCounter } from './useCounter'
it('increments the count', () => {
mountHook(() => useCounter()).then((result) => {
expect(result.current.count).to.equal(0)
result.current.increment()
expect(result.current.count).to.equal(1)
result.current.increment()
expect(result.current.count).to.equal(2)
})
})
After - Cypress 11 and mount
β
import { useCounter } from './useCounter'
it('increments the count', () => {
function Counter() {
const { count, increment } = useCounter()
return (
<>
<h1 name="count">Count is {{ count }}</h1>
<button onClick={increment}>Increment</button>
</>
)
}
cy.mount(<Counter />).then(() => {
cy.get('[name="count"]')
.should('contain', 0)
.get('button')
.click()
.get('[name="count"]')
.should('contain', 1)
})
})
React - unmount
Removedβ
unmount
from cypress/react
has been removed. Read more about the rationale
here.
We recommend using the API React provides for unmounting components,
unmountComponentAtNode.
Before - Cypress 10 and unmount
β
import { unmount } from 'cypress/react'
it('calls the prop', () => {
cy.mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
unmount()
// the component is gone from the DOM
cy.contains('My component').should('not.exist')
cy.get('@onUnmount').should('have.been.calledOnce')
})
After - Cypress 11 and unmountComponentAtNode
β
import { getContainerEl } from 'cypress/react'
import ReactDom from 'react-dom'
it('calls the prop', () => {
cy.mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
cy.then(() => ReactDom.unmountComponentAtNode(getContainerEl()))
// the component is gone from the DOM
cy.contains('My component').should('not.exist')
cy.get('@onUnmount').should('have.been.calledOnce')
})
Vue - mountCallback
Removedβ
mountCallback
from cypress/vue
has been removed. Read more about the
rationale
here.
We recommend using mount
.
Before - Cypress 10 and mountCallback
β
import { mountCallback } from 'cypress/vue'
beforeEach(mountCallback(MessageList))
it('shows no messages', () => {
getItems().should('not.exist')
})
After - Cypress 11 and mount
β
beforeEach(() => cy.mount(MessageList))
it('shows no messages', () => {
getItems().should('not.exist')
})
Angular - Providers Mounting Options Changeβ
There is one breaking change for Angular users in regards to providers. In
Cypress 10, we took any providers passed as part of the Mounting Options and
overrode the component providers via the TestBed.overrideComponent
API.
In Cypress 11, providers passed as part of the Mounting Options will be assigned
at the module level using the TestBed.configureTestingModule
API.
This means that module-level providers (resolved from imports or
@Injectable({ providedIn: 'root' })
can be overridden, but providers specified
in @Component({ providers: [...] })
will not be overridden when using
cy.mount(MyComponent, { providers: [...] })
.
To override component-level providers, use the TestBed.overrideComponent
API.
See a concrete example here.
Vite Dev Server (cypress/vite-dev-server
)β
When providing an inline viteConfig
inside of cypress.config
, any
vite.config.js
file is not automatically merged.
Before - Cypress 10 and viteConfig
β
import { defineConfig } from 'cypress'
export default defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'vite',
viteConfig: {
// ... custom vite config ...
// result merged with `vite.config` file if present
},
},
},
})
After - Cypress 11 and viteConfig
β
import { defineConfig } from 'cypress'
import viteConfig from './vite.config'
export default defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'vite',
viteConfig: {
...viteConfig,
// ... other overrides ...
},
},
},
})
Vite 3+ users could make use of the
mergeConfig
API.
Migrating to Cypress 10.0β
This guide details the changes and how to change your code to migrate to Cypress version 10.0. See the full changelog for version 10.0.
Cypress Changesβ
- The "Run all specs" and "Run filtered specs" functionality have been removed.
- The experimental "Cypress Studio" has been removed and will be rethought/revisited in a later release.
- Unsupported browser versions can no longer be run via
cypress run
orcypress open
. Instead, an error will display. - In 9.x and earlier versions,
cypress open
would bring you directly to the project specs list. In 10.0.0, you must pass--browser
and--e2e
or--component
as well to launch Cypress directly to the specs list.
Configuration File Changesβ
Cypress now supports JavaScript and TypeScript configuration files. By default,
Cypress will automatically load a cypress.config.js
or cypress.config.ts
file in the project root if one exists. The
Configuration guide has been updated to
reflect these changes, and explains them in greater detail.
Because of this, support for cypress.json
has been removed since Cypress v10
.
Related notes:
- If no config file exists when you open Cypress, the automatic set up process will begin and either a JavaScript or TypeScript config file will be created depending on what your project uses.
- You may use the
--config-file
command line flag or theconfigFile
module API option to specify a.js
or.ts
file. JSON config files are no longer supported. - Cypress now requires a config file, so specifying
--config-file false
on the command line or aconfigFile
value offalse
in the module API is no longer valid. - You can't have both
cypress.config.js
andcypress.config.ts
files. This will result in an error when Cypress loads. - A
defineConfig()
helper function is now exported by Cypress, which provides automatic code completion for configuration in many popular code editors. For TypeScript users, thedefineConfig
function will ensure the configuration object passed into it satisfies the type definition of the configuration file. - Many pages and examples throughout the documentation have been updated to show
configuration using
cypress.config.js
andcypress.config.ts
vs the oldercypress.json
. For example:
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:1234',
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:1234',
},
})
Plugins File Removedβ
Because Cypress now supports JavaScript and TypeScript configuration files, a
separate "plugins file" (which used to default to cypress/plugins/index.js
) is
no longer needed.
Support for the plugins file has been removed, and it has been replaced with the
new setupNodeEvents()
and
devServer
config options.
Related notes:
- The
cypress/plugins/index.js
plugins file is no longer automatically loaded by Cypress. - The
setupNodeEvents()
config option is functionally equivalent to the function exported from the plugins file; it takes the sameon
andconfig
arguments, and should return the same value. See the Config option changes section of this migration guide for more details. - The
devServer
config option is specific to component testing, and offers a much more streamlined and consistent way to configure a component testing dev server than using the plugins file. See the Config option changes section of this migration guide for more details. - Many pages and examples throughout the documentation have been updated to show
configuration in
setupNodeEvents
as well as the legacy plugins file. For example:
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
// bind to the event we care about
on('<event>', (arg1, arg2) => {
// plugin stuff here
})
},
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
// bind to the event we care about
on('<event>', (arg1, arg2) => {
// plugin stuff here
})
},
},
})
Config Option Changesβ
baseUrl
β
The baseUrl
config option is no longer valid at the top level of the
configuration, and may only be defined inside the
e2e
configuration object.
Attempting to set the baseUrl
config option at the top level of the
configuration will result in an error when Cypress loads.
componentFolder
β
The componentFolder
config option is no longer used, as it has been replaced
by the specPattern
testing-type specific option.
Attempting to set the componentFolder
config option will result in an error
when Cypress loads.
devServer
β
All functionality related to starting a component testing dev server previously
in the pluginsFile
has moved here. These options are not valid at the
top-level, and may only be defined in the
component
configuration object.
Related notes:
- Do not configure your dev server inside
setupNodeEvents()
, use thedevServer
config option instead.
See the dev server documentation for the UI framework you're using for more
specific instructions on what the devServer
should be for that framework. Some
examples can be found in our
framework documentation.
Variant 1 (webpack & vite dev servers)
Beforeconst { startDevServer } = require('@cypress/webpack-dev-server')
const webpackConfig = require('../../webpack.config.js')
module.exports = (on, config) => {
if (config.testingType === 'component') {
on('dev-server:start', async (options) =>
startDevServer({ options, webpackConfig })
)
}
}
- cypress.config.js
- cypress.config.js (verbose)
- cypress.config.ts
- cypress.config.ts (verbose)
const { defineConfig } = require('cypress')
const webpackConfig = require('./webpack.config.js')
module.exports = defineConfig({
component: {
devServer: {
framework: 'react', // or vue
bundler: 'webpack',
webpackConfig,
},
},
})
const { defineConfig } = require('cypress')
const webpackConfig = require('./webpack.config.js')
module.exports = defineConfig({
component: {
devServer(cypressConfig) {
return devServer({
framework: 'react', // or vue
cypressConfig,
webpackConfig,
})
},
},
})
import { defineConfig } from 'cypress'
import webpackConfig from './webpack.config'
export default defineConfig({
component: {
devServer: {
framework: 'react', // or vue
bundler: 'webpack',
webpackConfig,
},
},
})
import { defineConfig } from 'cypress'
import { devServer } from '@cypress/webpack-dev-server'
import webpackConfig from './webpack.config'
export default defineConfig({
component: {
devServer(cypressConfig) {
return devServer({
framework: 'react', // or vue
cypressConfig,
webpackConfig,
})
},
},
})
Variant 2 (react plugin dev servers)
Beforeconst devServer = require('@cypress/react/plugins/react-scripts')
module.exports = (on, config) => {
if (config.testingType === 'component') {
injectDevServer(on, config, {})
}
}
- cypress.config.js
- more verbose
const { defineConfig } = require('cypress')
module.exports = defineConfig({
component: {
devServer: {
framework: 'react', // or vue
bundler: 'webpack',
},
},
})
const { defineConfig } = require('cypress')
const webpackConfig = require('./webpack.config.js')
module.exports = defineConfig({
component: {
devServer(cypressConfig) {
return devServer({
framework: 'react', // or vue
cypressConfig,
webpackConfig,
})
},
},
})
experimentalStudio
β
This option is no longer used. The experimental "Cypress Studio" has been removed and will be rethought/revisited in a later release.
Attempting to set the experimentalStudio
config option will result in an error
when Cypress loads.
ignoreTestFiles
β excludeSpecPattern
β
The ignoreTestFiles
option is no longer used, and has been replaced with the
excludeSpecPattern
testing-type specific option.
Default values
e2e.excludeSpecPattern
default value is*.hot-update.js
(same as pervious ignore value)component.excludeSpecPattern
default value is['/snapshots/*', '/image_snapshots/*']
updated from*.hot-update.js
- The
**/node_modules/**
pattern is automatically added to bothe2e.specExcludePattern
andcomponent.specExcludePattern
, and does not need to be specified (and can't be overridden).
{
"ignoreTestFiles": "path/to/**/*.js"
}
{
component: {
excludeSpecPattern: "path/to/**/*.js"
},
e2e: {
excludeSpecPattern: "other/path/to/**/*.js"
}
}
Attempting to set the ignoreTestFiles
config option will result in an error
when Cypress loads.
Also, attempting to set the excludeSpecPattern
config option at the top level
of the configuration will result in an error when Cypress loads.
integrationFolder
β
This option is no longer used, as it has been replaced by the specPattern
testing-type specific option.
Attempting to set the integrationFolder
config option will result in an error
when Cypress loads.
pluginsFile
β
This option is no longer used, and all plugin file functionality has moved into
the setupNodeEvents()
and
devServer
options. See the
Plugins file removed section of this migration guide
for more details.
Attempting to set the pluginsFile
config option will result in an error when
Cypress loads.
setupNodeEvents()
β
All functionality related to setting up events or modifying the config,
previously done in the plugins file, has moved into the setupNodeEvents()
config options. This option is not valid at the top level of the config, and may
only be defined inside the component
or e2e
configuration objects.
More information can be found in the Plugins API documentation and the Configuration API documentation.
Before cypress/plugins/index.js
module.exports = (on, config) => {
if (config.testingType === 'component') {
// component testing dev server setup code
// component testing node events setup code
} else {
// e2e testing node events setup code
}
}
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
component: {
devServer(cypressConfig) {
// component testing dev server setup code
},
setupNodeEvents(on, config) {
// component testing node events setup code
},
},
e2e: {
setupNodeEvents(on, config) {
// e2e testing node events setup code
},
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
component: {
devServer(cypressConfig) {
// component testing dev server setup code
},
setupNodeEvents(on, config) {
// component testing node events setup code
},
},
e2e: {
setupNodeEvents(on, config) {
// e2e testing node events setup code
},
},
})
Alternately, you can continue to use an external plugins file, but you will need
to load that file explicitly, and also update it to move any component testing
dev server code into the devServer
config option.
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
const setupNodeEvents = require('./cypress/plugins/index.js')
module.exports = defineConfig({
component: {
devServer(cypressConfig) {
// component testing dev server setup code
},
setupNodeEvents,
},
e2e: {
setupNodeEvents,
},
})
import { defineConfig } from 'cypress'
import setupNodeEvents from './cypress/plugins/index.js'
export default defineConfig({
component: {
devServer(cypressConfig) {
// component testing dev server setup code
},
setupNodeEvents,
},
e2e: {
setupNodeEvents,
},
})
slowTestThreshold
β
The slowTestThreshold
configuration option is no longer valid at the top level
of the configuration, and is now a
testing-type specific option.
Note that the default values are unchanged (10000
for e2e
and 250
for
component
).
Attempting to set the slowTestThreshold
config option at the top level of the
configuration will result in an error when Cypress loads.
supportFile
β
The supportFile
configuration option is no longer valid at the top level of
the configuration, and is now a
testing-type specific option.
More information can be found in the
support file docs.
{
"supportFile": "cypress/support/index.js"
}
{
component: {
supportFile: 'cypress/support/component.js'
},
e2e: {
supportFile: 'cypress/support/e2e.js'
}
}
Attempting to set the supportFile
config option at the top level of the
configuration will result in an error when Cypress loads.
Also, for a given testing type, multiple matching supportFile
files will
result in an error when Cypress loads.
testFiles
β specPattern
β
The testFiles
option is no longer used, and has been replaced with the
specPattern
option, which must be defined inside the
component
and
e2e
configuration objects.
Default values:
- No longer matches with
.coffee
or.cjsx
. e2e.specPattern
default value iscypress/e2e/**/*.cy.{js,jsx,ts,tsx}
.component.specPattern
default value is**/*.cy.{js,jsx,ts,tsx}
.
Important note about matching:
- E2E tests will be found using the
e2e.specPattern
value. - Component tests will be found using the
component.specPattern
value but any tests found matching thee2e.specPattern
value will be automatically excluded.
Attempting to set the testFiles
config option will result in an error when
Cypress loads.
Also, attempting to set the specPattern
config option at the top level of the
configuration will result in an error when Cypress loads.
Updated Test File Locationsβ
Previously, you could specify the locations of test files and folders using the
configuration options: componentFolder
, or integrationFolder
, and
testFiles
. These options have been replaced with specPattern
, which is not
valid at the top-level, but within the
component
or
e2e
configuration objects. For
example:
{
"componentFolder": "src",
"integrationFolder": "cypress/integration",
"testFiles": "**/*.cy.js"
}
{
component: {
specPattern: 'src/**/*.cy.js'
},
e2e: {
specPattern: 'cypress/integration/**/*.cy.js'
}
}
Attempting to set componentFolder
, integrationFolder
, or testFiles
in the
config will result in an error when Cypress loads.
For Cypress Cloud users, changing your specPattern
and files names or
extensions of your spec files will result in a loss of data in Cypress Cloud.
Because of this, if we detect your project is using Cypress Cloud during
automatic migration, we won't suggest changing your spec files. We also don't
recommend doing it manually if you are a Cypress Cloud user.
Generated Filesβ
Generated screenshots and videos will still be created inside their respective
folders (screenshotsFolder
,
videosFolder
). However, the paths of generated files inside those folders will
be stripped of any common ancestor paths shared between all spec files found by
the specPattern
option (or via the --spec
command line option or spec
module API option, if specified).
Here are a few examples, assuming the value of videosFolder
is
cypress/videos
, screenshotsFolder
is cypress/screenshots
and
cy.screenshot('my-screenshot')
is called once per spec file:
Example 1
- Spec file found
cypress/e2e/path/to/file/one.cy.js
- Common ancestor paths (calculated at runtime)
cypress/e2e/path/to/file
- Generated screenshot file
cypress/screenshots/one.cy.js/my-screenshot.png
- Generated video file
cypress/videos/one.cy.js.mp4
Example 2
- Spec files found
cypress/e2e/path/to/file/one.cy.js
cypress/e2e/path/to/two.cy.js
- Common ancestor paths (calculated at runtime)
cypress/e2e/path/to
- Generated screenshot files
cypress/screenshots/file/one.cy.js/my-screenshot.png
cypress/screenshots/two.cy.js/my-screenshot.png
- Generated video files
cypress/videos/file/one.cy.js.mp4
cypress/videos/two.cy.js.mp4
Command / Cypress API Changesβ
cy.mount()
β
If you set up your app using the automatic configuration wizard, a basic
cy.mount()
command will be imported for you in your
support file from one our supported frameworks.
Cypress.Commands.add()
β
Cypress.Commands.add()
has been updated to
allow the built-in "placeholder" custom mount
and hover
commands to be
overwritten without needing to use Cypress.Commands.overwrite()
.
Component Testing Changesβ
Component Testing has moved from experimental to beta status in 10.0.0.
Component Testing can now be ran from the main app, and launching into component
testing via the command cypress open-ct
is now deprecated. To launch directly
into component testing, use the cypress open --component
command instead.
All the Component Testing dev servers are now included in the main cypress
npm
package. Configuring them is done via specifying a framework and bundler in the
devServer
config option, and the packages are no longer directly importable.
See
Framework Configuration
for more info.
The mount libraries for React and Vue have also been included in the main
cypress
package and can be imported from cypress/react
and cypress/vue
respectively.
Any previous dev servers or mounting libraries from the @cypress
namespace
should be uninstalled in Cypress 10.
Clashing Types with Jestβ
You may want to consider configuring your app with a separate tsconfig.json
to solve
clashing types with jest.
You will need to exclude cypress.config.ts
, cypress
, node_modules
in your
root tsconfig.json
file.
{
"exclude": ["cypress.config.ts", "cypress", "node_modules"]
}
Code Coverage Pluginβ
The Cypress Code Coverage plugin will need to be updated to version >= 3.10 to work with Cypress 10. Using a previous version will result in an error when tests are ran with code coverage enabled.
Migrating from cypress-file-upload
to .selectFile()
β
Selecting files with input elements or dropping them over the page is available
in Cypress 9.3. Read the .selectFile()
API docs
for more information on how this works and how to use it. This guide details how
to change your test code to migrate from the
cypress-file-upload
plugin
to .selectFile()
.
Quick guideβ
The argument signature is different for Cypress' builtin .selectFile()
command
than the .attachFile
command the cypress-file-upload
plugin provided. You
can follow the steps below for each argument in order to migrate:
When the first argument is a file path:
- Prefix the path with
cypress/fixtures/
.
When the first argument is an object:
filePath
: Rename the property tocontents
. Prefix the value withcypress/fixtures/
.fileContent
: Rename the property tocontents
. UseCypress.Buffer.from()
or other Buffer methods, rather thanCypress.Blob
.encoding
: Remove this property. It is no longer needed due to improved binary file handling in Cypress 9.0.mimeType
: No change necessary. In most cases you do not need to give a mimeType explicitly. Cypress will attempt to infer the MIME type based on the extension of the fileName if none is provided.
In the second argument:
subjectType
: Rename this property toaction
. Change the value fromdrag-n-drop
todrag-drop
or frominput
toselect
.allowEmpty
: Remove this property..selectFile()
does not check the length of a file read from disk, only its existence.force
: Works the same with.selectFile()
as it did incypress-file-upload
. No change necessary.
Examplesβ
Below are several examples of migrating various commands from
cypress-file-upload
to the builtin .selectFile()
command.
Read and attach a fixtureβ
Before Attaching a fixture from disk with cypress-file-upload
cy.get('[data-cy="file-input"]').attachFile('myfixture.json')
After Selecting a fixture from disk with .selectFile()
. Cypress follows paths from your project root (same as cy.readFile()
).
cy.get('[data-cy="file-input"]').selectFile('cypress/fixtures/myfixture.json')
// Or
cy.fixture('myfixture.json', { encoding: null }).as('myfixture')
cy.get('[data-cy="file-input"]').selectFile('@myfixture')
Using drag-n-dropβ
Before Dragging and dropping a file with cypress-file-upload
cy.get('[data-cy="dropzone"]').attachFile('myfixture.json', {
subjectType: 'drag-n-drop',
})
After Selecting a fixture from disk with .selectFile()
. Cypress follows paths from the root of your test folder (same as cy.readFile()
).
cy.get('[data-cy="dropzone"]').selectFile('fixtures/myfixture.json', {
action: 'drag-drop',
})
Overriding the file nameβ
Before Dragging and dropping a file with cypress-file-upload
cy.get('[data-cy="dropzone"]').attachFile({
filePath: 'myfixture.json',
fileName: 'customFileName.json',
})
After Selecting a fixture from disk with .selectFile()
. Cypress follows paths from the root of your test folder (same as cy.readFile()
).
cy.get('[data-cy="dropzone"]').selectFile({
contents: 'fixtures/myfixture.json',
fileName: 'customFileName.json',
})
Working with file contentsβ
Before Working with file contents before using using cypress-file-upload
const special = 'file.spss'
cy.fixture(special, 'binary')
.then(Cypress.Blob.binaryStringToBlob)
.then((fileContent) => {
// ...process file contents
cy.get('[data-cy="file-input"]').attachFile({
fileContent,
filePath: special,
encoding: 'utf-8',
lastModified: new Date().getTime(),
})
})
After Working with file contents before using with
.selectFile()
. The null
encoding introduced in Cypress 9.0 makes working with
binary data simpler, and is the preferred encoding for use with .selectFile()
.
const special = 'file.spss'
cy.fixture(special, { encoding: null }).then((contents) => {
// ...process file contents
cy.get('[data-cy="file-input"]').selectFile({
contents,
fileName: special,
lastModified: new Date().getTime(),
})
})
// Or
cy.fixture(special, { encoding: null })
.then((contents) => {
// ...process file contents
})
.as('special')
cy.get('[data-cy="file-input"]').selectFile('@special')
Specifying a custom mimeTypeβ
Before Specifying the MIME type with cypress-file-upload
cy.get('[data-cy="dropzone"]').attachFile({
filePath: 'myfixture.json',
fileName: 'customFileName.json',
})
After Specifying a MIME type with .selectFile()
.
cy.get('[data-cy="dropzone"]').selectFile({
contents: 'fixtures/myfixture.json',
mimeType: 'text/plain',
})
Migrating to Cypress 8.0β
This guide details the changes and how to change your code to migrate to Cypress 8.0. See the full changelog for 8.0.
cypress run
runs all browsers --headless
β
When running cypress run
previous to 8.0, some browsers would launch headed
while others were launched headless by default. In 8.0, we've normalized all
browsers to launch as headless by default.
This could cause a couple of changes to your existing runs:
- You may see the screenshot or video resolution of runs during
cypress run
change to the default of 1280x720. This is because headless browsers use the set screen size as opposed to the browser's size when opening headed. - Chrome extensions will not load during a
--headless
run. If your run depends on a Chrome extension being loaded duringcypress run
, you should explicitly pass the--headed
flag.
You can now remove the use of the --headless
flag during cypress run
as this
is the default for all browsers.
You should also update any use of the isHeaded
or isHeadless
property on
Cypress.browser
or the
browser launch API accordingly.
Before run headless browser
cypress run --browser=chrome --headless
cypress run --browser=firefox --headless
After All browsers headless by default, so you can
remove the --headless
flag during cypress run
.
cypress run --browser=chrome
cypress run --browser=firefox
Default screen size during --headless
β
The default screen size when running a headless browser has been reverted back to 1280x720 pixels. If you have any code in the browser launch API to set the screen size to 1280x720, this can be removed.
Before set screen size to 1280x720
// cypress/plugins/index.js
module.exports = (on, config) => {
on('before:browser:launch', (browser, launchOptions) => {
if (browser.name === 'chrome' && browser.isHeadless) {
launchOptions.args.push('--window-size=1280,720')
}
if (browser.name === 'electron' && browser.isHeadless) {
launchOptions.preferences.width = 1280
launchOptions.preferences.height = 720
}
if (browser.name === 'firefox' && browser.isHeadless) {
launchOptions.args.push('--width=1280')
launchOptions.args.push('--height=720')
}
return launchOptions
})
}
After no longer need overrides
// cypress/plugins/index.js
module.exports = (on, config) => {
// the default screen size is 1280x720 in all headless browsers
}
Migrating to Cypress 7.0β
This guide details the changes and how to change your code to migrate to Cypress 7.0. See the full changelog for 7.0.
cy.intercept()
changesβ
Cypress 7.0 comes with some breaking
changes to cy.intercept()
:
Handler ordering is reversedβ
Previous to Cypress 7.0, cy.intercept()
handlers were run in the
order that they are defined, stopping after the first handler to call
req.reply()
, or once all handlers are complete.
With Cypress 7.0, cy.intercept()
handlers are now run in reverse
order of definition, stopping after the first handler to call req.reply()
, or
once all handlers are complete.
This change was done so that users can override previously declared
cy.intercept()
handlers by calling cy.intercept()
again. See #9302 for more
details.
cy.intercept(url, (req) => {
/* This will be called first! */
})
cy.intercept(url, (req) => {
/* This will be called second! */
})
cy.intercept(url, (req) => {
/* This will be called second! */
})
cy.intercept(url, (req) => {
/* This will be called first! */
})
Read more about the cy.intercept()
interception lifecycle.
URL matching is stricterβ
Before Cypress 7.0, cy.intercept()
would match URLs against
strings by using minimatch
, substring match, or by equality.
With Cypress 7.0, this behavior is being tightened - URLs are matched against
strings only by minimatch
or by equality. The substring match has been
removed.
This more closely matches the URL matching behavior shown by cy.route()
.
However, some intercepts will not match, even though they did before.
For example, requests with querystrings may no longer match:
// will this intercept match a request for `/items?page=1`?
cy.intercept('/items')
// β
before 7.0.0, this will match, because it is a substring
// β after 7.0.0, this will not match, because of the querystring
// solution: update the intercept to match the querystring with a wildcard:
cy.intercept('/items?*')
Also, requests for paths in nested directories may be affected:
// will this intercept match a request for `/some/items`?
cy.intercept('/items')
// β
before 7.0.0, this will match, because it is a substring
// β after 7.0.0, this will not match, because of the leading directory
// solution: update the intercept to include the directory:
cy.intercept('/some/items')
Additionally, the matchUrlAgainstPath
RouteMatcher
option that was added in
Cypress 6.2.0 has been removed in Cypress 7.0. It can be safely removed from
tests.
Deprecated cy.route2()
command removedβ
cy.route2()
was the original name for cy.intercept()
during the experimental
phase of the feature. It was deprecated in Cypress 6.0. In Cypress 7.0, it has
been removed entirely. Please update existing usages of cy.route2()
to call
cy.intercept()
instead.
cy.route2('/widgets/*', { fixture: 'widget.json' }).as('widget')
cy.intercept('/widgets/*', { fixture: 'widget.json' }).as('widget')
res.delay()
and res.throttle()
have been renamedβ
The res.delay()
and res.throttle()
functions that exist on responses yielded
to response handlers have been renamed.
The new names are res.setDelay()
and res.setThrottle()
, respectively.
cy.intercept('/slow', (req) => {
req.continue((res) => {
// apply a delay of 1 second and a throttle of 56kbps
res.delay(1000).throttle(56)
})
})
cy.intercept('/slow', (req) => {
req.continue((res) => {
// apply a delay of 1 second and a throttle of 56kbps
res.setDelay(1000).setThrottle(56)
})
})
Read more about available functions on res
.
Falsy values are no longer dropped in StaticResponse
bodiesβ
Previously, falsy values supplied as the body
of a StaticResponse
would get
dropped (the same as if no body was supplied). Now, the bodies are properly
encoded in the response.
cy.intercept('/does-it-exist', { body: false })
// Requests to `/does-it-exist` receive an empty response body
cy.intercept('/does-it-exist', { body: false })
// Requests to `/does-it-exist` receive a response body of `false`
Errors thrown by request and response handlers are no longer wrappedβ
Previously, errors thrown inside of req
and res
handlers would be wrapped by
a CypressError
. In 7.0.0, errors thrown inside of these handlers are not
wrapped before failing the test.
This should only affect users who are explicitly asserting on global errors. See #15189 for more details.
Component Testingβ
In 7.0, component testing is no longer experimental. Cypress now ships with a dedicated component test runner with a new UI and dedicated commands to launch it.
Changes are required for all existing projects. The required changes are
limited to configuration and there are no breaking changes to the mount
API.
The migration guide contains the following steps:
- Update your Cypress configuration to remove
experimentalComponentTesting
- Install updated dependencies
- Update the plugins file
- Use CLI commands to launch
- Update the support file (optionally)
1. Remove experimentalComponentTesting
configβ
The experimentalComponentTesting
configuration is no longer needed to run
component tests. Remove this flag in order to run Cypress tests without
erroring.
Before experimentalComponentTesting flag is required for component testing
{
"experimentalComponentTesting": true,
"componentFolder": "src",
"testFiles": "**/*spec.{js,jsx,ts,tsx}"
}
After experimentalComponentTesting flag must be removed
{
"componentFolder": "src",
"testFiles": "**/*spec.{js,jsx,ts,tsx}"
}
2. Install component testing dependenciesβ
The Component Test Runner requires the following dependencies:
- Framework-specific bindings such as
@cypress/react
. - Development server adapter such as
@cypress/webpack-dev-server
. - Peer dependencies such as
webpack-dev-server
,vue
, orreact
.
Install React dependencies
- Upgrade to
@cypress/react
5.X. - Install
@cypress/webpack-dev-server
. - (Optional) Install
cypress-react-selector
if any tests usecy.react()
. - (Optional) Install code coverage, see installation steps).
npm install cypress @cypress/react @cypress/webpack-dev-server --save-dev
Install Vue 3 dependencies
- Upgrade to
@cypress/vue@next
(3.X and above). - Install
@cypress/webpack-dev-server
.
npm install cypress @cypress/vue@next @cypress/webpack-dev-server --save-dev
Install Vue 2 dependencies
- Upgrade to
@cypress/vue@2
(2.X only). - Install
@cypress/webpack-dev-server
.
npm install cypress @cypress/vue @cypress/webpack-dev-server --save-dev
3. Update plugins file to use dev-server:start
β
Re-using a project's local development server instead of file preprocessors
In 7.0 Cypress component tests require that code is bundled with your local
development server, via a new dev-server:start
event. This event replaces the
previous file:preprocessor
event.
Before Plugins file registers the file:preprocessor event
const webpackPreprocessor = require('@cypress/webpack-preprocessor')
const webpackConfig = require('../webpack.config.js')
module.exports = (on, config) => {
on('file:preprocessor', webpackPreprocessor(options))
}
After Plugins file registers the dev-server:start event
// The @cypress/webpack-dev-server package replaces @cypress/webpack-preprocessor
const { startDevServer } = require('@cypress/webpack-dev-server')
const webpackConfig = require('../webpack.config.js')
module.exports = (on, config) => {
// You must use the dev-server:start event instead of the file:preprocessor event
on('dev-server:start', (options) => {
return startDevServer({ options, webpackConfig })
})
}
Configure plugins.js
for React projects
Projects using React may not need to update their plugins file. If your project
is using a webpack scaffold or boilerplate, it is recommended to use a preset
plugin imported from
@cypress/react/plugins
.
Preset Plugins for React
If you are using a preset plugin within
@cypress/react
, you should not
need to update your plugins file. To check if you are using a preset, check to
see if your plugins file contains an import to a file inside of
@cypress/react/plugins
.
After An example plugins file to configure component testing in a React Scripts project
// The @cypress/react project exposes preset plugin configurations
// These presets automatically register the events to bundle the project properly
const injectReactScriptsDevServer = require('@cypress/react/plugins/react-scripts')
module.exports = (on, config) => {
// Internally, this method registers `dev-server:start` with the proper webpack configuration
// Previously, it registered the `file:preprocessor` event.
injectReactScriptsDevServer(on, config)
return config
}
Configure plugins.js
for Vue
Projects using Vue will likely be using either
@vue/cli
or manually defining webpack configuration.
These steps are identical to the manual setup steps, with the exception of how
you resolve the webpack configuration. To access the resolved webpack
configuration that contains any vue.config.js
setup or the default
@vue/cli
webpack setup, you must import the
configuration and pass it into
@cypress/webpack-dev-server
.
After An example plugins file to configure component testing in a Vue CLI project
const { startDevServer } = require('@cypress/webpack-dev-server')
// The resolved configuration, which contains any `vue.config.js` setup
const webpackConfig = require('@vue/cli-service/webpack.config.js')
module.exports = (on, config) => {
on('dev-server:start', (options) => {
return startDevServer({ options, webpackConfig })
})
}
Configuring a project with vanilla webpack
For projects with manually defined or ejected webpack configurations, the webpack configuration must be passed in.
After An example plugins file to configure component testing in a project with vanilla webpack
const { startDevServer } = require('@cypress/webpack-dev-server')
const webpackConfig = require('../webpack.config.js')
module.exports = (on, config) => {
on('dev-server:start', (options) => {
return startDevServer({ options, webpackConfig })
})
}
4. Use CLI commands to launchβ
To run your component tests you must use the dedicated component testing subcommands.
cypress open-ct
cypress run-ct
Component tests will no longer be picked up when launching Cypress from
cypress open
or cypress run
. Please use cypress open-ct
or
cypress run-ct
.
Before Commands launches both end-to-end and component tests.
cypress run
After Command launches Cypress Component Test Runner and executes component tests. End-to-end tests are run separately.
# open component testing runner
cypress open-ct
# run all component tests
cypress run-ct
# e2e tests
cypress open
cypress run
5. Update the support file (optionally)β
Previously, a support file was required to set up the component testing target node. This is no longer necessary.
Specifically for React users, if the support file contains the following line, please remove it. The import will fail in the future. We have left it in to avoid a breaking change, but the file does nothing.
Before The support file was required to import a script from @cypress/react
// support.js
// This import should be removed, it will error in a future update
import '@cypress/react/hooks'
Expanded stylesheet supportβ
Stylesheets are now bundled and imported within spec and support files.
Previously, many of mount
's mounting options such as stylesheets
,
cssFiles
, and styles
were required to import stylesheets into your component
tests. This often involved pre-compiling the stylesheets before launching the
component tests, which affected performance. Migrating to imports for these
styles is optional, but recommended.
Now, stylesheets should be loaded into the document
the same way they are in
your application. It is recommended you update your code like so:
Before Stylesheets were loaded using the filesystem
const { mount } = require('@cypress/react')
const Button = require('./Button')
it('renders a Button', () => {
// Mounting a button and loading the Tailwind CSS library
mount(<Button />, {
stylesheets: [
// Paths are relative to the project root directory and must be pre-compiled
// Because they are static, they do not watch for file updates
'/dist/index.css',
'/node_modules/tailwindcss/dist/tailwind.min.css',
],
})
})
After Stylesheets are supported via an import and mountingOptions.stylesheets
is not recommended
// In the majority of modern style-loaders,
// these styles will be injected into document.head when they're imported below
require('./index.scss')
require('tailwindcss/dist/tailwind.min.css')
const { mount } = require('@cypress/react')
const Button = require('./Button')
it('renders a Button', () => {
// This button will render with the Tailwind CSS styles
// as well as the application's index.scss styles
mount(<Button />)
})
Desktop GUI no longer displays component testsβ
Previously, the Desktop GUI displayed both end-to-end and component tests.
Now, component tests are only displayed when launching via the component
testing-specific subcommands. cypress open-ct
(or run-ct
in CI)
Executing all or some component testsβ
In 6.X, the Desktop GUI had support for finding and executing a subset of
component tests. In 7.0, this is possible with the --headed
command and a spec
glob, like so:
cypress run-ct --headed --spec **/some-folder/*spec.*
Coverageβ
Previously, the @cypress/react
4.X package embedded code coverage in your tests automatically.
If you still wish to record code coverage in your tests, you must manually install it. Please see our code coverage guide for the latest steps.
cypress-react-selectorβ
If you use cy.react()
in your tests, you must manually install
cypress-react-selector
with npm install cypress-react-selector --save-dev
. You do not need to update your support
file.
HTML Side effectsβ
As of 7.0, we only clean up components mounted by Cypress via
@cypress/react
or
@cypress/vue
.
We no longer automatically reset the document.body
between tests. Any HTML
side effects of your component tests will carry over.
Before All HTML content was cleared between spec files
const { mount } = require('@cypress/react')
describe('Component teardown behavior', () => {
it('modifies the document and mounts a component', () => {
// HTML unrelated to the component is mounted
Cypress.$('body').append('<div data-cy="some-html"/>')
// A component is mounted
mount(<Button data-cy="my-button"></Button>)
cy.get('[data-cy="some-html"]').should('exist')
cy.get('[data-cy="my-button"]').should('exist')
})
it('cleans up any HTML', () => {
// The component is automatically unmounted by Cypress
cy.get('[data-cy="my-button"]').should('not.exist')
// The HTML left over from the previous test has been cleaned up
// This was done automatically by Cypress
cy.get('[data-cy="some-html"]').should('not.exist')
})
})
After Only the components are cleaned up between spec files
const { mount } = require('@cypress/react')
describe('Component teardown behavior', () => {
it('modifies the document and mounts a component', () => {
// HTML unrelated to the component is mounted
Cypress.$('body').append('<div data-cy="some-html"/>')
// A component is mounted
mount(<Button data-cy="my-button"></Button>)
cy.get('[data-cy="some-html"]').should('exist')
cy.get('[data-cy="my-button"]').should('exist')
})
it('only cleans up *components* between tests', () => {
// The component is automatically unmounted by Cypress
cy.get('[data-cy="my-button"]').should('not.exist')
// The HTML left over from the previous test should be manually cleared
cy.get('[data-cy="some-html"]').should('not.exist')
})
})
Legacy cypress-react-unit-test
and cypress-vue-unit-test
packagesβ
For users upgrading from
cypress-react-unit-tests
or
cypress-vue-unit-tests
,
please update all references to use
@cypress/react
or
@cypress/vue
. These packages
have been deprecated and moved to the Cypress scope on npm.
Uncaught exception and unhandled rejectionsβ
In 7.0, Cypress now fails tests in more situations where there is an uncaught exception and also if there is an unhandled promise rejection in the application under test.
You can ignore these situations and not fail the Cypress test with the code below.
Turn off all uncaught exception handlingβ
Cypress.on('uncaught:exception', (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false
})
Turn off uncaught exception handling unhandled promise rejectionsβ
Cypress.on('uncaught:exception', (err, runnable, promise) => {
// when the exception originated from an unhandled promise
// rejection, the promise is provided as a third argument
// you can turn off failing the test in this case
if (promise) {
// returning false here prevents Cypress from
// failing the test
return false
}
})
Node.js 12+ supportβ
Cypress comes bundled with its own
Node.js version.
However, installing the cypress
npm package uses the Node.js version installed
on your system.
Node.js 10 reached its end of life on Dec 31, 2019 and Node.js 13 reached its end of life on June 1, 2019. See Node's release schedule. These Node.js versions will no longer be supported when installing Cypress. The minimum Node.js version supported to install Cypress is Node.js 12 or Node.js 14+.
Migrating cy.route()
to cy.intercept()
β
This guide details how to change your test code to migrate from cy.route()
to
cy.intercept()
. cy.server()
and cy.route()
are deprecated in
Cypress 6.0.0. In a future release, support for cy.server()
and cy.route()
will be removed.
Please also refer to the full documentation for cy.intercept().
Match simple routeβ
In many use cases, you can replace cy.route()
with cy.intercept()
and remove the call to cy.server()
(which is no longer necessary).
// Set up XHR listeners using cy.route()
cy.server()
cy.route('/users').as('getUsers')
cy.route('POST', '/project').as('createProject')
cy.route('PATCH', '/projects/*').as('updateProject')
// Intercept HTTP requests
cy.intercept('/users').as('getUsers')
cy.intercept('POST', '/project').as('createProject')
cy.intercept('PATCH', '/projects/*').as('updateProject')
Match against url
and path
β
The url
argument to cy.intercept() matches against the full url,
as opposed to the url
or path
in cy.route()
. If you're using the url
argument in cy.intercept()
, you may need to update your code
depending on the route you're trying to match.
// Match XHRs with a path or url of /users
cy.server()
cy.route({
method: 'POST',
url: '/users',
}).as('getUsers')
// Match HTTP requests with a path of /users
cy.intercept({
method: 'POST',
path: '/users',
}).as('getUsers')
// OR
// Match HTTP requests with an exact url of https://example.cypress.io/users
cy.intercept({
method: 'POST',
url: 'https://example.cypress.io/users',
}).as('getUsers')
cy.wait()
objectβ
The object returned by cy.wait()
is different from intercepted HTTP requests
using cy.intercept()
than the object returned from an awaited
cy.route()
XHR.
// Wait for XHR from cy.route()
cy.route('POST', '/users').as('createUser')
// ...
cy.wait('@createUser').then(({ requestBody, responseBody, status }) => {
expect(status).to.eq(200)
expect(requestBody.firstName).to.eq('Jane')
expect(responseBody.firstName).to.eq('Jane')
})
// Wait for intercepted HTTP request
cy.intercept('POST', '/users').as('createUser')
// ...
cy.wait('@createUser').then(({ request, response }) => {
expect(response.statusCode).to.eq(200)
expect(request.body.name).to.eq('Jane')
expect(response.body.name).to.eq('Jane')
})
Fixturesβ
You can stub requests and response with fixture data by defining a fixture
property in the routeHandler
argument for cy.intercept()
.
// Stub response with fixture data using cy.route()
cy.route('GET', '/projects', 'fx:projects')
// Stub response with fixture data using cy.intercept()
cy.intercept('GET', '/projects', {
fixture: 'projects',
})
Override interceptsβ
As of 7.0, newer intercepts are called before older intercepts, allowing users to override intercepts. See "Handler ordering is reversed" for more details.
Before 7.0, intercepts could not be overridden. See #9302 for more details.
Migrating to Cypress 6.0β
This guide details the changes and how to change your code to migrate to Cypress 6.0. See the full changelog for 6.0.
Non-existent element assertionsβ
Key takeway: Use .should('not.exist')
to assert that an element does not
exist in the DOM (not .should('not.be.visible')
, etc).
In previous versions of Cypress, there was a possibility for tests to falsely pass when asserting a negative state on non-existent elements.
For example, in the tests below we want to test that the search dropdown is no longer visible when the search input is blurred because we hide the element in CSS styles. Except in this test, we've mistakenly misspelled one of our selectors.
cy.get('input[type=search]').type('Cypress')
cy.get('#dropdown').should('be.visible')
cy.get('input[type=search]').blur()
// below we misspelled "dropdown" in the selector π
// the assertions falsely pass in Cypress < 6.0
// and will correctly fail in Cypress 6.0 +
cy.get('#dropdon').should('not.be.visible')
cy.get('#dropdon').should('not.have.class', 'open')
cy.get('#dropdon').should('not.contain', 'Cypress')
In 6.0, these assertions will now correctly fail, telling us that the #dropdon
element doesn't exist in the DOM.
Assertions on non-existent elementsβ
This fix may cause some breaking changes in your tests if you are relying on
assertions such as not.be.visible
or not.contains
to test that the DOM
element did not exist in the DOM. This means you'll need to update your test
code to be more specific about your assertions on non-existent elements.
Before Assert that non existent element was not visible
it('test', () => {
// the modal element is removed from the DOM on click
cy.get('[data-cy="modal"]').find('.close').click()
// assertions below pass in Cypress < 6.0, but properly fail in 6.0+
cy.get('[data-cy="modal"]').should('not.be.visible')
cy.get('[data-cy="modal"]').should('not.contain', 'Upgrade')
})
After Assert that non existent element does not exist
it('test', () => {
// the modal element is removed from the DOM on click
cy.get('data-cy="modal"').find('.close').click()
// we should instead assert that the element doesn't exist
cy.get('data-cy="modal"').should('not.exist')
})
Opacity visibilityβ
DOM elements with opacity: 0
style are no longer considered to be visible.
This includes elements with an ancestor that has opacity: 0
since a child
element can never have a computed opacity greater than that of an ancestor.
Elements where the CSS property (or ancestors) is opacity: 0
are still
considered actionable however
and
any action commands
used to interact with the element will perform the action. This matches
browser's implementation on how they regard elements with opacity: 0
.
Assert visibility of opacity: 0
elementβ
Before Failed assertion that opacity: 0
element is
not visible.
it('test', () => {
// '.hidden' has 'opacity: 0' style.
// In < 5.0 this assertion would fail
cy.get('.hidden').should('not.be.visible')
})
After Passed assertion that opacity: 0
element is
not visible.
it('test', () => {
// '.hidden' has 'opacity: 0' style.
// In 6.0 this assertion will pass
cy.get('.hidden').should('not.be.visible')
})
Perform actions on opacity: 0
elementβ
In all versions of Cypress, you can interact with elements that have
opacity: 0
style.
it('test', () => {
// '.hidden' has 'opacity: 0' style.
cy.get('.hidden').click() // β
clicks on element
cy.get('.hidden').type('hi') // β
types into element
cy.get('.hidden').check() // β
checks element
cy.get('.hidden').select('yes') // β
selects element
})
cy.wait(alias)
typeβ
cy.route()
is deprecated in 6.0.0. We encourage the use of
cy.intercept() instead. Due to this deprecation, the type yielded
by cy.wait(alias) has changed.
Before Before 6.0.0, cy.wait(alias)
would yield an object of type WaitXHR
.
After In 6.0.0 and onwards, cy.wait(alias)
will yield an object of type Interception
. This matches the new interception object
type used for cy.intercept().
Restore old behaviorβ
If you need to restore the type behavior prior to 6.0.0 for cy.wait(alias), you can declare a global override for cy.wait() like so:
declare global {
namespace Cypress {
interface Chainable<Subject = any> {
wait(alias: string): Chainable<Cypress.WaitXHR>
}
}
}
βdisable-dev-shm-usage
β
We now pass βdisable-dev-shm-usage
to the Chrome browser flags by default. If
you're passing this flag in your plugins
file, you can now remove this code.
Before Passing flag in plugins file.
// cypress/plugins/index.js
module.exports = (on, config) => {
on('before:browser:launch', (browser = {}, launchOptions) => {
if (browser.family === 'chromium' && browser.name !== 'electron') {
launchOptions.args.push('--disable-dev-shm-usage')
}
return launchOptions
})
}
After Remove flag from plugins file.
// cypress/plugins/index.js
module.exports = (on, config) => {}
Restore old behaviorβ
If you need to remove the flag in 6.0.0+, you can follow the workaround documented here: #9242.
Migrating to Cypress 5.0β
This guide details the changes and how to change your code to migrate to Cypress 5.0. See the full changelog for 5.0.
Tests retriesβ
Test retries are available in Cypress 5.0. This means that tests can be re-run a number of times before potentially being marked as a failed test. Read the Test Retries doc for more information on how this works and how to turn on test retries.
When test retries are turned on, there will now be a screenshot taken for every failed attempt, so there could potentially be more than 1 screenshot per test failure. Read the Test Retries doc for more information on how this works.
The
cypress-plugin-retries
plugin has been deprecated in favor of test retries built into Cypress. There's
guidance below on how to migrate from the
cypress-plugin-retries
plugin to Cypress's built-in test retries.
Configure test retries via the CLIβ
Before Setting retries with cypress-plugin-retries
via env vars
CYPRESS_RETRIES=2 cypress run
After Setting test retries in Cypress 5.0 via env vars
CYPRESS_RETRIES=2 cypress run
Configure test retries in the configuration fileβ
Before Setting retries with cypress-plugin-retries
via configuration
{
"env": {
"RETRIES": 2
}
}
After Setting test retries in Cypress 5.0 via configuration
{
"retries": 1
}
runMode
allows you to define the number of test retries when runningcypress run
openMode
allows you to define the number of test retries when runningcypress open
{
"retries": {
"runMode": 1,
"openMode": 3
}
}
Configure test retries per testβ
Before Setting retries with cypress-plugin-retries
via the test
it('test', () => {
Cypress.currentTest.retries(2)
})
After Setting test retries in Cypress 5.0 via test options
it(
'allows user to login',
{
retries: 2,
},
() => {
// ...
}
)
runMode
allows you to define the number of test retries when runningcypress run
openMode
allows you to define the number of test retries when runningcypress open
it(
'allows user to login',
{
retries: {
runMode: 2,
openMode: 3,
},
},
() => {
// ...
}
)
Module API resultsβ
To more accurately reflect result data for runs with
test retries, the structure of each run's runs
array resolved from the Promise
returned from cypress.run()
of the Module
API has changed.
Mainly there is a new attempts
Array on each test
which will reflect the
result of each test retry.
Before results.runs
Module API results
{
// ...
"runs": [{
// ...
"hooks": [{
"hookId": "h1",
"hookName": "before each",
"title": [ "before each hook" ],
"body": "function () {\n expect(true).to.be["true"];\n}"
}],
// ...
"screenshots": [{
"screenshotId": "8ddmk",
"name": null,
"testId": "r2",
"takenAt": "2020-08-05T08:52:20.432Z",
"path": "User/janelane/my-app/cypress/screenshots/spec.js/test (failed).png",
"height": 720,
"width": 1280
}],
"stats": {
// ...
"wallClockStartedAt": "2020-08-05T08:38:37.589Z",
"wallClockEndedAt": "2018-07-11T17:53:35.675Z",
"wallClockDuration": 1171
},
"tests": [{
"testId": "r2",
"title": [ "test" ],
"state": "failed",
"body": "function () {\n expect(true).to.be["false"];\n}",
"stack": "AssertionError: expected true to be false\n' +
' at Context.eval (...cypress/integration/spec.js:5:21",
"error": "expected true to be false",
"timings": {
"lifecycle": 16,
"test": {...}
},
"failedFromHookId": null,
"wallClockStartedAt": "2020-08-05T08:38:37.589Z",
"wallClockDuration": 1171,
"videoTimestamp": 4486
}],
}],
// ...
}
After results.runs
Module API results
{
// ...
"runs": [{
// ...
"hooks": [{
"hookName": "before each",
"title": [ "before each hook" ],
"body": "function () {\n expect(true).to.be["true"];\n}"
}],
// ...
"stats": {
// ...
"startedAt": "2020-08-05T08:38:37.589Z",
"endedAt": "2018-07-11T17:53:35.675Z",
"duration": 1171
},
"tests": [{
"title": [ "test" ],
"state": "failed",
"body": "function () {\n expect(true).to.be["false"];\n}",
"displayError": "AssertionError: expected true to be false\n' +
' at Context.eval (...cypress/integration/spec.js:5:21",
"attempts": [{
"state": "failed",
"error": {
"message": "expected true to be false",
"name": "AssertionError",
"stack": "AssertionError: expected true to be false\n' +
' at Context.eval (...cypress/integration/spec.js:5:21"
},
"screenshots": [{
"name": null,
"takenAt": "2020-08-05T08:52:20.432Z",
"path": "User/janelane/my-app/cypress/screenshots/spec.js/test (failed).png",
"height": 720,
"width": 1280
}],
"startedAt": "2020-08-05T08:38:37.589Z",
"duration": 1171,
"videoTimestamp": 4486
}]
}],
}],
// ...
}
Cookies whitelist
option renamedβ
The Cypress.Cookies.defaults() whitelist
option
has been renamed to preserve
to more closely reflect its behavior.
Before whitelist
option
Cypress.Cookies.defaults({
whitelist: 'session_id',
})
After preserve
option
Cypress.Cookies.defaults({
preserve: 'session_id',
})
blacklistHosts
configuration renamedβ
The blacklistHosts
configuration has been renamed to
blockHosts to more closely reflect its
behavior.
This should be updated in all places where Cypress configuration can be set
including via the Cypress configuration file, command line arguments, the
pluginsFile
, Cypress.config()
or environment variables.
Before blacklistHosts
configuration
{
"blacklistHosts": "www.google-analytics.com"
}
After blockHosts
configuration
{
"blockHosts": "www.google-analytics.com"
}
Return type of Cypress.Blob
changedβ
We updated the Blob library used
behind Cypress.Blob from 1.3.3
to 2.0.2
.
The return type of the Cypress.Blob methods
arrayBufferToBlob
, base64StringToBlob
, binaryStringToBlob
, and
dataURLToBlob
have changed from Promise<Blob>
to Blob
.
Before Cypress.Blob
methods returned a Promise
Cypress.Blob.base64StringToBlob(this.logo, 'image/png').then((blob) => {
// work with the returned blob
})
After Cypress.Blob
methods return a Blob
const blob = Cypress.Blob.base64StringToBlob(this.logo, 'image/png')
// work with the returned blob
cy.server()
whitelist
option renamedβ
The cy.server()
whitelist
option has been renamed to ignore
to more
closely reflect its behavior.
Before whitelist
option
cy.server({
whitelist: (xhr) => {
return xhr.method === 'GET' && /\.(jsx?|html|css)(\?.*)?$/.test(xhr.url)
},
})
After ignore
option
cy.server({
ignore: (xhr) => {
return xhr.method === 'GET' && /\.(jsx?|html|css)(\?.*)?$/.test(xhr.url)
},
})
Cookies sameSite
propertyβ
Values yielded by cy.setCookie(),
cy.getCookie(), and
cy.getCookies() will now contain the sameSite
property if specified.
If you were using the experimentalGetCookiesSameSite
configuration to get the
sameSite
property previously, this should be removed.
Before Cookies yielded before had no sameSite
property.
cy.getCookie('token').then((cookie) => {
// cy.getCookie() yields a cookie object
// {
// domain: "localhost",
// expiry: 1593551644,
// httpOnly: false,
// name: "token",
// path: "/commands",
// secure: false,
// value: "123ABC"
// }
})
After Cookies yielded now have sameSite
property
if specified.
cy.getCookie('token').then((cookie) => {
// cy.getCookie() yields a cookie object
// {
// domain: "localhost",
// expiry: 1593551644,
// httpOnly: false,
// name: "token",
// path: "/commands",
// sameSite: "strict",
// secure: false,
// value: "123ABC"
// }
})
dirname / filenameβ
The globals __dirname
and __filename
no longer include a leading slash.
Before __dirname
/ __filename
// cypress/integration/app_spec.js
it('include leading slash < 5.0', () => {
expect(__dirname).to.equal('/cypress/integration')
expect(__filename).to.equal('/cypress/integration/app_spec.js')
})
After __dirname
/ __filename
// cypress/integration/app_spec.js
it('do not include leading slash >= 5.0', () => {
expect(__dirname).to.equal('cypress/integration')
expect(__filename).to.equal('cypress/integration/app_spec.js')
})
Linux dependenciesβ
Running Cypress on Linux now requires the libgbm
dependency (on Debian-based
systems, this is available as libgbm-dev
). To install all required
dependencies on Ubuntu/Debian, you can run the script below:
apt-get install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6 xauth xvfb
TypeScript esModuleInteropβ
Cypress no longer forces the esModuleInterop
compiler option for TypeScript to
be true
for spec, support, and plugins files. We recommend setting it in your
project's tsconfig.json
instead if you need to.
// tsconfig.json
{
"compilerOptions": {
"esModuleInterop": true
/* ... other compiler options ... */
}
}
TypeScript 3.4+ supportβ
Cypress 5.0 raises minimum required TypeScript version from 2.9+ to 3.4+. You'll need to have TypeScript 3.4+ installed within your project to have TypeScript support within Cypress.
Node.js 10+ supportβ
Cypress comes bundled with its own
Node.js version.
However, installing the cypress
npm package uses the Node.js version installed
on your system.
Node.js 8 reached its end of life on Dec 31, 2019 and Node.js 11 reached its end of life on June 1, 2019. See Node's release schedule. These Node.js versions will no longer be supported when installing Cypress. The minimum Node.js version supported to install Cypress is Node.js 10 or Node.js 12+.
Migrating to Cypress 4.0β
This guide details the changes and how to change your code to migrate to Cypress 4.0. See the full changelog for 4.0.
Mocha upgradeβ
Mocha was upgraded from 2.5.3
to 7.0.1
, which includes a number of breaking
changes and new features outlined in their
changelog. Some
changes you might notice are described below.
Breaking Change: invoke done
callback and return a promiseβ
Starting with
Mocha 3.0.0,
invoking a done
callback and returning a promise in a test results in an
error.
This error originates from Mocha and is discussed at length here and here.
The reason is that using two different ways to signal that a test is finished is usually a mistake and there is always a way to only use one. There is a proposal to handle this situation without erroring that may be released in a future version of Mocha.
In the meantime, you can fix the error by choosing a single way to signal the end of your test's execution.
Example #1β
Before This test has a done callback and a promise
it('uses invokes done and returns promise', (done) => {
return codeUnderTest.doSomethingThatReturnsPromise().then((result) => {
// assertions here
done()
})
})
After You can remove the done
callback and return
the promise instead:
it('uses invokes done and returns promise', () => {
return codeUnderTest.doSomethingThatReturnsPromise().then((result) => {
// assertions here
})
})
Example #2β
Before Sometimes it might make more sense to use the
done
callback and not return a promise:
it('uses invokes done and returns promise', (done) => {
eventEmitter.on('change', () => {
// assertions
done()
})
return eventEmitter.doSomethingThatEmitsChange()
})
After In this case, you don't need to return the promise:
it('uses invokes done and returns promise', (done) => {
eventEmitter.on('change', () => {
// assertions
done()
})
eventEmitter.doSomethingThatEmitsChange()
})
Example #3β
Test functions using async/await
automatically return a promise, so they need
to be refactored to not use a done
callback.
Before This will cause an overspecified error.
it('uses async/await', async (done) => {
const eventEmitter = await getEventEmitter()
eventEmitter.on('change', () => done())
eventEmitter.doSomethingThatEmitsChange()
})
After Update to the test code below.
it('uses async/await', async () => {
const eventEmitter = await getEventEmitter()
return new Promise((resolve) => {
eventEmitter.on('change', () => resolve())
eventEmitter.doSomethingThatEmitsChange()
})
})
Tests require a titleβ
Tests now require a title and will error when not provided one.
// Would show as pending in Cypress 3
// Will throw type error in Cypress 4:
it() // Test argument "title" should be a string. Received type "undefined"
Chai upgradeβ
Chai was upgraded from 3.5.0
to 4.2.0
, which includes a number of breaking
changes and new features outlined in
Chai's migration guide. Some
changes you might notice are described below.
Breaking Change: assertions expecting numbersβ
Some assertions will now throw an error if the assertion's target or arguments
are not numbers, including within
, above
, least
, below
, most
,
increase
and decrease
.
// These will now throw errors:
expect(null).to.be.within(0, 1)
expect(null).to.be.above(10)
// This will not throw errors:
expect('string').to.have.a.length.of.at.least(3)
Breaking Change: empty
assertionsβ
The .empty
assertion will now throw when it is passed non-string primitives
and functions.
// These will now throw TypeErrors
expect(Symbol()).to.be.empty
expect(() => {}).to.be.empty
Breaking Change: non-existent propertiesβ
An error will throw when a non-existent property is read. If there are typos in property assertions, they will now appear as failures.
// Would pass in Cypress 3 but will fail correctly in 4
expect(true).to.be.ture
Breaking Change: include
checks strict equalityβ
include
now always use strict equality unless the deep
property is set.
Before include
would always use deep equality
// Would pass in Cypress 3 but will fail correctly in 4
cy.wrap([
{
first: 'Jane',
last: 'Lane',
},
]).should('include', {
first: 'Jane',
last: 'Lane',
})
After Need to specificy deep.include
for deep equality
// Specifically check for deep.include to pass in Cypress 4
cy.wrap([
{
first: 'Jane',
last: 'Lane',
},
]).should('deep.include', {
first: 'Jane',
last: 'Lane',
})
Sinon.JS upgradeβ
Sinon.JS was upgraded from 3.2.0
to 8.1.1
, which includes a number of
breaking changes and new features outlined in
Sinon.JS's migration guide.
Some changes you might notice are described below.
Breaking Change: stub non-existent propertiesβ
An error will throw when trying to stub a non-existent property.
// Would pass in Cypress 3 but will fail in 4
cy.stub(obj, 'nonExistingProperty')
Breaking Change: reset()
replaced by resetHistory()
β
For spies and stubs, the reset()
method was replaced by resetHistory()
.
Before Spies and stubs using reset()
.
const spy = cy.spy()
const stub = cy.stub()
spy.reset()
stub.reset()
After Update spies and stubs should now use resetHistory()
.
const spy = cy.spy()
const stub = cy.stub()
spy.resetHistory()
stub.resetHistory()
Plugin Event before:browser:launch
β
Since we now support more advanced browser launch options, during
before:browser:launch
we no longer yield the second argument as an array of
browser arguments and instead yield a launchOptions
object with an args
property.
You can see more examples of the new launchOptions
in use in the
Browser Launch API doc.
Before The second argument is no longer an array.
on('before:browser:launch', (browser, args) => {
// will print a deprecation warning telling you
// to change your code to the new signature
args.push('--another-arg')
return args
})
After Access the args
property off launchOptions
on('before:browser:launch', (browser, launchOptions) => {
launchOptions.args.push('--another-arg')
return launchOptions
})
Electron options in before:browser:launch
β
Previously, you could pass options to the launched Electron
BrowserWindow
in before:browser:launch
by modifying the launchOptions
object.
Now, you must pass those options as launchOptions.preferences
:
Before Passing BrowserWindow options on the launchOptions
object is no longer supported.
on('before:browser:launch', (browser, args) => {
args.darkTheme = true
return args
})
After Pass BrowserWindow options on the options.preferences
object instead.
on('before:browser:launch', (browser, launchOptions) => {
launchOptions.preferences.darkTheme = true
return launchOptions
})
Launching Chrome Canary with --browser
β
Before 4.0, cypress run --browser canary
would run tests in Chrome Canary.
Now, you must pass --browser chrome:canary
to select Chrome Canary.
See the
docs for cypress run --browser
for more information.
Before Passing canary
will no longer find a browser
cypress run --browser canary
After Pass chrome:canary
to launch Chrome Canary
cypress run --browser chrome:canary
Chromium-based browser family
β
We updated the Cypress browser objects of all
Chromium-based browsers, including Electron, to have chromium
set as their
family
field.
module.exports = (on, config) => {
on('before:browser:launch', (browser = {}, launchOptions) => {
if (browser.family === 'electron') {
// would match Electron in 3.x
// will match no browsers in 4.0.0
return launchOptions
}
if (browser.family === 'chromium') {
// would match no browsers in 3.x
// will match any Chromium-based browser in 4.0.0
// ie Chrome, Canary, Chromium, Electron, Edge (Chromium-based)
return launchOptions
}
})
}
Example #1 (Finding Electron)β
Before This will no longer find the Electron browser.
module.exports = (on, config) => {
on('before:browser:launch', (browser = {}, args) => {
if (browser.family === 'electron') {
// run code for Electron browser in 3.x
return args
}
})
}
After Use browser.name
to check for Electron
module.exports = (on, config) => {
on('before:browser:launch', (browser = {}, launchOptions) => {
if (browser.name === 'electron') {
// run code for Electron browser in 4.0.0
return launchOptions
}
})
}
Example #2 (Finding Chromium-based browsers)β
Before This will no longer find any browsers.
module.exports = (on, config) => {
on('before:browser:launch', (browser = {}, args) => {
if (browser.family === 'chrome') {
// in 4.x, `family` was changed to 'chromium' for all Chromium-based browsers
return args
}
})
}
After Use browser.name
and browser.family
to select
non-Electron Chromium-based browsers
module.exports = (on, config) => {
on('before:browser:launch', (browser = {}, launchOptions) => {
if (browser.family === 'chromium' && browser.name !== 'electron') {
// pass launchOptions to Chromium-based browsers in 4.0
return launchOptions
}
})
}
cy.writeFile()
yields null
β
cy.writeFile()
now yields null
instead of the contents
written to the
file. This change was made to more closely align with the behavior of Node.js
fs.writeFile
.
Before This assertion will no longer pass
cy.writeFile('path/to/message.txt', 'Hello World').then((text) => {
// Would pass in Cypress 3 but will fail in 4
expect(text).to.equal('Hello World') // false
})
After Instead read the contents of the file
cy.writeFile('path/to/message.txt', 'Hello World')
cy.readFile('path/to/message.txt').then((text) => {
expect(text).to.equal('Hello World') // true
})
cy.contains() ignores invisible whitespacesβ
Browsers ignore leading, trailing, duplicate whitespaces. And Cypress now does that, too.
<p>hello world</p>
cy.get('p').contains('hello world') // Fail in 3.x. Pass in 4.0.0.
cy.get('p').contains('hello\nworld') // Pass in 3.x. Fail in 4.0.0.
Node.js 8+ supportβ
Cypress comes bundled with its own
Node.js version.
However, installing the cypress
npm package uses the Node.js version installed
on your system.
Node.js 4 reached its end of life on April 30, 2018 and Node.js 6 reached its end of life on April 30, 2019. See Node's release schedule. These Node.js versions will no longer be supported when installing Cypress. The minimum Node.js version supported to install Cypress is Node.js 8.
CJSX is no longer supportedβ
Cypress no longer supports CJSX (CoffeeScript + JSX), because the library used to transpile it is no longer maintained.
If you need CJSX support, you can use a pre-2.x version of the Browserify preprocessor.
npm install @cypress/[email protected]
// cypress/plugins/index.js
const browserify = require('@cypress/browserify-preprocessor')
module.exports = (on) => {
on('file:preprocessor', browserify())
}