Попиксельное сравнение скриншотов ломается в тот момент, когда рендеринг шрифтов отличается между версиями ОС, анимация захвачена в промежуточном кадре или изменился timestamp. AI-инструменты визуального тестирования Percy и Applitools заменяют пиксельные диффы на семантическое сравнение: отличают настоящую регрессию вёрстки от шума рендеринга. Ниже разобрано как работает каждый подход, как обрабатывать динамический контент и когда стоимость AI-инструмента оправдана в сравнении со встроенными ассёртами скриншотов Playwright.

Проблема с попиксельным сравнением

// Традиционное сравнение скриншотов
test('homepage looks correct', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveScreenshot('homepage.png');  // Падает на разнице в 1px
});

Ломается из-за:

  • Различий рендеринга шрифтов между ОС и версиями браузера
  • Анимаций захваченных в разные кадры
  • Динамического контента (временные метки, имена пользователей, рекламные блоки)
  • Вариаций сглаживания
  • Различий положения прокрутки

Выбор невелик: постоянные ложные падения или смягчённые пороговые значения которые пропускают реальные визуальные баги.

Как работает AI-визуальное тестирование

AI-сервисы визуального тестирования используют компьютерное зрение и машинное обучение:

1. Понимают вёрстку: знают что кнопка это кнопка, а не просто пиксели

2. Игнорируют нерелевантные различия: вариации рендеринга текста, незначительные отступы

3. Помечают значимые изменения: сдвиги вёрстки, отсутствующие элементы, изменения цвета, наложения

4. Группируют похожие падения: 50 тестов показывающих один баг становятся одной сгруппированной проблемой

AI обучен на тысячах реальных изменений UI, различая баги от шума рендеринга.

Percy (BrowserStack)

Percy это наиболее устоявшийся AI-инструмент визуального тестирования, приобретённый BrowserStack.

Настройка с Playwright

npm install --save-dev @percy/cli @percy/playwright

// tests/visual.spec.ts
import { test } from '@playwright/test';
import percySnapshot from '@percy/playwright';

test('homepage visual', async ({ page }) => {
  await page.goto('/');
  await percySnapshot(page, 'Homepage');
});

test('login page visual', async ({ page }) => {
  await page.goto('/login');
  await percySnapshot(page, 'Login Page');
});

test('dashboard after login', async ({ page }) => {
  await page.goto('/login');
  await page.fill('[data-testid="email"]', 'user@test.com');
  await page.fill('[data-testid="password"]', 'ValidPass1');
  await page.click('[data-testid="submit"]');
  await page.waitForURL('/dashboard');
  
  await percySnapshot(page, 'Dashboard - Authenticated');
});

Запуск Percy

# Установи Percy токен (из app.percy.io)
PERCY_TOKEN=your_token npx percy exec -- npx playwright test

При первом запуске Percy создаёт базовые скриншоты. Последующие запуски сравниваются с базой: AI Percy отмечает реальные визуальные изменения для проверки человеком.

Интеграция с CI

# .github/workflows/visual-tests.yml
- name: Run Percy visual tests
  run: npx percy exec -- npx playwright test tests/visual.spec.ts
  env:
    PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}

Percy публикует результаты как проверку PR: визуальные диффы появляются прямо в GitHub pull request.

Applitools Eyes

Applitools использует движок «Visual AI», который позиционируется как более точный по сравнению с пиксельным сравнением. Поддерживает адаптивное тестирование и сравнение на уровне компонентов.

npm install --save-dev @applitools/eyes-playwright

import { test } from '@playwright/test';
import { Eyes, Target, Configuration } from '@applitools/eyes-playwright';

test('visual regression', async ({ page }) => {
  const eyes = new Eyes();
  
  const configuration = new Configuration();
  configuration.setApiKey(process.env.APPLITOOLS_API_KEY!);
  eyes.setConfiguration(configuration);
  
  await eyes.open(page, 'My App', 'Homepage Test');
  
  await page.goto('/');
  await eyes.check('Homepage', Target.window().fully());
  
  await page.goto('/products');
  await eyes.check('Products Page', Target.window().fully());
  
  await eyes.close();
});

Ultrafast Grid

Ключевая функция Applitools: Ultrafast Grid рендерит снимок DOM в нескольких браузерах и вьюпортах одновременно, без запуска браузеров на твоей машине:

import { VisualGridRunner, BrowserType, DeviceName, ScreenOrientation } from '@applitools/eyes-playwright';

const runner = new VisualGridRunner({ testConcurrency: 5 });

const configuration = new Configuration();
configuration.addBrowser(1280, 800, BrowserType.CHROME);
configuration.addBrowser(1440, 900, BrowserType.FIREFOX);
configuration.addDeviceEmulation(DeviceName.iPhone_12, ScreenOrientation.PORTRAIT);
configuration.addDeviceEmulation(DeviceName.iPad_Pro, ScreenOrientation.LANDSCAPE);

Один запуск Playwright-тестов, визуальные результаты для 4 конфигураций браузер/устройство.

Встроенное сравнение скриншотов Playwright

В Playwright есть базовое визуальное сравнение без AI:

