Browser-workflow мониторы (автоматизация браузера)
Обычный HTTP-монитор и content-монитор видят только тот HTML, что сервер отдал в первом ответе. Для современных SPA, страниц с тяжёлым JavaScript и всего, что прячется за авторизацией, этого мало: нужный текст появляется только после логина, нескольких кликов и отрисовки фронтендом.
Browser-workflow монитор прогоняет заданную последовательность действий в настоящем headless Chromium — как маленький Selenium-сценарий — и проверяет, что сценарий по-прежнему доходит до конца. Если любой шаг падает (элемент пропал, текст не совпал, страница не загрузилась) — приходит alert со скриншотом страницы в момент сбоя. Дополнительно монитор умеет извлекать текст со страницы и слать уведомление при его изменении.
Создание в админке
Заголовок раздела «Создание в админке»app.notifly.ru → Мониторы → Создать → «Что вы хотите проверять?» → «Сценарий из нескольких шагов» → «Реальный браузер с кликами». Готовые мониторы живут на вкладке Browser.
В диалоге каждый шаг описывается словами — кнопка «Подобрать действие» вызывает ИИ-подбор, который сам находит действие и селектор и сразу показывает скриншот результата. Шаги входа отмечаются флажком «Авторизация». «Тестовый прогон» выполняет весь сценарий и сохраняет cookies сессии — при сохранении монитора они шифруются.
Из чего состоит сценарий
Заголовок раздела «Из чего состоит сценарий»Монитор описывается полями:
appid— канал для алертов (свой или расшаренный вам с правом отправки).name— название,1..200символов.startUrl— начальный URL (обязателен, толькоhttp/https). С него начинается каждый прогон.steps— JSON-массив шагов, от 1 до 20 штук. Выполняются строго последовательно.cookies— опционально: сессия для авторизованных сценариев (см. ниже). Хранится в зашифрованном виде и наружу никогда не отдаётся.timeoutSec— бюджет одного прогона сценария (а не шага). По умолчанию60, максимум120; браузерный сервис держит прогон в рамках 30–110 с. Ограничение на отдельный шаг задаётся в самом шаге.intervalSec— как часто прогонять сценарий. Минимум 300 (5 минут), максимум86400(сутки).alertMessage(обязателен, до 2000 символов),alertTitle,alertMessageMarkdown,alertPriority(0…10) — что и с каким приоритетом слать при падении.notifyOnChange/notifyOnChangeVars— режим «уведомлять при изменении» извлечённых значений (см. ниже).
Шаг (BrowserStep)
Заголовок раздела «Шаг (BrowserStep)»Каждый элемент массива steps — это объект:
| Поле | Назначение |
|---|---|
name | человеко-читаемое имя шага (обязательно, попадает в alert при падении) |
description | описание шага на естественном языке — по нему ИИ подбирает action + selector |
action | действие (см. таблицу ниже) |
selector | селектор цели: CSS, XPath или text=… (см. ниже) |
value | значение: URL / текст / клавиша / JS-выражение; поддерживает подстановку {{переменная}} |
extractName | имя переменной для extractText и executeScript |
auth | шаг авторизации: выполняется только при невалидной сессии |
timeoutSec | таймаут этого шага, 1..120 с; 0 или отсутствие — встроенные ожидания действия |
Селекторы. По умолчанию — CSS. XPath начинается с //, (// или
xpath=; text=подстрока находит элемент по видимому тексту. Если основной
селектор перестал находить элемент, браузер пробует ослабленные варианты — это
снижает ложные срабатывания при косметических правках вёрстки. Подстановка
{{переменная}} работает и в value, и в selector.
Таймаут шага — собственный дедлайн шага; общий бюджет прогона остаётся
верхней границей. Он ограничивает шаг сверху, но не удлиняет встроенные
ожидания: появления элемента (около 8–14 с на попытку, с одной повторной
попыткой, если бюджет позволяет) и загрузки страницы при navigate (25 с). Для
executeScript он заменяет лимит по умолчанию в 20 с.
Действия (actions)
Заголовок раздела «Действия (actions)»Поддерживаются десять действий:
action | Что делает | Нужен selector | Нужен value |
|---|---|---|---|
navigate | перейти на URL (value = URL) | — | да (URL) |
click | кликнуть по элементу | да | — |
type | ввести value в поле | да | текст |
select | выбрать value в <select> | да | значение опции |
press | нажать клавишу value (например Enter) | — | клавиша |
waitFor | дождаться появления селектора | да | — |
assertText | проверить, что текст селектора содержит value | да | ожидаемый текст |
extractText | извлечь текст селектора в переменную extractName | да | — |
scroll | прокрутить страницу: value = top | bottom либо пиксели (300 вниз, -300 вверх); с селектором — прокрутить к элементу | опционально | — |
executeScript | выполнить JS-выражение value на странице; результат (строкой) — в переменную extractName, если она задана | — | JS-выражение |
Шаги click, type, select, waitFor, assertText, extractText
требуют непустой selector — без него создание/обновление монитора вернёт
400. Для navigate обязателен value (URL), для extractText —
extractName, для executeScript — value длиной до 4000 символов.
executeScript исполняется внутри страницы, промисы дожидаются (await), а
null/undefined превращаются в пустую строку. Шаг падает, если выражение
бросило исключение. Сетевые запросы из скрипта идут через тот же браузер и тот
же SSRF-фильтр.
Пример массива шагов
Заголовок раздела «Пример массива шагов»Проверяем, что после логина в личном кабинете виден баланс, и запоминаем его:
[ {"name": "Открыть дашборд", "action": "waitFor", "selector": "#dashboard"}, {"name": "Раскрыть счёт", "action": "click", "selector": "button.account-toggle"}, {"name": "Дождаться баланса", "action": "waitFor", "selector": ".balance-value"}, { "name": "Проверить валюту", "action": "assertText", "selector": ".balance-value", "value": "₽" }, { "name": "Запомнить баланс", "action": "extractText", "selector": ".balance-value", "extractName": "balance" }, { "name": "Число заказов", "action": "executeScript", "value": "document.querySelectorAll('.order-row').length", "extractName": "orders", "timeoutSec": 10 }]Авторизованные сценарии
Заголовок раздела «Авторизованные сценарии»Чтобы мониторить страницы за логином, нужна валидная сессия. Проще всего
описать вход прямо в сценарии и отметить эти шаги auth: true:
- Сохранённые
cookiesприменяются в начале каждого прогона. После успешного прогона монитор перешифровывает и сохраняет свежие cookies — сессия продлевается сама; при падении сохранённая сессия не трогается. - Если в сценарии есть auth-шаги и сохранена сессия, сначала выполняются только проверочные шаги. Прошли и браузер не оказался на странице входа — готово. Иначе сессия считается протухшей: сценарий прогоняется целиком, вместе с входом, и новая сессия сохраняется.
- «Страница входа» — URL первого auth-шага
navigate(сравниваются хост и путь). Поэтому вход удобно начинать с шагаnavigateна страницу логина. - Если проверочные шаги используют переменную, которая извлекается только на auth-шаге, короткий прогон невозможен и сценарий всегда выполняется целиком. Без сохранённой сессии — тоже.
Если сценарий закончился на странице входа, прогон считается упавшим: шаг
session — сессия протухла, login — повторный вход не помог.
Захват cookies (login)
Заголовок раздела «Захват cookies (login)»Сессию можно получить и отдельно: POST /browser-workflow/login выполняет шаги
входа в headless-браузере и возвращает итоговые cookies в открытом виде — их
затем передают строкой в поле cookies при создании/обновлении монитора, где
они шифруются перед сохранением.
curl -X POST "$NOTIFLY_URL/browser-workflow/login" \ -H "X-Notifly-Key: <api-token>" \ -H "Content-Type: application/json" \ -d '{ "startUrl": "https://app.example.com/login", "steps": [ {"action": "type", "selector": "#email", "value": "me@example.com"}, {"action": "type", "selector": "#password", "value": "secret"}, {"action": "click", "selector": "button[type=submit]"} ], "timeoutSec": 60 }'Ответ:
{ "cookies": [ /* массив cookie: name, value, domain, path, secure, httpOnly, expires */ ], "count": 7, "url": "https://app.example.com/dashboard", "screenshot": "/9j/4AAQ..."}screenshot — base64 JPEG итоговой страницы (best-effort, может отсутствовать).
Если браузер не смог выполнить шаги, эндпоинт отвечает 502 с причиной.
ИИ-подбор шага (build-step)
Заголовок раздела «ИИ-подбор шага (build-step)»Вручную подбирать CSS-селекторы скучно и хрупко. Поэтому есть эндпоинт
POST /browser-workflow/build-step: вы описываете шаг словами
(description), а ИИ сам подбирает action и стабильный selector, после чего
тут же проверяет кандидата в реальном браузере и возвращает результат.
Как это работает под капотом:
- Сервис открывает
startUrl, применяет переданныеcookiesи воспроизводит уже подтверждённыеsteps— получая актуальное состояние страницы. - LLM по описанию, тексту страницы и списку интерактивных элементов предлагает действие и до трёх ранжированных селекторов-кандидатов.
- Кандидаты дёшево предвалидируются по HTML (синтаксис + наличие на странице), затем все проверяются за один прогон браузера.
- Если действие сработало — возвращается
verified: trueвместе с примером значения (sampleValue) дляextractText/assertText. Если нет — ещё до двух попыток с учётом ошибки, затем последнее предложение сverified: falseи пояснением вnote.
Тело запроса:
{ "startUrl": "https://app.example.com/login", "steps": [ /* уже подтверждённые шаги: action, selector, value, extractName, timeoutSec */ ], "description": "нажать кнопку «Войти»", "cookies": [ /* опционально: сессия */ ], "sessionId": "", "timeoutSec": 60}sessionId — необязательный ключ тёплой браузерной сессии: передавайте одно и
то же значение на протяжении работы мастера, и соседние запросы смогут
переиспользовать открытый браузер (best-effort).
Ответ:
{ "action": "click", "selector": "button[type=submit]", "value": "", "extractName": "", "label": "Войти", "reason": "...", "sampleValue": "", "verified": true, "availableVars": ["balance"], "cookies": [ /* итоговые cookies сессии */ ], "screenshot": "/9j/4AAQ..."}Если модель сочла шаг медленным, в ответе есть и рекомендованный timeoutSec
шага. screenshot — base64 JPEG страницы после выполнения кандидата
(best-effort).
Поле description — от 1 до 500 символов. availableVars показывает переменные,
которые уже извлечены предыдущими шагами (extractText и executeScript с
extractName) и доступны для подстановки {{var}} на этом шаге. Возвращаемые
cookies стоит накапливать на клиенте и передавать в следующий build-step,
чтобы авторизованные шаги не пришлось переигрывать заново.
Тестовый прогон (test-step)
Заголовок раздела «Тестовый прогон (test-step)»Перед сохранением весь сценарий целиком можно прогнать через
POST /browser-workflow/test-step — это «кнопка Проверить». Сервис выполняет все
переданные шаги подряд (без логики короткого прогона) и сообщает, дошёл ли до
конца.
curl -X POST "$NOTIFLY_URL/browser-workflow/test-step" \ -H "X-Notifly-Key: <api-token>" \ -H "Content-Type: application/json" \ -d '{ "startUrl": "https://app.example.com/dashboard", "cookies": [ /* сохранённая сессия */ ], "steps": [ /* весь сценарий */ ], "timeoutSec": 60 }'Ответ:
{ "ok": true, "failedStep": -1, "url": "https://app.example.com/dashboard", "screenshot": "/9j/4AAQ...", "cookies": [ /* итоговые cookies всей сессии */ ], "count": 7, "vars": {"balance": "12 340 ₽", "orders": "3"}}Если сценарий упал, ok: false, а failedStep укажет индекс сломавшегося шага
(с нуля; -1 — сбой до первого шага, например не открылась стартовая страница) и error — причину. При
успехе vars содержит все извлечённые значения — удобно сразу проверить, что
забирается именно то, что нужно. Итоговые cookies админка сохраняет как сессию
монитора.
Режим «уведомлять при изменении»
Заголовок раздела «Режим «уведомлять при изменении»»Если включить notifyOnChange, монитор работает не только как «упал/не упал»,
но и как трекер значений: после каждого успешного прогона он сравнивает
извлечённые переменные (extractText, executeScript с extractName) с
прошлым успешным прогоном и шлёт уведомление, когда любое из них
изменилось.
notifyOnChangeVars— JSON-массив имён переменных для отслеживания (строкой, например"[\"balance\"]"). Пусто = следить за всеми извлечёнными переменными.- В тексте уведомления — строки
balance: старое → новое; заголовок по умолчанию — «Browser workflow «‹название›»: данные изменились». - Первое наблюдение значения уведомления не даёт; при падении прогона базовые значения сохраняются.
Так, например, можно ловить изменение цены товара, статуса заказа или остатка на складе на странице, которая рендерится только в браузере.
Алерты и статусы
Заголовок раздела «Алерты и статусы»Монитор создаётся со статусом pending и первой проверкой «на сейчас». Дальше
он переходит в up / down по результатам прогонов. При падении в карточке
монитора видны lastError и lastFailedStep — текст последней ошибки и метка
сломавшегося шага (#3 Дождаться баланса, либо session / login).
Алерт — заголовок alertTitle (пустой — «Browser workflow «‹название›»
failed») и текст alertMessage с дописанными строками Шаг: … и Ошибка: ….
- Алерт уходит при переходе в
downи когда сценарий начинает падать на другом шаге. Пока падает тот же шаг, повторов нет, даже если текст ошибки меняется: отметка отправленного алерта хранится вalertedAt. - Re-arm:
PUTс изменениемstartUrl,steps,intervalSec,timeoutSec,notifyOnChangeилиnotifyOnChangeVarsсбрасываетalertedAtи ставит прогон «на сейчас» — если сценарий всё ещё падает, придёт свежий алерт. Новыеcookiesи правка текстов ничего не перезапускают. - Отдельного уведомления о восстановлении нет: монитор просто возвращается в
up. - Сбои самого браузерного сервиса (браузер не поднялся и т.п.) не считаются падением сайта: алерта нет, статус не меняется, монитор перепроверится позже.
- В окне обслуживания прогоны идут и пишутся в
историю, но алерт о падении откладывается:
alertedAtне ставится, и если после окна сценарий всё ещё падает, первая же проверка пришлёт алерт. Уведомления об изменениях в окне не шлются, а базовые значения не сдвигаются — после окна сравнение пойдёт со значениями до работ.
Алерт — обычное сообщение канала: его досылка во внешние каналы подчиняется
тихим часам. Если монитор добавлен на
статус-страницу, переходы up↔down открывают и
закрывают на ней авто-инцидент.
POST /browser-workflow/:id/pause ставит статус paused: сценарий не
прогоняется, а результат уже запущенного прогона отбрасывается.
POST /browser-workflow/:id/resume возвращает pending и ставит прогон «на
сейчас». Оба отвечают 200 без тела.
Скриншот сбоя
Заголовок раздела «Скриншот сбоя»Когда шаг падает, браузер снимает JPEG страницы (до 1280 px по ширине) и кладёт
его в приватное объектное хранилище. Ключ кадра попадает в extras
алерт-сообщения под именем notifly::screenshot:
{"notifly::screenshot": "browser-shots/browser-workflow/42/1759000000000.jpg"}В ленте сообщений админки и Android-приложения под текстом алерта показывается
превью, по клику — полный размер. Через API картинка отдаётся по
GET /browser-shot/<key> (image/jpeg) — только владельцу монитора;
API-токен, ограниченный по каналам, видит лишь кадры мониторов своих каналов.
Чужой, удалённый или вытесненный кадр отвечает 404.
- Хранится только кадр, попавший в алерт. Скриншоты прогонов, не породивших сообщение (тот же шаг продолжает падать), удаляются сразу.
- Кадры занимают хранилище тарифа
(
storageMB: Free 50 МБ, Pro 1 ГБ, Business 10 ГБ): при переполнении самые старые кадры вытесняются, и у старых сообщений превью пропадает. - При удалении монитора удаляются и его скриншоты.
- Для сбоев
sessionиlogin(обнаруживаются уже после прогона) скриншота нет.
История и квота
Заголовок раздела «История и квота»Вкладка История монитора показывает прогоны и график сбоев по шагам; через
API — GET /monitor-history/browser_workflow_monitor/:id. В метаданных события
видно, понадобился ли повторный вход (reloggedIn).
Каждый прогон сценария — одно событие дневной квоты dailyEvents, независимо
от числа шагов. Если квота исчерпана, прогоны откладываются до 00:05 МСК
следующего дня — см. Квоты и тарифы.
REST API
Заголовок раздела «REST API»Авторизация — заголовок X-Notifly-Key с API-токеном (M…):
чтение — любым токеном, изменения — токеном с доступом write (иначе 403).
Токен, ограниченный по каналам, видит и меняет только мониторы своих каналов.
| Метод и путь | Права API-токена | Назначение |
|---|---|---|
GET /browser-workflow | read | список мониторов (без cookies) |
POST /browser-workflow | write | создать монитор |
PUT /browser-workflow/:id | write | обновить монитор |
DELETE /browser-workflow/:id | write | удалить монитор (вместе со скриншотами) |
POST /browser-workflow/:id/pause | write | поставить на паузу (status=paused) |
POST /browser-workflow/:id/resume | write | снять с паузы (status=pending) |
POST /browser-workflow/build-step | write | ИИ-подбор и проверка одного шага |
POST /browser-workflow/login | write | прогнать логин и вернуть cookies сессии |
POST /browser-workflow/test-step | write | тестовый прогон всего сценария |
GET /browser-shot/<key> | read | скриншот сбоя по ключу из extras сообщения |
Те же операции с мониторами есть в Go/Python SDK и MCP-сервере.
Пример: создание монитора
Заголовок раздела «Пример: создание монитора»curl -X POST "$NOTIFLY_URL/browser-workflow" \ -H "X-Notifly-Key: <api-token>" \ -H "Content-Type: application/json" \ -d '{ "appid": 12, "name": "Баланс в ЛК", "startUrl": "https://app.example.com/dashboard", "steps": "[{\"name\":\"Дождаться баланса\",\"action\":\"waitFor\",\"selector\":\".balance-value\"},{\"name\":\"Запомнить баланс\",\"action\":\"extractText\",\"selector\":\".balance-value\",\"extractName\":\"balance\"}]", "cookies": "[ /* plaintext cookies из /login */ ]", "intervalSec": 900, "timeoutSec": 60, "notifyOnChange": true, "alertMessage": "Сценарий «Баланс в ЛК» сломался" }'См. также: Активные мониторы · Content-монитор · Workflow-монитор (HTTP) · Полный API.