Доступность (a11y) Тестирование и процесс: автопроверки, ручной аудит, доступность в команде
0%

Тестирование и процесс: автопроверки, ручной аудит, доступность в команде

Тестирование и процесс: автопроверки, ручной аудит, доступность в команде

Восемь предыдущих статей трека — про то, как сделать интерфейс доступным. Эта — про то, как узнать, что вы это сделали, и как не потерять результат на следующем релизе. Разница принципиальная: доступность не состояние, которого достигают один раз, а свойство, которое протухает. В дизайн-систему приезжает новый цвет с контрастом 3.1:1, кто-то заворачивает форму в модалку без возврата фокуса, редактор загружает картинку с alt вида photo_2026_final(1).jpg — и через полгода после «мы всё починили» аудит находит примерно тот же список, что и до починки. Лечится это не героическим аудитом раз в год, а конвейером проверок на каждом коммите плюс ручными точками контроля там, где машина слепа.

Здесь предполагается, что вы уже прочитали вводный гайд и карту трека, понимаете, что такое семантика, ARIA и управление фокусом. Заново объяснять, почему div с onClick — плохая кнопка, мы не будем; будем разбираться, как это ловить автоматически, как проверять руками и кто в команде за это отвечает.

Одно замечание про суть. Тестирование доступности — не «проверка на соответствие благотворительному стандарту», а проверка того, работает ли продукт для части ваших пользователей, которые платят те же деньги и решают те же задачи, просто другим набором инструментов: скринридером, увеличением, голосом, переключателем, только клавиатурой. Дефект доступности — обычный дефект: сценарий не выполняется. К нему применимы обычные инструменты — воспроизведение, severity, регрессионный тест — и не нужны никакие особые слова.

Что вообще значит «протестировать доступность»

У вопроса «доступен ли наш продукт» нет ответа «да/нет» — есть три разных вопроса, и путать их дорого.

  1. Соответствие критериям. Выполняются ли критерии успеха WCAG 2.2 уровня A и AA на заданном наборе страниц. Формализуемо, нужно юристам и в тендерах. Проверяется аудитом.
  2. Работоспособность сценариев. Может ли человек со скринридером оформить заказ; может ли человек, который не пользуется мышью, отменить подписку. К критериям не сводится: можно выполнить все критерии AA и сделать сценарий, который проходится за двадцать минут мучений. Проверяется прохождением сценариев и тестами с пользователями.
  3. Отсутствие регрессий. Не сломали ли мы то, что вчера работало. Проверяется автотестами в 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 автоматически

Практика тестирования:

  1. Тестируйте задачами, а не страницами. «Найти тариф дешевле 500 рублей и подключить» — а не «послушать главную».
  2. Закройте глаза или выключите экран. Иначе мозг подставляет визуальный контекст, и вы не заметите, что вслух не сказано ничего.
  3. Пары браузер + скринридер имеют значение. NVDA обычно тестируют с Firefox и Chrome, JAWS — с Chrome, VoiceOver — с Safari. По опросам WebAIM на десктопе JAWS и NVDA вместе занимают около 80% ответов, VoiceOver — примерно десятую часть. Если ресурс один, берите NVDA + Chrome. Мобильные — отдельная проверка: VoiceOver на iOS и TalkBack на Android ведут себя иначе, там жесты вместо клавиш и другой набор багов.
  4. Сверяйтесь с ARIA-AT — проектом W3C, который систематически измеряет, как разные скринридеры озвучивают одни и те же паттерны. Полезно, когда спорите, «баг это у нас или у JAWS».

Полный аудит: методика WCAG-EM

Когда нужен формальный ответ про соответствие — перед крупным релизом, для тендера, для заявления о доступности — работает WCAG-EM, методика оценки соответствия от W3C. Пять шагов:

  1. Определить область. Что именно оценивается (домен, раздел, приложение), до какого уровня (обычно AA), на каких браузерах и вспомогательных технологиях.
  2. Изучить сайт. Ключевые страницы, типы контента, технологии, полные процессы (регистрация, оплата, восстановление пароля).
  3. Собрать выборку. Структурированная часть — по одному экземпляру каждого типа страницы плюс все страницы каждого процесса целиком; случайная часть — примерно 10% сверху. Типичный объём: 15–30 страниц.
  4. Проверить выборку по каждому критерию: автоматика, ручные проверки, скринридер. Каждое несоответствие — с примером, скриншотом и ссылкой на критерий.
  5. Оформить отчёт — удобно через 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: платите, спрашивайте про инструменты, возвращайтесь с результатом.

Источники

Что дальше

На этом трек закончен. Вы прошли путь от «кто и как пользуется интерфейсами» до процесса, который удерживает результат: семантика как фундамент, ARIA как точечная надстройка, клавиатура и фокус, скринридеры, визуальная доступность, формы и компоненты. Куда идти дальше:

Общая карта портала и порядок изучения треков — в роадмапе.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов