Визуальное регрессионное тестирование с помощью Cypress: прагматичный подход

Опубликовано: 2022-03-11

Каждый раз, когда выпускается новая версия нашей библиотеки компонентов Picasso, мы обновляем все наши клиентские приложения, чтобы получить максимальную отдачу от новых функций и согласовать наши проекты во всех частях нашего сайта.

В прошлом месяце мы выпустили обновление Picasso для Toptal Talent Portal, платформы, которую наши таланты используют для поиска работы и взаимодействия с клиентами. Зная, что выпуск будет сопровождаться серьезными изменениями в дизайне, и чтобы свести к минимуму непредвиденные проблемы, имело смысл использовать методы визуального регрессионного тестирования, чтобы помочь нам найти проблемы до выпуска.

Визуальное регрессионное тестирование — не новая концепция; многие другие проекты в Toptal уже используют его, в том числе сам Пикассо.

Такие инструменты, как Percy, Happo и Chromatic, можно использовать, чтобы помочь командам создать надежный конвейер визуальной регрессии, и мы сначала рассматривали возможность их добавления. В конце концов мы решили, что процесс установки займет слишком много времени и может нарушить наш график. У нас уже была назначена дата заморозки кода для начала миграции, и поскольку до крайнего срока оставалось всего несколько дней, у нас не было другого выбора, кроме как проявить творческий подход.

Визуальное регрессионное тестирование через тестирование пользовательского интерфейса

Хотя у нас не было визуальных регрессионных тестов в проекте, у нас было хорошее покрытие интеграционных тестов пользовательского интерфейса с использованием Cypress. Несмотря на то, что этот инструмент в основном используется не для этого, в документации Cypress есть одна страница, посвященная визуальному тестированию, и другая, на которой перечислены все доступные плагины, помогающие настроить Cypress для визуального тестирования.

От Cypress к скриншотам

Изучив доступную документацию, мы решили попробовать cypress-snapshot-plugin. Настройка заняла всего несколько минут, и как только мы это сделали, мы быстро поняли, что не стремимся к традиционному выводу визуальной регрессии.

Большинство инструментов визуальной регрессии помогают выявлять нежелательные изменения, сравнивая снимки и обнаруживая различия в пикселях между известной, принятой базовой линией и измененной версией страницы или компонента. Если разница в пикселях превышает установленный порог допуска, страница или компонент помечаются для проверки вручную. Однако в этом выпуске мы знали, что собираемся внести несколько небольших изменений в большинство наших компонентов пользовательского интерфейса, поэтому установка порогового значения не применялась. Даже если данный компонент оказался на 100% другим, он все равно может быть правильным в контексте новой версии. Точно так же небольшое отклонение в несколько пикселей может означать, что компонент в настоящее время не подходит для производства.

Снимок экрана, показывающий ожидаемый результат и фактический результат выполнения теста.
Рис. 1. Пример незначительных различий в пикселях, приводящих к ложноотрицательным результатам.

В этот момент стали ясны две противоположные вещи: обнаружение различий в пикселях не поможет выявить проблемы, а параллельное сравнение компонентов было именно тем, что нам было нужно. Мы отложили плагин моментальных снимков в сторону и решили создать коллекцию изображений с нашими компонентами до и после применения обновления Picasso. Таким образом, мы могли быстро просмотреть все изменения, чтобы определить, соответствуют ли новые версии потребностям сайта и стандартам библиотеки.

Новый план состоял в том, чтобы сделать скриншот компонента, сохранить его локально, сделать новый скриншот того же компонента в ветке с обновленной версией Picasso, а затем объединить их в одно изображение. В конечном счете, этот новый подход не слишком отличался от того, с чего мы начали, но он дал нам больше гибкости на этапе реализации, поскольку нам больше не нужно было импортировать подключаемый модуль и использовать его новые команды.

Диаграмма, показывающая поток визуального сравнения, как изображения новой и старой версии объединяются после запуска визуального теста.
Рисунок 2. Процесс визуального сравнения

Использование API для сравнения изображений

Имея в виду четкую цель, пришло время посмотреть, как Cypress может помочь нам получить нужные нам скриншоты. Как уже упоминалось, у нас было большое количество тестов пользовательского интерфейса, охватывающих большую часть Talent Portal, поэтому, стремясь собрать как можно больше критических компонентов, мы решили делать скриншоты отдельных элементов после каждого взаимодействия.

Альтернативным подходом было бы создание скриншотов всей страницы в ключевые моменты теста, но мы решили, что эти изображения будет слишком сложно сравнивать. Кроме того, такие сравнения могут быть более подвержены человеческим ошибкам, например, не учитывать, что нижний колонтитул изменился.

Третий вариант заключался в том, чтобы пройти каждый тестовый пример, чтобы решить, что захватить, но это заняло бы гораздо больше времени, поэтому придерживаться всех элементов, используемых на страницах, казалось практическим компромиссом.

Мы обратились к API Cypress для создания изображений. Команда cy.screenshot() может «из коробки» создавать отдельные образы компонентов, а API After Screenshot позволяет нам переименовывать файлы, менять каталоги и отличать прогоны визуальной регрессии от стандартных. Объединив их, мы создали прогоны, которые не повлияли на наши функциональные тесты и позволили нам хранить изображения в соответствующих папках.

