Перейти к содержанию

Проверки доступности в сценарии

Доступная форма должна не только показывать ошибку, но и помогать человеку найти поле, понять причину и продолжить действие. В предыдущих уроках мы отдельно проверяли отправку и клавиатуру. Теперь свяжем подпись поля, сообщение, состояние ошибки и путь исправления в одном сценарии.

Возьмём прежнюю форму «Сообщение об ошибке». Полные исходники находятся в lesson-22/start и expected архива продолжения. Новая зависимость для анализа доступности не добавляется: используем штатные локаторы и утверждения Playwright. Тесты, клавиатурное выполнение и ручная работа со скринридером сейчас не проводились.

Сценарий начинается с человека

Предположим, читатель обнаружил неточность и вводит почту wrong. После нажатия «Отправить сообщение» форма должна показать причину отказа и вернуть фокус полю почты. Человек исправляет адрес, клавишей Tab переходит к сообщению и кнопке, затем Enter отправляет форму. Такой путь описывает конкретную задачу, а не набор атрибутов вне их применения.

В исходном HTML поле находится внутри label с текстом «Электронная почта». У него есть aria-describedby="email-error", а область сообщения имеет соответствующий ID и role="alert". Пока ошибки нет, область скрыта. При неправильном вводе скрипт показывает текст, устанавливает aria-invalid=true и вызывает focus().

Эта связь полезна несколькими способами. Подпись позволяет найти поле по смыслу. Описание связывает управляющий элемент с пояснением. Признак invalid обозначает ошибочное состояние. Фокус помогает продолжить исправление, не разыскивая поле заново. Но сами атрибуты ещё не доказывают, что текст понятен или объявление удобно в конкретном скринридере.

Проверка отношений, а не цвета рамки

Сначала сценарий убеждается, что документ имеет язык ru и одну область main. Затем ограничивает поиск именованной формой, чтобы похожее поле в другом месте не подошло случайно. После ввода неправильной почты и сообщения нажимается кнопка.

const form = page.getByRole('form', {
  name: 'Сообщение об ошибке', exact: true
});
const email = form.getByLabel('Электронная почта', { exact: true });
await email.fill('wrong');
await form.getByLabel('Сообщение', { exact: true })
  .fill('Проверяем подпись поля');
await form.getByRole('button', {
  name: 'Отправить сообщение', exact: true
}).click();

В ответе интерфейса проверяются разные наблюдения. Поле становится invalid, его описанием остаётся существующая область сообщения, фокус находится на поле, а текст ошибки виден в элементе с ролью alert. Мы не ищем красный CSS-класс, поскольку он не выражает связь элемента с причиной отказа.

await expect(email).toHaveAttribute('aria-invalid', 'true');
await expect(email).toHaveAttribute('aria-describedby', 'email-error');
await expect(email).toBeFocused();
await expect(page.locator('#email-error')).toHaveAttribute('role', 'alert');
await expect(page.locator('#email-error'))
  .toHaveText('Введите корректный адрес почты');

ID используется только для проверки конкретной известной связи. Это не отменяет локаторы по роли и подписи для действий человека. Условие aria-describedby вместе с существующей областью обнаружит ошибку, когда HTML переименовал сообщение, а атрибут поля продолжает указывать на старое имя.

Продолжение с клавиатуры

Исправим значение на reader@example.test. Это вымышленный учебный адрес; сервер ничего не отправляет. Затем используем Tab и проверим, что фокус перемещается к сообщению, затем к кнопке. Нажатие Enter должно привести к принятому учебным сервером сообщению.

await email.fill('reader@example.test');
await email.press('Tab');
await expect(form.getByLabel('Сообщение', { exact: true })).toBeFocused();
await page.keyboard.press('Tab');
await expect(form.getByRole('button', {
  name: 'Отправить сообщение', exact: true
})).toBeFocused();
await page.keyboard.press('Enter');
await expect(page.getByTestId('feedback-status'))
  .toHaveText('Сообщение принято учебным сервером');

