Puppetaria: accessibility-first Puppeteer scripts
Hanna Horskaya
Puppeteer - это библиотека Node.js, которая позволяет автоматизировать процессы в Chromium браузере при помощи API высокого уровня через Chrome DevTools Protocol.
Самая важная задача браузера - это, конечно, просмотр веб-страниц. Автоматизация этой задачи сводится к автоматизации взаимодействия с веб-страницей.
В Puppeteer это достигается путем запроса элементов DOM с использованием строковых селекторов и выполнения таких действий, как щелчок или ввод текста на элементах. Например, сценарий, который открывает сайт developer.google.com, находит окно поиска и выполняет поиск, может выглядеть так:

До сих пор селекторы в Puppeteer ограничивались CSS и XPath, которые, не смотря на все свои преимущества имеют и недостатки.
Синтаксические и семантические селекторы
Селекторы CSS имеют синтаксический характер: они тесно связаны с внутренней работой текстового представления DOM дерева, то есть они ссылаются на идентификаторы и имена классов из DOM. Они предоставляют веб-разработчикам интегрированный инструмент для изменения или добавления стилей к элементу на странице, но в этом контексте разработчик имеет полный контроль над страницей и ее DOM деревом.
С другой стороны, сценарий Puppeteer является внешним наблюдателем страницы, поэтому, когда в контексте используются CSS селекторы, он предполагает как реализована страница, которую Puppeteer не может контролировать.
Такие сценарии могут быть хрупкими и подвержены изменениям исходного кода. Предположим, что вы используете Puppeteer для автоматического тестирования веб-приложения, содержащего третий дочернего дочерний элемент body: <button>Submit</button>.
Один фрагмент тестового сценария может выглядеть так:

Здесь мы используем селектор 'body:nth-child(3)', чтобы найти кнопку отправки, однако он жестко привязан к этой версии веб-страницы. Если позже над кнопкой будет добавлен элемент, этот селектор перестанет работать.
Это не новость для тестировщиков: пользователи Puppeteer уже давно пытаются найти селекторы, устойчивые к таким изменениям. Puppeteer aria предоставляет пользователям новые возможности решения этой проблемы.
Puppeteer теперь предоставляется с альтернативным обработчиком запросов, основанным на запросах дерева доступности, а не на CSS селекторах. Основная идея здесь заключается в том, что если конкретный элемент, который мы хотим выбрать, не изменился, то соответствующий узел доступности также не должен измениться.
Такие селекторы называются «ARIA». По сравнению с селекторами CSS они носят семантический характер. Они не привязаны к синтаксическим свойствам DOM, а вместо этого являются дескрипторами того, как страница просматривается с помощью вспомогательных технологий, таких как программы чтения с экрана.
В приведенном выше примере тестового скрипта мы могли бы использовать селектор aria/Submit[role="button"] для выбора нужной кнопки, где Submit относится к доступному имени элемента:

Теперь, если мы позже решим изменить текстовое содержимое нашей кнопки с «Submit» на «Done», тест снова завершится неудачно, но в данном случае это ожидаемо и желательно: изменяя имя кнопки, мы изменяем содержимое страницы, в отличие от ее визуального представления или того, как она структурирована в DOM. Наши тесты должны предупреждать нас о таких изменениях, чтобы гарантировать, что такие изменения являются преднамеренными.
Возвращаясь к более крупному примеру с панелью поиска, мы могли бы использовать новый обработчик aria чтобы найти строку поиска.
Заменив

на

В общем, использование селекторов ARIA может предоставить пользователям Puppeteer следующие преимущества:
- Сделать селекторы в тестовых сценариях более устойчивыми к изменениям исходного кода.
- Сделать тестовые сценарии более читабельными.
- Мотивируйте хорошие практики для присвоения свойств доступности элементам.
В оставшейся части этой статьи мы подробно рассмотрим, как реализован проект Puppetaria.
Процесс проектирования
Как указано выше, разработчики хотят включить запросы к элементам по их доступному имени и роли. Это свойства дерева доступности, двойственного к обычному дереву DOM, которое используется такими устройствами, как программы чтения с экрана для отображения веб-страниц.
Глядя на спецификацию для вычисления доступного имени, становится ясно, что вычисление имени для элемента - нетривиальная задача, поэтому с самого начала было решено использовать для этого существующую инфраструктуру Chromium.
Реализация
Даже ограничившись использованием дерева доступности Chromium, есть довольно много способов реализовать запросы ARIA в Puppeteer. Чтобы понять, почему, давайте сначала посмотрим, как Puppeteer управляет браузером.
Браузер предоставляет интерфейс отладки через протокол, называемый Chrome DevTools Protocol (CDP). Он предоставляет такие функции, как «перезагрузить страницу» или «выполнить этот фрагмент JavaScript на странице и вернуть результат» через интерфейс, не зависящий от языка.
Как интерфейс DevTools, так и Puppeteer использует CDP для общения с браузером. Для реализации команд CDP существует инфраструктура DevTools внутри всех компонентов Chrome. CDP заботится о маршрутизации команд в нужное место.
Действия Puppeteer выполняются с помощью команд CDP, таких как Runtime.evaluate, которые оценивают JavaScript непосредственно в контексте страницы и возвращают результат. Другие действия Puppeteer, такие как создание снимков экрана или захват элементов, используют CDP для непосредственного взаимодействия с процессом Blink.

Два пути для реализации функций запросов:
- Написать логику запросов на JavaScript и внедрить ее на страницу с помощью Runtime.evaluate
- Использовать конечную точку CDP, которая может обращаться к дереву доступности и запрашивать его непосредственно в процессе Blink.
Реализовано 3 прототипа:
- Обход JS DOM - основан на внедрении JavaScript на страницу
- Обход Puppeteer AXTree - на основе использования существующего доступа CDP к дереву доступности
- Обход CDP DOM - использование новой конечной точки CDP, специально созданной для запроса дерева доступности
JS DOM
Этот прототип выполняет полный обход DOM и использует element.computedName и element.computedRole, привязанные к флагу запуска ComputedAccessibilityInfo, чтобы получить имя и роль для каждого элемента во время обхода.
PuppeteerAXTree
Мы получаем полное дерево доступности через CDP и просматриваем его в Puppeteer. Полученные узлы доступности затем отображаются на узлы DOM.
CDP DOM
Для этого прототипа была реализована новая конечная точка CDP специально для запроса дерева доступности. Таким образом, запросы могут выполняться на сервере через реализацию C++, а не в контексте страницы через JavaScript.
Завершение
Когда конечная точка CDP была установлена, разработчики реализовали обработчик запросов на стороне Puppeteer. Суть работы заключалась в реструктуризации кода обработки запросов, чтобы запросы могли разрешаться напрямую через CDP, а не через JavaScript, оцениваемый в контексте страницы.
Что дальше?
Новый обработчик aria поставляется с Puppeteer v5.4.0 в качестве встроенного обработчика запросов. Пользователи уже могут внедрять его в свои тестовые сценарии.