- Переменные окружения
- Изменение значений fixtures “на лету”
- Глобально имитируем API-ответы
- Кастомные команды
- Ожидания
Переменные окружения
Разрабатывая веб-приложения, обычно используют как минимум два окружения. Это нужно для того, чтобы проверить работоспособность приложения в разных условиях, до того как оно попадет в продакшен. Скорее всего, эти окружения будут иметь разные базы данных, возможно там будут отличаться API-эндпоинты и переменные. Поэтому если работаешь в Cypress, очень желательно иметь отдельный конфиг-файл для каждого окружения.
Структура файлов и имена файла:
/src/cypress/config
/test.json
/staging.json
/production.json
Посмотрим на staging.json:
{
"baseUrl": "http://localhost:3000",
"env": {
"env": "staging",
"apiUrl": "https://staging-api.com/api/v1",
}
}
И production.json:
{
"baseUrl": "http://localhost:3000",
"env": {
"env": "production",
"apiUrl": "https://api.com/api/v1",
}
}
(При этом не забудь сохранить свои env-переменные внутри объекта env)
Затем обнови Cypress-скрипты в package.json, чтобы убедиться, что Cypress будет запускаться с правильным конфигом для каждого окружения:
"scripts": {
"cypress:run:staging": "cypress run --env configFile=staging",
"test:e2e:staging:run": "start-server-and-test 'npm run start' http://localhost:3000 'npm run cypress:run:staging'",
"cypress:run:production": "cypress run --env configFile=production",
"test:e2e:production:run": "start-server-and-test 'npm run start' http://localhost:3000 'npm run cypress:run:production'",
}
// same approach could be used for "cypress open" command.
Модуль «start-server-and-test» запускает приложение, и сразу запускает cypress-тесты.
Дальше вставь в src/cypress/plugins/index.js такой кусок:
const fs = require('fs')
const path = require('path')
function getConfigurationByFile(fileName) {
const pathToConfigFile = path.resolve(__dirname, `../config/${fileName}.json`);
let rawData;
try {
rawData = fs.readFileSync(pathToConfigFile);
} catch (e) {
console.error(e);
}
return JSON.parse(rawData);
}
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
// this value comes from *cypress run --env configFile=staging*
const configFile = getConfigurationByFile(config.env.configFile || 'test');
return { ...config, ...configFile };
};
Теперь Cypress будет запускать тесты с правильным конфигом.
Если все сделано правильно, можно экстрактировать переменные, вот так:
const { apiUrl, env } = Cypress.env();
// to extract baseUrl variable you should use Cypress.config()
// const { baseUrl } = Cypress.config();
Изменение значений fixtures “на лету”
В основном fixtures (фиксированные наборы данных) применяются, когда нужно имитировать API-ответ при тестировании. Вообще, так делать не очень рекомендуется. Но если есть несколько окружений, рано или поздно возникнет проблема: одинаковые запросы возвращают одни и те же данные из каждого окружения (кроме id). И может быть неудобно дублировать fixture.
Тогда можно записать fixture и env-переменную окружения; затем обновить значение “на лету” в тест-кейсе:
describe('it should test smth', function() {
beforeEach(() => {
// user is a env variable
const { user: userEnv } = Cypress.env();
cy.fixture('user.json').then(user => {
user.id = userEnv.id; // updating user id
// use updated fixture here (e.g. `cy.intercept`)
});
});
});
Внимание: если применяется beforeEach, надо обязательно “обернуть” его в describe, чтобы не влияло на остальные тесты. Подробнее в документации по Fixture.
Глобально имитируем API-ответы
Чтобы имитировать сетевой запрос “глобально”, открываем src/cypress/support/index.js, и вставляем туда кусок:
beforeEach(() => {
cy.intercept({ url: `${apiUrl}/profile*`, middleware: true }, req => {
req.reply({
fixture: 'getProfile.json',
});
});
Подробнее — в документации по intercept.
Кастомные команды
В Cypress возможны кастомные команды. Они позволяют избежать применения шаблонного boilerplate-кода.
Смотрим:
// you can turn this piece of code
it('should fill in discount form', function() {
cy.get('input[name="email"]').type('test@test.com');
cy.get('input[name="phone"]').type('1231231212');
cy.get('div[role="checkbox"]').click({ force: true });
cy.findByText(/Save/i).click({ force: true });
// ...rest of the test
});
// into a single line
it('should fill in discount form', function() {
cy.fillDiscountForm();
// ...rest of the test
});
Чтобы создать например команду заполнения форм cy.fillDiscountForm(), надо перейти к файлу src/cypress/support/commands.js и создать там кастомную команду:
Cypress.Commands.add('fillDiscountForm', function() {
cy.get('input[name="email"]').type('test@test.com');
cy.get('input[name="phone"]').type('1231231212');
cy.get('div[role="checkbox"]').click({ force: true });
cy.findByText(/Save/i).click({ force: true });
});
Теперь можно применять команду cy.fillDiscountForm() в любом тесте.
Документация тут.
Ожидания
До того как приложение отобразит данные, оно вероятно получит их с сервера. А если есть проблемы с интернетом, и тесты показали “fail” из-за невыполненных API-запросов? Тогда при каждом API-запросе надо добавить ожидание окончания (cy.wait).
it('should fill in discount form', function() {
cy.intercept(`${apiUrl}/settings`).as('getSettings');
cy.visit(`/settings-page`);
// once a request to get settings responds, 'cy.wait' will resolve
cy.wait('@getSettings');
// rest of the test
// cy.contains(/edit settings/i).click({ force: true });
});
Надо зарегистрировать “перехват” до захОда на страницу /settings-page. Как только заход на страницу настроек состоялся, это триггерит запрос GET ${apiUrl}/settings, и Cypress будет ожидать его завершения, и после этого продолжит выполнение.
Теперь, если API-вызов не прошел по какой-то причине, Cypress выдаст баг, и его намного проще устранить.
Документация по wait
***