QA Interview Hub · База знань · Тести

Cypress

Архітектура фреймворку

POM, команди, конфіг, cy.task

Як створити кастомну команду і типізувати її в TypeScript?

Команди реєструють у cypress/support/commands.ts через Cypress.Commands.add. Щоб TypeScript знав про нову команду, розширюють інтерфейс Chainable.

// cypress/support/commands.ts
Cypress.Commands.add('getByCy', (id: string) => {
  return cy.get('[data-cy=' + id + ']');
});

declare global {
  namespace Cypress {
    interface Chainable {
      getByCy(id: string): Chainable<JQuery<HTMLElement>>;
      login(role?: string): Chainable<void>;
    }
  }
}

// тест
cy.getByCy('submit').click();
На співбесіді: Кастомні команди я реєструю у support через Cypress.Commands.add. Щоб TypeScript підказував і перевіряв їх, розширюю глобальний інтерфейс Chainable у просторі імен Cypress. Типові приклади — getByCy для data-атрибутів і login.
А в Playwright:

Кастомних команд немає. Їхню роль виконують фікстури через test.extend<MyFixtures>() (типізація через generic), хелпер-функції і методи Page Object.

Page Object чи App Actions — що обрати?

Page Object — класи з методами, які обгортають cy-команди для конкретної сторінки. Тест читається як сценарій, локатори в одному місці.

App Actions — підхід, який просуває команда Cypress: замість кліків через UI керувати станом застосунку напряму, через API або функції, відкриті у window. Це швидше й стабільніше для підготовки стану.

На практиці їх поєднують: Page Object для дій, які тест перевіряє, API — для підготовки.

export class StatisticsPage {
  private chart = '[data-cy=stats-chart]';
  private period = '[data-cy=period-select]';

  visit() {
    cy.visit('/statistics');
    return this;
  }

  selectPeriod(value: string) {
    cy.get(this.period).select(value);
    return this;
  }

  chartShould(...args: Parameters<Cypress.Chainable['should']>) {
    return cy.get(this.chart).should(...args);
  }
}

// тест
new StatisticsPage().visit().selectPeriod('30d').chartShould('be.visible');
На співбесіді: Я використовую Page Object: класи з методами для кожної сторінки, тож локатори зібрані в одному місці, а тест читається як сценарій. Команда Cypress пропонує ще App Actions — керувати станом застосунку напряму, без UI. Я поєдную обидва: стан готую через API, а Page Object використовую для дій, які тест справді перевіряє. Така комбінація — типовий підхід у сучасних фреймворках.
А в Playwright:

Page Object з async-методами, а сторінки часто передаються в тест через фікстури — без new у кожному тесті. App Actions можна робити через page.evaluate() або API.

Що таке фікстури в Cypress? Це те саме, що в Playwright?

Ні, і на цьому легко заплутатися. У Cypress фікстура — це просто файл з тестовими даними в папці cypress/fixtures, який завантажують через cy.fixture() або використовують як відповідь у cy.intercept.

У Playwright фікстура — механізм dependency injection, який готує ресурси (сторінку, контекст, залогіненого користувача) і прибирає їх після тесту. Ближчий аналог у Cypress — хуки плюс кастомні команди.

cy.fixture('users/admin.json').then((admin) => {
  cy.get('[data-cy=email]').type(admin.email);
});

cy.intercept('GET', '/api/stats', { fixture: 'stats-empty.json' });
На співбесіді: У Cypress фікстура — це просто файл з тестовими даними, який я завантажую через cy.fixture або підставляю як відповідь в intercept. У Playwright це зовсім інше — механізм dependency injection, що готує і прибирає ресурси. Тому якщо питають про фікстури, я уточнюю, про який інструмент мова.
А в Playwright:

Тут фікстура — механізм dependency injection з setup і teardown. Тестові дані з JSON просто імпортують або читають через fs.

Як налаштувати різні середовища і змінні оточення?

