Документация UI-тестирование браузера и мобильных

UI-тестирование браузера и мобильных приложений

Управляйте реальным браузером (Chromium, Firefox, WebKit) или реальным Android-устройством через живую сессию — осматривайте экран, действуйте с элементами по смыслу, добавляйте проверки, записывайте сценарий как воспроизводимый тест, запускайте его и экспортируйте в код Playwright или Appium. Сделано сразу для двух аудиторий: люди работают руками, а ИИ-агент может сам создавать и прогонять UI-тесты от начала до конца.

Что вы можете

  • Логи — в живой браузерной сессии смотрите предупреждения и ошибки, с адресом источника и числом повторений, когда они доступны. Обычные сообщения console.log не записываются. В мобильной сессии показывается журнал устройства. Автообновление подгружает новые записи и прекращает опрос при уходе со страницы. Если старые записи не поместились в ограниченный журнал, появится уведомление.
  • Живая сессия — открыть целевой URL (или приложение) на раннере; раннер транслирует свой экран и принимает ваши команды.
  • Осмотр (inspect) — прочитать элементы на экране со стабильными селекторами (id, data-testid, aria-label, placeholder, видимый текст) и координатами. Вы выбираете элементы по смыслу, а не по пикселям.
  • Действие — клик, ввод текста, нажатие клавиши, тап по элементу по его тексту/селектору.
  • Проверка (assert) — проверить страницу в привычном виде: assertVisible, assertText, assertValue, assertEnabled, assertChecked, assertURL, assertTitle и другие. Каждая проверка выполняется вживую и становится шагом записи.
  • Запись → Сохранение → Воспроизведение — каждое действие и проверка записываются; сохраните как переиспользуемый UI-тест и воспроизведите позже на любом подходящем раннере. Сохранённый тест помнит платформу, на которой был записан (а для мобильного — и тестируемое приложение), поэтому повторный запуск — это один клик, без повторного ввода настроек.
  • Экспорт — превратить запись в идиоматичный код Playwright (TypeScript) или Appium (Python), чтобы забрать в свой репозиторий.

Страница UI Testing

Если Остановить не удалось, студия оставляет текущую сессию и запись открытыми и показывает ошибку. Повторите остановку; не закрывайте страницу, пока запрос не завершится успешно.

Для удалённого раннера сообщение Остановка запрошена означает, что отмена принята, но раннер ещё может завершать работу. Это не подтверждение того, что выполнение уже закончилось.

Всё перечисленное доступно визуально в разделе UI Testing в боковом меню:

  • Выберите платформу — Веб (браузер) или Android — и форма подстроится: движок + размер окна + стартовый URL для веба; приложение и необязательный диплинк для Android.
  • Движок выбирается на месте. Ряд «Движок» показывает ровно то, на чём этот стенд действительно может выполнить прогон: лёгкий движок, реальные браузеры — что есть в парке раннеров, включая сам сервер Mockarty. Того, чего нет, в выборе не будет: браузер, который никто не предоставляет, невозможно выбрать по ошибке. Наведите на вариант — увидите, сколько раннеров его дают. Авто (по умолчанию) отдаёт выбор серверу. Вариант, помеченный ⚠, выполняет шаги, но не даёт картинок: скриншоты, визуальные сравнения и живой просмотр на нём здесь недоступны — по той же причине такой движок не участвует в кросс-движковом прогоне.
  • Для Android выберите загруженный ранее APK, загрузите новый прямо здесь (Загрузить APK) или укажите package id приложения, уже установленного на устройстве.
  • Удалённое приложение сразу исчезает из списка и перестаёт скачиваться. Место в хранилище может освободиться позже после фоновой очистки. Если одновременно загружается много сборок, немного подождите и повторите отклонённую загрузку.
  • Запустите сессию и наблюдайте живой экран. Нажмите Осмотр, чтобы наложить поверх экрана найденные элементы; клик по элементу на стриме — тап по нему, а клик по строке в панели «Элементы» открывает действия: тап, ввод значения, проверка видимости, проверка текста, копирование селектора. Клик по свободному месту стрима — тап в эту точку.
  • Печатайте с клавиатуры. Кликните по полю на стриме и просто печатайте — нажатия идут в живую страницу/устройство (работают Enter, Backspace, Tab и стрелки).
  • Панель проверок выполняет ассерты вживую (assertVisible, assertText, assertURL, …) — каждая проверка становится шагом записи.
  • Чтобы выбрать цель проверки с live-экрана, выберите поле #селектор / текст, затем нажмите на элемент в трансляции. В поле подставится селектор, без нажатия на сам элемент тестируемого приложения. Введите ожидаемое значение, если оно нужно для выбранной проверки, и нажмите Проверить. Сессию можно развернуть на всё окно; длинный список элементов прокручивается внутри боковой панели. При остановке можно сохранить запись, удалить её или вернуться к сессии.
  • Запись — это редактируемый скрипт. Каждое действие и проверка появляются нумерованной карточкой в панели Шаги (у проверки — вердикт ✓/✗). Прямо во время записи можно перетащить карточку для перестановки, дважды кликнуть по заголовку, чтобы переименовать шаг из автогенеренного в понятный, отключить шаг (останется, но пропустится при следующем запуске) и удалить лишний шаг — так вы сохраняете именно тот сценарий, который нужен. Ещё два переключателя задают, что делать, если шаг не удался: разрешить шагу падать (он выполняется, промах фиксируется, но прогон продолжается и вердикт не меняется — для cookie-баннера, который видят не все) и выполнять только если прошёл более ранний именованный шаг (иначе шаг пропускается, а не падает: его не пробовали — для проверки, которая имеет смысл только после успешного входа). Сохранение сохраняет ваш порядок, имена и удаления; переименованные шаги попадают комментариями в экспортируемый код, туда же попадают и оба флага — экспорт, который их теряет, строже прогона, из которого он сделан.
  • Для Android в сессии есть аппаратные кнопки Назад и Домой, кнопка Повернуть (портрет ↔ ландшафт — стрим и рамка телефона поворачиваются вместе с устройством) и Лог устройства (последние строки logcat с обновлением и копированием).
  • Сохранить запись превращает поток действий в переиспользуемый UI-тест; левая панель показывает сохранённые тесты с поиском, бейджем платформы, кнопками Запустить (с отчётом по шагам), История запусков (все прошлые прогоны, каждый открывает свой полный отчёт), экспорта кода, Создать TCM-кейс и удаления.
  • Если подходящий раннер не запущен, страница покажет точные команды, чтобы поднять его за минуту.

Правка локатора и отладка одного шага

Когда вы редактируете действия UI-шага в тест-кейсе, у каждого действия есть выбор «Искать по» — как найти элемент: по видимому тексту, роли, Test ID (data-testid), подписи поля или сырому CSS. Это избавляет от ручного ввода синтаксиса селекторов: выбираете стратегию, вводите значение — Mockarty собирает локатор за вас (существующие селекторы остаются как есть).

Если шаг падает, не нужно перепрогонять весь кейс: поправьте локатор и нажмите «Тест шага» — Mockarty проиграет действия именно этого шага в браузере (нужен браузер-раннер) и покажет рядом результат по каждому действию (✓/✗). Так вы чините один локатор и проверяете его на месте, а потом сохраняете кейс.

Горячие клавиши: Ctrl+Enter — старт сессии, I — обновить инспектор, Ctrl+S — сохранить запись.

Страница UI-тестирования

Работает на сервере из коробки

Сервер Mockarty выполняет UI-тесты in-process на лёгком движке — отдельный раннер ставить не нужно. Нажмите Запустить, и сервер сам воспроизведёт тест (со скриншотами через встроенный рендер-движок). Внешний раннер добавляют только чтобы разгрузить сервер (загруженный сервер, или прогон на конкретной ОС/браузере): когда он онлайн — забирает задачу автоматически, иначе её выполняет сервер. Отключить in-process путь: MOCKARTY_UI_TESTS_INPROCESS=false (тогда нужен внешний раннер).

Живые сессии тоже. «Подключиться к браузеру» — интерактивный экран, по которому вы кликаете, печатаете и записываете шаги, — работает на самом сервере, поэтому и для записи теста ничего ставить не нужно. Такую сессию сервер открывает на настоящем браузере (живая картинка требует движка с отрисовкой), а воспроизведение остаётся на лёгком. Если внешний браузер-раннер онлайн — сессию заберёт он, и загруженный сервер не станет узким местом.

Нужен раннер (для разгрузки или реального Chromium)

UI-тесты выполняются на раннере, который объявляет нужную возможность — сам сервер считается раннером для лёгкого движка, либо внешний раннер для разгрузки / конкретного браузера:

