Tests d'intégration E2E
Les tests end-to-end exécutent l'application complète contre une stack Docker locale. Ils exercent des flux OAuth réels, des souscriptions relay et des uploads de médias sans aucun mock.
Démarrage rapide
cd mobile/
mise run local_up # Démarrer la stack Docker
mise run e2e_test # Exécuter tous les tests E2E avec profilage
mise run e2e_test integration_test/auth/auth_journey_test.dart # Test unique
mise run local_down # Arrêter la stack Docker
mise run local_reset # Effacer les données et redémarrer
mise run local_status # Vérifier la santé des services
Toujours utiliser mise run e2e_test — il gère local_up, le profilage des logs,
et les rapports de timeline fusionnés. Ne jamais appeler patrol test directement.
Stack Docker locale
Situé dans local_stack/. Services :
| Service | Port hôte | Objectif |
|---|---|---|
| Keycast | 43000 | OAuth + signer NIP-46 |
| FunnelCake Relay | 47777 | Relay Nostr (WebSocket) |
| FunnelCake API | 43001 | API REST |
| Blossom | 43003 | Serveur média |
| Postgres | 15432 | Base de données Keycast |
Toutes les images GHCR sont publiques — aucune connexion Docker requise. Si les pulls échouent avec
denied, vérifiez si vous avez une session docker login ghcr.io obsolète. Un token expiré
fait que Docker envoie de mauvaises credentials au lieu de revenir à l'accès anonyme. Solution : docker logout ghcr.io.
Stack ne démarre pas
Si mise run local_up échoue lors du pull d'images :
# Utiliser les images en cache, ignorer le pulling
COMPOSE_FILE=../local_stack/docker-compose.yml docker compose up -d --pull=missing
Puis exécutez le script de test directement :
bash ../local_stack/profile.sh integration_test/auth/
Émulateur Android
Configuration
L'émulateur atteint l'hôte via 10.0.2.2. Les constantes de port sont dans
lib/models/environment_config.dart et ré-exportées par
integration_test/helpers/constants.dart.
Lancement Linux / Hyprland
DISPLAY=:1 ANDROID_AVD_HOME=/home/daniel/.config/.android/avd \
emulator -avd Medium_Phone_API_36.1 -gpu host -no-snapshot-load
Toujours utiliser -gpu host pour le rendu vidéo (swiftshader ne peut pas rendre
les frames media_kit).
Problèmes de stockage
Les installations répétées d'APK remplissent /data. Symptômes : INSTALL_FAILED_INSUFFICIENT_STORAGE
ou 0 tests découverts avec code de sortie Gradle 1.
# Vérifier l'espace
adb shell df -h /data
# Libérer de l'espace
adb shell pm trim-caches 1G
# Option nucléaire : effacer l'émulateur
emulator -avd <name> -gpu host -wipe-data
Buffer Logcat
La valeur par défaut 256KB est trop petite — les logs du flux auth tournent avant les phases critiques.
adb logcat -G 16M # Augmenter à 16MB
adb logcat -c # Effacer avant l'exécution du test
adb logcat -d | grep 'flutter.*\[AUTH\]' # Capturer le flux auth
Filtrer par PID pour isoler les cas de test (chaque patrolTest s'exécute dans un nouveau processus) :
adb logcat -d | grep '<PID>.*flutter.*\[AUTH\]' | grep -v 'Router redirect'
Framework de test
Patrol
Les tests utilisent Patrol pour l'automatisation native de l'UI. Patrol encapsule
integration_test de Flutter avec la capacité de gérer les dialogues de permissions,
le bouton retour système, les notifications et les feuilles de partage.
patrolTest('my test', ($) async {
final tester = $.tester;
// Utiliser tester pour les interactions de widget Flutter
// Utiliser $ pour les interactions natives (permissions, UI système)
});
Bundling de tests Patrol (faux positifs)
Patrol regroupe TOUS les fichiers de test d'un répertoire dans un seul APK. Chaque fichier de test
s'exécute dans un processus d'instrumentation séparé. Quand le test B s'exécute, le code du test A est dans le bundle mais « non demandé » — Patrol le marque [E].
Ce sont des faux positifs, pas des vrais échecs. Fiez-vous uniquement aux lignes de statut final ✅/❌
du test runner. Le logcat affichera :
registered test "foo_test ..." was not matched by requested test "bar_test ..."
Motif de lancement d'app
Utiliser launchAppGuarded depuis test_setup.dart pour capturer les erreurs async relay :
final originalOnError = suppressSetStateErrors();
final originalErrorBuilder = saveErrorWidgetBuilder();
launchAppGuarded(app.main);
await tester.pumpAndSettle(const Duration(seconds: 3));
// ... corps du test ...
restoreErrorWidgetBuilder(originalErrorBuilder);
restoreErrorHandler(originalOnError);
drainAsyncErrors(tester);
Polling au lieu de pumpAndSettle
L'app a des timers de polling persistants qui empêchent pumpAndSettle de se stabiliser.
Utiliser des boucles pump manuelles :
for (var i = 0; i < 60; i++) {
await tester.pump(const Duration(milliseconds: 250));
if (find.text('Welcome').evaluate().isNotEmpty) break;
}
Pièges courants
Publication asynchrone → requête Relay
La publication vidéo est asynchrone — l'UI navigue vers le profil avant que l'upload blossom et la publication relay se terminent. Toujours interroger le relay :
var events = <Event>[];
for (var i = 0; i < 120; i++) {
await tester.pump(const Duration(milliseconds: 500));
events = await queryRelay(filter);
if (events.isNotEmpty) break;
}
Feuilles d'onboarding bloquant l'UI
Les nouvelles fonctionnalités peuvent ajouter des bottom sheets qui couvrent les boutons dont le test a besoin. Les fermer avant de continuer :
for (var i = 0; i < 20; i++) {
await tester.pump(const Duration(milliseconds: 250));
final gotIt = find.text('Got it!');
if (gotIt.evaluate().isNotEmpty) {
await tester.tap(gotIt);
await tester.pump(const Duration(milliseconds: 500));
break;
}
}
Crashes d'initialisation de provider Riverpod
Si un provider utilise requireIdentity ou des getters non-nullable similaires, il
crash au démarrage froid (avant l'auth) et Riverpod met en cache l'erreur
définitivement. Utiliser l'accès nullable (currentIdentity) dans les providers et
gérer null. L'erreur ressemble à :
ProviderException: Tried to use a provider that is in error state.
Bad state: requireIdentity called with no active NostrIdentity.
Ancêtre Material Widget
TextField requiert un ancêtre Material. Si un widget est utilisé dans un contexte
overlay ou transition sans Scaffold, le wrapper :
Material(
color: Colors.transparent,
child: TextField(...),
)
Helpers de test
Tous les helpers sont dans integration_test/helpers/ :
| Fichier | Objectif |
|---|---|
constants.dart |
Constantes de port + pgPort, appPackage |
db_helpers.dart |
Requêtes Postgres : jetons de vérification, jetons de rafraîchissement |
http_helpers.dart |
API Keycast : vérifier email, mot de passe oublié |
navigation_helpers.dart |
Interactions UI : registre, connexion, appuyer sur onglets, attendre les widgets |
relay_helpers.dart |
Publier des événements Nostr : kind 34236 vidéos, kind 0 profils |
test_setup.dart |
Suppression d'erreurs, lancement d'app, drainages d'erreurs async |
Débogage
# Logs des services
docker compose -f local_stack/docker-compose.yml logs keycast --tail=50
docker compose -f local_stack/docker-compose.yml logs funnelcake-relay --tail=50
docker compose -f local_stack/docker-compose.yml logs blossom --tail=50
# Vérifier blossom pour les uploads actuels (pas seulement les health checks)
docker compose -f local_stack/docker-compose.yml logs blossom | grep -v 'path=/'
# Trace du flux auth
adb logcat -d | grep 'flutter.*\[AUTH\]' | grep -v 'Router redirect'
# Rapports de test (timeline fusionnée Docker + logcat)
ls mobile/test_reports/*.jsonl