Визуальное тестирование и пограничные случаи
Updated Jul 2026
Функциональные тесты проверяют поведение; визуальные тесты проверяют внешний вид. Playwright поддерживает оба типа, а также набор возможностей для обработки пограничных случаев, которые часто ломают автоматизированные тесты: загрузка/скачивание файлов, множественные вкладки, диалоги аутентификации и проверки доступности.
Визуальное сравнение (скриншот-тестирование)
Playwright может делать скриншоты и сравнивать их попиксельно с эталонами. Визуальные тесты обнаруживают CSS-регрессии, сдвиги макета и проблемы отображения, которые функциональные тесты пропускают.
test('главная страница соответствует визуальному эталону', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png');
});
test('карточка товара отображается корректно', async ({ page }) => {
await page.goto('/products');
const card = page.getByTestId('product-card').first();
await expect(card).toHaveScreenshot('product-card.png');
});
Как это работает
- Первый запуск: Playwright делает скриншот и сохраняет его как эталон в директории
__snapshots__ - Последующие запуски: Playwright делает новый скриншот и сравнивает попиксельно
- При несовпадении: Тест падает и генерирует diff-изображение, показывающее точно, что изменилось
Работа с динамическим контентом
// Маскировка динамических элементов (метки времени, аватары, реклама)
await expect(page).toHaveScreenshot('dashboard.png', {
mask: [page.getByTestId('timestamp'), page.getByTestId('avatar')],
});
// Допуск небольшого пиксельного отклонения
await expect(page).toHaveScreenshot('chart.png', {
maxDiffPixelRatio: 0.01, // Допуск 1% отличающихся пикселей
});
// Ожидание завершения анимаций
await expect(page).toHaveScreenshot('animated-widget.png', {
animations: 'disabled',
});
Тестирование доступности
Playwright интегрируется с axe-core для автоматизированных проверок доступности:
import AxeBuilder from '@axe-core/playwright';
test('главная страница не имеет нарушений доступности', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
// Проверка конкретных правил или секций
test('форма входа доступна', async ({ page }) => {
await page.goto('/login');
const results = await new AxeBuilder({ page })
.include('#login-form')
.withRules(['color-contrast', 'label', 'aria-required-attr'])
.analyze();
expect(results.violations).toEqual([]);
});
Загрузка и скачивание файлов
Загрузка файлов
test('загрузка фото профиля', async ({ page }) => {
await page.goto('/profile');
// Стандартное поле ввода файла
await page.getByLabel('Фото профиля').setInputFiles('test-data/photo.jpg');
// Несколько файлов
await page.getByLabel('Вложения').setInputFiles([
'test-data/doc1.pdf',
'test-data/doc2.pdf',
]);
// Очистка выбора файла
await page.getByLabel('Фото профиля').setInputFiles([]);
});
// Загрузка перетаскиванием (не через input)
test('загрузка файла перетаскиванием', async ({ page }) => {
await page.goto('/upload');
const fileChooserPromise = page.waitForEvent('filechooser');
await page.getByText('Перетащите файлы сюда').click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles('test-data/document.pdf');
});
Скачивание файлов
test('скачивание отчёта', async ({ page }) => {
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Экспорт CSV' }).click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe('report.csv');
await download.saveAs('test-results/report.csv');
});
Обработка множественных вкладок и всплывающих окон
test('внешняя ссылка открывается в новой вкладке', async ({ page, context }) => {
await page.goto('/');
// Ожидание открытия новой страницы (вкладки)
const newPagePromise = context.waitForEvent('page');
await page.getByRole('link', { name: 'Документация' }).click();
const newPage = await newPagePromise;
await newPage.waitForLoadState();
await expect(newPage).toHaveURL(/.*docs/);
await expect(newPage).toHaveTitle(/Документация/);
});
Обработка диалогов
test('диалог подтверждения перед удалением', async ({ page }) => {
// Настройка обработчика диалога перед его вызовом
page.on('dialog', async (dialog) => {
expect(dialog.message()).toContain('Вы уверены?');
await dialog.accept();
});
await page.getByRole('button', { name: 'Удалить аккаунт' }).click();
await expect(page.getByText('Аккаунт удалён')).toBeVisible();
});
Геолокация и разрешения
test('показывает ближайшие магазины для Москвы', async ({ context }) => {
await context.grantPermissions(['geolocation']);
await context.setGeolocation({ latitude: 55.7558, longitude: 37.6173 });
const page = await context.newPage();
await page.goto('/stores');
await expect(page.getByText('Москва')).toBeVisible();
});
Ключевые выводы
- Визуальное тестирование обнаруживает CSS-регрессии и проблемы макета, которые функциональные тесты пропускают
- Используйте
maskиmaxDiffPixelRatioдля обработки динамического контента на скриншотах - Интегрируйте axe-core для автоматизированных аудитов доступности
- Playwright нативно обрабатывает загрузку/скачивание файлов, многовкладочные сценарии и диалоги
- Тестируйте геолокацию, разрешения и эмуляцию устройств через конфигурацию контекста
- Обработка пограничных случаев встроена в API — сторонние плагины не нужны