Привет, хабровчане! Меня зовут Александр и я Flutter-разработчик. В этой статье хочу рассказать о том как я подружил ИИ-агентов с интеграционными тестами Flutter, какой инструмент пришлось для этого написать и что вообще из этого вышло. Летс гоу.
Проблема
Представьте, что вы попросили агента написать для вас интеграционный тест. На моих проектах очень часто это выглядело следующим образом:
Агент изучает код
Пишет тест
Запускает flutter test
Тест не проходит
Агент пытается понять в чем дело, делает фикс
Переходит к пункту 3
И таких итераций может быть много. Каждая из них это сжигание токенов, контекстного окна, времени на очередное "я нашел в чем проблема, сейчас точно заработает" и времени на пересборку. По моему личному опыту, на такой цикл может потратиться и 15 и 20 минут или он вообще может закончиться без успешного результата, с забитым контекстым окном и несколькими саммарайзами.
Таким образом определились следующие узкие места при разработке интеграционных тестов:
Сжигание токенов и контекстного окна на чтение всех логов
Время на пересборку
Непонимание только по логам на каком этапе теста возникла проблема
Время и токены на исправление несуществующих проблем и создание новых
Отсутствие у агента визуального представления того что на экране
Все это приводит к дополнительным итерациям и увеличению времени каждой из них. В ходе решения этой дилеммы, я написал инструмент который покрывает все эти проблемы.
Решение
Testwire - это утилита для пошагового исполнения интеграционных тестов. Тест разбивается на логические шаги, которые имеют состояния: не выполнен, выполнен, выполняется, выполнен с ошибкой. Агент запускает тест, подключается по MCP к тесту через VM Service и контролирует его выполнение.
Как это выглядит с точки зрения кода (о том почему тест пишется в виде отдельного класса чуть позже):
class MyTest extends TestwireTest { MyTest() : super( 'Submit feedback form', setUp: (tester) async { app.main(); await tester.pumpAndSettle(); }, ); @override Future<void> body(WidgetTester tester) async { await step( description: 'Navigate to Leave Review', context: 'Tap the "Leave Review" tile on the home screen.', action: () async { await tester.tap(find.byKey(const Key('leave_review_tile'))); await tester.pumpAndSettle(); }, ); await step( description: 'Enter name', context: 'Type "Alex" into the name field.', action: () async { await tester.enterText( find.byKey(const Key('name_field')), 'Alex'); await tester.pumpAndSettle(); }, ); await step( description: 'Tap 5-star rating', context: 'Tap the 5th star to set rating to 5.', action: () async { await tester.tap(find.byKey(const Key('star_5'))); await tester.pumpAndSettle(); }, ); await step( description: 'Verify result', context: 'Check that the success message is displayed.', action: () async { expect(find.text('Thank you!'), findsOneWidget); expect(find.text('5 stars from Alex'), findsOneWidget); }, ); } }
Доступные MCP инструменты:
Инструмент | Что делает |
|---|---|
| Подключиться к тесту через VM service URI |
| Следующий шаг, потом пауза |
| Выполнить все оставшиеся шаги (стоп при ошибке) |
| Перепрогнать упавший шаг |
| Статус всех шагов |
| Hot reload с сохранением прогресса |
| Полный рестарт |
| Скриншот UI |
| Отключиться |
Hot Reload - ключевая механика
Для разработки, в агентном режиме, тест запускается через flutter run, таким образом позволяя агенту подключиться к VM (в том числе через Dart MCP), считывать состояние, делать скриншоты.
Если какой-то шаг зафейлился, то выполнение теста приостанавливается и агент выясняет причины фейла уже имея доступ не только к логам но и к VM, а так же к визуальному состоянию. После фикса агент делает hot reload и делает ретрай последнего шага.
Именно из-за того что агенту необходим hot reload, тесты в testwire это именно отдельный класс а не просто функция.
Как это работает

Три компонента:
Тест - запускается через
flutter run(неtest!) с--dart-define=AGENT_MODE=true. Приложение стартует в дебаг-режиме, тест регистрирует экстеншены Dart VM service и ждетtestwire_mcp - MCP сервер, который подключается к тесту через VM service, предоставляя агенту необходимые инструменты
ИИ-агент - ваш любимый MCP-клиент, использует предоставленные инструменты, имеет доступ к состоянию каждого шага
Как это меняет мою разработку
Неожиданным образом я так же обнаружил, что теперь могу дать агенту задачу и для верификации ее выполнения попросить написать интеграционный тест с использованием testwire (заранее подготовив для этого отдельный скилл), убивая при этом одним выстрелом двух зайцев: логически работающая фича, работающий интеграционный тест. Интеграционный тест при этом становится не чем-то что я может быть потом напишу, а может и нет, а обязательным пунктом, частью задачи.
Один файл - два режима
При этом на CI тест запускается в том же файле, но как обычный, без флага AGENT_MODE. То есть два режима:
# Агентский режим с hot reload flutter run --dart-define=AGENT_MODE=true integration_test/my_test.dart # Обычный CI прогон flutter test integration_test/my_test.dart
Заключение
Сам инструмент не гарантирует, что все с первого раза заведется и заработает в вашем конкретном случае. Интеграция в проект может потребовать дополнительных настроек в виде написания дополнительных агентских скиллов, документации по проекту и т.д.
Буду рад фидбэку. Посмотреть примеры и как стартануть можно по ссылке в GitHub репозитории.
