Коммерческие инструменты визуальной регрессии
Updated Jul 2026
Percy (BrowserStack)
Percy делает скриншоты в разных браузерах и viewport, затем подсвечивает визуальные изменения с интеллектуальным сравнением. Он интегрируется со всеми основными фреймворками тестирования и предоставляет веб-дашборд для ревью и одобрения изменений.
// tests/visual/homepage.spec.ts
import { test } from '@playwright/test';
import percySnapshot from '@percy/playwright';
test('homepage visual regression', async ({ page }) => {
await page.goto('/');
await page.waitForLoadState('networkidle');
// Capture at multiple widths
await percySnapshot(page, 'Homepage', {
widths: [375, 768, 1280],
minHeight: 1024,
percyCSS: `
/* Stabilize dynamic content for consistent screenshots */
[data-testid="timestamp"] { visibility: hidden; }
[data-testid="avatar"] { visibility: hidden; }
.animated { animation: none !important; }
`,
});
});
test('product card visual regression', async ({ page }) => {
await page.goto('/products');
// Wait for images to load
await page.waitForFunction(() => {
const images = document.querySelectorAll('img');
return Array.from(images).every(img => img.complete);
});
await percySnapshot(page, 'Product Listing', {
widths: [375, 1280],
});
});
test('checkout flow visual regression', async ({ page }) => {
// Navigate through checkout steps
await page.goto('/cart');
await percySnapshot(page, 'Cart Page', { widths: [375, 1280] });
await page.click('[data-testid="proceed-to-checkout"]');
await percySnapshot(page, 'Checkout - Shipping', { widths: [375, 1280] });
await page.fill('[data-testid="shipping-name"]', 'John Doe');
await page.fill('[data-testid="shipping-address"]', '123 Main St');
await page.click('[data-testid="continue-to-payment"]');
await percySnapshot(page, 'Checkout - Payment', { widths: [375, 1280] });
});
Конфигурация Percy для CI
# .github/workflows/percy.yml
name: Percy Visual Tests
on: [pull_request]
jobs:
visual:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx playwright install --with-deps
- name: Run Percy snapshots
env:
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
run: npx percy exec -- npx playwright test tests/visual/
Рабочий процесс одобрения Percy: скриншоты из PR-ветки сравниваются с базовой веткой. Неизменённые скриншоты одобряются автоматически. Изменённые скриншоты появляются в дашборде Percy для ревью. Одобренные изменения становятся новым базовым уровнем при слиянии PR.
Chromatic (Storybook)
Chromatic интегрируется напрямую со Storybook для тестирования компонентов в изоляции. Это ловит визуальные регрессии на уровне компонентов до того, как они накопятся в проблемы на уровне страницы.
// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
parameters: {
// Chromatic-specific configuration
chromatic: {
viewports: [320, 768, 1200],
delay: 300, // Wait for animations
diffThreshold: 0.063, // Sensitivity (default is 0.063)
},
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {
args: { variant: 'primary', children: 'Click me' },
};
export const Disabled: Story = {
args: { variant: 'primary', children: 'Disabled', disabled: true },
};
export const Loading: Story = {
args: { variant: 'primary', children: 'Loading', loading: true },
parameters: {
chromatic: {
// Capture after loading spinner appears
delay: 500,
},
},
};
export const AllVariants: Story = {
render: () => (
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="danger">Danger</Button>
<Button variant="ghost">Ghost</Button>
<Button size="sm">Small</Button>
<Button size="lg">Large</Button>
</div>
),
};
Интеграция Chromatic с CI
# In your CI pipeline
- name: Run Chromatic
uses: chromaui/action@latest
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
exitOnceUploaded: true # Don't wait for review
onlyChanged: true # Only snapshot changed stories
traceChanged: true # Detect which stories are affected by code changes
Applitools Eyes
Applitools использует проприетарный AI-движок («Visual AI»), который обеспечивает наиболее продвинутое интеллектуальное сравнение. Он может тестировать одновременно в нескольких браузерах и на нескольких устройствах из одного тестового прогона.
// tests/visual/applitools-checkout.spec.ts
import { test } from '@playwright/test';
import {
Eyes, Target, BatchInfo, Configuration,
BrowserType, DeviceName, ScreenOrientation
} from '@applitools/eyes-playwright';
test.describe('Checkout Flow Visual Tests', () => {
let eyes: Eyes;
test.beforeEach(async () => {
eyes = new Eyes();
const config = new Configuration();
config.setBatch(new BatchInfo('Checkout Flow'));
// Test across multiple browsers and devices simultaneously
config.addBrowser(1200, 800, BrowserType.CHROME);
config.addBrowser(1200, 800, BrowserType.FIREFOX);
config.addBrowser(1200, 800, BrowserType.SAFARI);
config.addDeviceEmulation(DeviceName.iPhone_15, ScreenOrientation.PORTRAIT);
config.addDeviceEmulation(DeviceName.Pixel_7, ScreenOrientation.PORTRAIT);
eyes.setConfiguration(config);
});
test('cart page visual check', async ({ page }) => {
await eyes.open(page, 'E-Commerce', 'Cart Page');
await page.goto('/cart');
// Full page check with layout matching (ignores text content changes)
await eyes.check('Cart with items', Target.window()
.fully()
.layout() // Match layout, not exact content
.ignoreDisplacements() // Ignore minor position shifts
);
// Specific region check with strict matching
await eyes.check('Price total', Target.region('#order-summary')
.strict() // Exact match for financial data
);
await eyes.close();
});
test.afterEach(async () => {
await eyes.abort();
});
});
Уровни сопоставления Applitools
| Уровень сопоставления | Что сравнивает | Сценарий использования |
|---|---|---|
| Strict | На уровне пикселей, с AI-фильтрацией | Финансовые данные, юридические тексты, точный контент |
| Layout | Позиции элементов, размеры, взаимосвязи | Страницы с динамическим контентом (имена пользователей, даты) |
| Content | Текстовый контент и структура | Страницы, где стили могут варьироваться, но контент важен |
| Ignore colors | Layout + контент, игнорируя изменения цвета | Изменения темы, тестирование тёмного режима |
Матрица сравнения инструментов
| Функция | Percy | Chromatic | Applitools |
|---|---|---|---|
| AI-сравнение | Базовое | Базовое | Продвинутое (Visual AI) |
| Кроссбраузерный рендеринг | Да (облачный рендеринг) | Да (облачный рендеринг) | Да (Ultrafast Grid) |
| Интеграция со Storybook | Через Percy CLI | Нативная (создан для Storybook) | Через SDK |
| Интеграция с Playwright | Нативная | Через Storybook | Нативный SDK |
| Контроль чувствительности | Процент порога | Процент порога | Несколько уровней сопоставления |
| Рабочий процесс одобрения | Веб-дашборд | Веб-дашборд | Веб-дашборд |
| Бесплатный уровень | 5 000 снимков/месяц | 5 000 снимков/месяц | Ограниченный |
| Лучше всего для | Тестирование полных страниц, множество фреймворков | Рабочие процессы вокруг Storybook | Корпоративное, кроссбраузерное в масштабе |
| Ценообразование | За снимок | За снимок | За контрольную точку |
Выбирайте на основе вашего рабочего процесса: Chromatic, если вы активно используете Storybook, Percy, если нужна широкая поддержка фреймворков, и Applitools, если нужно наиболее продвинутое AI-сравнение и кроссбраузерный рендеринг.