Все статьи
ИнструментыТестирование

Автоматизация браузера через agent-browser: скриншоты, авторизация, тестирование сценариев

Omni Router

Автоматизация браузера через agent-browser

Попросите AI-агента “зайди в админку и проверь, что кнопка экспорта работает”, и вы быстро упрётесь в два ограничения. Первое: DOM страницы это 3000-5000 токенов на каждый шаг, контекстное окно выходит за лимиты. Второе: CSS-селекторы, которые модель находит для клика, ломаются при первом же рефакторинге вёрстки.

Оба ограничения решает agent-browser от Vercel Labs. Это CLI + Skill на Rust, который отдаёт агенту компактное дерево доступности страницы с короткими ссылками-refs вместо полного DOM. Рядом с ним живёт второй проект той же команды, emulate: локальные эмуляторы Google, GitHub, Stripe и ещё десятка сервисов, чтобы вход в аккаунты и платежи можно было гонять в CI без реальных аккаунтов. Они хорошо работают по отдельности, но парой раскрываются интереснее. Ниже разбор обоих с рабочими примерами.

agent-browser: браузер, который говорит с агентом короткими фразами

Установка происходит через npm:

npm install -g agent-browser
agent-browser install   # скачивает Chrome для управления браузером

Переходим на любую страницу:

agent-browser open https://example.com
agent-browser snapshot -i

Получаем дерево доступности с разметкой интерактивных элементов:

@e1 [heading] "Example Domain" [level=1]
@e6 [button] "Sign In"
@e10 [input type="email"] placeholder="Email"
@e11 [input type="password"] placeholder="Password"
@e12 [button type="submit"] "Log In"

Это 200-400 токенов вместо нескольких тысяч, и действия теперь выглядят так:

agent-browser fill @e10 "user@example.com"
agent-browser fill @e11 "password123"
agent-browser click @e12

Флаг -i оставляет только интерактивные элементы, это режим по умолчанию для агентов. Если дерево всё равно велико, его режут дальше: -c убирает пустые структурные узлы, -d 3 ограничивает глубину, -s "#main" ограничивает область конкретным селектором. В сумме CLI даёт 50+ команд: навигация, формы, сетевые перехваты, cookies, файлы, вкладки, фреймы, скриншоты, отладка.

Здесь есть подвох, о котором легко забыть: refs живут ровно до изменения страницы. После клика, который меняет DOM, старые @eN указывают в никуда. Правило простое: любое действие, которое двигает страницу, заканчивается новым снимком.

Скриншоты и видео: зачем агенту глаза

Текстового дерева хватает для большинства действий, но не для проверки результата. “Кнопка нажалась” и “появилась таблица с экспортом” это разные вещи, и вторую дерево доступности показывает плохо.

Базовый скриншот:

agent-browser screenshot ./artifacts/dashboard.png

Полезнее аннотированный вариант: screenshot --annotate рисует поверх страницы номера интерактивных элементов, и каждая метка [N] соответствует ref @eN из снимка. Агент получает картинку и текст, которые ссылаются на одни и те же элементы, без гадания по координатам.

Когда текстовый лог не объясняет падение, включается запись видео:

agent-browser open https://app.example.com/login
agent-browser record start ./artifacts/login-flow.webm
agent-browser snapshot -i
agent-browser fill @e1 "demo@example.com"
agent-browser click @e3
agent-browser wait --url "**/dashboard"
agent-browser record stop

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

Авторизация: Переиспользование уже существующих данных для входа

Заставлять агента каждый раз проходить форму входа это трата времени и лишняя точка отказа. agent-browser умеет сохранять всё состояние сессии в файл:

agent-browser state save ./auth-state.json
# ...другой запуск, другой процесс:
agent-browser state load ./auth-state.json
agent-browser open https://app.example.com/dashboard

В файле лежат cookies, localStorage и sessionStorage. Самый быстрый способ получить такой файл: забрать состояние из реального Chrome, где вы уже вошли в аккаунт. Запускаете Chrome с флагом --remote-debugging-port=9222, входите в аккаунт как обычно, затем:

agent-browser --auto-connect state save ./my-auth.json

Две оговорки. Порт 9222 это полный контроль над браузером с localhost, держите его открытым только пока забираете состояние. И сам auth-файл это секрет: в нём рабочие cookies, его нельзя коммитить в репозиторий, а в CI его кладут в защищённый кэш или секреты.

Если профилей несколько (тестовый пользователь, админ, аноним), их разводят именованными сессиями. У каждой сессии свои cookies, хранилище и вкладки:

agent-browser --session admin open https://app.example.com/admin
agent-browser --session anon  open https://app.example.com/pricing

Это удобно и для параллельных агентов: два агента в одном репозитории не конкурируют за один инстанс браузера, а просто работают в разных сессиях.

Встраивание в процессы

