Вопрос или проблема
У меня есть тест 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
) завершаются до выполнения утверждений.
Вот как можно улучшить ваш код:
- Убедитесь, что все
Promise
правильно обернуты вasync/await
, чтобы Cypress мог их корректно ожидать. - Расширьте таймаут для команды
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 не может правильно отслеживать состояние приложения во время асинхронных операций.