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

Автоматизация браузера через 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 его кладут в защищённый кэш или secrets.

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

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

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

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

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

  • Трёхуровневая авторизация. Сначала persistent state из файла, если его нет, то секреты из менеджера паролей, и только в конце полный логин через форму. Полный флоу логина прогоняют редко, чтобы не зависеть от капчи.
  • 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 flow с 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 всё заработает: редиректы, сроки жизни токенов и edge cases с отзывом доступа стоит периодически проверять вручную против реального сервиса. Набор сервисов фиксирован, и если ваш стек опирается на что-то за пределами списка, эмулятор придётся писать самому.

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

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

Вывод

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

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