
Попросите 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 |
| 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 и прочие клиенты и запускать свои процессы тестирования с эмуляцией браузера.