Как заставить Cypress ждать тайм-аутов внутри разрешенного промиса задачи?

Вопрос или проблема

У меня есть тест Cypress, примерно такой:

it('перенаправляет с ID на slug', () => {
  cy.setupEnvironment(params).then((environment) => {
    cy.visit(`/?id=${environment.id}`);

    cy.location("pathname", { timeout: 10_000 }).should(
      "eq",
      `/${environment.slug}`
    );
  });
});

setupEnvironment — это пользовательская команда, которая оборачивает вызов cy.task, настраивающий базу данных и т.д.

// задача
async function setupEnvironmentTask(params) {
  await setUpDb();
  const { id, slug } = await setUpEntity(params);
  return { id, slug };
}

// это затем завернуто в команду
function setupEnvironment(params) {
  return cy.task("setupEnvironmentTask", params);
}

Cypress.Commands.add("setupEnvironment", setupEnvironment);

Проблема в том, что проверка на cy.location выполняется и сразу же завершается с ошибкой, не соблюдая время ожидания.

Я вижу, что тестовые данные создаются, начальный вызов visit начинает загружать страницу, но тест затем немедленно завершается с ошибкой.

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

Есть ли способ заставить Cypress завершить задачу и вернуть данные, которые можно использовать для проверок? Возможно, алиасировать их в переменную?

Вот простая репродукция, которая может помочь вам с отладкой.

Я настроил простой сервер для выполнения перенаправления при получении определённого id:

server.js

const express = require('express');
const app = express();
const PORT = 7000;

app.get("https://stackoverflow.com/", function (req, res) {
  if (req.query.id === '123') {
    setTimeout(() => {
      res.redirect('/user')
    }, 1000)                     // задержка здесь для составления страницы и т.д.
  } 
});

app.get('/user', function (req, res) {
  res.send("Перенаправлено на страницу пользователя");
});

app.listen(PORT, function (err) {
  if (err) console.log(err);
  console.log("Сервер слушает на PORT", PORT);
});

Я заполнил пробелы в вашем тестовом примере и запустил его

тест

function setupDb() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve()
    }, 1000)           // задержка здесь для моделирования происходящего

  })
}
function setUpEntity(params) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({id: '123', slug: 'user'})
    }, 1000)                            // снова задержка - всего 2 секунды настройки

  })
}
async function setupEnvironment(params) {
  await setupDb();
  const { id, slug } = await setUpEntity(params);
  return { id, slug };
}
Cypress.Commands.add("setupEnvironment", setupEnvironment);

it('перенаправляет с ID на slug', {baseUrl: 'http://localhost:7000'}, () => {
  const params = []
  cy.setupEnvironment(params).then((environment) => {
    cy.visit(`/?id=${environment.id}`)

    cy.location("pathname", { timeout: 3_000 })  // > время ответа сервера
      .should('eq', `/${environment.slug}`)
  })
})

Тест Cypress прошел

введите описание изображения здесь

Ответ или решение

Для того чтобы Cypress корректно ожидал тайм-ауты внутри возвращаемого решения промиса, можно воспользоваться встроенными механизмами Cypress для управления асинхронным поведением. Основная проблема, с которой вы столкнулись, связана с тем, что Cypress не ожидает окончания выполнения асинхронного кода, если это выполнение происходит за промежутками времени (например, использование setTimeout).

Во-первых, необходимо убедиться, что ваша функция setupEnvironment правильно определяет асинхронное поведение. Также необходимо убедиться, что все ваши асинхронные операции (например, те, что находятся в setupDb и setUpEntity) завершаются до выполнения утверждений.

Вот как можно улучшить ваш код:

  1. Убедитесь, что все Promise правильно обернуты в async/await, чтобы Cypress мог их корректно ожидать.
  2. Расширьте таймаут для команды cy.location, если ожидаете, что редирект может занять больше времени.

Вот обновленный пример кода:

// обновленный тест
it('redirects from ID to slug', {baseUrl: 'http://localhost:7000'}, async () => {
  const params = [];

  // использование await для ожидания результата
  const environment = await cy.setupEnvironment(params);

  // использование wait для того, чтобы гарантировать, что клик или переход завершен
  await cy.visit(`/?id=${environment.id}`);

  // ожидание изменения пути
  cy.location("pathname", { timeout: 5000 })  // увеличенный тайм-аут
    .should('eq', `/${environment.slug}`);
});

// определение функции с использованием async/await
async function setupDb() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, 1000);  // задержка для имитации работы
  });
}

async function setUpEntity(params) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ id: '123', slug: 'user' });
    }, 1000);  // задержка
  });
}

async function setupEnvironment(params) {
  await setupDb();
  const entity = await setUpEntity(params);
  return entity;  // возврат объекта с данными
}

Cypress.Commands.add("setupEnvironment", setupEnvironment);

Обратите внимание на следующие моменты:

  • Использование async/await в вашем тесте и в функциях setupDb, setUpEntity и setupEnvironment позволяет Cypress корректно ожидать завершения всех асинхронных действий.
  • Убедитесь, что cy.visit() завершает свой процесс. Если ваш сервер обрабатывает запросы с задержкой, это должно помочь.
  • Увеличьте значение тайм-аута, если ожидаете, что ответа будет больше времени для получения.

Ваша цель — убедиться, что Cypress понимает асинхронную природу операций, которые выполняются, поэтому использование async/await является важной частью этого процесса. Этот подход позволит вам избежать ошибок, связанных с тем, что Cypress не может правильно отслеживать состояние приложения во время асинхронных операций.

Оцените материал
Добавить комментарий

Капча загружается...