Dusk를 만들었습니다: Flutter 앱을 위한 Playwright MCP
요약
Flutter 앱 테스트를 위해 AI 에이전트가 사용할 수 있는 Playwright MCP 기반의 도구인 Dusk를 소개합니다. 기존의 느리고 불확실한 테스트 방식 대신, Semantics 트리를 활용하여 에이전트에게 안정적인 참조(ref)와 실행 능력을 제공합니다.
핵심 포인트
- Dusk는 VM Service 확장을 통해 실행 중인 Flutter 앱에 직접 연결됩니다.
- Semantics 트리를 스냅샷하여 안정적인 참조 토큰을 제공함으로써 좌표 추측을 방지합니다.
- 32개의 CLI 명령과 31개의 MCP 도구를 통해 에이전트의 '눈과 손' 역할을 수행합니다.
- 6단계 실행 가능성 게이트를 통해 테스트의 불안정성(Flakiness)을 최소화합니다.
지난주 저는 제 AI 에이전트가 Flutter 화면을 테스트하려고 시도하는 것을 지켜보았습니다. 에이전트는 테스트 파일을 작성하고, flutter test를 실행하고, 스택 트레이스(stack trace)를 프롬프트에 다시 복사하고, 스크린샷을 붙여넣은 뒤, 이를 워크플로(workflow)라고 불렀습니다. 그것은 느렸고, 추측에 의존하고 있었습니다.
웹에서는 에이전트들이 더 이상 그런 식으로 작동하지 않습니다. Playwright MCP는 에이전트에게 읽을 수 있는 접근성 트리(accessibility tree)와 동작할 수 있는 안정적인 참조(stable refs)를 제공합니다. 33k개의 스타를 보유한 이 방식은 스크린샷을 통한 추측이 필요 없습니다. Flutter에는 한 번도 그런 계층이 없었습니다.
그래서 저는 Dusk를 만들었습니다.
문제점
Flutter의 엔드 투 엔드(End-to-end) 테스트는 항상 여러 가지를 짜깁기한 의식과 같았습니다.
flutter_driver는 일회성 소켓 프로토콜을 제공하며 레거시(legacy) 트랙에 있습니다. integration_test는 시뮬레이션된 WidgetTester를 대상으로 프로세스 내(in-process)에서 실행되지만, 테스트 파일을 작성하고, 빌드하고, 실행하고, 기다려야 합니다. Maestro는 좋지만 동작당 약 3초가 소요됩니다. Patrol은 강력하지만 CI(지속적 통합) 환경에서 불안정한 경향이 있습니다.
더 깊은 문제는 루프(loop)입니다. 앱을 구동하려는 에이전트는 임시적인 flutter test 실행에 의존하고, 스택 트레이스를 수동으로 복사하며, 스크린샷을 다시 붙여넣습니다. 에이전트와 실행 중인 앱 사이에 라이브 연결(live connection)이 없습니다.
// 기존의 루프: 테스트 파일을 작성하고, 빌드하고, 실행하고, 기다리고, 실패 원인을 읽고, 반복합니다.
testWidgets('checkout flow', (tester) async {
await tester.tap(find.byKey(const Key('checkout')));
...
Dusk가 이를 처리하는 방식
Dusk는 VM Service 확장을 통해 실행 중인 Flutter 앱에 연결됩니다. 테스트 파일도, flutter_test 하네스(harness)도, 빌드 단계도 필요 없습니다. 앱을 시작하고 연결하기만 하면 에이전트에게 눈과 손이 생깁니다.
먼저 Semantics 트리(Semantics tree)를 스냅샷합니다:
dart run fluttersdk_dusk dusk:snap
그러면 안정적인 [ref=eN] 토큰이 포함된 YAML 트리가 반환됩니다. 모든 동작은 참조(ref)를 대상으로 하므로, 취약한 XPath나 좌표 추측이 필요 없습니다.
dusk:tap --ref=e7
dusk:type --ref=e3 --text "ada@fluttersdk.com"
dusk:screenshot
동일한 계약(contracts)이 여러분의 터미널과 AI 에이전트(AI agent)를 구동합니다. CLI에서의 dusk:tap --ref=e7과 MCP 도구로서의 dusk_tap은 정확히 동일한 코드 경로에 도달합니다. 32개의 CLI 명령과 31개의 MCP 도구—snap, tap, type, scroll, drag, observe, screenshot, 그리고 새로운 트리(tree), 스크린샷, 그리고 모든 예외 사항을 한 번의 호출로 반환하는 hot-reload-and-snap 라운드 트립(round trip)이 제공됩니다.
Flake(불안정성)가 발생하지 않는 이유
모든 제스처는 실행되기 전에 6단계의 실행 가능성 게이트(actionability gate)를 통과합니다: 무효하지 않음(not defunct), 활성화됨(enabled), 0이 아닌 사각형 영역(non-zero rect), 뷰포트 내 위치(on-viewport, 자동 스크롤 지원), 2프레임 동안 안정적임(stable across 2 frames), 그리고 실제로 히트 테스트(hit-testable)가 가능함. 따라서 여러분의 에이전트는 아직 실제로 존재하지 않는 버튼을 탭하는 일이 절대 없습니다.
이 부분이 바로 "앱을 제어한다"는 개념을 단순한 데모에서 신뢰할 수 있는 무언가로 바꾸어 놓는 지점입니다. 이 지루한 체크 과정이 바로 핵심입니다.
활용 분야
Dusk는 기존의 테스트 스위트(test suite)를 대체하지 않습니다. Dusk는 다른 틈새 영역, 즉 대본이 없는(unscripted) 실행 중인 앱을 담당합니다.
| 도구 | 정의 | Dusk의 위치 |
|---|---|---|
| integration_test | WidgetTester를 통해 작성된 테스트 파일 | 테스트 파일을 담당합니다. Dusk는 라이브 상태의 대본 없는 앱을 담당합니다. 둘 다 사용하세요. |
| ... |
시작하기
flutter pub add fluttersdk_dusk
dart run fluttersdk_dusk dusk:install
dusk:install은 kDebugMode 뒤에서 lib/main.dart를 패치하고 CLI를 구성(scaffold)합니다. 릴리스 빌드(Release builds)에서는 웹, 데스크톱, 모바일에 걸쳐 드라이버 전체를 트리 쉐이킹(tree-shake)하므로, Dusk는 프로덕션 환경에 절대 포함되지 않습니다.
다음 명령 하나로 에이전트에 연결하세요:
dart run fluttersdk_dusk mcp:install
이 명령은 Claude Code, Cursor, Windsurf, VS Code Copilot 및 모든 MCP 호환 에이전트를 위한 stdio MCP 서버를 등록합니다. Dusk는 자체적인 에이전트 스킬(agent skill)도 함께 제공하므로, 에이전트는 단순한 구문(syntax)뿐만 아니라 참조 문법(ref grammar)과 도구 인터페이스(tool surface)를 학습합니다.
배운 점
두 가지가 기억에 남습니다.
첫째, 접근성 트리(accessibility tree)는 웹에서와 마찬가지로 Flutter 상의 에이전트에게도 올바른 인터페이스입니다. 시맨틱 노드(Semantics nodes)는 안정적이고 비용이 저렴하며 이미 존재합니다. 스크린샷은 기본 방식이 아니라, 느리고 비용이 많이 드는 폴백(fallback) 수단입니다.
둘째, 도구의 개수보다 실행 가능성 게이트 (actionability gate)가 더 중요합니다. 아직 안정화되지 않은 위젯을 자신 있게 탭하는 에이전트는 자동화가 아예 없는 것보다 더 나쁩니다. 6단계 체크 과정이 나머지 기능들을 사용 가능하게 만들어 주는 핵심입니다.
문서: https://fluttersdk.com/dusk
에이전트 설정: https://fluttersdk.com/dusk/ai
여러분의 에이전트로 직접 테스트해 보신다면, 어떤 부분이 제대로 작동하지 않는지 꼭 듣고 싶습니다. 이상입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기