Тестирование и процесс: автопроверки, ручной аудит, доступность в команде
Восемь предыдущих статей трека — про то, как сделать интерфейс доступным. Эта — про то, как узнать, что вы это сделали, и как не потерять результат на следующем релизе. Разница принципиальная: доступность не состояние, которого достигают один раз, а свойство, которое протухает. В дизайн-систему приезжает новый цвет с контрастом 3.1:1, кто-то заворачивает форму в модалку без возврата фокуса, редактор загружает картинку с alt вида photo_2026_final(1).jpg — и через полгода после «мы всё починили» аудит находит примерно тот же список, что и до починки. Лечится это не героическим аудитом раз в год, а конвейером проверок на каждом коммите плюс ручными точками контроля там, где машина слепа.
Здесь предполагается, что вы уже прочитали вводный гайд и карту трека, понимаете, что такое семантика, ARIA и управление фокусом. Заново объяснять, почему div с onClick — плохая кнопка, мы не будем; будем разбираться, как это ловить автоматически, как проверять руками и кто в команде за это отвечает.
Одно замечание про суть. Тестирование доступности — не «проверка на соответствие благотворительному стандарту», а проверка того, работает ли продукт для части ваших пользователей, которые платят те же деньги и решают те же задачи, просто другим набором инструментов: скринридером, увеличением, голосом, переключателем, только клавиатурой. Дефект доступности — обычный дефект: сценарий не выполняется. К нему применимы обычные инструменты — воспроизведение, severity, регрессионный тест — и не нужны никакие особые слова.
Что вообще значит «протестировать доступность»
У вопроса «доступен ли наш продукт» нет ответа «да/нет» — есть три разных вопроса, и путать их дорого.
- Соответствие критериям. Выполняются ли критерии успеха WCAG 2.2 уровня A и AA на заданном наборе страниц. Формализуемо, нужно юристам и в тендерах. Проверяется аудитом.
- Работоспособность сценариев. Может ли человек со скринридером оформить заказ; может ли человек, который не пользуется мышью, отменить подписку. К критериям не сводится: можно выполнить все критерии AA и сделать сценарий, который проходится за двадцать минут мучений. Проверяется прохождением сценариев и тестами с пользователями.
- Отсутствие регрессий. Не сломали ли мы то, что вчера работало. Проверяется автотестами в CI.
Соответственно и проверки трёх сортов: автоматические (быстрые, дешёвые, узкие), ручные структурные (чеклист, аудит) и пользовательские (медленные, дорогие, единственно честные). Ни один сорт не заменяет другие.
Что автоматика ловит, а что нет
Самая полезная цифра в этой статье: автоматические сканеры находят порядка трети типовых нарушений. Оценки расходятся — WebAIM в ежегодном исследовании миллиона главных страниц ловит автоматикой шесть повторяющихся видов ошибок, покрывающих подавляющее большинство сайтов; Deque заявляет для axe покрытие выше — до 57% с учётом «управляемых» проверок, где инструмент задаёт человеку вопрос. Разброс объясняется методикой подсчёта, но вывод одинаковый в любой методике: больше половины проблем сканер не увидит принципиально, потому что для них нужно понять смысл, а не форму.
Граница проходит ровно по линии «формальное / смысловое»:
| Сканер видит | Сканер не видит |
|---|---|
alt отсутствует |
alt="banner_final_2.jpg" — атрибут есть, смысла нет |
| контраст текста к сплошному фону ниже 4.5:1 | контраст текста поверх фотографии или градиента |
| у кнопки нет доступного имени | доступное имя есть, но называет не то действие |
поле не связано с label |
подпись связана, но написана канцеляритом |
aria-labelledby указывает на несуществующий id |
ARIA расставлена синтаксически верно и семантически абсурдно |
положительный tabindex, дубли id |
порядок обхода не совпадает с визуальным |
у страницы нет lang |
после закрытия модалки фокус улетел в body |
| таблица без заголовочных ячеек | субтитры есть, но это необработанный автоперевод |
Отсюда практическое правило: автопроверки нужны не чтобы «доказать доступность», а чтобы бесплатно убрать шум. Когда axe молчит, ручной аудит не тратит время на отсутствующие alt и занимается тем, ради чего человек и нужен.
Пирамида проверок: шесть слоёв
Слои различаются по трём осям: частота запуска, стоимость прогона и класс находок. Нижние дают обратную связь мгновенно и стоят почти ноль, поэтому работают всегда; верхние стоят дорого и запускаются редко, зато находят то, ради чего всё затевалось. Обратите внимание на асимметрию: жёстко блокируют билд только правила с нулевой долей ложных срабатываний, всё остальное — сигнал в ревью. Гейт, который падает на спорных вещах, живёт ровно до первого горящего релиза, после которого его отключают навсегда.
Слой 0: линтер, который срабатывает на нажатие клавиши
Самая дешёвая проверка — та, что подчёркивает код прямо в редакторе. Для React это eslint-plugin-jsx-a11y, для Vue — eslint-plugin-vuejs-accessibility, для Angular — правила шаблонов в @angular-eslint, для Svelte a11y-предупреждения встроены в компилятор. Для обычной разметки и шаблонов — html-validate с включённым набором a11y.
// eslint.config.js — flat config, ESLint 9
import js from '@eslint/js';
import jsxA11y from 'eslint-plugin-jsx-a11y';
export default [
js.configs.recommended,
jsxA11y.flatConfigs.recommended,
{
rules: { // правила с нулевым шумом — сразу в error, они чинятся за минуту
'jsx-a11y/no-positive-tabindex': 'error',
'jsx-a11y/click-events-have-key-events': 'error',
'jsx-a11y/no-noninteractive-element-to-interactive-role': 'error',
'jsx-a11y/label-has-associated-control': ['error', { assert: 'either' }],
},
// Без этого линтер не знает, что наш <Button> — это <button>, и молча пропускает всё
settings: { 'jsx-a11y': { components: { Button: 'button', Link: 'a', Field: 'input' } } },
},
];
Что важно понимать про этот слой: линтер видит только исходный код и только статически. Он не знает, что role подставляется в рантайме, не умеет считать контраст и не проверит порядок фокуса. Зато он не даёт написать новый <div onClick> — то есть работает как ограждение, а не как детектор. Конфиг settings.components — обязательный шаг: без него плагин на проекте с дизайн-системой не находит вообще ничего.
Слой 1: axe в компонентных тестах
axe-core — движок, на котором работают почти все инструменты доступности: axe DevTools, вкладка Accessibility в Lighthouse, Storybook-аддон, линтеры в CI. Он реализует значительную часть ACT Rules — формализованных W3C правил проверки, — и умышленно почти не даёт ложных срабатываний: если axe сказал «нарушение», это нарушение.
В компонентных тестах он проверяет то, за что отвечает компонент.
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { axe, toHaveNoViolations } from 'jest-axe'; // для Vitest — vitest-axe
expect.extend(toHaveNoViolations);
const OPTS = {
runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa', 'wcag22aa'] },
rules: { 'color-contrast': { enabled: false } }, // jsdom не считает реальные цвета
};
test('аккордеон доступен в обоих состояниях', async () => {
const user = userEvent.setup();
const { container } = render(<Accordion title="Доставка">Текст</Accordion>);
expect(await axe(container, OPTS)).toHaveNoViolations(); // свёрнут
await user.click(screen.getByRole('button', { name: 'Доставка' }));
expect(await axe(container, OPTS)).toHaveNoViolations(); // раскрыт
// axe не заменяет проверку контракта: состояние должно быть в дереве доступности
expect(screen.getByRole('button', { name: 'Доставка' })).toHaveAttribute('aria-expanded', 'true');
});
Три практических нюанса:
color-contrastв jsdom не работает — там нет вычисленных цветов. Контраст проверяют в браузерных тестах (Vitest browser mode, Playwright component testing), в Storybook или отдельным тестом на дизайн-токены: «все пары из палитры дают не меньше 4.5:1».- Сканируйте каждое состояние — «загружается», «пусто», «ошибка», «раскрыт» — это четыре разные страницы для дерева доступности. И помните, что axe не заменяет ассерты про роль, имя и состояние: первое ловит регрессии, второе документирует замысел.
- Если есть Storybook, вешайте проверку на истории. Аддон
@storybook/addon-a11yгоняет axe на каждой истории в интерфейсе, а test-runner — в CI: автоматическое покрытие всех состояний, которые вы и так описали. В свежих версиях аддона параметрtest: 'error'роняет прогон на нарушении.
Слой 2: E2E — сканируем состояния, а не страницы
Главная ошибка внедрения — запустить сканер по списку URL и успокоиться. Современный интерфейс на URL не заканчивается: модалка, выпадающее меню, тост, шаг визарда, состояние ошибки формы — всё это состояния, которых нет ни в одном sitemap. Именно там живут самые тяжёлые дефекты: неработающая ловушка фокуса, потерянный фокус, невидимое сообщение об ошибке.
import { test, expect, type Page } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
const TAGS = ['wcag2a', 'wcag2aa', 'wcag22aa'];
async function scan(page: Page, label: string) {
const { violations } = await new AxeBuilder({ page })
.withTags(TAGS)
.exclude('#partner-map') // чужой виджет чиним тикетом, а не блокируем им релиз
.analyze();
const report = violations.map((v) => `${v.id} (${v.impact}) — ${v.helpUrl}`).join('\n');
expect(violations, `Нарушения в состоянии «${label}»:\n${report}`).toEqual([]);
}
test('оформление заказа доступно в каждом состоянии', async ({ page }) => {
await page.goto('/checkout');
await scan(page, 'исходное');
await page.getByRole('button', { name: 'Оплатить' }).click(); // 1. состояние ошибки
await expect(page.getByRole('alert')).toBeVisible();
await scan(page, 'ошибки валидации');
await expect(page.getByRole('textbox', { name: 'Телефон' })).toBeFocused();
await page.getByRole('button', { name: 'Изменить адрес' }).click(); // 2. открытая модалка
await expect(page.getByRole('dialog', { name: 'Адрес доставки' })).toBeVisible();
await scan(page, 'модалка адреса');
// 3. Esc закрывает и возвращает фокус на триггер — этого axe не проверит никогда
await page.keyboard.press('Escape');
await expect(page.getByRole('button', { name: 'Изменить адрес' })).toBeFocused();
});
Отдельно стоит упомянуть снимки дерева доступности: Playwright сравнивает структуру страницы так, как её видит вспомогательная технология.
await expect(page.getByRole('navigation')).toMatchAriaSnapshot(`
- navigation "Основное меню":
- list:
- listitem: - link "Каталог"
- listitem: - button "Профиль" [expanded=false]
`);
Это лучший известный способ поймать регрессию семантики: если завтра кто-то заменит <nav><ul> на набор div, тест упадёт с понятной диффой, хотя визуально ничего не изменится и axe промолчит. Клавиатурный проход тоже автоматизируется — page.keyboard.press('Tab') в цикле с записью document.activeElement даёт фактический порядок обхода для сравнения с ожидаемым. Ручную проверку это не заменяет (тест не заметит, что кольцо фокуса невидимо), но перестановки после рефакторинга ловит.
Гейт в CI, который не возненавидят
На живом проекте с историей первый же запуск axe по всем страницам даёт сотни нарушений. Поставить гейт «ноль нарушений» — значит либо остановить разработку, либо через неделю его выключить. Рабочая схема — храповик (ratchet): фиксируем текущее состояние как базовую линию, запрещаем ухудшение и автоматически подтягиваем линию при улучшении.
# .github/workflows/a11y.yml
name: a11y
on: [pull_request]
jobs:
a11y:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: npm }
- run: npm ci && npx playwright install --with-deps chromium
- run: npm run build && (npm run preview &) && npx wait-on http://localhost:4173
- run: npx playwright test tests/a11y/critical --reporter=line # жёсткий гейт
- run: node scripts/a11y-scan.mjs > a11y-report/summary.json # мягкий храповик
- run: node scripts/a11y-ratchet.mjs
- uses: actions/upload-artifact@v4
if: always()
with: { name: a11y-report, path: a11y-report/ }
// scripts/a11y-ratchet.mjs — число нарушений по каждому правилу может только уменьшаться
import { readFileSync, writeFileSync } from 'node:fs';
const FILE = '.a11y-baseline.json';
const base = JSON.parse(readFileSync(FILE, 'utf8')); // { 'color-contrast': 42, ... }
const now = JSON.parse(readFileSync('a11y-report/summary.json', 'utf8'));
const grown = Object.entries(base).filter(([r, max]) => (now[r] ?? 0) > max);
const fresh = Object.keys(now).filter((r) => !(r in base)); // раньше не нарушали — нельзя
for (const [r, max] of grown) console.error(`✗ ${r}: было ${max}, стало ${now[r]}`);
for (const r of fresh) console.error(`✗ новое правило: ${r}`);
// Улучшения фиксируем сразу: назад дороги нет
writeFileSync(FILE, JSON.stringify(Object.fromEntries(
Object.entries(base).map(([r, max]) => [r, Math.min(max, now[r] ?? 0)])), null, 2));
process.exit(grown.length + fresh.length ? 1 : 0);
Дальше базовая линия становится измеримой задачей: «в этом квартале снимаем color-contrast с 42 до 0». Про общие принципы гейтов — тесты в CI и основы CI. Чем ещё дополняют связку: pa11y-ci — когда фронтенд не на JS-стеке или нужно быстро прогнать список URL из sitemap (умеет предварительные действия: залогиниться, кликнуть, дождаться); Accessibility Insights — единственный массовый инструмент с проработанным ручным режимом и визуализацией табстопов; IBM Equal Access, ARC Toolkit, WAVE — альтернативные движки, находят немного разное, на важном релизе полезно прогнать двумя. А вот Lighthouse Accessibility score — взвешенное среднее по подмножеству правил axe: годится как индикатор тренда и категорически не годится как критерий приёмки, о чём Google пишет сам.
Отдельная тактика, которая не стоит ничего: писать обычные UI-тесты через роли и доступные имена. getByRole('button', { name: 'Оплатить' }) находит элемент так же, как скринридер; если селектор не работает — у элемента нет роли или имени, и тест падает раньше, чем это увидит пользователь. Бонусом такие тесты не ломаются от переименования CSS-класса — см. тестирование фронтенда и E2E и UI.
Смоук-протокол руками: пять минут перед мержем
Это главный ручной инструмент — короткий, воспроизводимый, применимый к любой фиче. Не «посмотреть, всё ли хорошо», а фиксированная последовательность с ожидаемым результатом. Пять минут на фичу находят больше, чем весь автоматический слой.
| № | Что делаем | Ожидаемый результат | Критерий |
|---|---|---|---|
| 1 | Убираем руку с мыши. Проходим сценарий целиком: Tab, Shift+Tab, Enter, Space, стрелки, Esc |
Сценарий выполняется полностью; ничего не пропущено, из виджетов можно выйти | 2.1.1, 2.1.2 |
| 2 | Смотрим на индикатор фокуса на каждом шаге | Виден всегда, не обрезан, не спрятан под липкой шапкой | 2.4.7, 2.4.11 |
| 3 | Сверяем порядок обхода с визуальным | Совпадает; фокус не прыгает через экран | 2.4.3 |
| 4 | Открываем модалку и закрываем Esc |
Фокус ушёл внутрь, вернулся на триггер, фон недоступен | 2.1.2, 2.4.3 |
| 5 | Ctrl + «+» до 400% в окне 1280px, затем увеличиваем только текст до 200% |
Одна колонка, горизонтальной прокрутки нет, текст не обрезан контейнерами | 1.4.10, 1.4.4, 1.4.12 |
| 6 | Смотрим структуру заголовков (расширение или document.querySelectorAll('h1,h2,h3')) |
Один h1, уровни без пропусков, заголовки описывают содержимое | 1.3.1, 2.4.6 |
| 7 | Отправляем форму с ошибкой | Ошибка озвучена, привязана к полю, объясняет, что исправить | 3.3.1, 3.3.3 |
| 8 | Прогоняем axe DevTools | Ноль нарушений либо список известных | — |
| 9 | Включаем скринридер и проходим ключевой шаг | Понятно, где мы и что произошло | 4.1.2 |
Шаги 1–4 занимают полторы минуты и находят большинство блокирующих дефектов; если времени совсем нет — делайте хотя бы их. Полезная мелочь для шага 3: в Accessibility Insights есть режим визуализации табстопов, который рисует поверх страницы пронумерованные кружки и линии между ними. Абсурдный порядок обхода видно мгновенно, и это отличная картинка для баг-репорта.
Скринридер в руках тестировщика
Важная оговорка: включая скринридер, вы не воспроизводите опыт незрячего пользователя. Человек, который пользуется NVDA десять лет, слушает речь на скорости, которую вы не разберёте, и знает сотню горячих клавиш. Вы проверяете другое, тоже полезное: корректно ли разметка транслируется в дерево доступности и озвучивается ли то, что нужно, — это проверка совместимости, а не эмуляция чужой жизни. Минимальный набор команд, которого хватает для 90% проверок (подробности — в статье про скринридеры):
| Задача | NVDA (Windows, бесплатно) | VoiceOver (macOS) |
|---|---|---|
| Включить / выключить | Ctrl+Alt+N / Insert+Q |
Cmd+F5 |
| Читать подряд / замолчать | Insert+↓ / Ctrl |
VO+A (VO = Ctrl+Option) / Ctrl |
| Следующий заголовок, ориентир | H, D |
ротор VO+U, стрелки |
| Следующая ссылка / кнопка / поле | K / B / F |
ротор VO+U |
| Список элементов страницы | Insert+F7 |
ротор VO+U |
| Режим чтения ↔ режим форм | Insert+Space |
автоматически |
Практика тестирования:
- Тестируйте задачами, а не страницами. «Найти тариф дешевле 500 рублей и подключить» — а не «послушать главную».
- Закройте глаза или выключите экран. Иначе мозг подставляет визуальный контекст, и вы не заметите, что вслух не сказано ничего.
- Пары браузер + скринридер имеют значение. NVDA обычно тестируют с Firefox и Chrome, JAWS — с Chrome, VoiceOver — с Safari. По опросам WebAIM на десктопе JAWS и NVDA вместе занимают около 80% ответов, VoiceOver — примерно десятую часть. Если ресурс один, берите NVDA + Chrome. Мобильные — отдельная проверка: VoiceOver на iOS и TalkBack на Android ведут себя иначе, там жесты вместо клавиш и другой набор багов.
- Сверяйтесь с ARIA-AT — проектом W3C, который систематически измеряет, как разные скринридеры озвучивают одни и те же паттерны. Полезно, когда спорите, «баг это у нас или у JAWS».
Полный аудит: методика WCAG-EM
Когда нужен формальный ответ про соответствие — перед крупным релизом, для тендера, для заявления о доступности — работает WCAG-EM, методика оценки соответствия от W3C. Пять шагов:
- Определить область. Что именно оценивается (домен, раздел, приложение), до какого уровня (обычно AA), на каких браузерах и вспомогательных технологиях.
- Изучить сайт. Ключевые страницы, типы контента, технологии, полные процессы (регистрация, оплата, восстановление пароля).
- Собрать выборку. Структурированная часть — по одному экземпляру каждого типа страницы плюс все страницы каждого процесса целиком; случайная часть — примерно 10% сверху. Типичный объём: 15–30 страниц.
- Проверить выборку по каждому критерию: автоматика, ручные проверки, скринридер. Каждое несоответствие — с примером, скриншотом и ссылкой на критерий.
- Оформить отчёт — удобно через WCAG-EM Report Tool, он ведёт по шагам и генерирует структурированный документ.
Ключевое правило выборки: процесс проверяется целиком. Если из пяти шагов оформления заказа недоступен один, весь процесс не соответствует — это прямо записано в критериях соответствия WCAG. Аудит десяти случайных страниц без прохождения процессов даёт красивый отчёт и бесполезный результат. Про юридический контекст — сертификация, EN 301 549, VPAT/ACR — см. стандарты и требования. Здесь важно другое: аудит — это фотография, и без слоёв 0–2 она перестаёт отражать реальность через три месяца.
Дефект доступности: как описать и как приоритизировать
Плохой баг-репорт: «страница недоступна для скринридера». Хороший — воспроизводимый, привязанный к критерию и к пользовательской задаче.
Заголовок: Оформление заказа: после закрытия модалки адреса фокус уходит в начало страницы
Окружение: Chrome 141 / Windows 11 / NVDA 2026.1; воспроизводится и без скринридера
Шаги: 1) табом дойти до кнопки «Изменить адрес», нажать Enter; 2) нажать Esc
Фактически: фокус на <body>; следующий Tab ведёт на первую ссылку в шапке
Ожидаемо: фокус возвращается на кнопку «Изменить адрес»
Затрагивает: всех, кто пользуется клавиатурой, скринридером, переключателем
Влияние: чтобы вернуться к форме, нужно 24 нажатия Tab; при повторной ошибке — ещё раз
Критерий: WCAG 2.2 — 2.4.3 Focus Order (A); Severity: критичный
Severity считается по влиянию на выполнение задачи, а не по уровню критерия: A/AA — про соответствие, а человеку, который не может нажать кнопку оплаты, безразлично, какой это уровень.
| Severity | Признак | Что делать |
|---|---|---|
| Блокирующий | Задачу нельзя выполнить в принципе | В текущий спринт, релиз держим |
| Критичный | Выполнима, но с несоразмерными усилиями или риском ошибки | В ближайший спринт |
| Существенный | Есть обходной путь, но он неочевиден | В бэклог с датой |
| Незначительный | Мешает, но не ломает сценарий | В бэклог, чинить при касании кода |
Жизненный цикл такого дефекта отличается от обычного одной деталью: закрывать его можно только после ручной перепроверки и с регрессионным тестом. Зелёный автотест доказывает, что правило axe больше не срабатывает, а не что сценарий стал проходимым; без регрессионного теста дефект вернётся через два рефакторинга, и вы найдёте его на следующем платном аудите.
Где в процессе точки контроля
Доступность дешевле всего там, где её ещё не сделали неправильно. Стоимость исправления растёт примерно на порядок на каждом этапе: «переставить поля в макете» → «переписать компонент» → «перепроектировать сценарий на проде».
Самая недооценённая стрелка здесь — третья: аннотации в макете. Дизайнер знает, что этот текст — заголовок второго уровня, а эта иконка означает «удалить»; разработчик этого не знает и угадывает. Аннотация — слой в макете с пометками: уровни заголовков, доступные имена иконок, порядок обхода, что читается вслух у декоративных элементов, какие состояния у интерактивных. Пятнадцать минут дизайнера экономят день переделок и почти все споры на ревью.
Кто за что отвечает
Доступность разваливается, когда за неё «отвечает фронтенд». Половина дефектов рождается вне кода: в тексте кнопки, в цвете из брендбука, в формулировке ошибки на бэкенде, в PDF, который приходит на почту.
| Роль | Зона ответственности | Артефакт |
|---|---|---|
| Продакт / аналитик | Доступность в требованиях, приоритет дефектов, бюджет на тесты с пользователями, публичное заявление о доступности | Критерии приёмки, заявление, канал обращений |
| Дизайнер | Контраст, размеры целей, видимый фокус, порядок чтения, аннотации | Макет с a11y-слоем, токены |
| Контент-редактор | alt, структура заголовков, осмысленные тексты ссылок, простой язык, субтитры |
Гайд по контенту, чеклист публикации |
| Фронтенд | Семантика, ARIA, клавиатура, компонентные и E2E тесты | Компонент + тесты + паспорт |
| Бэкенд | Тексты и коды ошибок, тайминги сессии, доступные PDF и письма, экспорт данных | API с человекочитаемыми ошибками |
| QA | Смоук-протокол, регресс, аудит выборки, воспроизведение обращений | Чеклист, баги с привязкой к критериям |
| Тимлид / архитектор | DoD, дизайн-система, гейты в CI, обучение | Definition of Done, roadmap |
Работающая организационная схема на средней компании — чемпионы: по одному человеку в каждой команде, который держит тему, ревьюит спорное и ходит в общую гильдию раз в две недели. Централизованная «команда доступности» без чемпионов превращается в бутылочное горлышко и внешний контролирующий орган, которого все избегают.
Definition of Done и критерии приёмки. Формулировки уровня «должно быть доступно» непроверяемы. Критерий приёмки должен звучать так, чтобы его можно было провалить.
Сценарий: Ошибки валидации сообщаются скринридеру
Допустим я на странице оформления заказа и пользуюсь только клавиатурой
Когда я отправляю форму с пустым полем «Телефон»
Тогда фокус переходит на сводку ошибок в начале формы
И сводка зачитывается как «Ошибок: 1. Телефон: укажите номер»
И ссылка из сводки переводит фокус в поле «Телефон»
Пункты Definition of Done, которые работают и не превращаются в ритуал: новые интерактивные элементы проходятся с клавиатуры, фокус виден; у каждого есть доступное имя, совпадающее с видимой подписью; axe не даёт новых нарушений на изменённых экранах; смоук-протокол (шаги 1–5) пройден и отмечен в тикете; новые цвета взяты из токенов, прошедших проверку контраста; изменение компонента дизайн-системы сопровождается обновлением его a11y-паспорта.
a11y-паспорт компонента — короткий раздел в документации: какая роль, как управляется с клавиатуры, что озвучивается, какие ARIA-атрибуты обязательны от потребителя, какие состояния протестированы. Он превращает доступность из устного знания в контракт (про контракты компонентов — фронтенд-архитектура).
И главный рычаг: дизайн-система. Если кнопка, поле, модалка, таб и меню сделаны доступно один раз в библиотеке, сто продуктовых экранов получают это бесплатно; если нет — каждый экран платит заново, и половина платит неправильно. Поэтому на легаси порядок внедрения один: сначала токены (контраст, фокус, размеры целей), потом десяток базовых компонентов, потом экраны. Обратный порядок — бесконечная работа с отрицательной отдачей. Показатель зрелости, который стоит отслеживать, — доля компонентов с паспортом, тестами и пройденной ручной проверкой: он растёт монотонно и понятен руководству, в отличие от «числа violations», скачущего от каждого редизайна.
Тестирование с людьми, которые пользуются вспомогательными технологиями
Никакая методика не заменит наблюдения за тем, как человек решает задачу своим инструментом. Пять участников находят большинство серьёзных препятствий — та же статистика, что и в обычном юзабилити-тестировании (см. UX и требования). Как организовать по-человечески:
- Платите. Участие в исследовании — работа, ставка та же, что и у остальных участников. «Протестируйте бесплатно, вы же заинтересованы» — это перекладывание вашей работы на пользователя.
- Ищите через сообщества и профильные организации, а не объявлением «нужен слепой». Формулируйте набор через инструменты и задачи: «люди, которые пользуются скринридером ежедневно», «люди, работающие с экранной лупой».
- Не просите менять настройки и не спрашивайте о диагнозе. Человек придёт со своим устройством, своей скоростью речи и шрифтом 24px — это и есть реальные условия эксплуатации. Спрашивать нужно про инструменты, привычки и барьеры.
- Давайте задачи, а не экскурсии: «оформите доставку на завтра» вместо «посмотрите наш новый дизайн, удобно ли». Не превращайте сессию в демонстрацию для руководства (запись — только с согласия, наблюдателей минимум) и возвращайтесь с результатом: сообщите участникам, что исправили. Это единственный способ построить долгие отношения с сообществом, которое обычно видит только разовые «инклюзивные инициативы».
Второй канал, который стоит не меньше и не стоит ничего: работающий приём обратной связи. Заявление о доступности с живым адресом и обещанием ответить за N дней приносит точные, воспроизводимые описания препятствий от людей, которые дошли до конца. Шаблон есть у W3C WAI.
План внедрения на легаси-проекте
Порядок здесь не случаен. Сначала узнать, где вы находитесь (иначе непонятно, что чинить); параллельно поставить ограждения, чтобы не становилось хуже; потом фундамент — токены и компоненты; и только потом процесс и обучение, когда людям уже есть чем пользоваться. Обучение до появления доступных компонентов создаёт мотивированную команду, которой нечем работать.
Метрики: что мерить и чего не мерить
Плохие метрики: Lighthouse score, «число violations», «процент доступности» — первое не про доступность, второе скачет от объёма страницы, третьего не существует. Рабочие метрики: доля ключевых сценариев, для которых есть автотест и пройденный смоук-протокол (растёт до 100% и держится); число открытых блокирующих дефектов (цель — ноль, любое отклонение обсуждается); значения храповика по правилам — видно, что именно уменьшается; доля компонентов дизайн-системы с паспортом и тестами; медианное время от обращения пользователя до исправления; дата последнего аудита и последней сессии с пользователями — если больше полугода, процесс остановился. Отчётность встраивается в обычное качество релиза — см. качество и поставка и стратегия автоматизации.
Инструменты: карта
Типичные ошибки процесса
- «Прогнали axe, ошибок нет — значит доступно». Пройден нижний слой пирамиды; про остальное неизвестно ничего.
- Гейт «ноль нарушений» на легаси. Проживёт до первого срочного релиза, потом будет выключен насовсем. Нужен храповик.
- Аудит без процесса. Отчёт на 80 страниц, из которого починили 12 пунктов, а через полгода вернулось всё.
- Проверка страниц вместо состояний и проверка одной страницы вместо всего процесса. Модалки, тосты, ошибки форм и шаги визарда в скан не попадают вообще, а один недоступный шаг из пяти делает недоступным весь процесс.
- Автотест как критерий закрытия дефекта. Закрывать только после ручной перепроверки сценария.
- Доступность на плечах одного энтузиаста. Уходит человек — уходит тема. Нужны DoD, гейты и чемпионы в командах.
- Скринридер «на слух вообще» без конкретной задачи: вы слушаете озвучку, а не проверяете выполнимость.
- Severity по уровню WCAG вместо влияния на задачу, и разовая «инклюзивная инициатива» вместо живого канала обратной связи.
Мини-итог
- Три разных вопроса: соответствие критериям, выполнимость сценариев, отсутствие регрессий. У каждого свой инструмент, ни один не заменяет другие.
- Автоматика находит около трети нарушений — всё, что формализуемо; смысловое (осмысленный
alt, понятная ошибка, разумный порядок фокуса) остаётся людям навсегда. - Пирамида: линтер на каждое нажатие → axe в компонентных тестах на коммит → axe по состояниям в E2E на пул-реквест → смоук-протокол руками перед мержем → аудит WCAG-EM перед крупным релизом → тесты с пользователями раз в квартал.
- В CI жёстко блокируют только правила без ложных срабатываний; остальное — храповик: хуже не становится, улучшения фиксируются автоматически. Пять минут ручной проверки (Tab, фокус, порядок, модалка, зум 400%) находят больше, чем весь автоматический слой. Скринридером вы проверяете совместимость разметки, а не воспроизводите чужой опыт: тестируйте задачами, с выключенным экраном.
- Дефект доступности — обычный дефект: воспроизведение, критерий WCAG, severity по влиянию на задачу, регрессионный тест при закрытии. Процесс держится на трёх опорах: доступные компоненты в дизайн-системе, проверяемые критерии в DoD, чемпионы в командах. Без них аудит — фотография, устаревающая за месяц.
- Люди, которые пользуются вспомогательными технологиями, — участники исследования, а не бесплатный QA: платите, спрашивайте про инструменты, возвращайтесь с результатом.
Источники
- WCAG 2.2, Understanding WCAG 2.2 и How to Meet WCAG — последний удобен как фильтруемый рабочий чеклист аудитора.
- WCAG-EM 1.0 и WCAG-EM Report Tool; ACT Rules — формализованные правила, на которых основаны движки сканеров.
- WAI-ARIA 1.2 и ARIA APG — эталонное поведение паттернов; MDN: Accessibility tooling. axe-core, @axe-core/playwright, jest-axe, pa11y-ci, Accessibility Insights.
- WebAIM Million и Screen Reader User Survey; ARIA-AT — измерение поддержки паттернов реальными скринридерами.
- W3C WAI: Accessibility Statement Generator, Planning and Managing Web Accessibility, Deque: Automated Testing Coverage. Смежное на портале: ручное тестирование, E2E и UI, тесты в CI, тестирование фронтенда.
Что дальше
На этом трек закончен. Вы прошли путь от «кто и как пользуется интерфейсами» до процесса, который удерживает результат: семантика как фундамент, ARIA как точечная надстройка, клавиатура и фокус, скринридеры, визуальная доступность, формы и компоненты. Куда идти дальше:
- Пишете интерфейсы. Фронтенд-трек — семантика HTML, формы и валидация, фронтенд-архитектура.
- Отвечаете за качество. Трек тестирования — проектирование тестов, стратегия автоматизации, тесты в CI.
- Строите процесс. Качество и поставка, UX и требования, приоритизация, основы CI и современные CI-платформы.
Общая карта портала и порядок изучения треков — в роадмапе.