Modern QA2026Визуальное тестирование и пограничные случаи
Join

Course13 Browser Automation with Playwright

Foundations · Chapter 13

Визуальное тестирование и пограничные случаи

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');
});

Как это работает

  1. Первый запуск: Playwright делает скриншот и сохраняет его как эталон в директории __snapshots__
  2. Последующие запуски: Playwright делает новый скриншот и сравнивает попиксельно
  3. При несовпадении: Тест падает и генерирует 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 — сторонние плагины не нужны