Цель Возможность
Веб-браузер ui-test
Android mobile-android
iOS не поддерживается. Продукт предлагает цели web и Android: драйвера для iOS и жизненного цикла iOS-устройства в нём нет. Задача с ios отклоняется по имени (токен ios_not_implemented сохранён как стабильный), а не выполняется

Запустите раннер, указав на ваш админ — один бинарь выполняет API, нагрузку и UI-тесты (UI на лёгком движке, Chromium ставить не нужно):

COORDINATOR_URL=http://localhost:5770 \
API_TOKEN=mki_ваш_токен_раннера \
RUNNER_NAME=ui-runner SHARED=true \
./mockarty-runner

Он регистрирует дополнительный раннер ui-runner-ui с capability ui-test. Отключить UI: RUNNER_UI_TESTS=false.

Для Android запустите мобильный раннер на машине с adb и работающим эмулятором или подключённым устройством:

MOCKARTY_ADMIN_URL=http://localhost:5770 \
MOCKARTY_RUNNER_TOKEN=mki_ваш_токен_раннера \
RUNNER_NAME=android-runner RUNNER_PLATFORM=android RUNNER_SHARED=true \
./mockarty-mobile-runner

Лёгкий движок — сотни параллельных сессий без Chromium

UI-тесты по умолчанию выполняются на паре лёгких движков вместо полного Chromium. Навигация, клики, заполнение форм, ожидания и проверки выполняются на специализированном headless-ядре, которому нужна лишь малая доля памяти Chromium — один раннер держит сотни параллельных сессий на скромной машине. Шаги, которым нужны пиксели (скриншоты, визуальные проверки), прозрачно выполняются на встроенном рендер-движке с переносом cookies — авторизованные страницы рендерятся корректно. Оба движка скачиваются автоматически при первом использовании (на air-gapped хостах укажите RUNNER_LIGHTPANDA_PATH / RUNNER_SERVO_PATH); ваши тесты и отчёты не меняются.

Если набору тестов важна именно точная сборка Chrome, Firefox или WebKit — задайте RUNNER_BROWSER_PROVIDER=local на раннере с Playwright: лёгкий движок — умолчание, реальные браузеры — опция.

Интерактивная сессия всегда идёт на настоящем браузере. Когда вы подключаетесь к браузеру из студии — тот самый живой экран, по которому вы кликаете, печатаете и записываете шаги, — раннер открывает именно эту сессию на полноценном браузере, даже если весь остальной набор работает на лёгком движке: у лёгкого ядра нет отрисовки, и картинку оно показать не может. Настраивать ничего не нужно; если хотите оставить интерактивные сессии на лёгком ядре (авторинг через инспектор элементов, без видео) — задайте RUNNER_LIVE_ENGINE=light.

Один раннер, два режима — выбор движка на запуск

Один раннер может обслуживать оба движка сразу, каждый со своим бюджетом потоков. Задайте RUNNER_UI_ENGINES списком движок[:потоки] через запятую:

# 8 лёгких сессий (без Chromium) + 2 реальных Chrome, один бинарь
RUNNER_UI_ENGINES=lightweight:8,chromium:2 \
COORDINATOR_URL=http://localhost:5770 API_TOKEN=mki_… ./mockarty-runner

Каждый движок регистрируется отдельной карточкой раннера с лейблом engine=<name> (lightweight, chromium, firefox, webkit). При запуске теста вы выбираете, каким подходом его выполнить, полем engine — запуск маршрутизируется на раннер с этим движком:

curl -s -X POST http://localhost:5770/api/v1/ui-tests/$UITEST_ID/run \
  -H "Authorization: Bearer $TOKEN" -d '{"engine": "chromium"}'   # реальный Chrome
curl -s -X POST http://localhost:5770/api/v1/ui-tests/$UITEST_ID/run \
  -H "Authorization: Bearer $TOKEN" -d '{"engine": "lightweight"}' # лёгкий движок

Оставьте engine пустым — запуск уйдёт на любой доступный UI-раннер. engine выбирает подход раннера; browser выбирает браузер внутри раннера с реальными браузерами. Сам сервер Mockarty всегда гоняет UI-тесты на лёгком движке из коробки — раннеры добавляют только для разгрузки или ради реальных браузеров.

Живые сессии и запись работают и на лёгком движке. Запустите сессию как обычно — студия покажет баннер движка и адаптируется: область экрана здесь — рендер-вид (обновляется при изменениях страницы), а инспектор элементов — основной способ действовать: выберите элемент из списка (по селектору, подписи или тексту) и кликните/заполните его; каждое действие записывается тем же воспроизводимым шагом, что и в сессии на реальном браузере, поэтому запись отсюда реплеится на любом движке. Клик прямо по рендер-виду тоже работает (точка клика разрешается в элемент), но на страницах с перекрывающимися элементами точный выбор — через инспектор. Видео прогона (зацикленная анимация в отчёте) доступно на любом движке; Playwright trace требует реальный браузер.

Браузерные тесты на реальном телефоне

Спаренный Android-телефон может работать браузерным узлом: приложение-компаньон поднимает WebView за кадром и выводит их DevTools-подключение наружу к серверу Mockarty, поэтому раннер управляет браузерным движком телефона так же, как настольным headless-движком — со скриншотами, отрисованными самим устройством, в реальном мобильном DPI.

Включите браузерный мост в компаньоне (пока мост поднят, телефон объявляет возможность browser-cdp и версию своего WebView меткой), затем направьте раннер на устройство:

RUNNER_BROWSER_PROVIDER=cdp \
RUNNER_CDP_ENDPOINT=ws://localhost:5770/api/v1/companion/cdp/<deviceId> \
COORDINATOR_URL=http://localhost:5770 API_TOKEN=mki_… ./mockarty-runner

GET /api/v1/companion/cdp-devices покажет телефоны, которые сейчас отдают мост в вашем пространстве имён. К устройству одновременно подключается один раннер (второй получит понятную ошибку «уже подключён»), а устройства из других пространств имён не видны вовсе. Телефон использует свой токен привязки и ID устройства для моста, активности, отчётов о сбоях и нагрузке, получения своих заданий и отключения; обычный токен раннера или другой телефон не может действовать под этим ID. Токен телефона не даёт права отправлять операторские команды или устанавливать приложения. В кластере временный ответ 503 означает, что общий список устройств не удалось проверить; повторите запрос, не считая его пустым списком. Браузерный движок здесь — тот, что установлен на телефоне, поэтому если тесту нужна конкретная возможность, закрепите минимальную версию WebView в селекторе.

Когда вы нажимаете «отключиться» на телефоне, компаньон сообщает серверу, что уходит: его карточка и сессия живого управления снимаются сразу, а не гаснут по таймауту. Телефон, который исчез молча — вынули батарею, убили приложение, пропала связь, — по-прежнему отваливается сам по таймауту, поэтому устройство может ещё недолго показываться в списке после того, как его уже нет.

Во время зеркалирования телефон можно ещё и слушать: кнопка звука в ряду удалённого управления просит устройство захватывать то, что оно воспроизводит (медиа, речь ассистента, звуки приложений — не микрофон), и передавать в зеркало, где звук играет в вашем браузере. Захват идёт только пока кнопка включена; выключение или уход с устройства останавливает его и на телефоне. Устройство, которому отказано в разрешении на запись, продолжает зеркалировать видео без звука.

Задание доходит до телефона в момент отправки. Компаньон держит на сервере открытый запрос по каждой из своих очередей, а не спрашивает по таймеру, и сервер отвечает на него сразу, как только на устройство поступает прогон, нагрузка, фаззинг, установка или команда живого управления — тап в живом управлении и отправленный прогон начинаются без ожидания следующего опроса, а телефон в простое шлёт запрос лишь примерно раз в 25 секунд. Если вы опрашиваете очередь устройства сами, эндпоинты next (/api/v1/companion/live/<device>/run/next, …/load/next, …/fuzz/next, …/control/next) принимают ?wait=<секунды> (до 25) и держат запрос, пока что-то не появится; без параметра они отвечают сразу — 204, если очередь пуста.

Услышать устройство — захват звука в реальном времени

