Как ИИ-агент проверяет экраны Android-приложений, не запуская само приложение
Разработчик научил ИИ-агента рендерить составные экраны Android из реальных XML-разметок с тестовыми данными — без запуска приложения, эмулятора и инфраструктуры.
Один XML-файл — это ещё не экран. Настоящий экран Android-приложения — это связка Activity, Fragment, списка с данными и всплывающих слоёв. Чтобы ИИ-агент мог визуально проверить результат своей работы над UI, пришлось научить его собирать эту связку целиком, но без запуска приложения.
Зачем ИИ-агенту рисовать экраны
Представьте, что вы — программист, и у вас есть помощник-агент на базе ИИ. Он умеет менять XML-разметки Android-приложения и должен показать вам, что получилось на экране. Раньше такой агент уже мог отрендерить одну простую разметку — взять XML-файл, «inflate» его (то есть развернуть в визуальные элементы) через фреймворк Robolectric и вернуть картинку с деревом виджетов. Этого хватало для кнопки или карточки.
Но когда речь зашла о реальном экране рабочего приложения, одного файла стало мало. Типичный экран в Android — это не один файл, а несколько слоёв: Activity (контейнер верхнего уровня), внутри него Fragment (отдельный экран или его часть), RecyclerView (прокручиваемый список элементов) и порой оверлей загрузки поверх всего. Запустить всё это в обычном режиме — значит потянуть за собой внедрение зависимостей, навигацию, сетевые запросы и кучу другого кода. Для визуальной проверки это избыточно и медленно.
Компромисс: реальные ресурсы, но виртуальное выполнение
Решение, которое предлагает проект android-ui-renderer-mcp от разработчика с ником hram, строится на одном ключевом принципе: визуальные ресурсы берутся из настоящего приложения, а вот состояние и данные задаются явно в запросе.
Что это значит на практике:
- XML-разметки Activity, Fragment и элементов списка — настоящие, из вашего проекта
- Drawable-ресурсы (иконки, фоны, бейджи) — тоже реальные
- А вот код Fragment, система внедрения зависимостей, навигация и сетевой слой — не запускаются вообще
- Данные для списка, состояние загрузки, выбранная книга — задаются в JSON-запросе вручную
Если говорить бытовым языком: это как собрать макет комнаты из настоящей мебели, но не включать электричество и воду. Вы видите, как всё выглядит, но не тратите время на подключение коммуникаций.
Как собирается экран из кусочков
Инструмент поддерживает цель рендеринга activity_fragment — она берёт XML-разметку Activity, находит в ней контейнер для фрагмента, потом inflate-ит отдельно XML-разметку Fragment и вставляет её внутрь. Класс Fragment при этом не создаётся — только визуальная структура из XML.
В запросе вы указываете, какие файлы разметки использовать, размеры экрана, плотность пикселей и ориентацию. Это важно: если ваше приложение работает на планшете с разрешением 1280×800 и плотностью 240 dpi, агент должен видеть именно эти параметры — иначе измерения элементов будут некорректными.
Список с тестовыми данными
Пустой RecyclerView — бесполезен для визуальной проверки. Но запускать настоящий адаптер с реальными данными — снова значит тянуть за собой слишком много кода. Поэтому данные для списка тоже описываются в запросе: указывается реальный XML-файл элемента списка, ориентация, количество строк и конкретное содержимое каждой строки.
Например, для строки «книга в библиотеке» можно задать заголовок, обложку, бейдж статуса и цвет выделения. Рендерер создаёт временный адаптер и inflate-ит настоящий элемент списка для каждой строки. Структура интерфейса — настоящая, данные — тестовые и полностью под контролем.
Это работает иначе, чем привычные tools:text и tools:visibility в Android Studio. Агент не угадывает значения — он явно описывает, что должно быть на экране.
Индикатор загрузки: заморозить, чтобы не мигал
Оверлей загрузки — это ещё одна XML-разметка, наложенная поверх Activity и Fragment. Тонкий момент: если в оверлее есть анимированный индикатор (ProgressBar с неопределённым прогрессом), два последовательных скриншота могут отличаться из-за разных фаз анимации. Для автоматизированной визуальной проверки это лишний источник случайности.
Решение простое: перед рендерингом индикатор «замораживается» в статичное кольцо. Это не проверяет, работает ли анимация, но подтверждает, что индикатор есть, находится в правильном месте и имеет ожидаемую геометрию. Для задач визуального контроля этого достаточно.
Повторяемость как требование
Автор проекта столкнулся с проблемой: сохранённый рендер (картинка и дерево виджетов) был на месте, а вот запрос, который его породил, — нет. Какой инструмент использовался, какие аргументы передавались, из какого модуля и варианта сборки — ничего не сохранилось. Получился воспроизводимый рендерер с невоспроизводимой историей запусков.
Теперь каждый рендер сохраняет два файла: request.json (нормализованный запрос) и replay.json (рецепт с параметрами проекта и Gradle-задачей). С этими файлами можно повторить рендеринг на той же ревизии кода и получить битово идентичную картинку. Не похожую, а именно такую же.
Что нашёл демо-проект
Любопытно, что открытый демо-проект (созданный специально для публикации изображений) обнаружил реальные проблемы, которых не было в рабочем проекте.
Во-первых, рендерер молча предполагал наличие JUnit в зависимостях. Рабочий проект, созданный по шаблону Android Studio, содержал JUnit, так что проблема не проявлялась. Демо-проект — нет, и первый рендер упал с ошибкой. Теперь JUnit 4.13.2 добавляется автоматически, если проект его не объявил.
Во-вторых, в самом демо приложении обнаружился баг контрастности в ночном теме: кнопка с обводкой была почти невидна на тёмном фоне. Рендер в ночном режиме это показал — а чтение XML нет.
В-третьих, увеличенный масштаб шрифта (fontScale: 1.3) обрезал длинный заголовок с многоточием. PNG-картинка выглядела нормально, но дерево виджетов показало, что 81 символ превратился в многоточие. Это та проблема, которую при беглом взгляде на скриншот легко пропустить.
Интересный технический нюанс: начиная с Android 14, масштабирование крупного текста стало нелинейным. 16sp при fontScale: 1.3 — это 40 пикселей, а не 41,6, как можно было бы ожидать.
Кто за что отвечал
Разработатель описывает распределение ролей между собой и ИИ-агентом. Человек выбирал, какую часть реальности инструмент должен моделировать и чего он не должен делать. Агент — изучал XML-разметку реального экрана, предлагал технические решения (например, детерминированный адаптер для RecyclerView), реализовывал их и писал демо-приложение.
Автор не набирал большую часть кода вручную. Его задача была в другом: определить границу между тем, что должно остаться реальным, и тем, что можно заменить детерминированной моделью.
Ограничения
Стоит понимать, что это не универсальный инструмент тестирования. Поддерживается только один тип цели — activity_fragment с одним контейнером и одним фрагментом. Встроенная навигация между фрагментами не моделируется. RecyclerView получает только явно заданные локальные строки — пагинация, асинхронная загрузка и реальные источники данных не учитываются. Замороженный индикатор проверяет наличие и геометрию, но не анимацию. И главное: Robolectric — это не настоящее устройство. Он приближается к реальности, но не заменяет тестирование на физическом планшете или телефоне.
Зачем это обычному пользователю
Вы, скорее всего, не будете запускать этот инструмент напрямую. Но если вы пользуетесь Android-приложениями, которые разрабатываются с участием ИИ-агентов, такие инструменты влияют на качество интерфейса. Агент, который может визуально проверить результат своих изменений в XML-разметке, реже отправит в продакшн сломанный экран с обрезанным текстом, невидимой кнопкой или перекрытым списком. И делает это быстрее, чем если бы каждый раз приходилось запускать эмулятор.
Проект android-ui-renderer-mcp выложен в открытый доступ на GitHub.