Привет! Я Мэтт — стажер в команде Backend Infrastructure CZI Education. Я только что завернул два B.S. степени в Калифорнийском университете в Лос-Анджелесе и вернется для получения степени магистра компьютерных наук. Я особенно увлечен CS и образованием, языками программирования и взаимодействием человека и компьютера (PL & HCI) и программным обеспечением с открытым исходным кодом.

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

В оставшейся части этого поста я кратко расскажу об одном из своих вкладов: новом правиле плагина ESLint для обнаружения неоднозначного текста ссылки (anchor-ambiguous-text). Это относительно небольшой проект, но я им особенно горжусь! Это также возможность поговорить об одном распространенном антишаблоне доступности. Давайте начнем!

Контекст

Доступность является основным компонентом миссии CZI Education: построить мир, в котором демографические факторы, такие как раса и социально-экономический статус — или инвалидность — не предсказывают результаты учащихся. С инженерной точки зрения это означает создание технологии, которую может использовать любой учащийся, независимо от того, как он использует свое устройство. Слепые или слабовидящие учащиеся часто используют программы чтения с экрана для взаимодействия с веб-сайтами. Чтобы предоставить им наилучшие возможности, инженеры должны приложить активные усилия, чтобы сделать свои приложения удобными для чтения с экрана; это часть гораздо более широкой области веб-доступности.

Одним из ключевых инструментов в нашем наборе инструментов доступности в CZI является автоматическое тестирование с помощью линтинга. ESLint и Stylelint — линтеры для JavaScript и CSS соответственно — имеют надежные системы плагинов, которые позволяют нам помечать недоступные шаблоны в редакторах кода. Это позволяет нам сдвинуться влево: вместо того, чтобы ловить ошибки доступности на этапах QA (или после развертывания), мы можем решить их раньше, на этапе реализации кода! Мы используем широкий спектр линтеров и инструментов статического анализа как в платформе Summit Learning, так и в Along.

Хотя автоматизированные тесты не могут выявить проблемы, влияющие на весь спектр инвалидности, усовершенствование наших автоматических тестов по-прежнему улучшает нашу способность писать более доступное программное обеспечение. И для стажера это отличное место для старта!

Проблема

Наша команда Frontend Infrastructure заметила одну недоступную модель: ссылки, которые не предоставляют контекст для программ чтения с экрана. Самыми нарушителями являются ссылки с текстом вида «нажмите здесь», «узнайте больше» или «ссылка». Вот один (реальный) пример:

The text-to-speech feature allows students to hear text read aloud in Focus Area Content Assessments. <a href=”…”>Learn more</a>

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

В идеале, только текст ссылки должен обеспечивать контекст:

Learn more about <a href=”…”>Text-to-Speech in Content Assessments</a>.

В качестве альтернативы можно добавить дополнительный текст для чтения с экрана (требуется некоторый CSS):

<a href=”…”>Learn more <span class=”sr-only”>about Text-to-Speech in Content Assessments</span></a>.

Эта проблема может быть особенно неприятной, если этот шаблон используется на всей странице. Другой пример:

Please click <a href=”…”>here</a> to download the template. Please <a href=”…”>click here</a> to view the errors. <a href=”…”>Learn more.</a>

Переход по ссылкам будет диктовать «здесь», «нажмите здесь», «узнать больше». Нет контекста того, на что идут ссылки или почему пользователь должен щелкнуть!

Этот шаблон распространен в Интернете и часто обсуждается (вот сообщение в блоге Стефани Лири, краткое резюме группы веб-доступа Калифорнийского университета в Беркли и статья WebAIM). К сожалению, у нас нет инструмента для автоматического обнаружения этих ошибок… пока!

Решение

Помните, как я упоминал ранее об использовании линтеров? Одним из основных плагинов в этой экосистеме является eslint-plugin-jsx-a11y. Он статически анализирует JSX для выявления таких проблем, как заголовки, не имеющие содержимого, доступного для чтения с экрана, медиафайлы без подписей или отсутствуют обязательные атрибуты ARIA. CZI Education уже использует этот плагин при разработке платформы Summit Learning и Along.

Я люблю открытый исходный код и воспользовался случаем, чтобы открыть проблему в репозитории eslint-plugin-jsx-a11y. Оказывается, это правило хотели и другие разработчики! После нескольких уточняющих вопросов с основным сопровождающим и нескольких дискуссий на CZI мы согласовали спецификацию правила: оно будет называться anchor-ambiguous-text.

Начальная спецификация правила проста.