Кнопка аудио в живом управлении включает захват звука на телефоне. Пока он включён, компаньон передаёт на сервер то, что устройство воспроизводит (и записывает), небольшими PCM-чанками — POST /api/v1/companion/live/<device>/audio?rate=<гц>&ch=<n>, сырые байты в теле запроса. Сервер хранит короткое недавнее окно на устройство (несколько мегабайт, самый старый чанк вытесняется первым, поэтому переполнение стоит крошечной паузы в прошлом, а не остановки потока), а живой вид читает его обратно через GET /api/v1/companion/live/<device>/audio — передайте ?since=<seq>, чтобы получить только чанки новее последнего увиденного (номер последовательности возвращается в заголовке X-Audio-Next-Seq; X-Audio-Rate / X-Audio-Channels несут формат PCM). Слишком большой чанк отклоняется с 413, а всё, что не PCM и не тип audio/*, — с 415: потерянный чанк — это пауза, поток никогда не копится в очереди. Выключение захвата, остановка живой сессии или прощание телефона с сервером удаляет буферизованный звук вместе с сессией.

Та же связка звука и доказательств видна в отчётах прогонов: шаг playAudio несёт идентификатор клипа, который он воспроизвёл, шаг recordAudio — идентификатор записанного клипа, а GET /api/v1/companion/runs/<runId> разрешает эти идентификаторы в манифест clips (имя, тип, размер в байтах), чтобы отчёт мог предложить воспроизведение рядом с результатом шага; сами байты клипа отдаёт GET /api/v1/companion/clips/<clipId>.

Сценарий телефона умеет также поворачивать экран и входить по одноразовому коду:

  • rotate поворачивает экран в portrait или landscape; после прогона телефон возвращается к своему повороту — пройден прогон или нет.
  • sms ждёт (время шага, по умолчанию минуту) сообщение с кодом — при желании только от отправителя, имя которого указано в тексте шага, — и запоминает код. Код читается из уведомления о сообщении, поэтому уведомления должны быть включены; отчёт показывает, сколько цифр было в коде, но не сам код.
  • otp вводит запомненный код в поле, названное в шаге, или в поле с фокусом. Шаг otp без предшествующего шага sms падает и объясняет почему.

Телефон без SIM-карты и версия приложения старше этих шагов отмечают их пропущенными с причиной.

Для агентов. Агент читает тот же грид через MCP, по инструменту на вопрос: grid_scenario_reports перечисляет отчёты прогонов устройств (или один полный отчёт через runId) — канонический читатель /companion/runs; grid_run_results покрывает исходы нагрузки и фаззинга. Остальная картина флота: companion_crashes_list (почему умер телефон — сторожевой таймер загружает последнее неперехваченное исключение), companion_usage_list (посуточная занятость и pass/fail по устройствам) и companion_clips_list (библиотека голосовых клипов, с которыми работают playAudio/recordAudio). Один эндпоинт — один инструмент: у поверхности отчётов прогонов намеренно нет второго имени.

Если сервер работает за Ingress, адрес панели в списке подключения может быть
помечен как непроверенный. Сервер не проверяет сетевым запросом адрес,
переданный браузером: отсканируйте QR телефоном и убедитесь, что соединение
работает. Для проверки со стороны сервера задайте MOCKARTY_PUBLIC_URL с
публичным адресом, который контролирует оператор. API проверки адреса
принимает только этот адрес или доступные сетевые интерфейсы самого сервера.

Подключение телефона к Desktop-приложению

Desktop-приложение держит администрирование только на этом компьютере, поэтому из коробки телефон в вашей Wi-Fi-сети до него не достучится. Чтобы подключить телефон, откройте Desktop → Телефон в этой сети, выберите сеть, в которой находится телефон, и на сколько открыть окно (по умолчанию 15 минут, максимум час), и нажмите Открыть окно связи. С этого момента QR на странице UI-тестирования ведёт на это окно: отсканируйте его на телефоне — он подключится точно так же, как к серверу. Окно отдаёт только API телефона-компаньона и ничего больше — администрирование из сети недоступно — и закрывается само по истечении времени (или по кнопке Выключить связь). Уже подключённый телефон сохраняет свой токен устройства; просто до следующего окна у него нет доступа.

Пока окно не открыто, Desktop не предлагает QR для телефона: страница
объясняет, как открыть окно. Ради подключения телефона не нужно публиковать
администрирование Desktop, меняя адрес HTTP-прослушивания.

Раннер регистрируется и отображается как online. Токен раннера mki_ создаётся в админе в разделе Интеграции (тип: test runner). Под одним токеном можно запускать несколько раннеров — каждый процесс раннера генерирует уникальный instance ID при старте, поэтому в админе они остаются отдельными online-раннерами (CI-флоты и реплики Kubernetes могут использовать один общий токен).

Управление через ИИ-агента

Автономный путь. В ИИ-чате доступны два специалиста:

  • Web UI Tester — управляет Chromium / Firefox / WebKit.
  • Mobile UI Tester — управляет Android-устройством/эмулятором. iOS продукт не поддерживает: возможности mobile-ios в предложении целей нет, жизненного цикла iOS-устройства не существует (провайдеры устройств только adb-семейства), поэтому задача с ios отклоняется по имени, а не падает на получении устройства.

Сформулируйте задачу обычным языком, например:

Протестируй http://localhost:18999/: введи «Alice» в поле Username, нажми Sign in и проверь, что на странице есть «Welcome, Alice». Сохрани как «Login flow».

Агент проверит, что раннер онлайн, откроет сессию, осмотрит экран, будет действовать по смыслу, добавит ваши проверки, сохранит запись и отчитается, какие шаги прошли, а какие нет. Затем он может запустить сохранённый тест и прочитать результаты по шагам.

Когда страница ведёт себя не так, агент читает её консоль

Если клик будто ничего не делает, форма не отправляется или область остаётся
пустой, агент может спросить у страницы, что она сказала — её ошибки,
предупреждения и необработанные исключения. Захват начинается при открытии
страницы, до первой навигации, поэтому заблокированный скрипт или падение
на старте тоже видны, хотя произошли до того, как кто-то смотрел.

Можно попросить и напрямую:

Открой https://example.com и покажи ошибки в консоли.

Повторяющиеся сообщения сворачиваются со счётчиком: сообщение, летящее в цикле
перерисовки, читается как одна проблема, а не как тысячи. Если страница очень
шумная, в ответе указано, сколько записей отброшено — чтобы урезанный хвост
не выдавался за чистый лог.

…и что она запрашивала у сети

Вторая половина того же вопроса. Снимок пустого списка сообщает, что список
пуст; 401 на запросе, который должен был его наполнить, сообщает почему.
Агент может прочитать запросы страницы — метод, адрес, статус — включая те,
что не завершились вовсе: заблокированные CORS, заблокированные как
смешанное содержимое или упавшие потому, что бэкенд просто не поднят.

Открой дашборд и покажи неудавшиеся запросы.

Повторы сворачиваются со счётчиком: экран, опрашивающий один эндпоинт,
читается одной строкой, а не сотнями.

Найти один элемент вместо чтения всей страницы

В реальном приложении чтение всего экрана возвращает сотни элементов. Когда
вы уже знаете, что вам нужно, агент может это найти:

Найди кнопку «Войти» и нажми её.

Поиск сопоставляет видимую подпись, селектор, ARIA-роль и тег, а точное
совпадение подписи ставит первым — поэтому на запрос «Сохранить» сверху не
окажется «Сохранить как черновик». Если запрос оказался слишком широким, в
ответе прямо сказано, что список урезан, а не показан молча кусок.

Проверка, что экраном можно пользоваться

Агент может проверить страницу на доступность в её текущем состоянии:

Проверь этот экран на проблемы доступности.

Он сообщит о тексте, чей контраст не проходит WCAG — включая текст,
практически невидимый на своём фоне, — об изображениях без alt, о кнопках и
полях без доступного имени и о странице без объявленного языка. Это реальные
замеры с отрисованной страницы, поэтому регрессия контраста, которую не
покажет ни один снимок экрана, видна сразу.

Поверх этих замеров аудит запускает полный набор правил axe-core — того же
движка, что стоит за Lighthouse: валидность ARIA, ориентиры (landmarks),
структура документа и десятки других проверок WCAG. Его находки попадают в тот
же список как записи axe:<правило> с привычными уровнями серьёзности
(critical / serious / moderate / minor), а секция axe отчёта хранит версию
движка и детали по каждому правилу со ссылками на рекомендации по исправлению.
Если движок не может выполнить axe-проход, отчёт прямо скажет об этом в
axe.skipped — базовые замеры есть в любом случае.

Нажать экран и увидеть, что ломается

Проверки выше отвечают на вопрос «страница отрисовалась и ею можно
пользоваться». Есть вопрос сложнее: работает ли то, что на ней нажимают.
Продукт, у которого кнопка «Сохранить» отвечает ошибкой, отрисовывается
идеально, набирает хорошие баллы доступности и проходит любую проверку, которая
не нажимает.

Шаг «Нажать и проверить» делает именно это. Он находит на текущем экране
формы и кнопки и отправляет каждую форму тремя способами:

  • пусто — человек нажал, не заполняя;
  • первым пунктом списка — там часто стоит «не выбрано» с пустым значением, и
    это самый частый способ получить сохранение «в никуда»;
  • заполненной — счастливый путь.

После каждого нажатия шаг читает, что ответил продукт, и сообщает о провале,
если это страница ошибки, пустой экран или выброс на форму входа. В отчёте
каждый провал приходит со строкой воспроизведения: где стоять, что ввести, что
нажать и куда это привело.

Что он не нажимает никогда. Выход из системы, удаление и очистку, оплату и
списание, отправку письма или SMS, блокировку и отзыв доступа, публикацию и
выкатку. Запрет живёт в самом движке, а не в кнопке, поэтому действует одинаково,
как бы шаг ни был добавлен — из интерфейса, через API или агентом. Отказ виден в
отчёте: элемент, который мы намеренно не нажали, — это не элемент, который
сработал.

Добавьте шаг кнопкой на панели записи или опишите его в списке действий:

{ "type": "exercise", "extras": { "max": 6 } }

max ограничивает число нажатий на этом экране (по умолчанию 6, максимум 25) —
на плотной админской странице без ограничения один шаг съел бы весь прогон.
follow (по умолчанию 0, максимум 8) велит шагу пройти дальше по ссылкам того же
сайта и нажать найденные там экраны тоже: продукт — это не та страница, на которую
вам дали ссылку, и сломанная кнопка часто находится в одном переходе от неё.

Шаг проверяет и обратный путь. Если введённое значение вернулось на страницу,
он открывает адрес заново и смотрит, осталось ли оно: сохранение, которое
показалось выполненным и не пережило повторного открытия, — это дефект, который
пользователь встретит на следующее утро, а проверка, останавливающаяся на
подтверждении, не увидит никогда. Обратное неверно: если значение не вернулось,
не утверждается ничего — продукт мог увести на список или показать номер записи
вместо текста.

На телефоне — тот же вопрос, другой ответ

Сессия на реальном устройстве отвечает на поиск так же: просите элемент —
получаете несколько подходящих, отранжированных. На телефоне агент сначала
сопоставляет видимую подпись, затем content description (часто
единственная подпись у иконочной кнопки — именно её читает вслух скринридер),
затем resource id и класс виджета.

Найди на телефоне кнопку «Sign in» и нажми её.

Чтение консоли, сети и аудит доступности — это браузерные замеры, и сессия
устройства прямо об этом сообщает, а не возвращает пустой ответ, который
читался бы как «замечаний нет». Для устройства эквиваленты такие: лог
устройства
— для сообщений, чтение экрана — для его структуры.

Шаг, для которого у раннера на телефоне нет реализации, — сегодня это чтение
кода из SMS и смена ориентации экрана — пропускается с причиной, называющей
недостающую возможность, а остальной сценарий продолжает выполняться. В отчёте
видно, какой шаг пропущен и почему; шаг не выдаётся ни за выполненный, ни за
анонимную ошибку.

Управление через API

Каждый шаг — обычный REST-вызов, удобно для CI/CD и скриптов. В примерах используется bearer-токен; замените localhost:5770 на ваш адрес.

1. Убедиться, что раннер онлайн

curl "http://localhost:5770/api/v1/runners?capability=ui-test" \
  -H "Authorization: Bearer $TOKEN"

2. Начать сессию записи

curl -X POST http://localhost:5770/api/v1/live-sessions \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"platform":"web","browser":"chromium","startUrl":"http://localhost:18999/","record":true,"viewport":"800x600"}'
# → {"sessionId":"…","signalPath":"…","platform":"web","record":true}

3. Осмотреть экран

curl -X POST http://localhost:5770/api/v1/live-sessions/$SID/inspect \
  -H "Authorization: Bearer $TOKEN" -d '{}'
# → {"elements":[{"selector":"#login","selectorKind":"id","text":"Sign in","x1":…,"clickable":true}, …]}

4. Действие — заполнить поле, затем клик по смыслу

# Заполнить поле по его координате
curl -X POST http://localhost:5770/api/v1/live-sessions/$SID/action \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"action":"fill","x":76,"y":90,"value":"Alice","record":true}'

# Клик по элементу по его тексту или селектору
curl -X POST http://localhost:5770/api/v1/live-sessions/$SID/action \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"action":"tap-element","match":"Sign in","record":true}'

5. Проверка

curl -X POST http://localhost:5770/api/v1/live-sessions/$SID/action \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"action":"assert","assert":"assertText","match":"#msg","value":"Welcome, Alice","record":true}'
# успех → {"action":{…}}   провал → {"error":"expected #msg to have text \"…\", got \"…\""}

6. Сохранить запись, затем остановить сессию

curl -X POST http://localhost:5770/api/v1/live-sessions/$SID/save \
  -H "Authorization: Bearer $TOKEN" -d '{"name":"Login flow"}'
# → {"uiTestId":"…","name":"Login flow","actions":4}

curl -X DELETE http://localhost:5770/api/v1/live-sessions/$SID \
  -H "Authorization: Bearer $TOKEN"

7. Воспроизвести сохранённый тест

Пустое тело {} воспроизводит запись ровно так, как она была сделана — тест помнит платформу записи, а для мобильного и приложение. Переопределения (browser, viewport, platform, existingAppId, envVars, …) передавайте только когда нужно что-то другое.

RUN=$(curl -s -X POST http://localhost:5770/api/v1/ui-tests/$UITEST_ID/run \
  -H "Authorization: Bearer $TOKEN" -d '{}')
# → {"taskId":"…","statusPath":"/api/v1/runner-tasks/…"}

# Опрос результата по шагам
curl "http://localhost:5770/api/v1/runner-tasks/$TASK_ID" -H "Authorization: Bearer $TOKEN"
# resultData.extras.steps[] → статус каждого шага, error (ожидалось-vs-получено), durationMs, healedWith

8. Экспорт в код

curl "http://localhost:5770/api/v1/ui-tests/$UITEST_ID/export?format=playwright" \
  -H "Authorization: Bearer $TOKEN"

для сценария выше выдаёт:

import { test, expect } from '@playwright/test';

test('Login flow', async ({ page }) => {
  await page.goto('http://localhost:18999/');
  await page.getByTestId('user').fill('Alice');
  await page.locator('#login').click();
  await expect(page.locator('#msg')).toContainText('Welcome, Alice');
});

Используйте format=appium для мобильной записи — получите тест Appium (Python).

Пересмотрите прогон — видео в отчёте

Браузерный запуск теста записывает сам себя и прикладывает к отчёту короткую
зацикленную анимацию всего воспроизведения — чтобы вы видели, что именно
произошло, а не только pass/fail по шагам. Включено по умолчанию для
каждого веб-запуска (когда на сервере настроена функция видео); шаги
взаимодействия помечаются маркером клика — яркой точкой на элементе,
который шаг нажал или заполнил, — так что анимация читается как
аннотированная раскадровка флоу в спокойном темпе. Отключить для отдельного
запуска: recordVideo: false:

curl -s -X POST http://localhost:5770/api/v1/ui-tests/$UITEST_ID/run \
  -H "Authorization: Bearer $TOKEN" -d '{"recordVideo": false}'

Раннер записывает воспроизведение, превращает его в лёгкую зацикленную картинку
и прикладывает к отчёту о запуске (откройте его из Истории запусков на рейле
сохранённых тестов или по адресу /ui/runs/uitest/<runId>/report). Анимация
появляется во вложениях отчёта рядом со скриншотами по шагам.

Работает на любом движке. На раннере с настоящим браузером всё
воспроизведение записывается сплошным видео; на лёгком движке анимация в отчёте
собирается из того, как страница выглядела на каждом шаге. Открываются они в
отчёте одинаково — настраивать ничего не нужно.

Замечания:

  • Только веб. Мобильные запуски и так стримятся вживую; видео-прогона — для браузерных воспроизведений.
  • Включено по умолчанию, отключается на запуск — передайте
    recordVideo: false, если запись не нужна (она добавляет немного нагрузки и
    места в хранилище).
  • Аккуратно при недоступности — и отчёт объясняет причину. Если раннер не
    может сформировать или сохранить анимацию (нет ffmpeg на хосте раннера, файл
    больше лимита, админ-узел отклонил загрузку), запуск всё равно завершается
    штатно, а в отчёте на месте видео появляется короткая заметка —
    run video (not stored).txt с причиной — вместо запуска, у которого молча нет
    видео. Такая же заметка появляется для трассы, которую не удалось сохранить.

Трейс с перемоткой — пройдите весь прогон пошагово

Для глубокого разбора, когда что-то пошло не так, браузерный тест может записать
полный трейс воспроизведения — пошаговый захват страницы (снимок DOM, сеть,
консоль), который можно пройти после прогона. Поставьте галочку Записать
трейс
на рейле сохранённых тестов или передайте recordTrace: true при запуске
веб-теста:

curl -s -X POST http://localhost:5770/api/v1/ui-tests/$UITEST_ID/run \
  -H "Authorization: Bearer $TOKEN" -d '{"recordTrace": true}'

В отчёт о прогоне попадёт вложение trace.zip для скачивания (откройте из
Истории прогонов на рейле сохранённых тестов или по адресу
/ui/runs/uitest/<runId>/report). Скачайте его и откройте локально:

playwright show-trace trace.zip

Откроется просмотрщик с перемоткой: таймлайн каждого действия с состоянием
страницы, сетевыми вызовами и выводом консоли на каждом шаге — идеально, чтобы
понять, почему упал шаг.

Замечания:

  • Только веб, реальный браузер. Мобильные прогоны стримятся вживую и так не
    трейсятся; лёгкий движок трейсы не пишет — для трейса запустите на реальном
    браузере (RUNNER_BROWSER_PROVIDER=local).
  • По запросу, по умолчанию выключено — трейс занимает место, поэтому
    запрашивается на каждый прогон.
  • Глубокий разбор, а не основной вид. Скриншоты по шагам в отчёте — это
    обзор с первого взгляда; трейс — подробная выгрузка для случаев, когда нужно
    пройти всё пошагово.
  • Корректно при недоступности. Если раннер не смог записать трейс, прогон
    всё равно завершается штатно, а в отчёте просто не будет трейса.

Визуальная регрессия — ловит то, что пропускают проверки

Проверки сверяют те значения, о которых вы подумали. Визуальная регрессия
ловит всё остальное — съехавшую кнопку, сломанную вёрстку, изменённый цвет,
пропавшую картинку — сравнивая скриншот каждого шага с сохранённым эталоном.

Включите через visualMode при запуске веб-теста (или галочкой Визуальная
регрессия
в панели сохранённых тестов):

curl -s -X POST http://localhost:5770/api/v1/ui-tests/$UITEST_ID/run \
  -H "Authorization: Bearer $TOKEN" -d '{"visualMode": "warn"}'

Как это работает:

  • Первый запуск снимает скриншот на каждом шаге и сохраняет его эталоном
    (ожидаемый вид). Сравнивать пока не с чем.
  • Каждый следующий запуск сравнивает свои скриншоты с эталоном. В отчёте по
    каждому шагу показаны ожидаемое (эталон), фактическое и diff-картинка
    с подсвеченными изменившимися пикселями — плюс процент расхождения.
  • warn (по умолчанию) — расхождения отмечаются в отчёте, но прогон не
    падает. Просматриваете весь флоу глазами и решаете, что важно — именно это
    экономит часы ручной проверки. fail делает шаг с превышением порога
    упавшим (гейт для CI). off выключает.
  • Сводка по прогону (сравнено / разошлось / новых эталонов) — в панели Environment отчёта.

Контрольные точки на шаге — когда снимать каждый шаг не нужно, добавьте
визуальную контрольную точку только на важных экранах, рядом с проверками. В
панели шагов нажмите Визуальная контрольная точка и назовите экран (напр.
«Корзина»); точка сверяет скриншот этого экрана с эталоном по её имени (вставка
шагов перед ней не сдвигает эталон). Она срабатывает, даже если визуальный режим
прогона выключен, — так можно проверять именно критичные состояния: первый прогон
записывает эталон, последующие сверяют, а вердикт на шаге (совпал / расхождение /
новый эталон) и кнопка Утвердить эталон показываются прямо в отчёте. В
Playwright-коде точка экспортируется как await expect(page).toHaveScreenshot('Корзина.png').

Поскольку эталон — это задокументированное ожидаемое поведение, картинки
ожидаемое / фактическое / diff показываются внутри прогона TCM тест-кейса, а
не только в отдельном отчёте — рецензент видит визуальную правду рядом с шагами и
проверками.

Если объектное хранилище, где лежат эталоны, недоступно, прогон сообщает об этом
явно, а не молча теряет доказательства: загрузка скриншота отвечает 503 с кодом
object_store_unavailable и первопричиной, а в отчёт прогона попадает заметка
(«visual baseline store unavailable — шаг выполнен, его скриншот не сохранён: …»)
рядом с артефактами. Шаги при этом выполняются; когда хранилище вернётся,
перезапустите прогон, чтобы собрать визуальные свидетельства.

Тонкая настройка:

  • visualThreshold (0..1, напр. 0.01 = 1%) задаёт допустимое расхождение шага
    при создании нового эталона. Существующий эталон хранит свой порог.
  • Каждый браузер + размер окна получают свой эталон автоматически — прогон
    Chromium 1280×800 никогда не сравнивается с эталоном Firefox 390×844.
  • Игнорируемые области — если часть страницы по природе динамична (часы,
    рекламный слот, случайный id), замаскируйте её, чтобы она не ломала diff.
    Откройте менеджер эталонов, нажмите Маски, протяните прямоугольники по
    игнорируемым зонам и сохраните — эти области исключаются из всех последующих
    сравнений.
  • Нужно настроенное объектное хранилище. Секрет подписи ссылок управляется
    автоматически — визуальная регрессия (как трейсы и видео прогонов) работает
    сразу после настройки хранилища, отдельный секрет прописывать не нужно. Чтобы
    задать свой ключ или ротировать его, установите MOCKARTY_UITEST_VISUAL_SECRET
    (в кластере — одинаковое значение на всех нодах).

Управление эталонами:

На странице UI-тестирование у каждого сохранённого теста есть кнопка Эталоны скриншотов: открывается менеджер со всеми эталонами (миниатюра на браузер / размер окна / устройство / шаг) и статусом — Активный, Ожидает (нужно ваше подтверждение) или Заменён. После намеренного изменения UI нажмите Принять как эталон на новом снимке (старый становится заменённым); Удалить убирает устаревший эталон, чтобы следующий прогон снял свежий. ИИ-агент делает то же тулами ui_visual_baselines_list / ui_visual_baseline_approve / ui_visual_baseline_delete.

Удаление сохранённого UI-теста закрывает и его визуальные эталоны. Файлы изображений очищаются после исчезновения всех активных ссылок; удаление одного эталона не удаляет изображение, которое использует другой.

Макет как эталон (дружелюбно к агенту). Вместо снимка прошлого прогона точка может
сверяться с загруженным дизайн-макетом: прогон тогда отвечает на вопрос «совпадает ли
свёрстанное с замыслом?», а не «изменилось ли оно?». В менеджере эталонов используйте
Загрузить макет, а ИИ-агент — тул ui_visual_baseline_mockup_b64 с картинкой в
base64 внутри JSON-тела (uiTestId, stepKey, imageBase64, опционально
browser/viewport/device). Оставляйте browser/viewport/device пустыми, если прогон их не
фиксирует: иначе макет ляжет под другим ключом и прогон запишет свежий эталон.
Эквивалентный вызов REST — POST /api/v1/ui-visual/baselines/mockup-64.

Через API:

# Список эталонов теста
curl "http://localhost:5770/api/v1/ui-visual/baselines?uiTestId=$UITEST_ID" -H "Authorization: Bearer $TOKEN"
# Принять снятый скриншот как новый эталон (после намеренного изменения UI)
curl -X POST "http://localhost:5770/api/v1/ui-visual/baselines/$BASELINE_ID/approve" -H "Authorization: Bearer $TOKEN"

Визуальная оценка — AI-ревью дизайна без эталона

Визуальной контрольной точке выше нужен сохранённый эталон для сравнения.
Визуальной оценке — нет: добавьте шаг типа Визуальная оценка (иконка
искры на панели шагов), и при следующем запуске визуальная LLM оценит
скриншот этого экрана так, как это сделал бы senior product-дизайнер: вёрстка
и выравнивание, обрезанный или наложенный контент, консистентность цветов и
контраст, типографика, отступы и визуальная иерархия, а также явные признаки
«сломанности» (нестилизованные элементы, отсутствующие иконки/картинки). Это
подходящий инструмент для первой дизайн-проверки экрана или для любого
экрана, где у вас ещё нет — или вы не хотите поддерживать — эталонный
скриншот.

Шаг прикрепляет к отчёту оценку 0-100, вердикт (good / acceptable
/ poor) и список найденных проблем (важность + суть). При добавлении шага
можно опционально задать промпт для ревью (например, «проверь соответствие
фирменным цветам»), чтобы сфокусировать оценку на конкретном дизайн-задании
для этого экрана; оставьте пустым — используется встроенный чек-лист качества
дизайна.

Для визуальной оценки нужен настроенный в админке профиль LLM с поддержкой
изображений
(Настройки → AI/LLM → отметьте «Поддерживает изображения» у
мультимодального профиля). Без него шаг всё равно выполняется и снимает
скриншот, а в отчёте вместо оценки появляется строка
visualAssess.notEvaluated с причиной. Разница важна: раздел дизайна без
замечаний означает, что модель посмотрела и претензий не нашла, а эта строка —
что не смотрел никто. На остальной прогон это не влияет ни в том, ни в другом
случае.

Галочке Mockarty не верит на слово, а проверяет: отправляет одну крошечную
тестовую картинку и берёт первый профиль, который её действительно принял.
Модель, заявленную как мультимодальную, но отклоняющую изображения на своём API,
система пропускает сама — так что прогон не окажется молча без оценки.

Оценка экрана прямо во время работы

Чтобы получить мнение, не обязательно сохранять тест и запускать его. Пока
открыта живая сессия — ваша или агентская — можно попросить то же ревью экрана
в том виде, в каком он сейчас:

curl -X POST "http://localhost:5770/api/v1/live-sessions/$SESSION_ID/visual-review" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "сверь с нашей фирменной палитрой", "record": true}'
{
  "report": {
    "score": 62,
    "verdict": "acceptable",
    "summary": "Форма пригодна к работе, но подписи прижаты к полям.",
    "findings": [
      {"severity": "medium", "title": "Тесные отступы у подписей", "detail": "Подписи касаются полей под ними по всей форме."}
    ]
  },
  "recorded": 7
}

Оба поля тела запроса необязательны. prompt заменяет встроенный чек-лист вашим
заданием; record: true дополнительно сохраняет ревью как шаг Визуальная
оценка
в записи — и тест, сохранённый из этой сессии, будет пересматривать
экран при каждом следующем прогоне. Тот же вызов доступен ИИ-агентам как
инструмент ui_session_visual_review и работает как с экраном телефона, так и
со страницей браузера.

Консоль браузера в каждом отчёте

Каждый браузерный прогон автоматически записывает предупреждения и ошибки консоли
страницы — никаких флагов включать не нужно. Именно здесь объявляют о себе поломки,
которые не увидит ни один селектор: скрипт, заблокированный Content-Security-Policy,
необработанное исключение при загрузке страницы, неудавшийся динамический импорт,
после которого страница осталась полуживой.

В отчёте о прогоне:

  • в шапке появляются счётчики console.errors и console.warnings — прогон
    с «шумной» консолью виден с одного взгляда;
  • к узлу прогона прикрепляется вложение console.txt с сообщениями в том виде,
    в каком их показывает devtools: уровень, счётчик повторов, шаг, во время которого
    сообщение появилось, и источник:
ERROR ×3: Refused to load the script 'https://app/main.js' (CSP)
    at https://app/:1:1
WARNING [step 4]: Deprecated API usage: ...

Примечания:

  • Только предупреждения и ошибки. Вывод console.log/info/debug не
    записывается — это страница разговаривает со своими разработчиками, а не ломается.
  • Повторы схлопываются. Сообщение, выстрелившее тысячи раз (цикл перерисовки),
    сохраняется один раз со счётчиком — отчёт остаётся читаемым, а сам счётчик
    говорит о масштабе.
  • Ограничено. Не более 50 различных сообщений на прогон; сверх того отчёт
    сообщает, сколько ещё было отброшено.

Разбор падений — какие тесты вас изводят и почему

Когда сьют гоняется по расписанию, вопрос перестаёт быть «прошёл ли этот
прогон» и становится «какие тесты постоянно падают, какие флачат и что их
обычно ломает». Кнопка Разбор падений на панели сохранённых тестов отвечает
ровно на это по вашей истории прогонов:

  • Падений % — доля завершённых прогонов, которые упали или сломались.
  • Флак % — как часто соседние прогоны переключаются между pass и fail.
    Высокий флак при умеренной доле падений — нестабильный тест (перезапустить,
    затем чинить селектор/ожидание); 0% флака при падениях — тест сломан
    стабильно
    : изменилось что-то реальное.
  • Частая ошибка — доминирующий класс ошибки среди падений: десять
    одинаковых SelectorNotFound читаются как одна проблема, а не десять.
  • Артефакты — записал ли последний упавший прогон трейс и/или видео, чтобы
    ещё до открытия отчёта знать, есть ли что смотреть.

Тесты отсортированы от худших. Тот же отчёт доступен по API —
GET /api/v1/ui-tests/triage?windowDays=30 — и ИИ-агентам через MCP-инструмент
ui_test_failure_triage, так что агент сам решает «флак → перезапуск» против
«реальная поломка → разбираться».

Производительность веб-страницы — насколько быстро она грузится

Функциональный pass/fail говорит, что страница работает. Производительность говорит, как она ощущается — как быстро отрисовывается, насколько стабильна вёрстка, сколько весит. Mockarty читает реальные метрики загрузки прямо из браузера и оценивает их по публичным порогам Web Vitals.

Снимается по каждой измеряемой странице:

  • LCP (Largest Contentful Paint) — когда появляется основной контент.
  • CLS (Cumulative Layout Shift) — насколько вёрстка «прыгает» при загрузке.
  • TTFB (Time To First Byte) — задержка бэкенда + сети до первого байта.
  • FCP (First Contentful Paint), DOMContentLoaded, время Load.
  • Запросы и общий объём передачи, с разбивкой по типам.

Каждая метрика получает оценку good / needs-improvement / poor (пороги web.dev), отчёт показывает общую оценку. Это информационно — медленная страница фиксируется, но шаг не падает.

На шаге тест-кейса. Включите Замер производительности на UI-шаге в конструкторе кейса. После прогона шага метрики страницы измеряются, и отчёт показывает карточку производительности на этом шаге плюс скачиваемый performance.json.

Разовый замер. Измерьте любую страницу одним вызовом:

curl -s -X POST http://localhost:5770/api/v1/ui-tests/measure-perf \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"url": "https://app.example.com/dashboard", "waitForSelector": "#main"}'

Вернётся taskId; опрашивайте /api/v1/runner-tasks/<taskId> — сводка Web Vitals лежит на измеренном шаге (resultData.extras.steps[].perfMetrics). Используйте storageStateId, чтобы измерить страницу за логином, и waitForSelector, чтобы дать странице устаканиться перед замером.

Замечания:

  • Только веб. Нужен онлайн браузерный раннер (capability ui-test).
  • Информационно. Никогда не валит шаг или прогон — отдаёт цифры, решение за вами.

Подмена запросов — гоняйте UI против мок-бэкенда

Управляйте реальным фронтендом, перехватывая его запросы к бэкенду — мокайте, блокируйте, подставляйте или задерживайте. Это то, чего нет у других в одном месте: UI-тест и мок того же бэкенда вместе. Тестируйте фронтенд в изоляции, симулируйте сбой, форсируйте ответ с ошибкой или вносите задержку — не трогая реальный бэкенд.

Для каждого совпавшего запроса выбираете действие:

  • Мок (редирект) — направить запрос на Mockarty-стаб того же бэкенда (redirectUrl). UI работает против моков, которые вы уже создали.
  • Блокировать — оборвать запрос (симуляция мёртвой зависимости / офлайна).
  • Подставить (stub) — ответить локально со статусом, заголовками и телом (без сети) — быстрый готовый ответ.
  • Задержка — пропустить запрос через N миллисекунд (инъекция задержки).

Правило матчится по шаблону URL: glob вида **/api/** или регэксп в форме /…/. Правила ставятся до первой навигации, так что даже первичная загрузка данных страницы попадает под них.

На шаге тест-кейса. Нажмите Подмена запросов на UI-шаге в конструкторе кейса и добавьте правила (шаблон → действие → параметр). Отчёт покажет сводку перехвата (сколько запросов замокано / заблокировано / подставлено / задержано).

Через API / из агента. Передайте networkRules при запуске теста:

curl -s -X POST http://localhost:5770/api/v1/ui-tests/$UITEST_ID/run \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"networkRules": [
        {"urlPattern": "**/api/orders", "action": "mock", "redirectUrl": "http://localhost:5770/stubs/myns/orders"},
        {"urlPattern": "**/api/health", "action": "block"},
        {"urlPattern": "**/api/slow", "action": "delay", "delayMs": 2000},
        {"urlPattern": "**/api/flags", "action": "stub", "status": 200, "body": "{\"beta\":true}"}
      ]}'

