automationvisual-testing

Visual Regression тестирование: гайд от А до Я

Функциональный тест зелёный, кнопка кликается, форма отправляется — а юзер смотрит на экран и видит, что иконка съехала на 4 пикселя влево и наезжает на текст. Классический разрыв между unit/E2E-тестами и реальностью UI. Visual regression закрывает эту дыру: ты автоматически сравниваешь скриншоты до и после изменения, и любое расхождение становится фейлом теста.

Что это и зачем

Визуальная регрессия — это автотест, который:

  • Открывает страницу/экран в одинаковом состоянии.
  • Делает скриншот.
  • Сравнивает с эталоном (baseline) попиксельно или с помощью visual diff.
  • Если расхождение > threshold — тест красный.

Главная ценность — ловит то, что не покрывает функциональное тестирование: смещения, переливающиеся цвета, кривые шрифты после миграции, поломанные тени, ошибки рендера на разных DPI.

Где это критично

  • Дизайн-системы и UI-библиотеки. Один компонент используется в 50 местах — изменил padding, не заметил, поломал 30 экранов.
  • E-commerce / pricing pages. Цена сместилась, скидка не там — конверсия упала, никто не знает почему.
  • Маркетинговые лендинги. Каждый пиксель важен, а билды собираются ежедневно.
  • Мобильные игры. UI меняется часто, новые экраны добавляются спринтами. Тут особенно ценно — функциональным тестом 100% не покроешь.
  • Cross-browser / cross-platform. Visual regression — единственный способ поймать что на Safari иконка SVG рендерится иначе чем в Chrome.

Где не надо

  • Highly dynamic content: ленты соцсетей, news feed, real-time charts. Скриншоты будут красными всегда — ложно-позитивные результаты.
  • Анимации и transitions без специальной подготовки — нужно либо отключать анимации, либо ждать стабильного состояния.
  • MVP-стадия: UI ещё меняется каждую неделю. Baselines надо обновлять чаще чем смотреть. Хаос.

Как работает workflow

  • Baseline capture: первый раз тест делает screenshot и сохраняет как «эталон» (обычно в репо или в облаке тула).
  • Test run: при следующем запуске делается новый screenshot.
  • Diff computation: пиксельное сравнение или AI-based diff. Расхождение измеряется в процентах или в количестве отличающихся пикселей.
  • Review: если diff > threshold, разработчик смотрит на скриншоты до/после, принимает решение — это баг или ожидаемое изменение. Если ожидаемое — обновляет baseline.

Тулы — обзор

Cloud-сервисы (платные, удобные)

  • Percy (BrowserStack) — самый известный. Интеграция с Selenium, Cypress, Playwright, Puppeteer. Cross-browser, parallel snapshot processing, PR-comment с визуальным diff. От $149/мес.
  • Chromatic — заточен под React/Vue/Angular + Storybook. Каждый компонент в Storybook автоматически становится visual-тестом. Идеален если у вас есть дизайн-система. Free tier на 5000 snapshots.
  • Applitools — самый умный AI diff. Не падает на анти-алиасинге, понимает «эту фичу переместили на 3 пикселя — это норма». Дорого ($прайс по запросу), но если бюджет позволяет — лучший в классе.

Open-source

  • BackstopJS — старая школа, JSON-конфиг, headless Chrome. Бесплатно, гибко. Self-hosted.
  • lost-pixel — open-source, интеграция со Storybook и Playwright. Можно self-host или использовать их облако.

Встроенные в фреймворки

  • Playwright Screenshots — нативная команда await expect(page).toHaveScreenshot(). Baseline хранится в репо. Бесплатно, без отдельной инфраструктуры. Идеально для старта.
  • Cypress Visual Testing — через плагины (cypress-image-snapshot, Percy/Applitools-интеграции).
  • Storybook Visual Testing — нативная поддержка через Chromatic.

Главная проблема: ложные срабатывания

80% времени с visual regression уходит на борьбу с flaky screenshots. Источники нестабильности:

  • Динамическое время — у вас на экране клок «Last updated 3 min ago». Каждый прогон — новое значение. Решение: мокать время через Date.now override, или замаскировать элемент.
  • Рандом — генерация UUID, случайные баннеры, A/B-тестируемые элементы. Решение: фиксированный seed для рандома в тестовом окружении.
  • Шрифты — Web Fonts загружаются после render, скриншот пойман в момент fallback-fonts. Решение: ждать document.fonts.ready перед snapshot.
  • Анимации — элемент в середине fade-in, момент пойман на 40% opacity. Решение: * { animation-duration: 0s !important; transition-duration: 0s !important; } в тестовом CSS.
  • Анти-алиасинг — на разных GPU/драйверах одна и та же кривая рендерится с разными краевыми пикселями. Решение: pixel-level threshold (3-5% allowed diff) или AI diff (Applitools умеет).
  • Loading states — изображения подгружаются асинхронно. Решение: ждать всех img на load или мокать через сервис-воркер.