Пройтись по AST (Абстрактное синтаксическое дерево) в поисках <a> тегов; для каждого якоря,

  • получить «доступный дочерний текст» (т. е. то, что объявит программа чтения с экрана)
  • если доступный дочерний текст точно совпадает с настраиваемым пользователем запрещенным списком (например, «нажмите здесь», «читать дальше» и т. д.), сообщите о предупреждении.

Получение доступного дочернего текста звучит просто — просто получите то, что находится между тегами! К сожалению, это не совсем просто.

Более глубокое погружение: доступный дочерний текст

Существуют различные пограничные случаи, которые переопределяют или скрывают текст от программы чтения с экрана. Стандарт Accessible Rich Internet Applications (ARIA) определяет различные атрибуты HTML, которые аннотируют существующие элементы для вспомогательных технологий.

При разработке правила lint я добавил логику для наиболее распространенных атрибутов ARIA (и других семантических атрибутов HTML). Давайте пройдемся по нескольким!

Если элемент имеет атрибут aria-label, программа чтения с экрана считывает значение вместо дочерних элементов. Правило lint учитывает это переопределение.

// this will not error; the screen reader would dictate “Chan Zuckerberg Initiative website”.
<a href=”…” aria-label=”Text-to-Speech in Content Assessments”>Learn more<a/>
// this will error; the screen reader would dictate “Read more”, which is not descriptive
<a href=”…” aria-label=”Learn more”>Text-to-Speech in Content Assessments</a>

Если изображение (и только изображение) имеет атрибут alt, программа чтения с экрана читает его как описательный текст. Правило lint также использует это поведение.

// this will error; the screen reader would dictate “Learn more”
<a href=”…”><img src=”…” alt=”Learn more” /><a/>

Если у элемента есть атрибут aria-hidden, программа чтения с экрана его пропускает. Обычно это используется для декоративных элементов, которые не передают смысловое значение. Итак, наше правило пропускает и его!

// this will error; the screen reader would dictate “Read more”
<a href=”…”>Learn more <span aria-hidden=”true”>about Text-to-Speech in Content Assessments</span><a/>

Правило также удаляет знаки препинания, сворачивает пробелы и переводит текст в нижний регистр.

Существуют и другие пограничные случаи (описанные в следующих шагах), но мы посчитали, что приведенное выше охватывает наиболее простые варианты использования.

Настройка

Правила, зависящие от контекста, вроде этого, должны быть настраиваемыми. Я использовал существующую инфраструктуру в eslint-jsx-plugin-a11y, чтобы лучше адаптировать это правило к их варианту использования.

Одна простая настройка — это настраиваемый список запретов. Это позволяет разработчикам добавлять другие недоступные слова в свой контекст (например, «нажмите здесь»), а также допускает интернационализацию ad-hoc; допустима любая строка, совместимая с JavaScript!

Другой — это настройка настраиваемого компонента для всего плагина. Это позволяет плагину легко работать с элементами JSX, которые обертывают/переопределяют тег привязки, например Next.js или собственная Система проектирования образования CZI.

Внедрение и развертывание

После всех этих размышлений я смог быстро реализовать черновик. Четко определенная спецификация означала, что написание тестов для поведения было четким, а существующая инфраструктура в jsx-ast-utils делала обход AST относительно простым.