Замечания:

  • На Android-телефоне те же правила действуют на HTTP-запросы телефона. HTTPS-запросы проходят без изменений, если не запустить живую сессию с Расшифровкой HTTPS (MITM) — тогда телефон попросит доверять сертификату прогона; приложения с закреплённым сертификатом правила всё равно обходят.
  • Информационная сводка. Правила никогда не валят шаг; битый шаблон пропускается. Вердикт по-прежнему определяют ваши проверки.

Запись сетевого трафика телефона — и его приватность

На Android-телефоне отметьте Трафик перед прогоном или живой сессией: запросы телефона на время прогона идут через Mockarty, а отчёт показывает, что запрашивало приложение, — метод, адрес, статус и размеры. У HTTPS-запросов виден только хост, если сессия не расшифровывает HTTPS.

Учётные данные в адресе заменяются на REDACTED ещё до сохранения: значения параметров вроде token, api_key или password, имя пользователя с паролем и части пути после имени вроде /token/ или похожие на токен, JWT, e-mail или номер карты. Секрет без такого имени и формы (голая шестнадцатеричная строка выглядит как любой id) не распознаётся — не кладите секреты в адреса.

Приватность трафика (кнопка со щитом рядом с Трафиком) задаёт два правила для всего пространства имён:

  • Прогоны могут записывать трафик устройства. Выключите — и ни один прогон в пространстве имён не записывает трафик, что бы он ни просил: опция Трафик становится неактивной и объясняет почему. Правила подмены запросов продолжают работать; ничего не записывается.
  • Стирать записанный трафик через N дней (0–365). Трафик старше этого срока стирается из отчётов; сам прогон, его шаги и вердикт остаются. 0 — хранить трафик, пока хранится прогон.

