Тестирование доступности с Playwright: проверки WCAG в вашем наборе тестов
Обновлено 14 октября 2026 г. · 7 мин чтения
Playwright управляет настоящим браузером, а axe-core — это правило-ориентированный движок с открытым кодом, на котором построено большинство автоматических проверок доступности. Вместе они превращают проверки WCAG в обычные тесты: страница, в которой появилась кнопка без имени или текст с низким контрастом, ломает сборку, не доходя до покупателей. В этом руководстве — рабочие примеры с @axe-core/playwright, варианты для компонентов React и Storybook, советы для CI и ограничения, о которых нужно знать.
Примеры следуют официальному руководству Playwright по тестированию доступности и README @axe-core/playwright. Тот же движок использует наш собственный сканер.
Установка
npm install --save-dev @playwright/test @axe-core/playwright
Версия пакета следует версии axe-core, которую он поставляет. В README отмечено, что пакет не придерживается семантического версионирования, поэтому закрепите версию и обновляйте её осознанно: новый выпуск axe может добавить правила, которые находят новые проблемы.
Первый тест WCAG
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
const WCAG_AA = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'];
test('home page has no detectable WCAG A/AA violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page }).withTags(WCAG_AA).analyze();
expect(results.violations).toEqual([]);
});
withTags ограничивает запуск правилами, помеченными этими версиями и уровнями WCAG. Без него axe запускает ещё и правила лучших практик: они полезны, но не являются требованиями WCAG.
Тестируйте состояния, которые люди действительно видят
Страница — это больше, чем её первая отрисовка. Сначала откройте меню, выдвижную корзину или диалог cookie, а затем просканируйте только эту часть через include:
test('mobile menu is accessible when open', async ({ page }) => {
await page.goto('/');
await page.getByRole('button', { name: 'Menu' }).click();
const results = await new AxeBuilder({ page })
.include('#navigation-menu')
.withTags(WCAG_AA)
.analyze();
expect(results.violations).toEqual([]);
});
Хорошие кандидаты для магазина: страница товара с выбранными вариантами, корзина с товарами, оформление заказа с показанными ошибками проверки, форма входа в личный кабинет. Использование getByRole в ваших тестах само по себе — небольшой тест доступности: если Playwright не может найти кнопку по роли и имени, скорее всего, не сможет и пользователь экранного диктора.
Известные проблемы без отключения тестов
В реальных проектах есть проблемы, которые нельзя исправить сегодня, например внутри стороннего виджета. В README предложены три инструмента:
const results = await new AxeBuilder({ page })
.exclude('#third-party-chat') // skip an element
.disableRules(['color-contrast']) // skip a rule (use rarely)
.withTags(WCAG_AA)
.analyze();
Лучший приём из руководства Playwright — снимок отпечатков: вы записываете, какие правила нарушаются на каких элементах, и тест падает, только когда появляется что-то новое.
const fingerprints = (results) =>
results.violations.map((v) => ({
rule: v.id,
targets: v.nodes.map((n) => n.target),
}));
expect(fingerprints(results)).toMatchSnapshot();
Два замечания из README: include и exclude принимают один селектор за вызов (для нескольких соединяйте вызовы в цепочку), а повторный вызов withTags, withRules, disableRules или options перекрывает предыдущий.
Сначала падайте на самом важном
Если накопившихся проблем много, начните с самых серьёзных. У каждого нарушения есть impact: minor, moderate, serious или critical:
const blocking = results.violations.filter((v) =>
['serious', 'critical'].includes(v.impact),
);
expect(blocking).toEqual([]);
Приложите полный результат к отчёту, чтобы тот, кто будет исправлять, видел каждый элемент:
// inside test('…', async ({ page }, testInfo) => { … })
await testInfo.attach('accessibility-scan-results', {
body: JSON.stringify(results, null, 2),
contentType: 'application/json',
});
Руководство Playwright также показывает, как обернуть AxeBuilder в фикстуру теста, чтобы каждый тест получал одни и те же теги и исключения из одного места.
Компоненты React: jest-axe и vitest-axe
Для тестов компонентов, которые отрисовываются в JSDOM, jest-axe и vitest-axe добавляют проверку toHaveNoViolations:
import { render } from '@testing-library/react';
import { axe } from 'vitest-axe';
import 'vitest-axe/extend-expect';
it('renders an accessible form', async () => {
const { container } = render(<SignupForm />);
expect(await axe(container)).toHaveNoViolations();
});
Они быстрые и рано ловят отсутствующие подписи и имена. Два ограничения из их README: jest-axe отключает проверки контраста цветов, потому что в JSDOM они не работают, а vitest-axe не работает со средой happy-dom. Контраст, раскладка, масштабирование и фокус требуют настоящего браузера, поэтому тесты Playwright для целых страниц стоит сохранить.
Storybook
Дополнение Storybook для доступности запускает axe-core для каждой истории:
npx storybook add @storybook/addon-a11y
Задайте parameters.a11y.test равным 'error' для историй, которые должны оставаться чистыми: тогда нарушения приводят к падению теста в интерфейсе Storybook и в CI. 'todo' показывает их только как предупреждения, а 'off' пропускает автоматическую проверку.
Советы для CI
- Запускайте в том же конвейере, что и остальные тесты. В руководстве Playwright по CI используются
npm ci,npx playwright install --with-depsиnpx playwright test, а папкаplaywright-report/загружается как артефакт. - Тестируйте собранную страницу, близкую к рабочей, а не сервер разработки с отладочными наложениями.
- Выберите короткий список ключевых адресов — главная, категория, товар, корзина, оформление заказа, вход, страница с контентом — вместо обхода всего на каждый коммит.
- Закрепите версию axe и обновляйте её в отдельном pull request.
- Не прячьте сбои через
disableRulesнадолго; лучшеexcludeдля одного элемента или приём со снимком.
Чего автоматические тесты не расскажут
В README axe-core сказано, что автоматически можно найти в среднем 57 % проблем WCAG, а руководство Playwright так же ясно говорит, что автоматическое тестирование не может выявить все виды нарушений WCAG. Пустой массив violations не означает, что страница соответствует WCAG 2.2 AA. По-прежнему нужны проход по странице с клавиатуры, проверка масштабирования и перекомпоновки и тест с экранным диктором. Эту часть описывает наше руководство по тестированию WCAG, а чек-лист WCAG 2.2 отмечает, какие пункты требуют человека.
Какое место занимает Pandus
Тесты в CI защищают код, который вы меняете. Они не видят, что происходит на живом сайте потом: обновление темы, новое приложение или плагин, маркетинговый баннер, описание товара, вставленное от поставщика. Pandus проверяет рабочий сайт: каждую страницу раз в неделю тем же движком axe-core в настоящем браузере и с отчётом, который сможет прочитать владелец магазина.
- API запускает проверки и возвращает результаты в JSON, а вебхуки сообщают, когда проверка закончилась или появились новые проблемы, — удобно после развёртывания.
- Расширение для браузера проверяет страницу так, как её показывает ваш браузер, включая тестовые среды и страницы за авторизацией.
- Бесплатный WCAG тестер проверяет одну публичную страницу без аккаунта.
Pandus — это автоматическая проверка, а не полный аудит и не юридическая консультация. Сравнение других инструментов — в материале инструменты тестирования доступности.
Частые вопросы
Как проверять доступность с помощью Playwright?
Установите @axe-core/playwright, откройте страницу в тесте Playwright, выполните new AxeBuilder({ page }).analyze() и проверьте, что массив violations пуст. Ограничьте запуск правилами WCAG через withTags, например wcag2a, wcag2aa, wcag21a, wcag21aa и wcag22aa.
Означает ли успешный тест axe, что страница соответствует WCAG?
Нет. Автоматические правила находят только часть проблем WCAG. Deque, создатель axe-core, оценивает её в среднем примерно в 57 % проблем. Порядок работы с клавиатуры, осмысленный альтернативный текст, порядок фокуса и понятные сообщения об ошибках по-прежнему проверяет человек.
Можно ли использовать jest-axe или vitest-axe вместо этого?
Да, для компонентов, отрисованных в JSDOM. Они быстрые, но jest-axe отключает проверку контраста цветов, потому что в JSDOM она не работает. Для целых страниц используйте Playwright в настоящем браузере, где можно проверить контраст, раскладку и фокус.
Как проверять доступность компонентов Storybook?
Добавьте @storybook/addon-a11y. Он запускает axe-core для каждой истории; если задать parameters.a11y.test равным error, нарушения приводят к падению теста в интерфейсе Storybook и в CI.
Должна ли сборка падать при каждом нарушении?
Начните с падения на серьёзных и критических проблемах и сохраните снимок известных, чтобы новые проблемы ломали сборку, а старые исправлялись постепенно. Ужесточите правило, когда накопившиеся проблемы будут устранены.
Источники
- Playwright, Accessibility testing: https://playwright.dev/docs/accessibility-testing
- Playwright, Setting up CI: https://playwright.dev/docs/ci-intro
- Deque, @axe-core/playwright README: https://github.com/dequelabs/axe-core-npm/blob/develop/packages/playwright/README.md
- Deque, axe-core README: https://github.com/dequelabs/axe-core
- jest-axe README: https://github.com/NickColley/jest-axe
- vitest-axe README: https://github.com/chaance/vitest-axe
- Storybook, Accessibility tests: https://storybook.js.org/docs/writing-tests/accessibility-testing