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

Cypress

Мережа: cy.intercept

перехоплення й моки

Що таке cy.intercept і де саме відбувається перехоплення?

cy.intercept перехоплює HTTP-запити, які робить сторінка. Перехоплення відбувається не в браузері, а в Node-процесі Cypress, через який проходить увесь трафік як через проксі. Тому він бачить і запити від сторонніх скриптів.

Два режими: spy — лише спостерігати й перевіряти запит і відповідь; stub — підмінити відповідь. WebSocket-повідомлення cy.intercept не перехоплює, лише HTTP.

cy.intercept('GET', '/api/products*').as('products');   // spy
cy.intercept('GET', '/api/products*', { fixture: 'products.json' }); // stub
На співбесіді: Intercept перехоплює HTTP-запити сторінки, і робить це в Node-процесі Cypress, бо весь трафік іде через його проксі. Можна просто спостерігати за запитом і перевіряти його, а можна підмінити відповідь фікстурою чи будь-якими даними. WebSocket він не перехоплює, тільки HTTP.
А в Playwright:

page.route() перехоплює запити на рівні протоколу браузера, а не через проксі. Маршрут прив'язаний до сторінки або контексту. На відміну від Cypress, WebSocket теж можна перехопити й замокати через page.routeWebSocket().

Як дочекатися запиту і перевірити його через alias?

Реєструєш intercept, даєш йому alias через .as(), виконуєш дію, а потім cy.wait('@alias'). Він віддає об'єкт interception з request і response, тож можна перевірити, що саме фронтенд відправив і що отримав.

cy.intercept('POST', '/api/orders').as('createOrder');
cy.get('[data-cy=checkout]').click();

cy.wait('@createOrder').then(({ request, response }) => {
  expect(request.body.items).to.have.length(2);
  expect(response.statusCode).to.eq(201);
});
На співбесіді: Я реєструю intercept з alias, виконую дію, яка викликає запит, і роблю cy.wait на цей alias. Він повертає і запит, і відповідь, тож я перевіряю, що фронт відправив правильне тіло і що бекенд відповів очікуваним статусом.
А в Playwright:

Aliases немає: очікування запиту створюють до дії і чекають після неї.

const orderResp = page.waitForResponse('**/api/orders');
await page.getByTestId('checkout').click();
const resp = await orderResp;
expect(resp.status()).toBe(201);
expect(resp.request().postDataJSON().items).toHaveLength(2);

Як підмінити або змінити відповідь сервера?

Статичний stub — третім аргументом: фікстура, тіло, статус, затримка. Динамічний — через обробник req: можна змінити запит перед відправкою, повністю відповісти через req.reply або пропустити запит на сервер і змінити справжню відповідь у req.continue.

// помилка сервера
cy.intercept('GET', '/api/stats', { statusCode: 500, body: { error: 'boom' } });

// повільна мережа
cy.intercept('GET', '/api/stats', { fixture: 'stats.json', delay: 2000 });

// справжня відповідь, але з підміною одного поля
cy.intercept('GET', '/api/stats', (req) => {
  req.continue((res) => {
    res.body.total = 0;
  });
});
На співбесіді: Для простих випадків передаю відповідь третім аргументом: фікстуру, статус-код, затримку. Так я перевіряю, як UI поводиться при помилці п'ятсот чи при повільній мережі. Якщо треба точково змінити справжню відповідь, пишу обробник: пропускаю запит на сервер через req.continue і міняю одне поле у відповіді.
А в Playwright:

Підміна — route.fulfill(), пропуск — route.continue(), обрив — route.abort(). Модифікація справжньої відповіді — route.fetch() і fulfill з новим тілом.

await page.route('**/api/stats', async (route) => {
  const response = await route.fetch();
  const json = await response.json();
  json.total = 0;
  await route.fulfill({ response, json });
});

Чому intercept треба ставити до дії і що буде, якщо їх кілька на один URL?

Intercept спрацьовує лише на запити, які відбулись після його реєстрації. Якщо поставити його після cy.visit чи кліку, запит може проскочити — і cy.wait впаде за таймаутом. Класична причина флакі-тестів.

Якщо на один запит підходять кілька intercept, першим спрацьовує той, що визначений пізніше. Обробники можуть передавати запит далі, а опція times: 1 обмежує intercept однією відповіддю — зручно для сценарію «спершу помилка, потім успіх». Усі intercept скидаються перед кожним тестом.

cy.intercept('GET', '/api/user', { statusCode: 200, fixture: 'user.json' });
cy.intercept('GET', '/api/user', { times: 1 }, { statusCode: 503 });
// перший запит → 503 (визначений пізніше, спрацьовує першим)
// наступні запити → 200
На співбесіді: Intercept бачить тільки ті запити, що відбулися після його реєстрації, тому я завжди ставлю його до visit або кліку. Інакше запит проскочить і wait впаде — це часта причина флакі. Якщо на один URL кілька intercept, першим спрацьовує останній визначений, а через times один можна зробити сценарій, де спершу помилка, а потім успіх.
А в Playwright:

Правила схожі: маршрут має бути зареєстрований до дії, останній зареєстрований обробник спрацьовує першим, передати запит далі можна через route.fallback(), а опція { times: 1 } теж є.

cy.intercept

cy.intercept перехоплює HTTP-запити застосунку за методом і URL-патерном. Його використовують для очікування запитів, стабінгу відповідей і перевірки того, що саме застосунок відправив. Перехоплення треба оголосити до дії, яка ініціює запит.

Для очікування: cy.intercept('GET', '/api/users').as('getUsers'), потім дія і cy.wait('@getUsers'). Після цього можна перевірити тіло запиту чи відповіді й статус-код. Для стабу передають статичну відповідь або фікстуру, наприклад cy.intercept('GET', '/api/users', { fixture: 'users.json' }).

Можна імітувати помилки й повільну мережу: statusCode: 500, forceNetworkError: true, delay. Також можна змінювати запит або відповідь на льоту через колбек. Це дає змогу тестувати UI ізольовано від нестабільного бекенду.