
Эта простая задача до сих пор не имеет 100% надёжного решения - и агенты часто спотыкаются на совсем простых и очевидных вещах.
Так как типичная LLM обучена работать с текстом, первые попытки были просто давать модели чистый HTML. И как не странно, это даже работало, причём надёжнее, чем ожидалось скептиками. Можно придумать 1000+ доказательств ущербности этого подхода, но это достаточно для 90% задач, когда надо просто прочитать текст, например новости или пост. Сразу же появились различной степени эффективности фильтры, убирающие «лишнее» в этой простыне.
В случае факапа АИ переключалась на модели, заточенные на распознавание скриншотов.
Я не нашёл ни одной production-ready web-ориентированной системы, опирающейся исключительно на скриншоты. Это остаётся уделом систем для работы с desktiop-приложениями. Спойлер: там тоже не всё хорошо, чемпион сейчас GPT-5.2 cо средней 86% точностью. Но точность на то и средняя, что в кнопки АИшка попадает обычно первого раза, зато потом долго тупит на date picker‘ах. Расход токенов тоже не радует, а отслеживание изменений на экране открывает портал в ад, настолько там много подводных камней.
Одновременно в параллельной вселенной существовали E2E тесты, которые имитировали живых юзеров, нажимали на кнопки и заполняли поля. И этим тестам тоже как-то надо было отслеживать изменения на экране. Например, чтобы понять, что разработчик Вася переделал форму и не сообщил тестерам. Сравнение скиншотов оказалось крайне не надёжным методом. Тут разработчики Playwright – это известный open source фреймворк для E2E тестов, под крылом Microsoft - вспомнили про ARIA и экранные читалки.
ARIA – случайный успех
В далёком 2014 W3C опубликовала первый стандарт WAI-ARIA 1.0. В то время все хотели WEB 2.0 но мало кто знал как его делать правильно. В результате интернет наводнили сайты с кастомными реализациями выпадающих меню, панелей и прочего JavaScript - UI. Внутри был винегрет из <DIV>, что полностью дезориентировало читалки того времени.
ARIA представляла дополнительные свойства которые разработчик добровольно должен был внедрять в свой HTML код:
roles (что это за элемент)
names (как это называется)
states (что происходит с элементом прямо сейчас).
Почти никто, конечно, не собирался немедленно следовать этим рекомендациям. Но тут европейские в первую очередь законодатели привнесли немного бюрократии и обязали государственные сайты быть доступными для всех, в том числе для людей вынужденных использовать читалки. За ними подтянулись штаты с хайпом по инклюзивности и большие корпорации тоже зашевелились. Google тоже подтянулся и в 2021 наконец сделал нормальную реализацию accesibility tree в Crome DevTools.
Казалось, вот оно, готовое решение для AI! Бери и пользуйся! Но, как всегда, вылезли подводные камни:
Кросс браузерная совместимость. Забудьте про Firefox и WebKit.
Ориентация на скрин-ридеры. Требуется сохранить совместимость с существующими ридерами, которые ориентированы на использование людьми. У человеков контекстное окно сильно меньше, у думают они немного по-другому. LLM без проблем выберет нужный элемент среди тысячи по ID, человек сломается на 10.
Отсюда ограниченная поддержка инкрементальных изменений.
Зависимость от Google, у которого вообще нет причин любить разработчиков Playwright. Последние переметнулись от него в Microsoft в 2020 как раз под предлогом того, что Google хотела привязать Puppeteer(это предшественник Playwright) к Chrome.
Собственно это было основной причиной, почему разработчики Playwright сделали свой кросс-браузерный велосипед. Продукт оказался очень удачным. Он не использует Chrome-зависимый API. Необходимый Javascript - код инжектируется в браузер с помощью стандартных Playwright-методов, и там внутри творит свою чёрную магию. Он анализирует DOM и в соответствии с правилами, и, с учётом кучи специальных случаев, формирует ARIA – снэпшот.
Что же всё-таки видит LLM
ARIA снэпшоты которые делает Playwright - это YAML, оптимизированный для потребления LLM. Выглядит он примерно так
- navigation [ref=e1]: - link "Home" [ref=e2]: - /url: / - link "Settings" [ref=e3] [active]: - /url: /settings - main [ref=e4]: - heading "Account Settings" [level=1] - group "Profile": - textbox "Display name" [ref=e5]: John Doe - textbox "Email" [ref=e6]: john@example.com - /placeholder: you@example.com - group "Preferences": - checkbox "Email notifications" [ref=e7] [checked] - checkbox "Dark mode" [ref=e8] - button "Save Changes" [ref=e9] [cursor=pointer]
Никакого CSS. Никакой разметки. Никакого <div class="very_important_another_class"> . Чистое семантическое дерево. Три базовых понятия:
Роль соответствует возможностям взаимодействия. button можно нажать. textbox можно заполнить. checkbox можно переключить. Модели не нужно догадываться о том, интерактивен элемент или нет — это явно объявлено.
Имя предоставляют человекочитаемый идентификатор, вычисляемый по спецификации. Используется цепочка приоритетов:
сначала Plywright ищет aria-labelledby . Если не находит, смотрит на aria-label, далее <label>, потом содержимое элемента, title и, наконец, placeholder.
Именно поэтому
<button><svg class="icon-save"/></button>
невидим как для большинства экранных читалок или AI-агентов — буквально нечего использовать для вычисления имени. Добавьте aria-label="Save" — и проблема исчезает.
Состояние ([checked], [expanded], [disabled], [selected], [pressed]) фиксирует динамическое состояние UI. Модель понимает, что чекбокс уже отмечен, без необходимости визуально смотреть на маленький квадратик с галочкой. (Если вы когда-нибудь отлаживали агента на основе зрения, который “проваливался” на чекбоксах, вы знаете, о чём речь).
Генерация ссылок
Это уже Playwright-фишка, она появляется только в снимках предназначенных для АИ и в документации её нет. Каждый элемент получает уникальный ref - идентификатор, который напрямую соответствует элементу DOM. Когда модель хочет выполнить действие, она использует его, а не XPATH, CSS или прочие селекторы.
{ "name": "browser_click", "arguments": { "element": "Save Changes button", "ref": "e9" } }
Инкрементальные различия: экономим токены
Сложное веб-приложение может содержать тысячи семантических элементов. Отправлять полный снимок после каждого действия агента — расточительно, а при оплате API за токен “расточительно” имеет очень конкретную денежную стоимость.
Playwright реализует инкрементальные различия: после начального полного снимка последующие наблюдения содержат только то, что изменилось.
- <changed> main [ref=e4]: - ref=e5 [unchanged] - textbox "Email" [ref=e6]: john.doe@newdomain.com - ref=e7 [unchanged] - checkbox "Dark mode" [ref=e8] [checked] - ref=e9 [unchanged]
Маркеры [unchanged] говорят модели: «это поддерево идентично тому, что у тебя уже есть в контексте». На практике это даёт сокращение размера передаваемых данных примерно на 94% — с ~5КБ до менее чем 300 байт на страницах с 100+ элементами.
Именно эта функция делает экономически целесообразными многошаговые рабочие процессы агента. Без неё выполнение формы из 20 шагов означало бы постоянное расходование токенов на повторное описание всей страницы. С ней модель поддерживает внутреннюю “ментальную модель” и обрабатывает только дельты.
Под капотом: почему это сложнее, чем кажется
Сериализация инклюзивного семантического дерева кажется простой – пока не столкнёшься с подводными камнями. Реализация в Playwright обрабатывает целый ряд сложных случаев:
<template> и <slot>: ARIA-снимок должен показывать итоговый вид а не шаблоны с плэйсхолдерами.
aria-owns позволяет логически связывать элементы, даже если они расположены в разных местах DOM. Например, выпадающий список комбобокса может быть далеко от поля ввода, но логически всё равно к нему относиться. Движок учитывает такие связи.
Содержимое ::before и ::after видно пользователю и экранным читалкам. Оно включается в снимок.
Видимость display: none, visibility: hidden, aria-hidden="true", элементы с нулевыми размерами — всё отфильтровано. Снимок отражает то, что реально видит пользователь.
Каждый из этих случаев — сложный и запутанный сценарий. Случай с aria-owns сам по себе требует построения виртуального дерева, которое не совпадает с реальной структурой DOM — по сути, вы делаете графовую хирургию “на лету”.
К сожалению, разработчики Playwright иногда придерживаются принципиальной позиции – не Playwright должен подстроиться под «плохие» сайты, а последние должны начать соответствовать стандартам. Поэтому до сих пор, например, поля ввода, полностью перекрытые модальным окном показываются «активными». Да, на них можно сфокусироваться с клавиатуры и даже ввести что-нибудь, но кто так делает в реальной жизни?
Слепые зоны
Что ARAI не показывает:
Визуальный дизайн - цвета, отступы, типографика, иконки. Красное сообщение об ошибке и дружелюбное зелёное сообщение об успехе выглядят одинаково в YAML.
Пространственные отношения - «кнопка под формой» или «рядом с формой»? Дерево не хранит информацию о расположении. (Некоторые агенты экспериментируют с аннотациями bounding-box, но это пока не стандарт.)
Canvas / WebGL - игры, графики, сложные визуализации не видны, если разработчик целенаправленно этим не занимался (спойлер: обычно нет).
Неявная визуальная семантика - «выделенная строка» работает только если “highlighted” задано как ARIA-состояние, а не просто через CSS-класс.
Это фундаментальная компромисс: возможность получить чистое, структурированное и недорогое представление потеряв визуальный контекст. Для большинства задач веб-автоматизации (формы, навигация, ввод данных) этого более чем достаточно. Но не для визуальной проверки или пространственно-сложных интерфейсов.
Сравнение агентов и инструментов
Claude (MCP) | Playwright ARIA-снимки + инкрементальные различия |
Cursor | та же конвейерная схема, что у Claude |
GitHub Copilot | весной 2026 перешёл на Playwright + ARIA |
ChatGPT Browsing | анализ DOM |
Anthropic Computer Use | cкриншоты, не специализирован для веб |
Stagehand | анализ HTML |
LaVague | скриншоты + анализ HTML |
Примечание: почти все агенты могут использовать скриншоты как резерв. Общая тенденция: миграция на Playwright – ARIA.
Кумулятивный эффект
Playwright предоставляет ARIA-снимки как канонический формат наблюдений, и каждый агент (Claude, Cursor и растущая экосистема) использует один и тот же семантический язык.
Это создаёт положительный цикл:
Агенты стандартизируются на ARIA-снимках → разработчики улучшают инклюзивность → ARIA-снимки становятся лучше → агенты работают надёжнее → больше агентов принимают стандарт.
Сообщество по инклюзивности потратило 20 лет, убеждая разработчиков писать семантический HTML. Экономика AI может сделать за 2 года то, чего активизм не смог за 20.
Что это значит (часть, где я притворяюсь, что разбираюсь в бизнесе)
Дерево инклюзивности больше не просто галочка для соответствия стандартам. Оно становится де-факто API для программного взаимодействия с вебом. Несколько важных последствий:
Для AI-инженеров: если ваш агент по-прежнему в основном ориентирован на визуальные данные для веб-задач, вы теряете производительность и деньги. Архитектура “семантика в первую очередь + визуальный fallback” побеждает.
Для веб-разработчиков: ваш следующий самый требовательный “пользователь” — робот. Правильно размеченный <button> — это не только хорошая доступность, но и совместимость с AI. Кнопка только с иконкой без aria-label теперь является багом в двух измерениях.
Для индустрии: подход с ARIA-снимками приводит к реальной совместимости. Одна и та же страница одинаково работает для Claude, Copilot, Browser-Use и любых будущих агентов. Такое единство — редкость для индустрии и стало возможным благодаря тому, что базовый стандарт (ARIA) уже давно проверен на практике.
Для тестирования: вместо хрупких проверок CSS-селекторов можно писать семантические тесты, которые переживут редизайн. Например: «На странице должен быть отмеченный чекбокс с меткой ‘Email notifications’» — это и тест, и человекочитаемая спецификация.
Инклюзивный слой веба тихо превратился в его слой машинной читаемости. Оказывается, инженеры, которые годами продвигали семантический HTML, на самом деле строили самое важное API будущего. Они просто ещё не знали об этом.
Немного рекламы: бесплатное OpenSource расширение Playwright Write-and-Run для VSCode запускает Playwright код «на лету» без перезапуска теста. Позволяет пошагово проверить выполнение каждой команды, тут же поправить её если надо и продолжить дальше. Не нужно каждый раз перезапускать браузер и ждать логина/инициализации.
