- Переменные окружения
- Изменение значений 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
***