
Как проверить, что ИИ-агент в IDE работает, если на одинаковые запросы LLM отвечает по-разному? Ответы модели недетерминированы, а интерфейс и бизнес-логика вполне детерминированы, и их нужно тестировать отдельно.
Мы делаем ИИ-агента, встраиваемого в JetBrains IDE. В статье расскажу, как мы выстроили UI-автоматизацию плагина так, чтобы тесты ловили регрессии в интерфейсе, бизнес-логике и при этом не «моргали» из-за нестабильности LLM.
Статья пригодится, если вы QA-инженер или разработчик и вам интересны:
Выстраивание UI-автоматизации на примере IDE-плагина
Тестирование приложений с ИИ-функциями
Разделение ответственности между детерминированной и недетерминированной частями системы
Подход к автоматизации
Veai — это встраиваемый в JetBrains IDE ИИ-агент для написания кода, отладки и тестирования.
В условиях ограниченных QA-ресурсов мы в первую очередь фокусируемся на высокоприоритетной функциональности. В то же время не забываем инвестировать и в автоматизацию тестирования. Как и большинство команд, мы сталкиваемся с проблемой фич, добавленных в последний момент. Поэтому поставили перед собой задачу улучшить Quality Gates.
В проекте уже были автотесты на разных уровнях: тесты на основе модели IDE, тесты для разных LLM-провайдеров, агентский бенчмарк. Наш продукт, в отличие от консольных ИИ-агентов, имеет пользовательский интерфейс, но классических UI-тестов в проекте не было. То есть пирамида тестирования у нас в целом присутствует, и UI-тесты были бы ее верхним уровнем, а не заменой остальным. Вывод: нужны UI-тесты.
Тестирование приложений с LLM-функциями условно делим на два направления:
тестирование классических функций, таких как хранение настроек пользователя,
и ИИ-функций, таких как получение ответа LLM-провайдера.
Отправляя агенту вопрос, можно получить как ответ в чате, так и вывод в терминале IDE. С точки зрения автоматизации тестирования это может привести к нестабильности тестов, но мы нашли решение — разделили тесты на несколько уровней:
Запуск | Когда | Что проверяет | LLM | Длительность |
|---|---|---|---|---|
Smoke | Каждый PR | Базовая работоспособность с акцентом на UI | Без LLM | 4-5 минут |
Full | Ночной запуск | Все тесты (включая ожидание ответов LLM) | Реальный сервер | 10-12 минут (параллельно для каждой IDE) |
Benchmark (не UI-тесты) | Ночной запуск | Пользовательские сценарии и оценка результатов агента | Реальный сервер | около 5-6 часов |
В Full-прогоне мы не мокируем ответы LLM, а работаем с реальным сервером и проверяем, что ответы приходят. Так мы сохраняем более полную цепочку подсистем плагина. Полная цепочка — это IDE-плагин, сервер лицензий, LLM-сервер, вспомогательные внутренние библиотеки и другие компоненты. Если один из серверных компонентов недоступен или не работает одна из вариаций сценария «предусловие — запрос пользователя — ответ агента», то мы узнаем об этом быстрее.
При запуске тестов на реальном LLM-сервере мы отправляем один и тот же короткий запрос: «2+2=? Ответь покороче». Это позволяет агенту быстрее перейти в состояние Ready. Мы не стремимся получить ответ «4», а проверяем, что ответ приходит после предусловий в тесте и интерфейс отображает этот ответ. Соответственно, нужно дожидаться завершения стриминга токенов LLM.
Качество агентских сценариев проверяем бенчмарком с использованием подхода LLM-as-a-Judge. Бенчмарк оставим за пределами этой статьи, так как коллеги планируют выпустить материал и по этой теме.
Техническое решение
Starter и Driver
Для автоматизации тестирования плагинов JetBrains IDE актуальное решение — это библиотеки Starter и Driver.
Starter позволяет подготавливать IDE, настраивать тестовый проект и собирать итоговый вывод
Driver позволяет взаимодействовать с интерфейсом и обращаться к внутреннему состоянию IDE через прокси (об этом ниже)
Решение позволяет описывать части интерфейса с помощью паттерна Page Object. Несмотря на то что мы работаем с десктопом, в решении можно описывать иерархию элементов по аналогии с Web и искать элементы с помощью XPath.
Информация о том, как искать элементы, представлена в документации.
Привожу пример Page Object:
fun Finder.chatPromptEditor(action: ChatPromptEditor.() -> Unit = {}): ChatPromptEditor { return x(xQuery { byClass("PromptEditorLocator") }, ChatPromptEditor::class.java).apply(action) } class ChatPromptEditor(data: ComponentData) : UiComponent(data) { val inputField = x { byAccessibleName("Editor") } val sendButton = x { byAccessibleName("Send") } fun sendRequest(text: String) { inputField.click() driver.ui.pasteText(text) sendButton.click() } }
К нему можно обращаться в DSL-стиле за счет Type-safe builders.
chatPromptEditor { sendRequest("2+2=?") }
Иногда в иерархии элементов нет подходящих локаторов для использования в Driver, поэтому в продуктовом коде приходится добавлять accessibleContext.accessibleName для поиска элементов по byAccessibleName. Где-то приходится менять правила обфускации для UI-элементов, чтобы можно было продолжать обращаться к ним по byClass.
Автотестам не требуется каждый раз начинать с Welcome-экрана, так как мы можем подготавливать состояние в виде XML-настроек плагина. Это дает возможность писать более быстрые тесты. Также Driver позволяет с помощью JMX (Java Management Extensions) писать прокси для внутренних компонентов запущенной IDE и получать информацию о ее состоянии. Это дает возможность делать более полные проверки.
В Driver встроена поддержка Allure, поэтому после небольшой настройки и завершения тестов появляется папка allure-results, из которой можно сгенерировать Allure Report.
Теперь рассмотрим пример Smoke-теста с использованием приведенного выше Page Object:
@Test fun `WHEN user uses slash skill THEN skill badge appears in chat`() { Starter.newContext( CurrentTestMethod.qualifiedName(), Projects.simpleProject ).apply { configureEnvironment() // путь до собранного плагина, ускорение VM опциями configureEnterpriseKey(EnterpriseKey.DUMMY) // заглушка лицензии, чтобы не нагружать LLM }.runIdeWithDriver().useDriverAndCloseIde { setDefaultBehavior() // выключение онбординга ideFrame { step("Open Agent") { ideRightToolbar { openAgent() } // индексация IDE, открытие Агента } step("Use slash skill") { chatPromptEditor { // Page Object в DSL-стиле sendRequest(SKILL_PROMPT) // отправка сообщения с командой SKILL } } step("Await used skill in chat messages") { chatMessages().awaitUsedSkillBadge() // ожидание UI-элемента, соответствующего SKILL assertInChatDump(driver, project) { // использование JMX для получения внутреннего состояния IDE (чата) expect(USER_PROMPT_JSONPATH, SKILL_PROMPT) // поиск конкретного вызова SKILL } } } } } private companion object { const val USER_PROMPT_JSONPATH = "$.messages[0].prompt" const val SKILL_PROMPT = "/init " // на примере SKILL для создания AGENTS.md }
Заметим, что в процессе выполнения этого теста с использованием EnterpriseKey.DUMMY на UI отобразится сетевая ошибка, но она не повлияет на стабильность теста. Важнее то, что запрос в этом сценарии не уйдет в LLM без необходимости.
Также заметим, что assertInChatDump — это наша утилита, использующая JMX для получения внутреннего состояния IDE в виде чата ИИ-агента и позволяющая делать проверки через JSONPath.
Continuous Integration
В нашем CI мы поддерживаем несколько комбинаций: версии IntelliJ, разные JetBrains IDE, feature flags и необходимость обфускации.
Обфускация интересна тем, что бывают ситуации, когда новая функция проверена разработчиком, но в итоговом артефакте после обфускации обнаруживается регрессия. Зачастую это связано с потоками данных в плагине и не касается UI. Решение: запускаем тесты на обфусцированном артефакте в ночной сборке. Только на ночной, потому что обфускация увеличивает время сборки.
В IntelliJ Platform регулярно выходят новые версии (2025.1/2025.2/2025.3/2026.1). Также требуется поддерживать IDE для разных языков (IDEA/PyCharm/Rider/WebStorm и других).
Отметим, что возможность запуска на разных вариантах IDE может принести пользу долгосрочно, потому что покрывать все варианты ручным тестированием крайне ресурсозатратно.
Ниже пример конфигурации CI для ручных запусков:

Отдельно стоит сказать про режим запуска (headless vs non-headless). Driver работает с реальным графическим интерфейсом вплоть до движения курсора. При локальном запуске тест может красть фокус окна, что мешает параллельной работе. В CI на Linux эта проблема решается с помощью виртуального дисплея — пакета Xvfb (X Virtual Framebuffer), который эмулирует графическую среду без монитора.
Если говорить о более частых для QA проблемах, то мы используем «ретраи» тестов с помощью Test Retry Gradle plugin. Сейчас каждый тест перезапускается не более одного раза, и на весь запуск может быть не больше трех падений. Это компромиссные значения, которые пересматривать не приходилось.
Результаты
Покрытие
Примеры функций плагина, на которые мы уже написали UI-тесты (несколько десятков тестов):
настройки плагина
переходы между состояниями чата
внутреннее состояние JSON чата (выбор агента и LLM)
вызов SKILL
навигация по истории чатов
вход нового пользователя
Багрепорты
Примеры найденных тестами проблем (за первые месяцы):
при добавлении в чате drag & drop файлов copy & paste ломались 🙂
при доработке прогресс-бара контекста учитывались не все LLM-провайдеры, что могло привести к некорректной отрисовке прогресс-бара и несвоевременному сжатию контекста чата
при доработке выбора агента из чата на UI учитывались не все LLM-провайдеры
при сценарии восстановления агента после ошибки начали «моргать» тесты, потому что и сам доработанный UI начал «моргать»
Была ситуация, когда для целей тестирования деплоилась новая конфигурация LLM-сервера, и при ночном запуске падали тесты. Это дало больше информации о нестабильности конкретной конфигурации.
Итоги
В этой статье мы рассмотрели один из способов обеспечения качества IDE ИИ-агента — UI-автоматизацию. Показали, как с помощью автотестов мы отвечаем на вопрос о работоспособности плагина после его доработок.
Мы видим, что даже небольшое число UI-автотестов находит ошибки. Если какие-то критичные баги пока еще не покрыты, то мы дописываем тесты и в следующий раз узнаем о проблемах быстрее.
Приглашаем попробовать Veai плагин. Обратную связь можно оставлять в Telegram-чате с командой разработки.
Материалы по автоматизации для плагинов
[GitHub] JetBrains/intellij-ide-starter — фасад starter
[GitHub] JetBrains/intellij-community starter — starter core
[GitHub] JetBrains/intellij-community driver — driver core
[Doc] IntelliJ Platform Testing Overview — формат документации
[Blog] Maxim Kolmakov - author — формат статей
[YouTube] Integration tests for the IDE by Roman Ivanitskii — доклад 2025 года
[YouTube] Plugin Testing: Performance, UI, and Functional Testing by Maxim Kolmakov — доклад 2024 года
Plugin Testing: Performance, UI, and Functional Testing by Maxim Kolmakov — презентация к докладу 2024 года