Best practices

Detеrministic state

Перед snapshot привести систему в идентичное состояние:

  • Мокать backend API (MSW, WireMock, Mirage) — одинаковые данные при каждом прогоне.
  • Зафиксировать time/date: clock.tick() в Cypress, page.clock в Playwright.
  • Отключить анимации глобально в test-окружении.
  • Ждать конкретного события «всё загрузилось» (a network idle + custom signal), не sleep.

Маскирование dynamic regions

Если не получается полностью застабилизировать — маскируйте. Playwright: await expect(page).toHaveScreenshot({ mask: [page.locator('.timestamp')] }) — закрасит чёрным область с динамическим контентом, остальное сравнит.

Branch-based baseline

Делать baseline на main, тестировать против неё в feature-branches. Когда merge — новые baseline становятся master. Так нет хаоса «у меня локально другой эталон».

Threshold tuning

Не ставьте 0% diff — будет постоянно красное от анти-алиасинга. 0.1-0.5% — типичный starting point. Если падают тесты на real-changes — снижайте. Если шумят — повышайте.

Размер скриншотов и storage

Скриншоты тяжёлые. 1000 тестов × 5 экранов × 3 viewport = 15000 файлов. В Git это не лежит — используйте либо облачный тул (Percy/Chromatic хранят у себя), либо Git LFS, либо S3.

Storybook + Chromatic — золотой стандарт для веба

Если у вас компонентная архитектура (React/Vue/Angular):

  • Каждый компонент в Storybook с пачкой stories (разные пропсы, состояния).
  • Chromatic подключается одной командой и автоматически генерирует visual test на каждую story.
  • На каждый PR — Chromatic комментирует «изменены такие-то компоненты, посмотри здесь».
  • Покрытие получается очень большое за минимум усилий, потому что Storybook у вас и так был.

Для мобильных приложений

  • Native iOS / Android: snapshot-testing от PointFree для iOS (популярный), screenshot-tests-for-android от Facebook (старый, но рабочий), Paparazzi (нативный JVM, не требует эмулятора).
  • React Native: react-native-storybook + Chromatic, либо @storybook/react-native + lost-pixel.
  • Unity: нет out-of-the-box тула. Делают вручную через ScreenCapture + image diff библиотеку, интегрируют в build pipeline. Применимо для UI Canvas (HUD, popups), не для 3D-сцен с динамикой.

CI интеграция

Минимальный pipeline:

  • Установить тул (npm/pip).
  • Запустить тесты с baseline-режимом на main — сохранить эталоны.
  • В PR-пайплайне: запустить с compare-режимом — фейлить если diff.
  • Запостить визуальный diff в коммент PR (Percy/Chromatic это делают автоматически, для open-source тулов — отдельно).
  • Optional: auto-merge baseline после ручного approve («yes, this change is intentional»).

Когда лучше не внедрять

  • Команда из 1 разработчика и QA, продукт меняется каждую неделю — overhead обновления baseline превысит пользу.
  • UI на 90% состоит из dynamic content (data tables, feeds, charts) — слишком много маскирования, тесты теряют смысл.
  • Нет процесса review diff’ов — никто не смотрит на скриншоты, тесты просто фейлятся → их игнорируют → выключают. Лучше не заводить.

С чего начать

  • Выберите 5-10 самых важных экранов: главный, страница оплаты, корзина, профиль, ключевые popups.
  • Возьмите Playwright Screenshots (бесплатно, в репо) или Chromatic free tier (для Storybook).
  • Зафиксируйте mock-данные и время. Отключите анимации в тестовом CSS.
  • Прогоните 2-3 раза подряд — убедитесь что тесты стабильны на одинаковом коде.
  • Намеренно сломайте что-нибудь (поменяйте padding на 5px) — проверьте что тест краснеет.
  • Подключите в CI как blocking тест на PR.
  • Через 2 недели смотрите статистику: сколько раз поймал реальные регрессии vs ложные срабатывания. Корректируйте threshold.

Полезные ссылки