QA Interview Hub · Knowledge base · Quizzes
Cypress
State, sessions, isolation
login, cookies, several domains
How does Cypress isolate tests from each other?
Since Cypress 12 testIsolation is on by default: before every test the page is cleared to about:blank, and cookies, localStorage and sessionStorage are erased for all domains.
The consequence: each test must prepare its own state itself, and you can't rely on the previous test having left the user logged in or a page open.
The difference from Playwright: there isolation is given by a new BrowserContext, while here the state is cleared in the same browser.
In an interview: Since version twelve Cypress clears the page, cookies, localStorage and sessionStorage before every test: this is testIsolation. So tests are independent, and each prepares its own state. Unlike Playwright, where each test gets a new context, Cypress clears the state in the same browser.
And in Playwright:Isolation "out of the box" is different: each test gets a new BrowserContext, a separate profile like incognito. Nothing is erased, the context is simply created anew in milliseconds.
What is cy.session and how do you avoid logging in in every test?
cy.session(id, setup) performs the login once, saves cookies, localStorage and sessionStorage, and in later tests restores them without logging in again. The option validate checks that the session is still valid, otherwise the login runs again. cacheAcrossSpecs: true shares the session between spec files.
It is better to do the login inside setup through the API, as it is faster.
Cypress.Commands.add('login', (role = 'user') => {
cy.session(role, () => {
cy.request('POST', '/api/auth/login', {
email: Cypress.env(role + '_email'),
password: Cypress.env(role + '_password'),
}).its('body.token').then((token) => {
window.localStorage.setItem('token', token);
});
}, {
validate() {
cy.request('/api/me').its('status').should('eq', 200);
},
cacheAcrossSpecs: true,
});
});
beforeEach(() => {
cy.login('admin');
cy.visit('/dashboard');
});
In an interview: For this there is cy.session: it performs the login once, saves the cookies and storages, and in later tests simply restores them. Validate checks that the session is still alive, and if not, logs in again. I do the login itself inside through the API. A separate session id per role lets you switch between users quickly.
And in Playwright:The equivalent is storageState: a setup project logs in, saves cookies and localStorage to JSON, and other projects pick up the file through dependencies. A separate file per role, and in one test you can have two contexts with different roles at once.
How do you work with several domains through cy.origin?
A test is bound to one origin. When a scenario moves to another domain, for example login through an external provider or a payment page, the commands for it are written in a cy.origin block.
The block runs in the context of the other domain, so variables from outside don't get in there: they must be passed explicitly through args. Custom commands inside are also unavailable without separate registration.
const user = { email: 'qa@test.com', password: 'secret' };
cy.visit('/');
cy.get('[data-cy=sso-login]').click();
cy.origin('https://auth.provider.com', { args: user }, ({ email, password }) => {
cy.get('#email').type(email);
cy.get('#password').type(password, { log: false });
cy.get('button[type=submit]').click();
});
cy.url().should('include', '/dashboard');
In an interview: For scenarios with another domain, such as an SSO login, there is cy.origin: the commands for the other domain are written inside that block. An important nuance is that variables from outside don't get in there, so I pass them explicitly through args. In practice I more often bypass the external login through the API and keep cy.origin for the single test that checks the flow itself.
And in Playwright:There is no restriction: the test simply goes to another domain, and all commands work as usual. Several tabs and contexts within a test are also normal.
Why is programmatic login better than login through the UI?
A UI login in every test is slow, and if the login form breaks, all tests fail even though they were checking something quite different. A programmatic login through the API plus cy.session is faster and more stable.
The UI login flow itself is checked by one separate test: for the form, validation and the redirect.
In an interview: If every test logs in through the form, it is slow, and a broken form fails the whole suite even though the tests check something else. So I log in through the API and cache the session with cy.session, and I verify the login form itself with one separate test.
And in Playwright:The approach is the same: login through the API, the result is saved in storageState, and the UI login is checked by one separate test.
Custom commands and cy.session
Custom commands are added with Cypress.Commands.add in the file cypress/support/commands.js. They encapsulate repeated actions: login, creating data through the API, filling in forms. In TypeScript their types are declared in the Chainable interface.
A UI login in every test is slow, so it is better to log in with an API request cy.request. Even better is to wrap this in cy.session, which caches cookies, localStorage and sessionStorage between tests. The session is identified by a key, for example the user's login, so each role has its own cache.
cy.session has a validate parameter, a function that checks the session is still valid; if not, the login runs again. The option cacheAcrossSpecs lets you reuse the session between files. This is the equivalent of storageState in Playwright.
Tabs, iframes, cross-origin
Cypress works in one tab, so new tabs don't open. A typical solution is to remove the target="_blank" attribute with invoke('removeAttr', 'target') and open the link in the same tab. Or simply check the href attribute and go to it with cy.visit.
For an iframe there is no built-in command: you get contentDocument.body through .its() and wrap it in cy.wrap. This is often moved into a custom command, or the cypress-iframe plugin is used. A cross-origin iframe won't work like this without turning off browser security.
To move to another domain within a test, since version 12 there is cy.origin, inside which commands for the other domain run. A typical case is login through a third-party SSO provider. Variables are passed into cy.origin explicitly through args, because the callback runs in a different context.