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

Часто приходилось видеть тесты опосредовано проверяющие визуальное отображение html-элемента, что-то в стиле expect(elem.classList.contains("visible")).toBe(true). Говорить о надежности таких тестов конечно-же не приходится, так как изменив содержимое css-селектора стилизующий данный класс, данный тест все еще будет зелёным, несмотря на то что по факту элемент будет скрыт.

Результат от подобных тестов вполне ожидаемый. Обновили версию UI-библиотеки и на всем проекте поехала верстка? Тесты зелёные. Случайно переопределили CSS-переменную и теперь вместо приятной тщательно подобранной дизайнером гаммы цветов вы видите лишь кислотно-вырвиглазную солянку? “Бывает, надо было ручками протестировать” - скажет менеджер.

Решить данную проблему нам поможет добавление скриншот-тестирования на проект.

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

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

Что это такое?

Скриншот-тестирование - метод тестирования ПО, заключающийся в сравнении актуального состояния интерфейса программы с ее эталонным изображением.

Слева - эталонное изображение, справа - актуальное, посередине - дифф.
Слева - эталонное изображение, справа - актуальное, посередине - дифф.

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

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

Какие преимущества дает нам использование данного типа тестирования?

  1. Тестирование верстки и визуальной части интерфейса. Тут, как говорится, "аналогов нет". Если структуру DOM-дерева еще можно как-то отрендерить и проверять в снапшотах, то вот сломанные стили вам поможет “увидеть” либо ручное тестирование, либо скриншот-тестирование.

  2. Достаточно быстрое базовое покрытие тестами всего проекта и достижение высокого показателя code-coverage - сами тесты это по сути просто вызов метода “сделай скриншот” в различных кейсах, что не представляет большой сложности и вполне осуществимо даже при поверхностных знаниях используемых инструментов.

  3. Наглядность. Упавший тест конкретно визуализирует что сломалось, генерируя так называемый “дифф” - изображение наглядно показывающее разницу между эталонным и актуальным скриншотом. 

  4. Возможность использования полноценного TDD. Скриншот-тесты открывают двери к настоящему TDD. Вместо написания логических проверок, мы можем использовать макеты из Figma как визуальные эталоны — и писать код до тех пор, пока отрендеренный компонент не станет визуально идентичным макету. До знакомства с скриншот-тестами я всегда считал TDD странной, плохо применимой на практике технологией, которую можно использовать только лишь в учебных целях. Скриншот-тесты кардинально изменили это представление - зачем вообще открывать браузер, если прямо в своём IDE, нажав лишь на кнопку запуска теста - я получаю отрендеренный компонент, который будет сравнен с макетом в “пиксель-перфект” режиме?

  5. Возможность прогона тестов на разных разрешениях и браузерах. Если ваше приложение критично к кросс-браузерности или поддерживает совсем старый Internet Explorer, то скриншот-тесты помогут автоматизировать рутинную ручную проверку в разных браузерах.

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

Принцип работы

Для того чтобы скриншот-тесты заработали на нашем проекте нам нужны 3 основные их составляющие:

1) Тестовый фреймворк или тест-раннер; - Jest, Mocha.js, Vitest.

2) Инструмент способный эмулировать браузер или содержащий в себе браузерный движок для того чтобы отрендерить наше приложение и сделать скриншот - Selenium, Playwright, Puppeteer.

3) Инструмент сравнения изображений;

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

Выбирать эмулятор браузера уже нужно более тщательно.

Тут нужно выбирать из вашего сценария тестирования. Так, если вы хотите запускать тесты на разных браузерах, включая самые древние по типу Internet Explorer, то возможно лучшим выбором будет Selenium, так как он поддерживает большее количество браузеров и имеет более нативную поддержку Safari. Это позволяет нам проводить кросс-браузерное тестирование.

Если же вам нужно быстродействие, то следует присмотреться к Puppeteer, он запускает браузерный движок в headless-режиме и гораздо быстрее того же селениума. Из недостатков - он создавался для работы исключительно с Chromium движком, однако недавно была добавлена поддержка Firefox.

Playwright - самый новый из указанных мной инструментов, также имеет headless режим, но и работает уже с большим количеством браузеров.

Следующий, последний ингредиент в рецепте это то, как мы будем сравнивать различные изображения. Здесь существуют разные алгоритмы и инструменты того как это делать. Какие-то инструменты сравнивают хэши изображений, какие-то используют алгоритм SSIM (расшифровывается как “сравнение по структурному сходству”), но наиболее популярные до сих пор это инструменты реализующие простое попиксельное сравнение.

При этом попиксельное сравнение считается менее стабильным, чем тот же SSIM. Но из плюсов, попиксельное сравнение отдаёт более человекочитаемый дифф - разницу между скриншотами и в некоторых реализациях не уступает по скорости.

Из инструментов реализующих попиксельное сравнение какое-то время были популярны решения основанные на библиотеках blink-diff и resemble.js

Несколько позже появился pixelmatch и сейчас используется как стандарт (по-крайней мере на Javascript) и является наиболее популярной и распространенной имплементацией попиксельного сравнения.

Я использую в своих проектах инструмент jest-image-snapshot, который под коробкой использует pixelmatch, плюс также имеет поддержку ssim алгоритма, который должен увеличить стабильность сравнения по-сравнению с попиксельным сравнением.

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

Основные проблемы.

Во время настройки и поддержки скриншот-тестирования на проекте вы можете встретить следующие проблемы:

Проблема #1: Хранение скриншотов в репозитории.

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

Решение: GIT LFS.

Это расширении для гита, которая позволяет оптимизировать хранение статических файлов в репозитории, перенося их в отдельное хранилище. В истории же гита хранятся только текстовые указатели на файлы.

