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, бо колбек виконується в іншому контексті.