Maestro: tests E2E para Flutter que van más allá de tu app
Tests unitarios cubiertos. Tests de widget cubiertos. El pipeline verde. Y aun así tienes miedo de que algo se rompa en producción porque nunca has validado el flujo completo en un dispositivo real.
Eso es lo que el testing E2E resuelve. Pero en Flutter, llegar hasta ahí tiene sus obstáculos.
El dolor del testing E2E en Flutter
La opción oficial es integration_test. Funciona, pero tiene un coste: antes de ejecutar nada, el compilador tiene que montar la app entera en modo test. Una suite básica tarda fácilmente 4 minutos en compilar en CI. Y si el entorno tiene cualquier problema —versión de Xcode, emulador, variables de entorno— el test falla antes de correr una sola línea tuya.
Hay opciones nativas: Espresso en Android, XCUITest en iOS. Son potentes, pero significa escribir Kotlin o Swift en un proyecto Flutter. Y mantener dos suites separadas para la misma app.
Pero el problema más silencioso es otro.
Llegas hasta el momento en que iOS pide permiso para las notificaciones. El test se congela. Buscas el elemento en el árbol de widgets: no existe. No es un bug de tu código —es el límite de la herramienta. El diálogo de permisos es del sistema operativo. Vive fuera de tu app. Y las herramientas que corren dentro de tu app no pueden tocarlo.
Toda la cobertura E2E se detiene justo en el momento más crítico del onboarding.
Qué es Maestro
Imagina que en lugar de código dentro de tu app, hay un dedo humano tocando la pantalla. Ese dedo no sabe nada de Flutter, ni de React Native, ni del lenguaje en que está escrita la app. Solo ve lo que aparece en la pantalla —y puede tocar cualquier cosa que el usuario pueda tocar.
Eso es, arquitecturalmente, lo que hace Maestro. En lugar de inyectarse dentro del proceso de tu app, opera a nivel del dispositivo. Ve la pantalla. Toca. Escribe. Swipea. Y puede interactuar con cualquier elemento visual, independientemente de quién lo dibujó.
Los flujos se describen en YAML. Sin Kotlin, sin Swift, sin Dart:
# launch_app.yaml
appId: xyz.albertomontesdeoca.oposas
---
- launchApp
- tapOn: "Entrar como invitado"
- assertVisible: "Inicio"
Para ejecutarlo, un solo comando desde la terminal:
maestro test launch_app.yaml
Funciona en simuladores y en dispositivos físicos (con algunas limitaciones). La instalación en macOS es brew install maestro; los tests de iOS requieren macOS por Xcode. Para Android o para Windows y Linux, consulta la documentación oficial.
La magia: interacciones nativas
Diálogo de permisos del sistema. La app acaba de pedir permiso para enviar notificaciones. iOS muestra su propio diálogo —fuera de tu app, fuera del árbol de widgets. Con Maestro, es una línea:
- tapOn:
text: "Allow"
Ese tapOn no le pregunta a Flutter nada. Le pregunta al dispositivo qué hay en pantalla. Y el dispositivo responde: hay un botón que pone “Allow”. Lo toca.
Teclado del sistema. Escribir en un campo de búsqueda usando el teclado nativo:
- tapOn:
id: "search_field"
- inputText: "temario SAS 2024"
Bandeja de notificaciones. La app ha enviado una notificación push y el flujo necesita verificar que aparece. El mismo YAML funciona en iOS y Android —Maestro abstrae la diferencia de gesto entre plataformas:
- openNotifications
- tapOn: "Nueva pregunta disponible"
Maestro no habla con tu app. Habla con el dispositivo. Por eso puede tocar cualquier cosa que el usuario pueda tocar.
Un ejemplo real: OpoSAS
OpoSAS es una app de preparación de oposiciones que tengo en producción. Su flujo de onboarding toca exactamente el caso difícil: el diálogo de permisos de notificaciones al final del wizard.
El flujo completo, desde cero:
# maestro/oposas_onboarding.yaml
appId: xyz.albertomontesdeoca.oposas
---
- clearState # Limpia el estado previo para un flujo determinista
- launchApp
- tapOn: "Entrar como invitado"
- tapOn: "Siguiente" # Paso 1 del onboarding
- tapOn: "Siguiente" # Paso 2 del onboarding
- tapOn: "Empezar" # Fin del onboarding
- tapOn:
text: "Allow" # Diálogo de permisos de notificaciones (iOS/Android)
- assertVisible: "Inicio" # La pantalla principal está visible
La clave es clearState: reinicia la app a su estado de primera instalación. Sin esto, una app que recuerda que ya completaste el onboarding saltaría directamente al inicio —y el diálogo de permisos nunca aparecería. Siempre úsalo para flujos que dependen del comportamiento de primera apertura.
Para ejecutarlo:
maestro test maestro/oposas_onboarding.yaml
Los flujos viven en una carpeta maestro/ en la raíz del proyecto. El código de OpoSAS es privado, pero el YAML de arriba es ilustrativo —puedes adaptarlo a tu app cambiando el appId y los textos visibles en tu onboarding.
No importa con qué esté hecha la app
El mismo YAML funciona contra una app Flutter, una app React Native y una app nativa de iOS o Android. Maestro no sabe ni le importa el stack.
Esto es especialmente relevante si tu equipo tiene un QA que no conoce Dart. No necesita aprender el stack —solo YAML. Puede escribir y mantener los flujos sin tocar código.
Cuándo usar Maestro (y cuándo no)
Úsalo para:
- Flujos críticos de usuario — login, onboarding, checkout. Las cosas que si se rompen afectan a todos.
- Cualquier flujo que toca diálogos del sistema — permisos, teclado nativo, notificaciones.
- Smoke tests en CI antes de un release — un conjunto pequeño de flujos que confirman que lo fundamental funciona.
Y siendo honesto sobre las limitaciones:
- No reemplaza los tests unitarios ni de widget. La pirámide de tests sigue siendo válida. Maestro vive en la cima.
- Flakiness en animaciones lentas. Si un tap llega antes de que termine una animación, el test puede fallar. La solución es añadir
waitForAnimationToEndowait: 2000antes del siguiente tap en los pasos que lo necesiten. - El debugging de YAML puede ser verboso para condiciones complejas. Maestro Studio ayuda mucho aquí —puedes grabar flujos visualmente y ver en tiempo real qué está pasando en pantalla.
Maestro MCP
Hace poco el equipo de Maestro lanzó un servidor MCP que conecta el tool con agentes de IA como Claude o Cursor. La idea: describes en lenguaje natural el flujo que quieres testear, y el agente genera el YAML, lo ejecuta en el dispositivo y lo corrige si falla.
Es especialmente útil para la creación inicial de tests —identificar los IDs de los elementos y la estructura de la pantalla es lo más tedioso de escribir flows desde cero— y para el mantenimiento cuando la UI cambia. Más información en maestro.dev/blog/maestro-mcp-an-introduction.
¿Recuerdas ese diálogo de permisos que dejaba el test paralizado? Así es exactamente como Maestro lo resuelve. Un tapOn: "Allow" que habla con el dispositivo, no con la app.
Para empezar ahora mismo: instala Maestro con brew install maestro, abre Maestro Studio, y graba tu primer flujo sin escribir una sola línea de YAML.