Менять политику может тот, у кого есть права на запись в пространство имён. ИИ-агент читает и меняет её инструментами ui_traffic_policy_get и ui_traffic_policy_set; скрипт — через тот же адрес:

curl -s -X PUT "http://localhost:5770/api/v1/ui-tests/traffic-policy?namespace=myns" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"captureEnabled": false, "retentionDays": 30}'

Передавайте только то, что меняется, — не переданное поле сохраняет значение.

Запуск UI-тестов внутри тест-плана

Сохранённую запись можно запускать как элемент тест-плана — рядом с
функциональными, нагрузочными, fuzz- и TCM-кейс-элементами — чтобы один план
покрывал весь набор. Добавьте элемент типа UI-тест в конструкторе плана и
выберите запись; прогон плана отправит её на браузерный/мобильный раннер,
дождётся и покажет результат в отчёте плана рядом со всем остальным.

Через API элемент плана — это {"type": "ui_test", "refId": "<id-ui-теста>"};
агент добавляет его MCP-тулом create_test_plan (тип элемента ui_test). UI-тесты —
часть сита api-tester (того же, что владеет рекордером и раннером).

Войдите один раз — и переиспользуйте для всех запусков

Логиниться при каждом запуске долго, а если для входа нужен одноразовый код из SMS, который можно ввести только руками, — это и вовсе ломает автоматизацию. Вместо этого войдите один раз и сохраните авторизованное состояние браузера — cookies и localStorage — а каждую следующую сессию или запуск теста начинайте уже залогиненным.

