What’s New in Cypress 12? Features and Changes
Cypress 12 made cy.origin() and cy.session() generally available and enabled test isolation by default. Here are the changes and a practical upgrade checklist.
Cypress 12 made cy.origin() and cy.session() generally available for end-to-end tests, enabled test isolation by default, and changed several migration details. The biggest practical upgrade risk is a test that expected cookies, storage, or page state to persist from one test to the next.
This guide explains the release changes, shows how to use the two now-stable APIs, and gives you a checklist for upgrading a Cypress 11 project. Cypress’s migration guide specifies Node.js 14, 16, or 18 or later for installing the Cypress npm package; Node.js 12, 15, and 17 are no longer supported for that installation. Cypress 12 migration guide
What changed in Cypress 12
| Change | What it means | What to check |
|---|---|---|
cy.origin() is generally available |
Tests can interact with a second origin in the same end-to-end test, such as an external authentication domain. | Move cross-origin flows to the documented API and check callback serialization and dependency requirements. |
cy.session() is generally available |
Cookies, local storage, and session storage can be cached and restored to reuse browser authentication state. | Use it for intentional session reuse, and validate that a restored session is still valid for the test. |
| Test isolation defaults to enabled | Before each test, Cypress resets the page and clears cookies and local and session storage when isolation is enabled. | Find tests that relied on state left behind by earlier tests. |
testIsolation configuration uses booleans |
The previous on/off values changed to true/false. |
Update any existing setting that uses the old values. |
| DOM element resolution changed | Cypress improved re-querying after DOM updates to reduce detached-element errors. | Keep selectors resilient; this improvement reduces the likelihood of detachment errors but does not guarantee they cannot occur. |
| Query command category added | Some commands are classified as queries, which affects command customization. | Review custom Cypress.Commands.overwrite() calls against the migration guide’s list. |
| Installation Node.js requirements changed | The guide specifies Node.js 14, 16, or 18 or later; versions 12, 15, and 17 were dropped. | Check the system Node.js used to install the npm package. This is distinct from Cypress’s bundled Node runtime. |
cy.request() query string handling changed |
Some qs values may produce different URL behavior. |
Check constructed request URLs for tests using complex query parameters. |
| Cookie preservation APIs removed | Cookies.defaults and Cookies.preserveOnce are removed. |
Consider cy.session() for session state that needs to be reused between tests. |
The Cypress Team’s announcement describes the default isolation behavior as resetting cookies, storage, and page state before each test. Cypress 12.0.0’s dedicated release page gives December 5, 2022 as its release date; Cypress’s changelog and announcement label it December 6, so the official pages differ by one day. Cypress 12 announcement · Cypress changelog
Use cy.origin() for cross-origin flows
cy.origin() lets a test run commands against another origin. This is useful when a flow leaves your application, for example to authenticate on an identity provider and then return. The following is a runnable pattern; replace the example domains and selectors with those used by your application.
describe('sign-in through an identity provider', () => {
it('returns to the application after authentication', () => {
cy.visit('https://app.example.com/login')
cy.get('[data-cy=sign-in]').click()
cy.origin('https://identity.example.com', () => {
cy.get('input[name=username]').type('test-user')
cy.get('input[name=password]').type('test-password')
cy.get('button[type=submit]').click()
})
cy.location('origin').should('eq', 'https://app.example.com')
cy.get('[data-cy=account-menu]').should('be.visible')
})
})
Use the origin that the browser actually navigates to, including scheme and hostname. Keep callback code self-contained when possible. External dependencies in a cy.origin() callback are not enabled by default; the migration guide documents e2e.experimentalOriginDependencies: true as the opt-in setting when the callback needs them. Review the official guide before enabling that configuration. Cross-origin migration notes
Reuse authentication with cy.session()
cy.session() caches and restores cookies, local storage, and session storage. A setup callback establishes the session, and an optional validation callback checks that a restored session remains usable. Here is a complete test pattern with placeholder application routes and selectors:
function login() {
cy.session('test-user', () => {
cy.visit('/login')
cy.get('[name=email]').type('test-user@example.com')
cy.get('[name=password]').type('test-password')
cy.get('button[type=submit]').click()
cy.url().should('include', '/dashboard')
}, {
validate() {
cy.visit('/dashboard')
cy.get('[data-cy=account-menu]').should('be.visible')
}
})
}
describe('dashboard', () => {
beforeEach(() => {
login()
})
it('shows the signed-in dashboard', () => {
cy.visit('/dashboard')
cy.get('h1').should('contain', 'Dashboard')
})
})
Use a session identifier that distinguishes materially different users or authentication contexts. Put assertions that prove the session is valid in validate; otherwise a cached but expired session may not be caught until a later assertion fails. A session restores browser state, so tests should still navigate to and assert the page state they need.
Understand test isolation and state between tests
With isolation enabled, each end-to-end test starts with a reset page and cleared cookies and local and session storage. Tests should establish their own required state. This makes a test less dependent on execution order and reduces hidden coupling, but a suite that intentionally shared a login or application state needs to be reworked.
For a test that needs authenticated state, create or restore it in setup with cy.session(). For a test that needs data in the application, seed or create that data as part of its setup. If the project deliberately needs a different isolation policy, configure it explicitly using the boolean values supported in Cypress 12, and make the consequences clear to the team.
// cypress.config.js
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
testIsolation: true
}
})
Use false only when tests are designed to share browser state and the resulting order dependence is acceptable. Do not use it as a blanket repair for failures caused by missing setup: that can hide the dependency and make tests harder to run independently.
Upgrade checklist from Cypress 11
- Check the installer’s Node.js version. Confirm the system Node.js used to install Cypress meets the guide’s version requirements. Do not confuse it with Cypress’s bundled Node runtime.
- Find state dependencies. Search for tests that assume a prior test left the page, cookies, local storage, or session storage in place. Give each test explicit setup or a deliberate session strategy.
- Update isolation configuration. Replace
testIsolation: 'on'or'off'withtrueorfalse, respectively, where those old values exist. - Replace removed cookie APIs. Remove
Cookies.defaultsandCookies.preserveOnce; usecy.session()when the goal is to preserve and restore a browser session. - Audit cross-origin tests. Use
cy.origin()for interactions with another origin. If its callback imports or requires external dependencies, decide whether the documentede2e.experimentalOriginDependenciesopt-in is needed. - Inspect command overwrites. If the project overwrites built-in commands, check whether a command is now a query and whether Cypress permits it to be overwritten.
- Check request URLs. For calls to
cy.request()withqs, inspect the resulting query string, especially for nested or unusual values. - Run tests independently. Start with tests that previously depended on shared state. Verify that each passes when run alone and that the full suite does not depend on test order.
Common upgrade errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Install fails on an unsupported Node version | The system Node used for npm installation is Node 12, 15, or 17, or otherwise outside the guide’s specified versions. | Switch the installer environment to Node.js 14, 16, or 18 or later, then reinstall as appropriate. |
| A test starts unauthenticated or loses local data | Default test isolation cleared cookies or storage before the test. | Establish state in that test or restore an intentional session with cy.session(). |
| A test fails after a previous test passes | The test relied on shared page or browser state and is now isolated. | Make setup explicit; avoid relying on test order. |
cy.origin() cannot access an imported helper |
External dependencies in the callback are not enabled by default. | Keep callback code self-contained or review the documented e2e.experimentalOriginDependencies: true opt-in. |
| A custom command overwrite fails or behaves differently | The command may have been reclassified as a query or may no longer be eligible for overwrite(). |
Check the migration guide’s affected-command list and use the supported extension approach for that command. |
| A request goes to an unexpected URL | cy.request() query-string handling changed. |
Inspect the final URL and adjust the qs input to match the expected encoding and structure. |
| Detached element errors still occur | The DOM can still change between locating an element and acting on it; Cypress’s re-query improvements reduce but do not eliminate this possibility. | Use stable selectors and retryable queries/assertions, and avoid carrying a stale element reference across an application update. |
Performance, reliability, and cost considerations
cy.session() can avoid repeating an authentication flow, and Cypress describes it as a way to reduce suite execution time. The release sources provide no independent benchmark, so the actual time saved depends on the application, login flow, and suite. Session caching also makes validation important: a fast restore is useful only if the session is still valid for the test.
Isolation adds setup work for tests that previously inherited state, but it makes each test’s prerequisites visible and reduces order coupling. Cypress 12’s DOM re-query improvements may reduce detached-DOM failures, but do not treat them as a guarantee. The migration itself has no license or service price described in the cited release material; practical costs are engineering time to update assumptions and the compute time needed to run the suite.
Or skip the browser setup
If the task is to capture a page for a test artifact, bug report, or documentation image, ScreenshotNeo offers a screenshot API and MCP server. It is separate from Cypress and does not replace Cypress interaction or assertions. One GET request returns an image or PDF; this example requests a screenshot of the Cypress site. See the ScreenshotNeo API documentation for options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://www.cypress.io -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://www.cypress.io"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://www.cypress.io' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before the capture. Bot checks, blank pages, and failed loads are not billed; response headers report the page verdict and billing status. Its MCP server exposes screenshot and page-info tools for AI agents. The free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month.
FAQ
Did Cypress 12 make cross-origin testing possible for the first time?
The release made cy.origin() generally available. Use it for supported interactions with another origin, following Cypress’s migration guidance.
Does cy.session() preserve the page between tests?
It caches and restores cookies and web storage. It is a session-state mechanism; navigate to and assert the page each test needs.
Does Cypress 12 eliminate detached-DOM errors?
No. Cypress improved element re-querying to reduce their likelihood, but a changing page can still invalidate an element.
Which date should I use for the Cypress 12 release?
The dedicated release page says December 5, 2022; the changelog and announcement say December 6, 2022. Cite the specific official page when date precision matters.


