Snapshot testing
Урок: Snapshot testing
Введение
Представь, что ты разрабатываешь интерфейс: страница логина, список товаров, карточка пользователя. Ты открываешь страницу, смотришь — всё выглядит правильно. Через неделю вносишь изменения в код, запускаешь проект… и вроде бы всё работает, но что-то «поехало»: кнопка сместилась, текст исчез или класс изменился.
Такие изменения легко пропустить, особенно если проект большой.
Теперь представь, что у тебя есть «фотография» страницы или компонента в правильном состоянии. И каждый раз после изменений ты автоматически сравниваешь текущее состояние с этой «фотографией». Если что-то изменилось — тест падает.
Это и есть идея snapshot testing — сравнение текущего состояния с сохранённым эталоном.
В Playwright это особенно полезно, потому что мы тестируем реальный интерфейс в браузере, а значит можем проверять не только данные, но и внешний вид.
Официальная документация: Visual comparisons | Playwright. API сравнения страницы/элемента: expect(page).toHaveScreenshot, expect(locator).toHaveScreenshot.
Что такое snapshot testing простыми словами
Snapshot testing — это проверка, при которой мы:
- Сохраняем текущее состояние (например, HTML или скриншот);
- В следующих запусках сравниваем новое состояние с сохранённым;
- Если есть различия — тест падает.
Важно понимать:
мы не пишем вручную ожидания вроде 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:
- Делается новый скриншот;
- Он сравнивается с сохранённым;
- Если разница превышает допустимый порог — тест падает.
Можно управлять чувствительностью и составом кадра.
Пример с настройками
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.