Cypress и Docker
Рассуждения
Cypress можно разворачивать сразу в ранере Гитлаба, но тут могут возникнуть проблемы, связанные с отрисовкой, в первую очередь, шрифтов.
Не будем наступать на эти грабли и сразу сделаем все нормально!
Теория
Итак, нам надо запустить Cypress в контейнере. Желательно было бы сделать так, чтобы мы могли запускать одни и те же тесты через десктопное приложение Cypress и контейнер не меняя никаких конфигураций. Отлично было бы сделать так, чтобы запуск тестов осуществлялся простой командой — и нам меньше в итоге печатать, и разработчик сможет легко запустить тест, лишний раз не отвлекаясь.
Простейшие тесты
Для начала нам нужны простейшие тесты, которые не зависят от работы приложения. Например, будем проверять равна ли 1, собственно 1 для заведомо успешного теста. Также напишем какой-нибудь подобный заведомо провальный тест. Это пригодится нам на этапе настройки конфигурации.
Синхронное завершение Cypress и Docker
Очень важно чтоб при падении тестов контейнер падал с такой же ошибкой. Иначе при запуске на ранере у нас развернется контейнер, тесты упадут, но ранер скажет что все нормально, программа ведь запустилась, работает, значить все нормально.
Директивный базовый URL при запуске контейнера
При работе на локальной машине и внутри контейнера URL'ы приложения будут различны. Нам нужно сделать так, чтобы не приходилось настраивать их перед каждым запуском. Для этого мы просто директивно переопределим соответствующую переменную при запуске контейнера.
Сохранение результатов
Результаты теста не должны погибнуть вместе с контейнером. Нам нужно правильно связать томами директории локальной машины и контейнера.
Защита от медленного приложения
Несмотря на то что довольно легко настроить очередность запуска контейнеров, сама программа Cypress может запуститься раньше. То есть контейнеры запускаются поочередно, но программы, разворачивающиеся внутри контейнеров, запускаются с разной скоростью. Если приложение тупанет/тормознет, тесты упадут. Нужен способ защититься от этого.
Защита имени хоста в хэдере
Существует неочевидная проблема. Если vue-приложение разворачивается на каком-либо хосте, оно проверяет какие хосты указаны в хэдерах идущих к нему запросов. Во внутриконтейнерной сети адрес и этот заголовок хэдера будут немного иными, из-за чего нам выдаст ошибку. Нужно устранить эту проблему. Подробнее в статье Отключение защиты хоста vue-приложений.
Практика
Рассмотрим настройку на примере ХХХ, проекта на Vue
Для начала создадим файл с тестом Cypress, например test.spec.js. Если ты не любишь добавлять .spec в название файла, то спешу огорчить, когда Cypress разворачивается командой, он автоматически запускает файлы, содержащие это расширение. Так что это очень важная часть.
Файл будет содержать следующее:
describe('Простая проверка', () => {
it('Равна ли единица единице', () => {
expect(1).to.be.equal(1)
})
})
Простейшая заведомо успешная проверка.
Структура проекта будет примерно следующая:
<Папки приложения, которые нас не интересуют>
tests
<не интересующие нас папки>
cypress
<Папки Cypress, которые в данный момент не рассматриваем>
integration
test.spec.js
Dockerfile
cypress.json
wait-for
<Файлы, которые нас не интересуют в данный момент>
Dockerfile
docker-compose.cypress.yml
package.json
vue.config.js
Где:
docker-compose.cypress.yml — файл с настройками сборщика контейнеров приложения и докера
Dockerfile в корневой папке — файл образа для запуска приложения, им занимаются разработчики, не трогаем.
Dockerfile в папке Cypress — файл образа для запуска контейнера Cypress
package.json — сюда мы впишем свою команду, которая легко запускает тесты в контейнерах
vue.config.js — здесь будет проведена важная настройка, на тему которой будет отдельная!!! ЭТА НЕБОЛЬШАЯ СТАТЬЯ !!!
wait-for — файл с shell-скриптом, официальный незаменимый костыль докера для ожидания ответа сервера приложения
Для начала создадим файл образа Cypress:
FROM cypress/included:6.8.0
WORKDIR /e2e
RUN npm init -y
RUN npm install --save-dev cypress-image-snapshot
COPY. .
RUN chmod +x ./wait-for
ENTRYPOINT []
Разберем каждую строку:
FROM cypress/included:6.8.0 — мы воспользуемся этим образом. Это неплохая сборка контейнера для Cypress, уже содержащая браузер.
WORKDIR /e2e — внутри пустого контейнера мы создаем директорию e2e и переходим в нее. Теперь это основная директория, команды будут выполняться относительно нее
RUN npm init -y — данная команда во время билда контейнера инициализирует создание зависимостей и настроек. Важно для следующей команды
RUN npm install --save-dev cypress-image-snapshot — устанавливаем плагин для скриншот-тестов и все зависимости, нужные для него
COPY. . — копируем все-все-все из корневой папки, в которой находится Dockerfile (то есть из cypress) в рабочую папку в контейнере (то есть в e2e)
RUN chmod +x ./wait-for — даем разрешение на то, чтобы файл wait-for был исполняемым
ENTRYPOINT [] — очищаем entrypoint, который по умолчанию был предустановлен в образе (который мы использовали в команде FROM
Создадим файл docker-compose.cypress.yml:
version: '3.7'
services:
frontend:
build:
context: .
dockerfile: Dockerfile
environment:
— VUE_APP_IS_CYPRESS_ENV=true
ports:
— '5000:5000'
command: [«команда для запуска приложения»]
cypress:
build:
dockerfile: Dockerfile
context: ./tests
depends_on:
— frontend
environment:
— CYPRESS_baseUrl=http://frontend:5000
command: ["./wait-for", "--timeout=1200", «frontend:5000», "--", «cypress», «run»]
volumes:
— ./tests/artifacts:/e2e/cypress/videos
— ./tests/artifacts:/e2e/cypress/screenshots
— ./tests/cypress/ScreenRefs:/e2e/cypress/ScreenRefs
— ./tests/artifacts/:/e2e/Failure_Screenshot_Matching
Разберем подробнее ключевые элементы:
environment:
— VUE_APP_IS_CYPRESS_ENV=true — очень важная часть, которая выключает проверку хэдера хоста. Подробнее в!!! ЭТОЙ НЕБОЛЬШОЙ СТАТЬЕ !!!
build:
dockerfile: Dockerfile
context: ./tests — здесь указываем что для сборки используем Dockerfile и указываем к нему относительно docker-compose.cypress.yml
depends_on:
— frontend — благодаря этому контейнер cypress развернется строго после контейнера frontend
environment:
— CYPRESS_baseUrl=http://frontend:5000 — директивное изменение базового URL'а. Можно тестировать в контейнере и в приложении, не меняя настроек.
command: ["./wait-for", "--timeout=1200", «frontend:5000», "--", «cypress», «run»] — команда гоняет ничего не делающий скрипт wait-for пока не получит какой-нибудь вразумительный ответ от сервера, после чего запускает cypress. Если ответа не было 1200 секунд (20 минут), контейнер упадет с ошибкой.!!! ПОДРОБНЕЕ ЗДЕСЬ !!!
volumes:
— ./tests/artifacts:/e2e/cypress/videos
— ./tests/artifacts:/e2e/cypress/screenshots
— ./tests/cypress/ScreenRefs:/e2e/cypress/ScreenRefs
— ./tests/artifacts/:/e2e/Failure_Screenshot_Matching — связываем томами директории контейнера и директории на машине хоста для сохранения результатов теста.
Во vue.config.js добавляем две строки:
const IS_CYPRESS_ENV = /true/i.test(process.env.VUE_APP_IS_CYPRESS_ENV) // Добавили эту строку
module.exports = {
что-то там: true,
что-то там: ['нас не интересует'],
devServer: {
disableHostCheck: IS_CYPRESS_ENV, // Добавили эту сторку
// Далее другие настройки, но нам они не важны
Вот что мы сделали:
disableHostCheck: IS_CYPRESS_ENV — добавили отключение проверки заголовка хоста и приравняли его к глобальной переменной
const IS_CYPRESS_ENV = /true/i.test(process.env.VUE_APP_IS_CYPRESS_ENV) — создаем глобальную переменную, по умолчанию false
Команда для запуска тестов:
docker-compose --file docker-compose.cypress.yml up --exit-code-from cypress --build cypress
Что же обозначает каждый элемент команды? Разберем:
docker-compose --file docker-compose.cypress.ymlup — поднимаем контейнеры посредством docker-compose, используя настройки из определенного файла (эта часть подчеркнута)
--exit-code-from cypress — весь мультиконтейнер упадет вместе с контейнером cypress, с таким же кодом. Вот мы и передали ошибку, если она возникнет
--build cypress — перед каждым запуском будет происходить билд, а логи будут только от контейнера cypress
Но зачем нам каждый раз писать такую длинную и сложную команду? Может можно как-то попроще? Можно!
В файле package.json добавляем строку:
{
// не интересующие нас настройки
«scripts»: {
«test:integration»: «docker-compose --file docker-compose.cypress.yml up --exit-code-from cypress --build cypress», // Добавляем эту строку
// Что там дальше нас не интересует
Таким образом мы легко сможем запустить тесты простой командой:
npm run test:integration
Проверка настроек
Осталось проверить настройки. Просто запустим несколько раз простой командой заведомо успешные тесты, затем перепишем на заведомо провальные и прогоним их.
Следим чтоб контейнеры выходили с тем же кодом, с которым выходит Cypress.
Напишем тест, совершающий простое взаимодействие с приложением. Например, просто зайдем на главную страницу и найдем какой-нибудь заведомо существующий элемент. Защита заголовков хоста должна быть отключена, мы успешно находим нужный элемент.