QA Interview Hub · База знань · Тести
Cypress
Стан, сесії, ізоляція
логін, cookies, кілька доменів
Як Cypress ізолює тести один від одного?
З Cypress 12 за замовчуванням увімкнено testIsolation: перед кожним тестом сторінка очищається до about:blank, а cookies, localStorage і sessionStorage стираються для всіх доменів.
Наслідок: кожен тест має сам підготувати свій стан, і не можна покладатися на те, що попередній тест залишив користувача залогіненим чи сторінку відкритою.
Різниця з Playwright: там ізоляцію дає новий BrowserContext, а тут очищення стану в тому самому браузері.
На співбесіді: З дванадцятої версії Cypress перед кожним тестом очищає сторінку, cookies, localStorage і sessionStorage — це testIsolation. Тому тести незалежні, і кожен сам готує свій стан. На відміну від Playwright, де кожен тест отримує новий контекст, Cypress чистить стан у тому самому браузері.
А в Playwright:Ізоляція «з коробки» інша: кожен тест отримує новий BrowserContext — окремий профіль на кшталт інкогніто. Нічого не стирається, контекст просто створюється заново за мілісекунди.
Що таке cy.session і як не логінитись у кожному тесті?
cy.session(id, setup) виконує логін один раз, зберігає cookies, localStorage і sessionStorage, а в наступних тестах відновлює їх без повторного логіну. Опція validate перевіряє, що сесія ще дійсна, інакше логін виконується заново. cacheAcrossSpecs: true ділить сесію між спек-файлами.
Логін усередині setup краще робити через API — це швидше.
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');
});
На співбесіді: Для цього є cy.session: він один раз виконує логін, зберігає cookies і сховища, а в наступних тестах просто відновлює їх. Validate перевіряє, що сесія ще жива, і якщо ні — логінить заново. Сам логін усередині я роблю через API. Окремий id сесії на кожну роль дає змогу швидко перемикатися між користувачами.
А в Playwright:Аналог — storageState: setup-проєкт логіниться, зберігає cookies і localStorage у JSON, інші проєкти підключають файл через dependencies. Окремий файл на роль, а в одному тесті можна мати два контексти з різними ролями одночасно.
Як працювати з кількома доменами через cy.origin?
Тест прив'язаний до одного origin. Коли сценарій переходить на інший домен — наприклад, логін через зовнішній провайдер або платіжна сторінка — команди для нього пишуть у блоці cy.origin.
Блок виконується в контексті іншого домену, тому змінні ззовні туди не потрапляють: їх треба передати явно через args. Кастомні команди всередині теж недоступні без окремого підключення.
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');
На співбесіді: Для сценаріїв з іншим доменом, наприклад SSO-логіну, є cy.origin: команди для чужого домену пишуться всередині цього блоку. Важливий нюанс — змінні ззовні туди не потрапляють, тож я передаю їх явно через args. На практиці зовнішній логін я частіше обходжу через API, а cy.origin лишаю для одного тесту, що перевіряє сам потік.
А в Playwright:Обмеження немає: тест просто переходить на інший домен, і всі команди працюють як звичайно. Кілька вкладок і контекстів у межах тесту — теж норма.
Чому програмний логін кращий за логін через UI?
UI-логін у кожному тесті — це повільно, і якщо зламалася форма логіну, падають усі тести, хоча перевіряли зовсім інше. Програмний логін через API плюс cy.session швидший і стабільніший.
Сам UI-потік логіну перевіряє один окремий тест — на форму, валідацію й редирект.
На співбесіді: Якщо кожен тест логіниться через форму, це повільно, і поломка форми валить увесь набір, хоча тести перевіряють інше. Тому я логінюся через API і кешую сесію через cy.session, а саму форму логіну перевіряю одним окремим тестом.
А в Playwright:Підхід той самий: логін через API, результат зберігається в storageState, а UI-логін перевіряє один окремий тест.
Custom commands і cy.session
Custom commands додають через Cypress.Commands.add у файлі cypress/support/commands.js. Вони інкапсулюють повторювані дії: логін, створення даних через API, заповнення форм. У TypeScript для них оголошують типи в інтерфейсі Chainable.
Логін через UI в кожному тесті повільний, тому краще логінитися через API-запит cy.request. Ще краще — обгорнути це в cy.session, який кешує cookies, localStorage і sessionStorage між тестами. Сесію ідентифікує ключ, наприклад логін користувача, тож для кожної ролі буде свій кеш.
У cy.session є параметр validate — функція, яка перевіряє, що сесія ще дійсна; якщо ні, логін виконується знову. Опція cacheAcrossSpecs дозволяє перевикористати сесію між файлами. Це аналог storageState у Playwright.
Вкладки, iframe, cross-origin
Cypress працює в одній вкладці, тому нові вкладки не відкриваються. Типове рішення — прибрати атрибут target="_blank" через invoke('removeAttr', 'target') і відкрити посилання в тій самій вкладці. Або просто перевірити атрибут href і перейти за ним через cy.visit.
Для iframe вбудованої команди немає: отримують contentDocument.body через .its() і обгортають у cy.wrap. Часто це виносять у custom command або використовують плагін cypress-iframe. Cross-origin iframe працювати так не буде без вимкнення безпеки браузера.
Для переходу на інший домен у межах тесту з версії 12 є cy.origin, всередині якого виконуються команди для іншого домену. Типовий кейс — логін через сторонній SSO-провайдер. Змінні передаються в cy.origin явно через args, бо колбек виконується в іншому контексті.