Полный сценарий в конце проверяет скрытие прежней ошибки и отсутствие aria-invalid=true. Исправленный ввод не должен оставаться ошибочным в доступном состоянии, даже если зелёное сообщение о приёме уже появилось. Таким образом, исследуем переход, а не только первую неудачную отправку.

Фокусная последовательность относится к выбранному настольному Chromium и текущему DOM. Она не описывает все режимы управления на мобильных устройствах и все комбинации браузера с помощником. Кроме того, fill() подготавливает значение, а не воспроизводит каждую печатную клавишу. Наблюдение ограничено договорённым путём исправления.

Проверяемая ошибка относится именно к почте. В первом действии сценарий также заполняет сообщение, чтобы его отсутствие не стало второй конкурирующей причиной отказа. После исправления адреса форма может продолжить ту же задачу без изменения остальных данных. Это делает ожидания понятнее, чем пример, в котором сразу несколько полей пусты и сценарий случайно проверяет первое сообщение в текущем порядке валидации.

В нашей форме ошибка почты проверяется клиентом до POST. Поэтому первый отказ не доказывает работу серверной проверки адреса: её можно исследовать отдельным API-сценарием. После исправления текст принятия связан с ответом 201 учебного сервера, но даже он не означает отправленное письмо. Сервер только подтверждает входные данные и ничего не сохраняет. Содержание сообщения в статье намеренно вымышленное.

При изменении области уведомления нужно оценить и момент её появления. Сообщение может быть связано с полем правильно, но появляться настолько поздно, что человек уже перешёл дальше. Штатные ожидания сценария ограничивают допустимое время своим timeout, однако они не измеряют удобство объявления. Такое наблюдение требует отдельной оценки потока вместе с используемой вспомогательной технологией.

Что остаётся ручному разбору

Автоматический сценарий может заметить исчезновение подписи, неверную связь ID или потерю фокуса. Он не отвечает полностью на вопрос, насколько удобно человеку пройти форму. Нужно отдельно оценить понятность сообщения, порядок объявлений скринридера, контраст, видимость фокуса, масштабирование и ситуации, когда несколько полей ошибочны одновременно.

Руководство Playwright по доступности рассматривает сочетание автоматических и ручных методов. В нём есть анализатор axe, но наш урок не устанавливает этот пакет и не заявляет его результат. Штатные утверждения защищают выбранный поток; они не превращаются от этого в полный аудит WCAG.

Допустим, рамка фокуса в CSS стала практически невидимой, но браузер всё ещё сообщает активный элемент. toBeFocused() останется верным. Это показывает предел выбранного сигнала. Чтобы оценить видимость, нужны визуальная и ручная проверка в договорённых условиях. Аналогично роль alert может присутствовать, а сообщение быть слишком общим: «Ошибка» не помогает исправлению, хотя семантическое поле заполнено.

В обратную сторону красивое сообщение без подписи и связи тоже не решает задачу. Пользователь скринридера может не получить нужный контекст. Поэтому внешний вид и семантика наблюдаются вместе, а автоматический сценарий служит ранним сигналом о конкретной регрессии. Он полезен именно потому, что его условия понятны и ограничены.

Доступность как сохраняемое поведение

При дальнейшем редактировании общей формы сценарий должен продолжать выражать ту же задачу. Изменение верстки допустимо, если подпись, причина и путь исправления сохраняются. Если продукт сознательно меняет порядок действий, ожидания обновляют после оценки нового потока. Автоматическое изменение всех утверждений вслед за DOM уничтожило бы смысл регрессионной защиты.

После будущего выполнения можно будет оценить описанные наблюдения в выбранной среде. Пока есть текст и полный сценарий, а фактических объявлений скринридера, логов и успешной отправки нет. Следующий урок рассмотрит другой источник ложного спокойствия: сценарий, который проходит только благодаря удачному времени ответа.