На странице UI Testing (веб-сессии) в панели есть кнопка Сохранить вход: войдите внутри живой сессии, нажмите её, дайте состоянию имя. После этого в форме запуска появляется выбор Состояние входа — выберите сохранённое состояние, и браузер откроется минуя экран логина. Ваши записи остаются сфокусированы на проверяемой функции и не обязаны повторять вход.

Сохранённое состояние содержит cookies и localStorage браузера. Администратор может включить шифрование при хранении, задав MOCKARTY_PII_ENCRYPTION_KEY до запуска узлов. Все узлы с общей базой должны использовать один ключ; узел без ключа не прочитает зашифрованное состояние и отклонит запрошенный авторизованный старт. Ранее сохранённые незашифрованные состояния остаются читаемыми.

Через API это два эндпоинта:

# Войдя внутри живой сессии, захватите её авторизованное состояние
curl -X POST "http://localhost:5770/api/v1/live-sessions/$SESSION_ID/save-state" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"acme-prod-login"}'
# → {"id":"…","name":"acme-prod-login","platform":"web"}

# Список сохранённых состояний (тела не возвращаются — только метаданные)
curl "http://localhost:5770/api/v1/ui-storage-states" -H "Authorization: Bearer $TOKEN"

# Запустите НОВУЮ сессию — или запуск теста — уже залогиненным
curl -X POST "http://localhost:5770/api/v1/live-sessions" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"platform":"web","startUrl":"https://app.acme.test/dashboard","storageStateId":"…"}'