Во-первых, мы расширили файл index.js в нашем каталоге плагинов, чтобы он поддерживал два новых типа запуска (базовый и сравнительный). Затем мы устанавливаем путь для наших изображений в соответствии с типом запуска:

 // plugins/index.js const fs = require('fs') const path = require('path') module.exports = (on, config) => { // Adding these values to your config object allows you to access them in your tests. config.env.baseline = process.env.BASELINE || false config.env.comparison = process.env.COMPARISON || false on('after:screenshot', details => { // We only want to modify the behavior of baseline and comparison runs. if (config.env.baseline || config.env.comparison) { // We keep track of the file name and number to make sure they are saved in the proper order and in their relevant folders. // An alternative would have been to look up the folder for the latest image, but this was the simpler approach. let lastScreenshotFile = '' let lastScreenshotNumber = 0 // We append the proper suffix number to the image, create the folder, and move the file. const createDirAndRename = filePath => { if (lastScreenshotFile === filePath) { lastScreenshotNumber++ } else { lastScreenshotNumber = 0 } lastScreenshotFile = filePath const newPath = filePath.replace( '.png', ` #${lastScreenshotNumber}.png` ) return new Promise((resolve, reject) => { fs.mkdir(path.dirname(newPath), { recursive: true }, mkdirErr => { if (mkdirErr) { return reject(mkdirErr) } fs.rename(details.path, newPath, renameErr => { if (renameErr) { return reject(renameErr) } resolve({ path: newPath }) }) }) }) } const screenshotPath = `visualComparison/${config.env.baseline ? 'baseline' : 'comparison'}` return createDirAndRename(details.path .replace('cypress/integration', screenshotPath) .replace('All Specs', screenshotPath) ) } }) return config }

Затем мы вызывали каждый из запусков, добавляя соответствующую переменную среды к вызову Cypress в package.json проекта:

 "scripts": { "cypress:baseline": "BASELINE=true yarn cypress:open", "cypress:comparison": "COMPARISON=true yarn cypress:open" }

Как только мы запустили наши новые команды, мы увидели, что все скриншоты, сделанные во время запуска, были перемещены в соответствующие папки.

Скриншот, показывающий изображения, сделанные во время прогона и перемещенные в папки.
Рисунок 3. Визуальные результаты запуска

Затем мы попытались перезаписать cy.get() , основную команду Cypress для возврата элементов DOM, и сделать скриншот любых вызываемых элементов вместе с его реализацией по умолчанию. К сожалению, cy.get() — сложная команда для изменения, так как вызов исходной команды в ее собственном определении приводит к бесконечному циклу. Предлагаемый подход для обхода этого ограничения состоит в том, чтобы создать отдельную пользовательскую команду, а затем сделать снимок экрана этой новой командой после обнаружения элемента:

 Cypress.Commands.add("getAndScreenshot", (selector, options) => { // Note: You might need to tweak the command when getting multiple elements. return cy.get(selector).screenshot() }); it("get overwrite", () => { cy.visit("https://example.cypress.io/commands/actions"); cy.getAndScreenshot(".action-email") })

Однако наши вызовы для взаимодействия с элементами на странице уже были заключены во внутреннюю getElement() . Так что все, что нам нужно было сделать, это убедиться, что при вызове обертки был сделан снимок экрана.

Результаты, полученные с помощью визуального регрессионного тестирования

Когда у нас были скриншоты, оставалось только объединить их. Для этого мы создали простой скрипт узла с помощью Canvas. В итоге скрипт позволил нам сгенерировать 618 сравнительных изображений! Некоторые различия было легко обнаружить, открыв Портал талантов, но некоторые проблемы были не столь очевидны.

До и после пример неправильного использования Пикассо, отображающий красный и черный цвета в элементе.
Рисунок 4. Пример несоблюдения новых правил Пикассо; ожидалась разница, но в новой версии должен был быть красный фон и белый текст

Пример «до» и «после» слегка нарушенного макета компонента со смещенным текстом рядом с флажком на изображении «После».
Рис. 5. Пример слегка нарушенной компоновки компонентов

Добавление ценности в тестирование пользовательского интерфейса

Прежде всего, добавленные визуальные регрессионные тесты оказались полезными и выявили несколько проблем, которые мы могли бы пропустить без них. Несмотря на то, что мы ожидали различий в наших компонентах, знание того, что на самом деле было изменено, помогло сузить проблемные случаи. Итак, если в вашем проекте есть интерфейс, но вы еще не выполняете эти тесты, приступайте к нему!

Второй урок здесь, и, возможно, более важный, заключается в том, что нам еще раз напомнили, что идеальное — враг хорошего. Если бы мы исключили возможность запуска визуальных регрессионных тестов для этого выпуска, потому что не было предварительной настройки, мы могли бы пропустить несколько ошибок во время миграции. Вместо этого мы согласовали план, который хоть и не был идеальным, но был быстрым в исполнении, мы работали над ним, и он окупился.

Для получения более подробной информации о реализации надежного конвейера визуальной регрессии в вашем проекте, пожалуйста, обратитесь к странице визуального тестирования Cypress, выберите инструмент, который лучше всего соответствует вашим потребностям, и посмотрите обучающие видео.