Как добавить в свой проект тестирование снимков экрана с помощью Cypress
Разработчики обычно озабочены качеством своего кода. Существуют различные виды тестов, которые помогают нам избежать нарушения кода при добавлении новой функции в проект. Но что мы можем сделать, чтобы компоненты не менялись со временем?
В этом посте вы узнаете, как использовать Cypress для захвата частей страниц веб-сайта. После этого вы интегрируете инструмент тестирования в CI, чтобы гарантировать, что в будущем никто не внесет нежелательные изменения в ваш проект.
Моя мотивация к созданию этой стратегии тестирования пришла из работы. В Thinkific у нас есть внутренняя система дизайна, и мы добавили Cypress, чтобы избежать сюрпризов при работе с файлами CSS / JS.
К концу поста у нас будут PR с тестами Cypress:
Прежде чем мы начнем
Я создал образец веб-сайта, имитирующий библиотеку компонентов. Это очень простой веб-сайт, созданный с помощью TailwindCSS и размещенный на Vercel. Он документирует 2 компонента: значок и кнопку.
Вы можете ознакомиться с исходным кодом на GitHub. Сайт статичен и находится внутри папки public
. Вы можете увидеть сайт локально, запустив npm run serve
и выполнив проверку в браузере http: // localhost: 8000.
Добавление Cypress Image Snapshot и Cypress
Начните с клонирования репозитория примеров. Затем создайте новую ветку и установите Cypress Image Snapshot, пакет, отвечающий за захват / сравнение снимков экрана.
git checkout -b add-cypress
npm install -D cypress cypress-image-snapshot
После добавления пакетов необходимо выполнить несколько дополнительных шагов, чтобы добавить Cypress Image Snapshot в Cypress.
Создайте файл 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 сделает снимок экрана каждого примера на странице. На странице значка есть 2 примера (по умолчанию и таблетка), а на странице кнопки есть 3 примера (по умолчанию, таблетка и схема). Все эти примеры находятся внутри элемента
<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"
}
Отсюда есть 2 варианта: запустить Cypress в автономном режиме npm run cypress run
или использовать Cypress Test Runner с npm run cypress open
.
Headless вариант
При использовании npm run cypress run
вывод должен быть похож на следующее изображение:
Тесты пройдут, и в папке /snapshots/screenshot.spec.js
будут созданы 5 изображений.
Вариант Test Runner
Используя npm run cypress open
, откроется Cypress Test Runner, и вы сможете шаг за шагом следить за тестами.
Наша первая веха пройдена, поэтому давайте объединим эту ветку в master. Если вы хотите увидеть проделанную работу, перейдите в мой Pull Request.
Использование Cypress внутри Docker
Если вы запустите вышеуказанный тест попеременно между Headless и Test Runner, вы можете заметить, что снимок экрана будет отличаться.
Используя Test Runner с компьютером с дисплеем Retina, вы можете получить изображения (2x), в то время как режим Headless не дает вам скриншотов высокого качества.
Также важно сказать, что снимки экрана могут отличаться в зависимости от вашей операционной системы.
Например, в Linux и Windows есть приложения с видимыми полосами прокрутки, а в macOS полоса прокрутки скрыта.
Если содержимое, захваченное на снимке экрана, не подходит для компонента, у вас может быть полоса прокрутки, а может и нет. Если ваш проект использует шрифты ОС по умолчанию, снимки экрана также будут отличаться в зависимости от среды.
Чтобы избежать этих несоответствий, тесты будут запускаться внутри Docker, поэтому компьютер разработчика не повлияет на снимки экрана.
Начнем с создания новой ветки:
git checkout -b add-docker
Cypress предлагает различные образы Docker - вы можете проверить подробности в их документации и их блоге.
В этом примере я буду использовать изображение cypress/included
, которое включает Electron и готово к использованию.
Нам нужно внести два изменения: изменить baseUrl
в файле сypress.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
Обратите внимание, что я добавляю к команде test префикс переменной среды CYPRESS_updateSnapshots
.
Второй этап пройден. Если вам нужна помощь, ознакомьтесь с моим pull request.
Давайте объединим эту ветку и двинемся дальше.
Добавление 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 создают 2 блока: первый блок, «Build Dependencies», будет запущен
npm install
, загружая нужные нам зависимости. Второй блок, «Tests», будет запускать Cypress с некоторыми отличиями. - В строке 27 мы динамически устанавливаем переменную окружения
CYPRESS_baseUrl
в зависимости от того, какой IP Docker использует в данный момент. Это заменитhttp://host.docker.internal:8000/
, что может не работать во всех средах. - В строке 28 мы, наконец, запускаем тест, используя
start-server-and-test
: как только сервер будет готов к подключению, Cypress запустит набор тестов.
Еще одна веха пройдена, пора объединить нашу ветку! Вы можете проверить Pull request, который содержит все файлы из этого раздела, и проверить сборку внутри 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'"
}
Это почти все, что нужно сделать. В Pull request мы видим интеграцию cypress.io в комментариях. Есть даже глубокая ссылка, которая ведет нас на их панель управления и показывает все скриншоты. Посмотрите видео ниже:
Пора объединить нашу работу, и это конец нашей интеграции.
Тестирование в реальной жизни
Представьте, что мы работаем над изменением, которое влияет на заполнение кнопок: пора проверить, улавливает ли Cypress разницу.
В примере веб-сайта давайте удвоим горизонтальный отступ с 16 до 32 пикселей. Это изменение довольно просто, поскольку мы используем Tailwind CSS: px-4
заменяется на px-8
. Вот этот Pull request.
Как и следовало ожидать, Cypress зафиксировал, что кнопка не соответствует снимкам экрана. Зайдя на страницу, мы можем проверить скриншот неработающего теста:
Файл сравнения показывает исходный снимок экрана слева, текущий результат справа, и они объединены в середине. У нас также есть возможность загрузить изображение, чтобы лучше понять проблему:
Если это не проблема, обновите скриншоты:
CYPRESS_updateSnapshots=true npm run test
Конец
На сегодня все. Я надеюсь, что вы узнали, как Cypress может быть полезен, чтобы гарантировать, что никто не добавит неожиданные изменения в проект.