Змінні доступні через Cypress.env('NAME'). Джерела: файл cypress.env.json (не комітиться), змінні оточення ОС з префіксом CYPRESS_, прапорець --env, блок env у конфігу.

Для кількох середовищ у setupNodeEvents читають назву середовища і підставляють відповідний baseUrl. У CI секрети передають через secrets пайплайна.

// CI: CYPRESS_USER_PASSWORD=*** npx cypress run --env envName=staging

// cypress.config.ts
setupNodeEvents(on, config) {
  const urls = {
    dev: 'https://dev.example.com',
    staging: 'https://staging.example.com',
  };
  config.baseUrl = urls[config.env.envName || 'dev'];
  return config;
}
На співбесіді: Змінні я читаю через Cypress.env. Локально вони лежать у cypress.env.json, який не комітиться, а в CI передаються як змінні оточення з префіксом CYPRESS_ із секретів пайплайна. Середовище обираю прапорцем env, а в setupNodeEvents підставляю потрібний baseUrl.
А в Playwright:

Змінні — звичайний process.env, часто разом із dotenv. Середовища задають через use.baseURL або окремі projects.

Що таке cy.task і setupNodeEvents?

Код тесту виконується в браузері, тому не може звернутися до бази даних, файлової системи чи бібліотек Node. cy.task передає виклик у Node-процес, де задачі зареєстровані в setupNodeEvents.

Задача повинна повернути значення або Promise. Якщо вона нічого не повертає, треба повернути null, інакше Cypress видасть помилку.

// cypress.config.ts
import { Client } from 'pg';

setupNodeEvents(on) {
  on('task', {
    async 'db:query'(sql) {
      const client = new Client({ connectionString: process.env.DB_URL });
      await client.connect();
      const res = await client.query(sql);
      await client.end();
      return res.rows;
    },
    log(message) {
      console.log(message);
      return null;
    },
  });
}

// тест
cy.task('db:query', "SELECT status FROM orders WHERE id = 1042")
  .its('0.status').should('eq', 'paid');
На співбесіді: Оскільки тест виконується в браузері, він не може напряму звернутися до бази чи файлів. Для цього є cy.task: я реєструю задачу в setupNodeEvents, вона виконується в Node і повертає результат у тест. Наприклад, перевіряю в PostgreSQL, що замовлення отримало правильний статус. Задача обов'язково має щось повернути, хоча б null.
А в Playwright:

Аналог не потрібен: тест і так виконується в Node, тож клієнт PostgreSQL імпортуєш прямо в тест або у фікстуру.

Fixtures у Cypress vs Playwright

У Cypress fixtures — це статичні файли з тестовими даними, зазвичай JSON, у папці cypress/fixtures. Їх завантажують через cy.fixture('user.json') або передають у cy.intercept як тіло відповіді. По суті це просто сховище даних.

У Playwright фікстури — механізм dependency injection: функції, які готують середовище перед тестом і прибирають після нього. Вони мають скоупи test і worker, можуть залежати одна від одної й передаються в тест як параметри. Вбудовані фікстури — page, context, browser, request.

Аналогом фікстур Playwright у Cypress є хуки beforeEach і before, custom commands та cy.session. Це частий момент плутанини, тому варто одразу уточнювати, в якому сенсі вживається термін.

Page Object vs App Actions

Page Object — клас, який інкапсулює селектори й дії сторінки, тож тест працює з методами на кшталт loginPage.login(user). Він дає перевикористання й одне місце для зміни селекторів. Мінус у Cypress — зайвий шар абстракції над chainable-командами і спокуса робити все через UI.

App Actions — підхід, рекомендований командою Cypress: стан застосунку готується напряму через його внутрішні функції, доступні через window, або через API. Наприклад, замість кліків по формі створення задач тест викликає функцію застосунку й одразу отримує потрібний стан. Це швидше і стабільніше, але вимагає доступу до коду застосунку.

На практиці я б поєднував: Page Object або модулі з функціями для UI-взаємодій, а підготовку даних — через API і custom commands. Через UI проходимо лише те, що саме тестується.