ИИ-агент делает то же самостоятельно: перед тестированием авторизованного сайта он вызывает ui_storage_states_list, переиспользует сохранённый вход, если он есть, и сохраняет новый (ui_session_save_state) при первом входе — так последующие запуски полностью пропускают логин, пока действительны учётные данные.

Названный storageStateId либо исполняется, либо отклоняется — тихого выбрасывания нет: если состояние неизвестно, удалено или принадлежит другому пространству имён, запрос отвечает 400 с saved sign-in state "…" not found or expired, а не стартует разлогиненный браузер, который падает далеко от причины. Поле опускают, когда холодный старт и есть то, что нужно. Правило действует для прогона сохранённого теста, live-сессии и отладочного повтора шага. Сохранённое состояние входа — браузерная сущность: назвать его для прогона, сессии или шага тест-кейса на Android/iOS нельзя — ответ тот же 400, потому что мобильные движки его не читают; мобильный прогон входит через deviceLeaseId (тёплое, уже залогиненное устройство) или snapshotId (восстановленные данные приложения).

Если состояние нельзя прочитать, в том числе из-за несовпадения ключей, авторизованный старт отклоняется как временно недоступный. Список и удаление также возвращают ошибку сервера при сбое хранилища; удаление отсутствующей записи отвечает 404.

Чтение страницы как контента

Живая сессия может вернуть текущую страницу как контент, а не только дерево элементов — один вызов вместо скриншотов и перебора элементов. Передайте mode в inspect-эндпоинт (или в агентский тул ui_session_inspect):

  • text — страница как чистый текст;
  • markdown — заголовки, списки, выделение, код и ссылки в markdown (лучший способ прочитать статью или документацию);
  • outline — дерево заголовков плюс сводка форм и ориентиров (самый быстрый первый взгляд на незнакомую страницу);
  • links — все видимые ссылки как {text, href} с абсолютными URL (выбор, куда переходить дальше);
  • elements (по умолчанию) — классическое дерево интерактивных элементов.
curl -X POST "http://localhost:5770/api/v1/live-sessions/{sessionId}/inspect" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"mode":"markdown"}'

Извлечение не зависит от вёрстки страницы, поэтому облегчённый движок возвращает тот же контент, что и настоящий браузер, — массовое чтение страниц работает на движке по умолчанию, без Chromium.

Сохранённые состояния живут в вашем пространстве имён и переиспользуются, пока на целевом сайте не истечёт сессия. Устаревшее удаляйте через DELETE /api/v1/ui-storage-states/{id}.

На мобильных: удержите залогиненное устройство

У нативного приложения нет браузерного состояния, а вход часто требует одноразового кода из SMS. Поэтому на мобильных та же идея принимает другую форму — удержание залогиненного устройства. Вы входите один раз на реальном устройстве или эмуляторе, и это устройство остаётся зарезервированным — последующие запуски переиспользуют его уже авторизованным, без переустановки и без второй SMS. Работает на любых устройствах, включая без рута и фермы устройств.

На странице UI Testing переключитесь на платформу Android (iOS продукт не поддерживает и не предлагает как цель), выберите приложение и нажмите Удержать устройство. Когда устройство готово, запустите на нём сессию и войдите один раз. После этого в лаунчере появляется выбор Залогиненное устройство — выберите его, и сессия (или запуск теста) откроется сразу в авторизованном приложении.

Через API:

# Удержать устройство под приложение (выбирается онлайн mobile-раннер)
curl -X POST "http://localhost:5770/api/v1/ui-device-leases" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"acme-android-login","platform":"android","existingAppId":"com.acme.app"}'
# → {"lease":{"id":"…","status":"pending"},"holdTaskId":"…"}
# Дождитесь завершения задачи, затем проверяйте список аренд до статуса active.

# Переиспользовать залогиненное устройство — сессия или запуск стартует авторизованным
curl -X POST "http://localhost:5770/api/v1/live-sessions" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"platform":"android","deviceLeaseId":"…"}'

# Освободить, когда закончили
curl -X DELETE "http://localhost:5770/api/v1/ui-device-leases/{id}" -H "Authorization: Bearer $TOKEN"

