Разработчиков, как правило, заботит качество кода. Есть различные виды тестов, которые помогают избежать ситуаций, когда код ломается после добавления в проект новой функции. Но как можно гарантировать, что компоненты с течением времени не изменятся внешне?
Из этой статьи вы узнаете, как захватывать фрагменты веб-страниц с помощью Cypress, а затем интегрируете этот инструмент тестирования в CI, чтобы в будущем никто не вносил в проект нежелательных изменений.
На создание этой стратегии тестирования меня мотивировала работа. В Thinkific есть внутренняя система проектирования, и мы добавили к ней Cypress, чтобы избежать сюрпризов при работе с файлами CSS/JS.
К концу этой статьи у нас будет репозиторий с тестами Cypress:
Прежде чем начать
Для имитации библиотеки компонентов (Component Library) я создал сэмпл веб-сайта. Это очень простой сайт, сделанный с помощью TailwindCSS и размещенный в Vercel. Он документирует два компонента: значок и кнопку.
С исходным кодом можете ознакомиться на GitHub. Веб-сайт статический и находится в папке public
. Вы можете просмотреть веб-сайт локально, запустив npm run serve
и перейдя в браузере на http://localhost:8000:
Добавление Cypress и Cypress Image Snapshot
Начните с клонирования репозитория-образца. Затем создайте новую ветку и установите Cypress Image Snapshot, пакет, отвечающий за захват и сравнение скриншотов.
git checkout -b add-cypressnpm install -D cypress cypress-image-snapshot
После добавления пакетов необходимо выполнить несколько дополнительных шагов, чтобы добавить в Cypress Cypress Image Snapshot.
Создайте файл cypress/plugins/index.js
со следующим содержимым:
const { addMatchImageSnapshotPlugin } = require('cypress-image-snapshot/plugin');
module.exports = (on, config) => {
addMatchImageSnapshotPlugin(on, config);
};
Далее создайте файл cypress/support/index.js
, содержащий:
import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command';
addMatchImageSnapshotCommand();
Создание теста скриншотов
Пришло время создать тест для скриншотов. План таков:
- Cypress заходит на каждую страницу проекта (значок и кнопку).
- Cypress делает скриншот каждого примера на странице. Страница значка содержит два примера (по умолчанию и “таблетка”), в то время как страница кнопки содержит три (по умолчанию, “таблетка” и контур). Все эти примеры находятся внутри элемента
<div>
иcypress-wrapper
. Этот класс был добавлен с единственной целью — определить, что нужно протестировать.
Первый шаг — создание конфигурационного файла Cypress (cypress.json)
:
{
"baseUrl": "http://localhost:8000/",
"video": false
}
BaseUrl
здесь — веб-сайт, который запущен локально. Как уже было сказано, npm run serve
поставляет содержимое папки public
. Вторая опция, video
, отключает для Cypress запись видео, поскольку в данном проекте нам это не понадобится.
Время создавать тест. В cypress/integration/screenshot.spec.js
добавим:
const routes = ['badge.html', 'button.html'];
describe('Component screenshot', () => {
routes.forEach((route) => {
const componentName = route.replace('.html', '');
const testName = `${componentName} should match previous screenshot`;
it(testName, () => {
cy.visit(route);
cy.get('.cypress-wrapper').each((element, index) => {
const name = `${componentName}-${index}`;
cy.wrap(element).matchImageSnapshot(name);
});
});
});
})
В коде выше динамически создаются тесты, основанные на массиве routes
. Тест создаст по одному изображению на каждый элемент .cypress-wrapper
, который есть на странице.
Наконец, внутри package.json
создадим команду для запуска тестов:
{
"test": "cypress"
}
И здесь появляются два варианта: запускать Cypress в headless-режиме через npm run cypress run
или воспользоваться Cypress Test Runner через npm run cypress open
.
Вариант Headless
Вывод npm run cypress run
должен быть похож на следующее изображение:
Тесты будут пройдены, и будут созданы пять изображений в папке /snapshots/screenshot.spec.js
.
Вариант Test Runner
Через npm run cypress open
откроется Cypress Test Runner, и вы сможете следить за тестами шаг за шагом.
Первую веху мы преодолели, так что давайте объединять текущую ветку с веткой master. Если хотите увидеть текущее состояние проекта, переходите прямо к моему пулл-реквесту.
Cypress внутри Docker
Если вы запустите тест выше, чередуя Headless и Test Runner, то, возможно, заметите, что скриншоты отличаются.
На компьютерах с retina-дисплеем, вы можете получить retina-изображения (2x) через Test Runner, в то время как headless-режим не предоставляет скриншотов в высоком качестве.
Кроме того, важно отметить, что скриншоты могут отличаться в зависимости от вашей операционной системы.
В Linux и Windows, например, полоса прокрутки у приложений видима, а macOS скрывает полосу прокрутки.
Если содержимое на скриншоте не соответствует компоненту, у вас может быть (или отсутствовать) полоса прокрутки. Если ваш проект опирается на шрифты, включенные в ОС по умолчанию, скриншоты также будут отличаться в зависимости от среды.
Чтобы избежать этих несоответствий, тесты будут выполняться внутри Docker, чтобы компьютер разработчика не влиял на снимки экрана.
Давайте начнем с создания новой ветки:
git checkout -b add-docker
Cypress предлагает различные Docker-образы — с подробностями можно ознакомиться в их документации и блоге.
Для этого примера я выбрал образ cypress/included
, который включает в себя Electron и готов к использованию.
Нам нужно внести два изменения: поменять baseUrl
в файле cypress.json
:
{
"baseUrl": "http://host.docker.internal:8000/",
}
и команду test
в файле package.json
:
{
"test": "docker run -it -e CYPRESS_updateSnapshots=$CYPRESS_updateSnapshots --ipc=host -v $PWD:/e2e -w /e2e cypress/included:4.11.0"
}
При запуске npm run test
возникнет проблема:
Изображения немного отличаются, но почему? Давайте посмотрим, что находится внутри папки __diff_output__
:
Именно то, о чем я упоминал чуть раньше: несоответствия шрифтов! Компонент Button
(кнопка) использует шрифт, установленный в ОС по умолчанию. Поскольку Docker работает внутри Linux, отрисованный шрифт не будет таким же, как тот, который стоит на macOS.
Так как мы перешли на Docker, эти скриншоты устарели. Время их обновить:
CYPRESS_updateSnapshots=true npm run test
Пожалуйста, обратите внимание: я снабжаю тест-команду префиксом переменной окружения CYPRESS_updateSnapshots
.
Вторая веха позади. На случай, если вам понадобится помощь, сравните ваш результат с моим пулл-реквестом.
Давайте сольем эту ветку с основной и двинемся вперед.
Добавление CI
Наш следующий шаг — добавление тестов в CI. На рынке существуют различные CI-решения, но для этого руководства я обращусь к Semaphore.
Конфигурация проста, и ее можно адаптировать к другим решениям, таким как CircleCI или Github Actions.
Прежде чем создавать конфигурационный файл Semaphore, давайте подготовим наш проект к запуску в CI.
Первый шаг — установка start-server-and-test. Как следует из названия пакета, он запустит сервер, дождется URL-адреса и затем выполнит тестовую команду:
npm install -D start-server-and-test
Во-вторых, отредактируйте файл package.json
:
{
"test": "docker run -it -e CYPRESS_baseUrl=$CYPRESS_baseUrl -e CYPRESS_updateSnapshots=$CYPRESS_updateSnapshots --ipc=host -v $PWD:/e2e -w /e2e cypress/included:4.11.0",
"test:ci": "start-server-and-test serve http://localhost:8000 test"
}
В скрипте test
мы добавляем переменную окружения CYPRESS_baseUrl
. Это позволит динамически изменять базовый URL-адрес, используемый Cypress. Кроме того, добавим скрипт test:ci
, который запустит только что установленный пакет.
И, наконец, Semaphore. Создайте файл .semaphore/semaphore.yml
со следующим содержимым:
1 version: v1.0
2 name: Cypress example
3 agent:
4 machine:
5 type: e1-standard-2
6 os_image: ubuntu1804
7 blocks:
8 - name: Build Dependencies
9 dependencies: []
10 task:
11 jobs:
12 - name: NPM
13 commands:
14 - sem-version node 12
15 - checkout
16 - npm install
17 - name: Tests
18 dependencies: ['Build Dependencies']
19 task:
20 prologue:
21 commands:
22 - sem-version node 12
23 - checkout
24 jobs:
25 - name: Cypress
26 commands:
27 - export CYPRESS_baseUrl="http://$(ip route | grep -E '(default|docker0)' | grep -Eo '([0-9]+\.){3}[0-9]+' | tail -1):8000"
28 - npm run test:ci
Давайте разберемся в этом подробнее:
- Строки 1–6 определяют, какой тип экземпляра мы будем использовать в среде.
- Строки 8 и 16 создают два блока: первый блок,
Build Dependencies
, будет запускатьnpm install
, загружая нужные нам зависимости. Второй блок,Test
, будет запускать Cypress с небольшими отличиями. - В строке 27 мы динамически задаем переменную окружения
CYPRESS_baseUrl
на основе IP-адреса, который в данный момент использует Docker. Это заменитhttp://host.docker.internal:8000/
, который может работать не во всех средах. - В строке 28 мы наконец запускаем тест с помощью
start-server-and-test
: как только сервер будет готов к подключению, Cypress запустит тестовый набор.
Еще одна веха пройдена, пора делать слияние нашей ветки! По желанию просмотрите пулл-реквест, содержащий все файлы из этого раздела, и проверьте сборку внутри Semaphore.
Запись тестов в cypress.io
Чтение выходных данных тестов в CI не очень дружелюбно по отношению к пользователю. На этом этапе мы интегрируем наш проект с cypress.io.
Следующие шаги основаны на документации Cypress.
Давайте начнем с того, что получим идентификатор проекта и ключ записи. В терминале создайте новую ветку и запустите:
git checkout -b add-cypress-recording
CYPRESS_baseUrl=http://localhost:8000 ./node_modules/.bin/cypress open
Ранее я упоминал, что мы будем пользоваться Cypress внутри Docker. Но здесь мы открываем Cypress локально, так как это единственный способ интеграции с информационной панелью веб-сайта.
Внутри Cypress перейдем на вкладку Runs, нажмем кнопку Set up project to record (“Настроить проект для записи”) и выберем имя и настройку видимости. Мы получим projectId
, который автоматически добавляется в файл cypress.json
, и закрытый ключ записи. Вот видео этих шагов:
В Semaphore я добавил ключ записи в качестве переменной окружения CYPRESS_recordKey
. Теперь давайте обновим тестовый скрипт с учетом этой переменной:
{
"test:ci": "start-server-and-test 'serve' 8000 'npm run test -- run --record --key $CYPRESS_recordKey'"
}
На этом практически все. В пулл-реквесте можно видеть интеграцию cypress.io
в комментариях. Есть даже глубинная ссылка, которая ведет к их приборной панели и показывает все скриншоты. Посмотрите видео ниже:
Пришло время сделать последнее слияние и закончить на этом интеграцию.
Тестирование в реальной жизни
Представьте, что мы работаем над изменением, которое влияет на заполнение кнопок: время проверить, будет ли Cypress улавливать разницу.
На примере веб-сайта давайте удвоим горизонтальное заполнение с 16px до 32px. Это довольно простое изменение, так как мы используем Tailwind CSS: px-4
заменяется на px-8
. Вот пулл-реквест.
Как и следовало ожидать, Cypress зафиксировал, что кнопка не соответствует скриншотам. Посетив страницу, мы можем проверить скриншот сломанного теста:
Файл diff показывает исходный скриншот слева, текущий результат справа, и их наложение — в середине. Есть также возможность загрузить изображение, чтобы лучше рассмотреть проблему:
Если это на самом деле не проблема, обновите скриншоты:
CYPRESS_updateSnapshots=true npm run test
Конец
На сегодня все. Надеюсь, вы узнали, как с пользой применять Cypress, чтобы гарантировать, что никто не добавляет в проект неожиданные изменения.
Читайте также:
- ПО без тестирования - что самолет без крыльев
- Топ 10 бесплатных инструментов для автоматизированного тестирования
- Модульное тестирование с имитацией сетевых вызовов
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Leonardo Faria: How to Add Screenshot Testing with Cypress to Your Project