// Встроенное: пиксельное сравнение с настраиваемым порогом
test('homepage screenshot', async ({ page }) => {
  await page.goto('/');
  
  // Допускаем до 1% разницы пикселей
  await expect(page).toHaveScreenshot('homepage.png', {
    maxDiffPixelRatio: 0.01,
  });
  
  // Или устанавливаем абсолютное количество пикселей
  await expect(page).toHaveScreenshot('homepage.png', {
    maxDiffPixels: 50,
  });
});

// Маскируем динамический контент
await expect(page).toHaveScreenshot('homepage.png', {
  mask: [
    page.getByTestId('timestamp'),
    page.getByTestId('user-avatar'),
    page.getByTestId('ad-banner'),
  ],
});

Когда использовать встроенное: простые проекты, статичные страницы, контролируемое окружение. Когда использовать AI-инструменты: кросс-браузерное тестирование, несколько устройств, динамический контент, командный ревью визуальных изменений.

Обработка динамического контента

Главный вызов в визуальном тестировании: контент который меняется в штатном режиме.

Маскирование динамических элементов

// Percy
await percySnapshot(page, 'Dashboard', {
  percyCSS: `
    [data-testid="timestamp"] { visibility: hidden; }
    [data-testid="user-avatar"] { filter: blur(10px); }
  `,
});

// Встроенный Playwright
await expect(page).toHaveScreenshot({
  mask: [
    page.locator('[data-testid="timestamp"]'),
    page.locator('.ad-container'),
  ],
});

Ожидание стабильного состояния

test('chart visual test', async ({ page }) => {
  await page.goto('/analytics');
  
  // Ждём завершения анимаций
  await page.waitForLoadState('networkidle');
  await page.waitForTimeout(500);  // Буфер для CSS-переходов
  
  // Ждём конкретный элемент означающий загрузку данных
  await page.waitForSelector('[data-testid="chart-loaded"]');
  
  await percySnapshot(page, 'Analytics Dashboard');
});

Заморозка времени

// Замораживаем Date чтобы timestamps не менялись между запусками
await page.addInitScript(() => {
  const fixedDate = new Date('2026-01-15T12:00:00Z');
  Date.now = () => fixedDate.getTime();
  Date = class extends Date {
    constructor(...args) {
      if (args.length === 0) {
        super(fixedDate.getTime());
      } else {
        super(...args);
      }
    }
  };
});

Визуальное тестирование компонентов

Вместо скриншотов целых страниц: тестируй отдельные компоненты.

test('button variants visual test', async ({ page }) => {
  await page.goto('/storybook/button');
  
  // Тестируем каждый вариант кнопки
  const variants = ['primary', 'secondary', 'danger', 'ghost'];
  
  for (const variant of variants) {
    await page.click(`[data-story="${variant}"]`);
    await percySnapshot(page, `Button - ${variant}`);
  }
});

Тестирование компонентов устойчивее, чем тестирование целых страниц: меньше движущихся частей, легче изолировать что изменилось.

Организация рабочего процесса визуального тестирования

1. Создание базовых скриншотов

Первый запуск создаёт базовые скриншоты. Просматривай их внимательно: «одобряй» только корректные визуальные состояния.

2. Рабочий процесс PR

  • Разработчик вносит изменения
  • CI запускает визуальные тесты
  • Диффы публикуются в PR
  • QA или разработчик просматривает диффы
  • Одобряет ожидаемые изменения (новая функция выглядит правильно), отклоняет неожиданные (баг)

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

При намеренном изменении UI нужно обновить базу. Для Percy: одобри диффы в дашборде Percy. Для Playwright: запусти с флагом --update-snapshots.

npx playwright test --update-snapshots

Закоммить обновлённые скриншоты вместе с PR.

Сравнение стоимости

| Инструмент | Бесплатный уровень | Платный |

|------------|-------------------|---------|

| Playwright (встроенный) | Бесплатно | Бесплатно |

| Percy | 5 000 скриншотов/мес | от $99/мес |

| Applitools | Ограниченный trial | По запросу |

| Chromatic | 5 000 снимков/мес | от $149/мес |

Для небольших проектов: встроенный Playwright с маскированием и настройкой порогов. Для команд с частыми релизами: Percy или Applitools; AI-ревью диффов сэкономит часы ручного сравнения.

Итог

| Подход | Точность | Стоимость | Лучше всего для |

|--------|----------|-----------|-----------------|

| Playwright (встроенный) | Попиксельный | Бесплатно | Статичные страницы, контролируемая среда |

| Percy | AI-powered | от $99/мес | Кросс-браузерное, командный ревью |

| Applitools | Visual AI + grid | По запросу | Enterprise, несколько браузеров |

Ключевые принципы:

  • Маскируй динамический контент (временные метки, реклама, аватары)
  • Жди стабильного состояния перед скриншотом
  • Просматривай базовые скриншоты внимательно перед одобрением
  • Группируй визуальные тесты по странице или компоненту для удобства ревью

AI-визуальное тестирование не устраняет ложные срабатывания полностью: оно резко сокращает их. Диффы которые доходят до стадии ревью гораздо чаще оказываются реальными проблемами.

→ See also: Визуальное регрессионное тестирование с Playwright: toHaveScreenshot без Applitools | Assertions в Playwright: полное руководство | Кросс-браузерное тестирование с Playwright: Chrome, Firefox, Safari