22. Visual Testing

Что такое Visual Testing?
Заголовок раздела «Что такое Visual Testing?»Visual testing сравнивает скриншоты: при первом запуске создаётся эталон, при последующих — сравнивается с ним.
Screenshot assertions
Заголовок раздела «Screenshot assertions»// Весь viewportawait expect(page).toHaveScreenshot('homepage.png');
// Отдельный элементawait expect(page.locator('.hero-section')).toHaveScreenshot('hero.png');
// С настройкамиawait expect(page).toHaveScreenshot('dashboard.png', { maxDiffPixels: 100, // допустимое число отличающихся пикселей threshold: 0.2, // порог разницы (0-1) animations: 'disabled', // отключить анимации mask: [page.locator('.timestamp')], // скрыть динамические элементы});Первый запуск (создание эталона)
Заголовок раздела «Первый запуск (создание эталона)»# Создать эталонные скриншотыnpx playwright test --update-snapshots
# Эталоны сохраняются в:# tests/e2e/__snapshots__/homepage.spec.ts-snapshots/Маскирование динамических элементов
Заголовок раздела «Маскирование динамических элементов»// Скрыть элементы с динамическим контентомawait expect(page).toHaveScreenshot('page.png', { mask: [ page.locator('.timestamp'), page.locator('.user-avatar'), page.locator('[data-dynamic]'), ], maskColor: '#FF00FF', // цвет маски (для отладки)});
// Стабилизировать перед скриншотомawait page.evaluate(() => { // Остановить анимации document.querySelectorAll('*').forEach(el => { (el as HTMLElement).style.animationPlayState = 'paused'; });});await expect(page).toHaveScreenshot('stable.png');Полная страница
Заголовок раздела «Полная страница»await expect(page).toHaveScreenshot('full-page.png', { fullPage: true, // весь контент, не только viewport});Playwright Report — просмотр diff
Заголовок раздела «Playwright Report — просмотр diff»npx playwright show-reportВ отчёте видно:
- Эталон
- Актуальный скриншот
- Diff (разница выделена красным)
Pixel-by-pixel сравнение (самостоятельно)
Заголовок раздела «Pixel-by-pixel сравнение (самостоятельно)»import { PNG } from 'pngjs';import pixelmatch from 'pixelmatch';
test('visual comparison', async ({ page }) => { await page.goto('/dashboard');
const screenshot = await page.screenshot(); const baseline = fs.readFileSync('./baselines/dashboard.png');
const img1 = PNG.sync.read(baseline); const img2 = PNG.sync.read(screenshot);
const diff = new PNG({ width: img1.width, height: img1.height }); const numDiffPixels = pixelmatch( img1.data, img2.data, diff.data, img1.width, img1.height, { threshold: 0.1 } );
expect(numDiffPixels).toBeLessThan(100);});Responsive тестирование
Заголовок раздела «Responsive тестирование»const viewports = [ { width: 320, height: 568, name: 'mobile' }, { width: 768, height: 1024, name: 'tablet' }, { width: 1440, height: 900, name: 'desktop' },];
for (const viewport of viewports) { test(`looks correct on ${viewport.name}`, async ({ page }) => { await page.setViewportSize({ width: viewport.width, height: viewport.height }); await page.goto('/');
await expect(page).toHaveScreenshot(`home-${viewport.name}.png`); });}
// Или через playwright.config.ts projectsprojects: [ { name: 'Mobile', use: { ...devices['iPhone 12'] }, }, { name: 'Desktop', use: { viewport: { width: 1440, height: 900 } }, },],Интеграция с Storybook
Заголовок раздела «Интеграция с Storybook»// Визуальное тестирование UI компонентов в Storybook// npx playwright test --config=storybook.config.ts
import { test, expect } from '@playwright/test';
test('Button component visual', async ({ page }) => { await page.goto('http://localhost:6006/iframe.html?id=button--primary');
await expect(page.locator('#storybook-root')).toHaveScreenshot('button-primary.png');});// 1. Отключить анимации в конфигеexport default defineConfig({ use: { animations: 'disabled', },});
// 2. Очищать динамические данные перед скриншотомawait page.evaluate(() => { document.querySelectorAll('.timestamp').forEach(el => { el.textContent = 'Jan 1, 2024'; });});
// 3. Подождать стабильностьawait page.waitForLoadState('networkidle');await page.waitForTimeout(500); // иногда нужноПрактические задания
Заголовок раздела «Практические задания»- Добавь visual тесты для главной страницы и страницы товаров
- Настрой маскирование для timestamp и avatars
- Проверь responsive дизайн на трёх разных разрешениях
- Visual testing ловит неожиданные визуальные регрессии
- toHaveScreenshot() — автоматическое сравнение с эталоном
- mask — скрыть динамические элементы
- —update-snapshots — обновить эталоны после изменений дизайна