Главный сдвиг: с refs агент может прогонять сквозные процессы тестирования без захардкоженных селекторов, а значит скрипт переживает редизайн. Практики, которые сложились вокруг этого (по материалам issue #1116 в репозитории, где сообщество предложило готовый навык тестирования):

  • Трёхуровневая авторизация. Сначала сохранённое состояние из файла, если его нет, то секреты из менеджера паролей, и только в конце полный вход через форму. Полный сценарий входа прогоняют редко, чтобы не зависеть от капчи.
  • HAR-захват на каждый сценарий. Сетевой лог кладётся в артефакты вместе со скриншотами, и по нему видно, какой запрос вернул 500 до того, как упал UI.
  • Визуальная регрессия с порогом. Скриншоты сравниваются с эталоном, порог по пикселям примерно 1% для строгих страниц и 2% как порог для CI. Ниже порога анимации и шрифты гарантированно сделают тест мигающим.
  • Тестовые данные с префиксом. Всё, что сценарий создаёт в базе, помечается префиксом вроде e2e-, и после прогона удаляется через API. Иначе через месяц тестовая среда превращается в свалку, и тесты начинают мешать друг другу.

Отдельно отмечу вариант remote-agent-browser: библиотека, которая поднимает agent-browser внутри изолированной microVM в Vercel Sandbox. Нужна, когда браузер должен жить в облаке, а не рядом с агентом, или когда параллельных сессий много и локальный Chrome становится узким местом.

emulate: эмулируем всё! (Google Auth / Github / Stripe / Email)

Теперь вторая половина связки. Любой реальный E2E-сценарий упирается во внешние сервисы: OAuth через Google, письмо с кодом подтверждения, уведомление от Stripe. В CI это значит реальные аккаунты, сетевой доступ и лимиты, процесс тестирования может быть усложнён…

emulate решает это на уровне API. Команда npx emulate поднимает локальные сервисы эмуляции, которые имитируют продуктовые сервисы (в основном зарубежные):

Сервис Порт по умолчанию
Vercel 4000
GitHub 4001
Google 4002
Slack 4003
Apple 4004
Microsoft 4005
Okta 4006
AWS 4007
Resend 4008
Stripe 4009
MongoDB Atlas 4010
Clerk 4011
Linear 4012
Twilio 4013

Создатели настаивают на формулировке “not mocks”, и это видно по Google-эмулятору. Там не пара заглушек, а полноценный OAuth 2.0-поток с PKCE, ID-токены, OIDC discovery, JWKS для проверки подписей, плюс рабочие приложения Gmail (сообщения, черновики, цепочки писем, метки), Calendar и Drive. Приложение перенаправляется на эмулятор одной переменной:

export GOOGLE_EMULATOR_URL=http://localhost:4002
# https://accounts.google.com/o/oauth2/v2/auth
#   -> $GOOGLE_EMULATOR_URL/o/oauth2/v2/auth
# https://oauth2.googleapis.com/token
#   -> $GOOGLE_EMULATOR_URL/oauth2/token

Stripe-эмулятор подписывает уведомления настоящим заголовком Stripe-Signature поверх timestamp и сырого тела запроса, то есть код проверки подписи в вашем backend работает в тестах так же, как в боевой среде.

Для тестов эмуляторы поднимаются программно:

// vitest.setup.ts
import { createEmulator, type Emulator } from 'emulate'

let google: Emulator

beforeAll(async () => {
  google = await createEmulator({ service: 'google', port: 4002 })
  process.env.GOOGLE_EMULATOR_URL = google.url
})

afterEach(() => google.reset())   // чистое состояние между тестами
afterAll(() => google.close())

Метод reset() после каждого теста это и есть изоляция, которую обычно вымучивают транзакциями и случайными email-ами.

Как это склеивается в один прогон

Связка выглядит таким образом: поднимаете приложение локально, оно смотрит на GOOGLE_EMULATOR_URL. Рядом крутится agent-browser. Агент открывает страницу входа, кликает “Войти через Google”, попадает на страницу авторизации эмулятора (она локальная, капчи и 2FA нет), подтверждает вход и оказывается на дашборде. Финальный скриншот и видео уходят в артефакты CI (или любое другое место для сохранения этапов тестирования).

Письмо с кодом подтверждения проверяется через Gmail-эмулятор тем же прогоном: приложение шлёт письмо в Resend-эмулятор или письмо уже лежит в seed-данных Gmail, агент забирает его по API и вводит код. Весь сценарий, который в обычной жизни требует живого аккаунта Google и ручной работы, проходит за секунды и полностью детерминирован.

Где у этой связки границы

Честные ограничения, чтобы не было иллюзий:

  • Эмулятор повторяет API на момент версии пакета, а реальный Google меняет поведение. Зелёный прогон против эмулятора не гарантирует, что против настоящего OAuth всё заработает: редиректы, сроки жизни токенов и пограничные случаи с отзывом доступа стоит периодически проверять вручную против реального сервиса. Набор сервисов фиксирован, и если ваш стек опирается на что-то за пределами списка, эмулятор придётся писать самому.

  • У agent-browser свои цены: refs инвалидируются при каждом изменении страницы, и агент, забывший сделать новый снимок, будет кликать мимо. state save сохраняет состояние, но есть свои нюансы: токены доступа истекают, и через пару недель сохранённая сессия протухнет. Ну и навык “прогоняй сценарий головой, а не скриптом” требует от модели нормального контекста: снимки компактные, но длинный сценарий всё равно съедает десятки тысяч токенов. Поэтому желательно запускать это в отдельном субагенте.

  • npx emulate нацелен на зарубежные сервисы, но для установки Yandex Oauth/Yookassa - есть специальные навыки. (Дайте знать, если вам они бы пригодились)

Вывод

agent-browser убирает главную боль “агент + браузер”: контекст перестал переполняться из-за огромного DOM, а действия перестали зависеть от хрупких селекторов. emulate убирает вторую боль, внешние зависимости, заменяя Google, Stripe и GitHub на локальные stateful-копии. По отдельности это удобные утилиты; вместе они превращают тестирование браузера из ритуала с реальными аккаунтами в обычный CI-прогон, который может выполнить сам агент.

Агенту для таких прогонов нужна модель. Через Omni Router вы можете подключить Claude Code, Codex, Kimi, OpenCode и прочие клиенты и запускать свои процессы тестирования с эмуляцией браузера.