Блог
Автотесты/Playwright

Скриншот-тестирование в Playwright: toHaveScreenshot

Скриншот-тестирование (visual regression testing) — это сравнение текущего вида страницы или элемента с ранее сохранённым эталонным скриншотом, чтобы поймать неожиданные визуальные изменения — съехавшую вёрстку, пропавший отступ, изменившийся цвет — которые обычные функциональные тесты не заметят, потому что элемент технически есть на странице и на него можно кликнуть, просто он выглядит не так. В Playwright это делается через expect(page).toHaveScreenshot().

Базовое сравнение скриншотов

test('главная страница выглядит как эталон', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png');
});

При первом запуске такого теста эталонного скриншота ещё нет — Playwright создаёт его автоматически и сохраняет рядом с тестом (в папке *-snapshots), но сам прогон в этом случае всё равно завершается ошибкой (статус fail, а не pass) с сообщением о том, что эталона не было и он только что записан как новый — это важно учитывать в CI, где первый прогон скриншот-теста красным упадёт даже без реального бага. При всех последующих запусках toHaveScreenshot() делает новый скриншот страницы и попиксельно сравнивает его с сохранённым эталоном. Если разница превышает допустимый порог — тест падает, и Playwright сохраняет три файла: эталон, актуальный скриншот и diff-изображение с подсвеченными различиями.

Скриншот конкретного элемента, а не всей страницы

const card = page.getByTestId('pricing-card-middle');
await expect(card).toHaveScreenshot('pricing-card.png');

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

Порог допустимых различий

await expect(page).toHaveScreenshot('homepage.png', {
maxDiffPixelRatio: 0.02,
});

Полное побитовое совпадение на практике почти недостижимо — рендеринг шрифтов, сглаживание, мелкие различия в рендер-движке между окружениями (локальная машина и CI) дают минимальные визуальные отличия даже при полностью идентичной вёрстке. maxDiffPixelRatio задаёт долю пикселей, которые могут отличаться, прежде чем тест считается упавшим (здесь — до 2%). Слишком строгий порог даёт ложные падения из-за шума рендеринга, слишком мягкий — пропускает реальные визуальные баги.

Обновление эталонных скриншотов

npx playwright test --update-snapshots
Когда вёрстка осознанно изменилась (новый дизайн, добавили элемент) и старые эталоны устарели законно, а не из-за бага — эталоны обновляют командой --update-snapshots. Playwright перезаписывает сохранённые скриншоты текущим состоянием страницы. Важно: эту команду стоит запускать осознанно, после того как разработчик или дизайнер подтвердил, что новый вид — это то, что нужно, а не автоматически при каждом падении теста — иначе скриншот-тесты перестанут защищать хоть от чего-то.

Нестабильность скриншотов между окружениями

Один из главных практических подводных камней скриншот-тестирования — рендеринг зависит от операционной системы, версии браузера и даже видеокарты: шрифты и сглаживание на macOS и Linux отличаются достаточно, чтобы эталон, сделанный на одной машине, не совпадал с реальным рендером на другой. Из-за этого скриншот-тесты обычно запускают и обновляют эталоны в одном фиксированном окружении — чаще всего внутри Docker-контейнера с зафиксированной версией браузера, который используется одинаково и локально, и на CI, чтобы исключить расхождения, вызванные разным окружением, а не реальными изменениями вёрстки.

Частые вопросы про скриншот-тестирование в Playwright

Что произойдёт при самом первом запуске toHaveScreenshot(), если эталона ещё нет? Playwright создаст новый эталонный скриншот автоматически и сохранит его — но сам прогон при этом завершится ошибкой (fail, а не pass), с сообщением о том, что эталона не было. Это стоит заранее учитывать в CI: первый запуск нового скриншот-теста упадёт красным, даже если реального визуального бага нет.
Почему скриншот-тесты падают на CI, хотя локально всё совпадает? Чаще всего из-за разницы в рендеринге между операционными системами или версиями браузера. Решение — фиксировать окружение для скриншот-тестов, например через Docker-образ с одной и той же версией браузера, используемый одинаково локально и в CI.
Как проверить только один элемент, а не всю страницу? Вызвать toHaveScreenshot() не на page, а на локаторе конкретного элемента: await expect(locator).toHaveScreenshot('name.png'). Это даёт более узкий и стабильный тест.
Нужно ли скриншот-тестировать вообще всё приложение? Обычно нет — скриншот-тесты дороже в поддержке, чем обычные функциональные, из-за чувствительности к любым визуальным изменениям, в том числе намеренным. Практичнее покрывать ключевые, редко меняющиеся экраны и переиспользуемые компоненты (например, карточки, кнопки, header), а не каждую страницу целиком.

Закрепить скриншот-тестирование на практике

Понять, какие компоненты стоит покрывать визуальными тестами, а какие нет, проще на реальном проекте. Бесплатный тренажёр Playwright Arena от Quality Academy — 30 задач в трёх модулях.

-------

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

Сайт Quality Academy:
/main
Telegram-канал:
https://t.me/quality_academy
YouTube-канал:
https://www.youtube.com/@quality_academy
ВКонтакте:
https://vk.com/quality_academy
Канал отзывов учеников (58+ отзывов):
https://t.me/quality_academy_reviews
Задать вопрос менеджеру:
https://t.me/quality_academy_bot
Тест «Подойдёт ли вам тестирование»:
https://quiz.quality-academy.ru
5000+ вопросов с собесов тестировщика — бот-тренажёр:
https://t.me/quality_academy_interview_bot
3 практические задачи и дорожная карта:
https://t.me/quality_academy_tasks_bot
Бесплатные тренажёры — SQL Arena, Playwright Arena, Python Arena:
SQL Arena · Playwright Arena · Python Arena