Проблема #2: Возможная разница в рендеринге скриншотов на разных машинах.

Даже запуская тесты в одном и том же браузере, но на разных ОС допустим Windows и Macos, могут появиться различия в отрендеренных изображениях. Такая же проблема существует при запуске тестов в CI - результат выполнения может быть иным нежели при запуске локально.

Решением здесь будет запуск тестов локально и на CI из под докера с заранее настроенным окружением.

Однако если этот вариант слишком муторный и у Вас в команде все работают более-менее работают с похожим железом и Unix-подобными ОС, то в принципе можно постараться просто синхронизировать окружения разработчиков. Также стоит отметить что у скриншот-тестов есть “чувствительность”, которой можно управлять и понижение которой позволит тесту завершиться “Успешно” даже если были какие-то минорные различия.

Проблема #3: быстродействие когда тестов становится много.

Тут главное, распараллелить тесты и подобрать оптимальные подходящие Вам инструменты сравнения изображений.

Проблема #4: динамический контент.

Если у Вас сайт с полностью статическим контентом и без анимаций, то данная проблема обойдет Вас стороной, но на остальных 98% проектов это обернётся нестабильностью тестов до тех пор пока вы не научитесь превращать ваш сайт с динамическим контентом в “статический”. Здесь стоит внимательно рассмотреть несколько основных типов динамики которые мы можем встретить:

  1. Данные.

    Для данных приходящих из внешних источников, например из ответов на http-запросы на сервер нам нужно по-максимуму использовать технику мокинга данных. Тот же Puppeteer дает возможность перехватывать запросы на сервер и возвращать те данные которые мы хардкодим в самом тесте.

  2. Лоадеры и анимации загрузки. Здесь нам поможет свойство в Puppeteer networkidle0, которое позволяет дождаться состояния когда все запросы завершены и сеть находится в состоянии покоя.

    await page.goto(fullUrl, { waitUntil: 'networkidle0' })
  3.  Анимации.

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

    Делая скриншот страницы с анимацией, мы не можем быть уверены что точно попадём на тот кадр который нам нужен
    Делая скриншот страницы с анимацией, мы не можем быть уверены что точно попадём на тот кадр который нам нужен

     Перво-наперво вспоминаем что существуют два типа анимаций - css-анимации и  js-анимации. И решаются они по-разному. 

    Для css-анимаций поможет следующий хак, который “обнулит” все анимации на странице, и сразу приведет их к конечному кадру. 

    *, ::after, ::before {
      transition-delay: 0s !important;
      transition-duration: 0s !important;
      animation-delay: -0.001s !important;
      animation-duration: 0s !important;
      animation-play-state: paused !important;
    }

    Также стоит помнить о том что в Chrome есть такое свойство как playbackRate, которое Puppeteer нам позволяет менять, и который отвечает за скорость выполнения всех анимаций.

    С js-анимациями все гораздо сложнее. Фактически, это исполнительный код нашей программы, менять который “снаружи” довольно проблематично и, скорее всего, данные анимации придётся отключать изнутри. Например можно обернуть приложение в react-context, в который добавить значение которое компоненты-подписчики будут считывать и отключать анимации.

  4. Всякие внешние интергации типа сторонних микрофронтендов, рекламных блоков и баннеров мы просто скрываем. Можно просто добавить через css-селектор opacity: 0.

Как и где применять

Скриншот-тесты применимы в любом проекте с визуальным интерфейсом. Но особенно они полезны в следующих случаях:

1. Если требуется Пиксель-перфект верстка

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

2. Кроссбраузерность и адаптивность

Когда важно, как страница выглядит в Safari, Chrome и Firefox, или при разных разрешениях экрана — визуальные тесты помогают автоматизировать эти проверки.

3. Регрессионное тестирование

После каждого рефакторинга или обновления зависимостей вы можете не заметить, что “поехала” верстка. Скриншот-тесты сработают как защита от таких неожиданных багов.

4. Минимизация ручного тестирования

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

5. Разработка UI-библиотек и компонентов

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

6. TDD для фронтенда

Можно использовать макеты из Figma как “ожидаемый результат” и писать компоненты до тех пор, пока они не совпадут со скриншотом. Это открывает путь к визуальному TDD, где результат виден сразу.

Заключение

Скриншот-тестирование закрывает ту зону, которую игнорируют юнит- и е2е-тесты — визуальное отображение интерфейса. Это мощный инструмент для регрессии, TDD и отказа от ручных проверок, особенно если на Вашем проекте практикуется пиксель-перфект верстка и поддержка различных браузеров.

Несмотря на первоначальную сложность, инструменты становятся проще, быстрее и стабильнее. А в будущем — вероятно, скриншот-тесты станут таким же стандартом, как eslint или unit-тесты сегодня.

Если вы ещё не пробовали — самое время. Даже один визуальный тест может показать баг, который не видит остальной тестовый набор.

Комментарии (2)


  1. apevzner
    05.07.2025 11:58

    Говорят, в Gnome используется visual recognition для тестирования экранного отображения.

    Мы сейчас в OpenPrinting работаем над аналогичным проектом для тестирования преобразования изображения фильтрами при печати.

    Так что похоже, это постепенно становится main stream...

    Но попиксельно сравнивать в бровсере, такое себе. Тут надо бы проверять, что структура картинки соответствует ожидаемому. А расхождения на уровне пикселей могут зависеть от фонтов, видеокарты. Наверное, даже особенности реализации floating point math на целевой платформе могут на это повлиять.


  1. olku
    05.07.2025 11:58

    Не рассматривали возможность использовать векторизацию для сравнения? Зачастую пиксельная точность никому не нужна и только удорожает тестирование.