Я открыл и объединил три PR (#873, #880, #886) для реализации описанного поведения. Если вы собираете пакет из исходного кода, правило полностью применимо! Ожидается второстепенный релиз; как только это выйдет, разработчики смогут обновить свою eslint-jsx-plugin-a11y версию и использовать правило вживую.

Тем временем я обновил плагин ESLint для проверки этого правила.

В Аутлете мы не обнаружили нарушений! Большой!

Напротив, на платформе Summit Learning было 24 ошибки lint в различных точках входа в приложение. Мы также обнаружили, что это поведение встроено в нашу кодовую базу: различные компоненты были названы LearnMoreLink или renderLearnMore. Мы определили, что ни один из них не был ложноположительным. Затем мы работали с различными командами, чтобы исправить копию, чтобы сохранить исходное значение, а также предоставить больше контекста для пользователей программ чтения с экрана. Я рад сообщить, что теперь нет ошибок lint (в кодовой базе из ~78 тысяч строк JS)!

Мы можем быстро просмотреть один пример на нашей странице условий обслуживания для студентов:

«Здесь» слишком двусмысленно! Команда, владеющая этой страницей, указала, что изменение копии будет рассматриваться. Итак, я изменил копию, чтобы точно описать видео.

Это обеспечивает больше контекста — не только для пользователей программ чтения с экрана, но и для любого пользователя!

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

Открытая проблема: ссылочные элементы

Одной из открытых проблем правила являются «референтные элементы». Эти специальные возможности включают ссылки на другие элементы, которые могут или не могут существовать где-либо в DOM.

  • Элемент ‹label› предоставляет программам чтения с экрана семантическое значение для элементов управления формы (например, <input> или <textarea>).
  • Атрибуты aria-describedby и aria-labelledby ссылаются на другие элементы (по идентификатору), которые обеспечивают семантическое значение для элемента и диктуются программой чтения с экрана. В частности, поведение здесь менее прямолинейно: aria-labelledby может содержать несколько идентификаторов, быть самореферентным, упорядоченным и не может быть связан.
  • Атрибут aria-details предоставляет больше информации программам чтения с экрана, хотя он часто не используется при сканировании ссылок.

Это создает различные проблемы.

Что должно делать правило, если элемента нет в AST?

  • eslint-jsx-plugin-a11y есть некоторые правила, обеспечивающие такое поведение, но не все — и пользователи не могут использовать эти правила.
  • Ошибка каждый раз, вероятно, приводит к множеству ложных срабатываний: упомянутые элементы могут находиться в родственных или родительских компонентах, которые могут быть в отдельном выражении JSX.

Как мы должны искать эти узлы?

  • Одним из вариантов является сохранение состояния при прохождении AST. Состояние в линтерах может привести к нестабильности, и их часто сложно реализовать и поддерживать.
  • Тем не менее, сохранение этого состояния без сохранения состояния, вероятно, потребует многих повторных обходов AST, что неэффективно (критический аспект линтеров).

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

Предостережения

Есть также две большие категории проблем с линтингом как решением этой проблемы.

  1. Инструменты Lint имеют слабое семантическое понимание содержимого и обычно используются для синтаксиса. Напротив, постановка задачи носит фундаментально семантический характер.
  2. Инструменты Lint представляют собой форму статического анализа и не оценивают код. Это упускает из виду динамическое взаимодействие, которое составляет огромную часть веб-приложений.

Я не думаю, что это правило lint является всеобъемлющим при работе с неоднозначным текстом ссылки. Вместо этого это один небольшой инструмент в арсенале каждого разработчика для создания доступных приложений. Чтобы решить основные проблемы этого инструмента, вам нужны инструменты, которые являются динамичными, семантическими и, что наиболее важно, включают реальных людей.

Если вам интересно, Эндрю Хут написал отличный обзор других наших практик автоматизированного тестирования — особенно я большой поклонник Сборника историй и Хроматики!

В целом, эта работа согласуется с постоянными усилиями CZI по аудиту и разработке большего количества решений, чтобы сделать все наши объекты и технологии более доступными.

Заключение и спасибо

В общем, это очень мотивировало. Я люблю открытый исходный код, и мне очень нравится работать над инструментами, которые нужны другим разработчикам! Я узнал больше об инструментах доступности и линтинга, и у меня была возможность поработать с несколькими другими инженерами и дизайнерами. И все это я делал в качестве стажера, что говорит о том, насколько полезным был мой опыт стажировки в CZI!

Многие люди сделали мое время в CZI прекрасным. Самая большая благодарность Кэти Хо, моему менеджеру-стажеру — она старалась изо всех сил быть отзывчивой, поддерживающей и доброй на протяжении всей программы, и она была лучшим менеджером-стажером, который у меня был! В целом команда BE Infra была теплым и гостеприимным домом в CZI!

Кроме того, команда Frontend Infrastructure работала со мной над спецификацией и поведением правила. Спасибо Дидре Ратер, которая предложила это правило, а также Эндрю Хуту, Энни Ху, Джеремайе Клотье и Джин Ли, которые внесли свой вклад! Также спасибо Джордану Харбанду (сопровождающему eslint-jsx-plugin-a11y) за работу со мной над этим проектом. И, спасибо замечательной группе стажеров, сформировавших за это лето звездное сообщество!

Спасибо, что прочитали это; Надеюсь, это было хотя бы несколько информативно! По крайней мере, двусмысленный текст ссылки теперь должен быть у вас в голове. Если вы используете ESLint, у меня может быть инструмент специально для вас…

Заинтересованы в программе стажировки CZI? Узнайте больше об этом на Странице карьеры CZI.

Вы пользуетесь этим правилом? Или есть предложения, отзывы, жалобы? Я хотел бы знать!