Snapshot testing

Урок: Snapshot testing

Введение

Представь, что ты разрабатываешь интерфейс: страница логина, список товаров, карточка пользователя. Ты открываешь страницу, смотришь — всё выглядит правильно. Через неделю вносишь изменения в код, запускаешь проект… и вроде бы всё работает, но что-то «поехало»: кнопка сместилась, текст исчез или класс изменился.

Такие изменения легко пропустить, особенно если проект большой.

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

Это и есть идея snapshot testing — сравнение текущего состояния с сохранённым эталоном.

В Playwright это особенно полезно, потому что мы тестируем реальный интерфейс в браузере, а значит можем проверять не только данные, но и внешний вид.

Официальная документация: Visual comparisons | Playwright. API сравнения страницы/элемента: expect(page).toHaveScreenshot, expect(locator).toHaveScreenshot.


Что такое snapshot testing простыми словами

Snapshot testing — это проверка, при которой мы:

  1. Сохраняем текущее состояние (например, HTML или скриншот);
  2. В следующих запусках сравниваем новое состояние с сохранённым;
  3. Если есть различия — тест падает.

Важно понимать: мы не пишем вручную ожидания вроде expect(text).toBe(...). Мы говорим: «вот эталон — сравни с ним».


Как это выглядит в Playwright

В Playwright snapshot testing чаще всего используется через скриншоты.

Пример: проверка страницы через скриншот

import { test, expect } from '@playwright/test';

test('главная страница выглядит правильно', async ({ page }) => {
  await page.goto('https://example.com');

  await expect(page).toHaveScreenshot();
});

Что происходит в этом коде

  • toHaveScreenshot() делает снимок страницы;
  • при первом запуске создаётся snapshot (файл изображения);
  • при следующих запусках Playwright сравнивает текущий скриншот с сохранённым;
  • если есть различия — тест падает.

Важный момент

Эталонные файлы попадают в каталог <имя-файла-теста>-snapshots, рядом со спеком (его нужно коммитить в git). Имя файла включает имя снимка, браузер и платформу (или имя проекта из playwright.config, если проектов несколько) — у разных ОС и движков рендер отличается, поэтому эталоны обычно разные.

При первом успешном прогоне Playwright делает серию снимков, пока два подряд не совпадут, и сохраняет последний — так снижается гонка с незаконченной отрисовкой.

Предупреждение из документации: скриншоты зависят от ОС, версии браузера, шрифтов, headless/headed, питания ноутбука и др. Для стабильности прогоняй сравнение в том же окружении, где создавался эталон (часто фиксируют Docker или один и тот же CI-раннер).


Snapshot конкретного элемента

Иногда не нужно проверять всю страницу — только часть интерфейса.

Пример

test('карточка товара выглядит корректно', async ({ page }) => {
  await page.goto('https://example.com/product');

  const card = page.getByTestId('product-card');

  await expect(card).toHaveScreenshot();
});

Что здесь важно

  • locator находит конкретный элемент;
  • snapshot создаётся только для него;
  • это делает тест более стабильным и точным.

Почему это полезно

Если проверять всю страницу, тест может падать из-за мелких изменений (баннер, реклама, время). А проверка конкретного элемента делает тест более надёжным.


Как работает сравнение

Когда Playwright сравнивает snapshot:

  1. Делается новый скриншот;
  2. Он сравнивается с сохранённым;
  3. Если разница превышает допустимый порог — тест падает.

Можно управлять чувствительностью и составом кадра.

Пример с настройками

await expect(page).toHaveScreenshot({
  maxDiffPixels: 100,
  fullPage: true,
  animations: 'disabled',
});

Что это значит

  • maxDiffPixels / maxDiffPixelRatio — допуск по библиотеке pixelmatch; общие значения можно задать в конфиге: expect: { toHaveScreenshot: { … } } в defineConfig — см. Options.
  • fullPage — снимок всей прокручиваемой страницы.
  • animations: 'disabled' — останавливает CSS-анимации/переходы перед снимком, чтобы убрать «размазанные» кадры.
  • mask — список локаторов, которые закрываются цветной подложкой (например, динамический баннер или часы).
  • stylePath — подключить CSS на время снимка (скрыть iframe, шумные блоки) — stylePath.
  • threshold — чувствительность на уровне пикселя для pixelmatch.

Имя файла эталона можно задать первым аргументом: toHaveScreenshot('landing.png') или массивом сегментов пути внутри каталога -snapshots — см. Generating screenshots. Шаблон путей настраивается через snapshotPathTemplate в конфиге.


Обновление snapshot

Иногда изменения в интерфейсе — это нормально. Например, ты изменил дизайн кнопки.

В этом случае snapshot нужно обновить.

В Playwright это делается через запуск тестов с флагом:

npx playwright test --update-snapshots

Что происходит

  • старые snapshot-файлы заменяются новыми;
  • теперь новые скриншоты считаются эталоном.

Важно: обновлять snapshot нужно осознанно, а не «чтобы тесты зелёные были».

Для выборочного обновления можно указывать файлы/строки grep у playwright test, чтобы не перезаписать весь проект эталонов.


Не только картинки: toMatchSnapshot для текста и данных

Помимо toHaveScreenshot, Playwright Test поддерживает expect(value).toMatchSnapshot(name) для текста или бинарных буферов — тип контента определяется автоматически. Файлы эталонов лежат в том же каталоге *-snapshots, их тоже версионируют.

Пример из документации:

import { test, expect } from '@playwright/test';

test('заголовок героя', async ({ page }) => {
  await page.goto('https://playwright.dev');
  expect(await page.locator('.hero__title').textContent()).toMatchSnapshot('hero.txt');
});

Для HTML можно снимать innerHTML() / textContent() и сравнивать через toMatchSnapshot, но большие HTML-снимки плохо ревьюятся; часто выгоднее сузить проверку до текста, ARIA или toMatchAriaSnapshot.


Когда snapshot testing действительно полезен

Snapshot testing особенно хорошо работает в задачах:

  • проверка UI (layout, стили, визуальные элементы);
  • регрессионное тестирование интерфейса;
  • дизайн-системы и компоненты;
  • страницы с фиксированным контентом.

Например, карточка товара, кнопка, форма — идеальные кандидаты.


Ограничения и подводные камни

Snapshot testing — мощный инструмент, но его легко использовать неправильно.

Главная проблема — «слепое обновление snapshot».

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

Также:

  • динамический контент (дата, время, анимации) ломает snapshot;
  • слишком большие snapshot сложно читать и поддерживать;
  • изменение шрифтов или окружения может давать ложные падения.

Поэтому важно:

  • изолировать стабильные части интерфейса;
  • не использовать snapshot там, где лучше явные проверки;
  • понимать, что именно ты проверяешь.

Практический смысл

В реальной работе snapshot testing:

  • защищает от незаметных изменений UI;
  • ускоряет написание тестов (меньше ручных проверок);
  • помогает быстро находить регрессии;
  • особенно полезен в CI/CD — сразу видно, что «сломалось».

В командах это часто используется как дополнительный слой защиты поверх обычных тестов.


Итоговое понимание

Snapshot testing в Playwright — это в первую очередь expect(...).toHaveScreenshot() (страница или локатор) и при необходимости toMatchSnapshot для текста/бинарных данных.

Из документации:

  • эталоны в *-snapshots, имена завязаны на браузер/платформу или проект;
  • первый прогон создаёт файлы; дальше — сравнение с допусками (maxDiffPixels и др.);
  • для стабильности — animations: 'disabled', mask, stylePath, снимок компонента, а не всей страницы;
  • обновление — --update-snapshots, только после ревью диффа.

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