ИИ-агент управляет этим через ui_device_leases_list, ui_device_lease_hold и ui_device_lease_release, переиспользуя удержанное устройство передачей deviceLeaseId в ui_session_start / ui_test_run.

Новая аренда имеет статус pending, пока runner получает устройство и запускает приложение. Список аренд показывает active только после успешного результата удержания с идентификатором устройства; после этого аренду можно выбрать для сессии или теста. Неудачное удержание переводит её в error. Аренда pending резервирует ёмкость, но ещё не подходит для повторного использования. Если сервер перезапустится между завершением задачи и обновлением статуса, чтение списка сверит сохранённый результат и восстановит состояние.

Попытка указать аренду pending в сессии или запуске возвращает 409 с просьбой дождаться существующей задачи удержания. Для аренды error или просроченной аренды ответ — 400: нужно удержать устройство заново.

Названный deviceLeaseId либо исполняется, либо отклоняется — тихой подмены не бывает: если lease неизвестен, просрочен или принадлежит другому пространству имён, запрос отвечает 400 с device lease "…" not found or expired, а не уходит молча на холодное устройство без логина. Поле опускают, когда холодное устройство и есть то, что нужно. Для ui_device_snapshot_capture правило то же. Если недоступен сам реестр lease, ответ — 503 с device lease "…" could not be checked: lease по-прежнему ваш, повторите запрос, а не удерживайте новое устройство.

Создание новой аренды отвечает 409, если другой запрос уже зарезервировал последнее готовое устройство, в том числе через другой серверный узел. Освободите аренду или подключите ещё одно устройство. Если сервер не может проверить или зарезервировать ёмкость, он отвечает 503 с device lease capacity temporarily unavailable; новое удержание при таком ответе не отправляется runner.
Необязательное поле ttlHours принимает 0–8760 часов; 0 означает значение по умолчанию — 30 дней.

На рутованном устройстве: сохраните залогиненное состояние и восстановите где угодно

Удержание занимает одно устройство. Если же вам нужно переносимое залогиненное состояние — снять один раз и восстанавливать на любом свежем устройстве перед запуском — используйте снапшот устройства. Снапшот сохраняет данные приложения на устройстве (сессию входа, токены, настройки) как переиспользуемый файл. Перед запуском теста вы восстанавливаете его, и приложение открывается уже авторизованным — даже на устройстве, которое это приложение никогда не видело.

Снапшоты требуют рутованного устройства/эмулятора или режима разработчика (userdebug), либо отлаживаемой (debuggable) сборки приложения — они читают приватную директорию данных приложения, закрытую на продакшен-устройствах. На закрытых устройствах используйте удержание устройства (выше). Функция включается администратором (нужно настроенное объектное хранилище).

# Снять текущее залогиненное состояние приложения (выполняется на раннере со снапшотами)
curl -X POST "http://localhost:5770/api/v1/ui-device-snapshots" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"acme-android-loggedin","existingAppId":"com.acme.app"}'
# → {"snapshot":{"id":"…","hasBlob":false},"captureTaskId":"…"}  (поллите задачу; hasBlob станет true после сохранения)

# Список ваших снапшотов
curl "http://localhost:5770/api/v1/ui-device-snapshots" -H "Authorization: Bearer $TOKEN"

# Запустить тест, восстановив снапшот первым — приложение стартует залогиненным
curl -X POST "http://localhost:5770/api/v1/ui-tests/{testId}/run" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"platform":"android","existingAppId":"com.acme.app","snapshotId":"…"}'

# Удалить снапшот, когда он больше не нужен
curl -X DELETE "http://localhost:5770/api/v1/ui-device-snapshots/{id}" -H "Authorization: Bearer $TOKEN"

Используйте удержанное устройство для одного всегда-доступного залогиненного устройства, которым может управлять любой; используйте снапшот, когда нужно поднимать много свежих устройств, стартующих из одного и того же залогиненного состояния.

Названный snapshotId либо исполняется, либо отклоняется — тихого выбрасывания нет: если снапшот неизвестен, удалён, принадлежит другому пространству имён или ещё не содержит снятых данных (именно так выглядит незавершённая съёмка), прогон отвечает 400 с device snapshot "..." is not usable, а не стартует на неподготовленном устройстве. Узел, на котором перенос снапшотов не настроен, отвечает 503 (could not be checked) — это состояние платформы, поэтому повторяйте запрос, а не снимайте снапшот заново. Поле опускают, когда снапшот не нужен.

По умолчанию снапшоты хранятся, пока вы их не удалите: это ценные залогиненные состояния. Если в вашем окружении нужна автоочистка по возрасту, администратор может задать TTL переменной окружения MOCKARTY_DEVICE_SNAPSHOT_RETENTION (например, 720h — 30 дней); фоновый сборщик (только на лидере кластера) закроет снапшоты старше указанного срока. Общая очистка хранилища удалит tar после проверки, что на него больше не ссылается живой снапшот или другой ресурс. Без этой переменной автоматическое истечение срока снапшотов выключено.

Селекторы

При осмотре каждый элемент получает самый стабильный доступный селектор, выбранный в таком порядке:

id → data-testid → aria-label → placeholder → видимый текст → alt → title → CSS-путь.

Вы действуете с элементом, передавая его selector или text как match. Записи хранят цепочку альтернативных селекторов: если при воспроизведении элемент не находится по основному селектору, автоматически пробуются альтернативные, и прогон продолжается — в результате отмечается, какой селектор «вылечил» шаг, чтобы вы знали, что запись стоит обновить.

Проверки

Verb Проверяет
assertVisible / assertHidden элемент виден / не виден
assertText элемент содержит ожидаемый текст
assertValue значение поля равно ожидаемому
assertEnabled / assertDisabled элемент активен / неактивен
assertChecked / assertUnchecked чекбокс/радио отмечен / не отмечен
assertCount селектор находит ожидаемое число элементов
assertAttribute атрибут равен ожидаемому значению
assertURL / assertTitle URL / заголовок страницы содержит ожидаемый текст

Мобильные проверки: assertVisible, assertHidden, assertText и assertSMS (ожидание прихода одноразового кода).

Провалившаяся проверка сообщает ожидаемое и фактическое значение — и в живой сессии, и в результате воспроизведения по шагам.

Движки, браузеры и размер окна

Веб-прогон выполняется на движке, который вы выбрали: лёгкий (без установки Chromium) или реальный браузер — Chromium, Firefox, WebKit. В интерфейсе это ряд «Движок»; через API — поле engine при старте сессии, при запуске теста и при отладке шага. Пустое значение = любой доступный раннер.

Что доступно именно у вас, отвечает сам сервер:

curl -s http://localhost:5770/api/v1/ui-tests/engines -H "Authorization: Bearer $TOKEN"
# → {"engines":[{"engine":"lightweight","name":"Lightweight","runners":1,"canRender":true,"local":true}],"default":""}

runners — сколько раннеров дают этот движок, canRender — можно ли на нём получить скриншоты и визуальные сравнения, local — движок обслуживается самим сервером Mockarty, отдельный раннер не нужен. Просить движок, которого в ответе нет, бессмысленно: такой запуск некому выполнить.

Живой экран на разных движках. canRender отвечает на вопрос «даёт ли движок картинки» — скриншоты, визуальные сравнения и видео прогона работают на Chromium, Firefox и WebKit. Живое зеркало сессии зависит от движка: Chromium показывает плавное видео, Firefox и WebKit — серию снимков (около двух в секунду и только когда страница меняется), о чём зритель пишет в полосе над экраном. Управлять сессией, записывать шаги и получать скриншоты можно одинаково на всех трёх. Для плавного видео выберите Chromium (лёгкий движок для живой сессии тоже переключается на Chromium).
Это сообщение появится и при выборе «Авто», если выбранный раннер по умолчанию запускает Firefox или WebKit. То же ограничение действует при просмотре прогона сохранённого теста: сам прогон завершится, а снимки останутся доступны в отчёте.

Список собирается сам. И сервер, и раннер объявляют то, что действительно могут запустить: лёгкий движок есть всегда (он не требует установленного браузера), реальные браузеры — те, что установлены. Ничего настраивать не нужно.

Если предлагать нужно НЕ всё, задайте на сервере или раннере ограничение — RUNNER_UI_ENGINES=lightweight,chromium. Это фильтр поверх найденного: движок, которого на машине нет, в списке не появится, даже если он там назван.

Движок реального браузера сам задаёт и browser, поэтому отдельно указывать его не нужно — передайте browser, только если хотите назвать браузер внутри раннера явно. Задайте размер окна через viewport ("ШИРИНАxВЫСОТА") и подставьте уже залогиненное состояние через сохранённый набор cookie/localStorage, чтобы сценарий начинался после экрана входа.

Смотрите также