Обработка асинхронных операций

Асинхронность является одной из ключевых особенностей веб-приложений. Cypress построен на синхронной модели команд, но под капотом каждая операция может быть асинхронной. Понимание того, как Cypress обрабатывает асинхронные действия, позволяет создавать надёжные и стабильные тесты.


Цепочки команд и очередь выполнения

Cypress использует очередь команд (command queue). Каждая команда, например cy.get(), cy.click(), cy.request(), добавляется в очередь и выполняется последовательно, несмотря на асинхронность внутренних операций браузера.

Пример:

cy.get('button').click();
cy.get('input').type('Cypress');
cy.get('.result').should('contain', 'Success');

Даже если cy.get('button') требует времени для поиска элемента, следующая команда cy.get('input') не выполнится до завершения предыдущей.

Особенность: нельзя напрямую использовать промисы JavaScript без специальных подходов, так как Cypress ожидает работу через свою цепочку команд.


Использование then() для асинхронных данных

Метод then() позволяет получить результат асинхронной операции и использовать его в тесте:

cy.get('input').then(($input) => {
  const value = $input.val();
  expect(value).to.equal('default');
});
  • $input — это объект jQuery, возвращаемый Cypress.
  • Все проверки внутри then() выполняются после того, как команда cy.get() завершила поиск.

then() также может использоваться для возврата значений и передачи их в цепочку:

cy.get('input').invoke('val').then((text) => {
  cy.get('.output').should('contain', text);
});

cy.wrap() для интеграции с промисами

Когда нужно работать с обычными JavaScript промисами, используется cy.wrap():

const fetchData = () => Promise.resolve('data');

cy.wrap(fetchData()).then((result) => {
  expect(result).to.equal('data');
});

cy.wrap() преобразует промис в команду Cypress, добавляя его в очередь. Без wrap цепочка команд Cypress может не дождаться результата промиса.


Таймауты и ожидания

Cypress автоматически ожидает элементы и состояния с помощью встроенных таймаутов.

Пример:

cy.get('.loading', { timeout: 10000 }).should('not.exist');
  • Команда будет повторяться до 10 секунд или до исчезновения элемента .loading.
  • Это исключает необходимость использовать setTimeout или ручные задержки.

should() и and() выполняются многократно до того момента, пока условие не станет истинным или не закончится таймаут. Это делает тесты более устойчивыми к асинхронным изменениям на странице.


Асинхронные запросы к серверу

Cypress предоставляет cy.request() для работы с HTTP-запросами:

cy.request('/api/data')
  .then((response) => {
    expect(response.status).to.eq(200);
    expect(response.body).to.have.property('items');
  });
  • Запрос выполняется асинхронно, но Cypress автоматически добавляет его в очередь команд.
  • Можно использовать алиасы и cy.wait() для синхронизации с сетевыми запросами:
cy.intercept('/api/data').as('getData');
cy.visit('/dashboard');
cy.wait('@getData').its('response.statusCode').should('eq', 200);

Асинхронные циклы

Использование стандартного цикла forEach для асинхронных операций в Cypress может привести к неожиданным результатам. Правильный подход — использовать цепочку команд:

const items = ['A', 'B', 'C'];

items.forEach((item) => {
  cy.get('input').type(item).should('have.value', item);
});

Если нужно асинхронно обработать данные через промис, следует комбинировать с cy.wrap:

items.reduce((prev, item) => {
  return prev.then(() => cy.wrap(Promise.resolve(item)).then((val) => {
    cy.get('input').type(val).should('have.value', val);
  }));
}, Cypress.Promise.resolve());

Практические советы

  1. Не смешивать Cypress команды и промисы напрямую. Использовать cy.wrap или then.
  2. Избегать async/await внутри Cypress цепочек, так как это может нарушить последовательность выполнения команд.
  3. Использовать алиасы и cy.wait() для синхронизации с асинхронными событиями (AJAX, WebSocket, таймеры).
  4. Проверки через should предпочтительнее прямых проверок в then, так как should повторяется до успешного выполнения.

Асинхронная обработка в Cypress строится на принципах очереди команд, ожидания состояния элементов и интеграции промисов через wrap. Понимание этих механизмов позволяет создавать стабильные и предсказуемые тесты, которые не ломаются при изменениях скорости работы приложения или задержках сети.