· Workflow · 9 min de lecture

Générer des captures d'écran App Store pour Flutter : integration_test, golden tests et une voie plus rapide (2026)

Générer des captures d'écran App Store pour Flutter : integration_test, golden tests et une voie plus rapide (2026)
TL;DR. Flutter ne fournit aucune commande « générer mes captures d'écran du store ». Les options réalistes sont : capturer de vraies frames avec integration_test + flutter drive, rendre des snapshots de widgets avec des golden tests (excellent pour la régression, peu adapté aux assets du store), ou encapsuler l'une ou l'autre dans Fastlane/Codemagic en CI. Aucune de ces approches ne compose le carrousel marketing — légendes, cadres, fonds, textes en 50 langues. C'est une étape distincte, et c'est là que Mokbi intervient : il compose et localise les captures d'écran que tu as capturées. Il ne les capture pas.

Si tu développes avec Flutter et que tu as déjà soumis sur l'App Store ou le Play Store, tu as forcément rencontré ce vide : il n'existe pas de commande flutter screenshots. Apple exige des PNG au pixel près à des dimensions comme 1320 × 2868 (iPhone 6.9 pouces) et 2064 × 2752 (iPad 13 pouces) ; Flutter te donne un moteur de rendu et un harnais de test, et te laisse construire le pipeline store toi-même. Cet article est la carte honnête de ce qui fonctionne vraiment en 2026, avec du code que tu peux copier-coller, et une frontière claire entre les rôles de chaque outil. Si tu veux d'abord une vue d'ensemble spécifique à Flutter, consulte Mokbi pour les développeurs Flutter.

Un cadrage utile d'emblée, parce qu'il évite beaucoup de confusion : capturer une frame (piloter l'app vers un état et sauvegarder un PNG de ce qui est à l'écran) et composer une capture d'écran pour le store (cette frame dans une maquette d'appareil, avec un titre, un fond et du texte localisé, exportée à chaque dimension requise) sont deux problèmes distincts. La plupart des outils Flutter présentés ci-dessous résolvent le premier. Aucun ne résout le second.

Option 1 : integration_test + flutter drive (frames capturées en conditions réelles)

C'est ce qui se rapproche le plus d'un pipeline officiel de captures d'écran dans Flutter. Le package integration_test lance toute ton app sur un appareil réel ou un émulateur, la pilote avec la même API finder/tester que les widget tests, et expose binding.takeScreenshot(). Le hic : takeScreenshot renvoie les octets au processus de test — il n'écrit pas de fichier — donc tu as besoin du driver étendu pour recevoir ces octets côté hôte et les sauvegarder.

D'abord, le test. Note la danse spécifique à Android : convertFlutterSurfaceToImage() doit être appelée (et une frame pompée) avant la capture, car Android affiche Flutter dans une SurfaceView que le framebuffer ne peut pas lire directement.

// integration_test/screenshot_test.dart
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;

void main() {
  final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('home + detail screenshots', (tester) async {
    app.main();
    await tester.pumpAndSettle();

    // Android needs the surface converted to an image before capture.
    if (Platform.isAndroid) {
      await binding.convertFlutterSurfaceToImage();
      await tester.pumpAndSettle();
    }

    await binding.takeScreenshot('01_home');

    await tester.tap(find.byKey(const Key('open-detail')));
    await tester.pumpAndSettle();
    await binding.takeScreenshot('02_detail');
  });
}

Ensuite, le driver, qui tourne sur la machine hôte et écrit les octets PNG renvoyés par le test. Ce fichier se place dans test_driver/ et utilise integration_test_driver_extended.dart (le simple integration_test_driver.dart n'a pas de hook onScreenshot) :

// test_driver/integration_test.dart
import 'dart:io';
import 'package:integration_test/integration_test_driver_extended.dart';

Future<void> main() async {
  await integrationDriver(
    onScreenshot: (String name, List<int> bytes,
        [Map<String, Object?>? args]) async {
      final file = await File('screenshots/$name.png').create(recursive: true);
      file.writeAsBytesSync(bytes);
      return true; // false fails the test (e.g. on a diff mismatch)
    },
  );
}

Il faut absolument lancer ça avec flutter drive, pas flutter test — seul le chemin driver invoque onScreenshot :

flutter drive \
  --driver=test_driver/integration_test.dart \
  --target=integration_test/screenshot_test.dart \
  -d emulator-5554

Ce que tu obtiens et ce que tu n'obtiens pas :

  • Vrais pixels de l'app. Ce sont des captures d'écran de tes widgets en cours d'exécution, pas un nouveau rendu — polices, thème, données dynamiques, tout est natif.
  • États déterministes. Tu pilotes l'app exactement vers les écrans que tu veux et tu captures à la demande.
  • La classe d'appareil = l'appareil sur lequel tu lances. La résolution de sortie est celle de l'émulateur/appareil. Pour atteindre exactement 1320 × 2868 d'Apple, tu lances sur un simulateur iOS de classe 6.9 pouces ; pour le jeu iPad 13 pouces, tu lances ce simulateur-là. Il n'existe pas d'option magique « redimensionner pour l'App Store ».
  • La locale, c'est ton affaire. Tu peux passer une locale à ton app au démarrage et relancer par langue, mais ça capture l'interface de l'app dans cette locale — ça n'écrit pas le texte de ta légende marketing.
  • Le coût de maintenance est réel. Les finders (find.byKey, les taps) cassent quand l'UI change, comme n'importe quel test UI.

Option 2 : golden tests — et pourquoi ils ne sont pas des assets pour le store

Les golden tests de Flutter (matchesGoldenFile, ou le niveau supérieur testGoldens de packages comme golden_toolkit) rendent un arbre de widgets en image et le comparent octet par octet à une référence committée. Ils sont excellents, mais conçus à une fin différente des captures d'écran pour le store : la régression pixel-perfect. L'objectif est de détecter le jour où un changement de padding ou une substitution de police décale silencieusement ton UI. Le golden est le contrat ; un écart est un test en échec.

Deux raisons de ne pas utiliser les golden tests bruts comme pipeline pour le store :

  • Les goldens rendent les widgets dans un environnement de test headless, pas sur la vraie surface de l'appareil. Les platform views, certains shaders et tout ce qui dépend du vrai compositeur GPU peuvent se rendre différemment (ou pas du tout) par rapport à ce que voit un utilisateur. Pour un contrat de régression c'est acceptable — tu te compares à toi-même. Pour un asset du store, tu veux la vraie frame, ce que donne l'Option 1.
  • Le matching pixel-perfect te crée des obstacles. Les goldens vanilla attendent une correspondance exacte, donc les différences d'antialiasing et de hinting de police entre machines provoquent des échecs flaky. C'est la rigueur qu'il faut pour la régression, et exactement la mauvaise pour « produire une belle image marketing ».

Cela dit, il existe une voie intermédiaire légitime. Des packages comme golden_screenshot détournent délibérément la machinerie des goldens pour générer des assets de store : ils utilisent testGoldens, encadrent ton écran dans de vrais cadres d'appareils (téléphone/tablette/bureau), activent un comparateur flou qui tolère un petit écart (~0.1 %, configurable) au lieu d'exiger la perfection pixel-perfect, et écrivent les fichiers dans une arborescence de style Fastlane (metadata/<locale>/images/...). Si tes écrans sont du Flutter pur (pas de platform views) et que tu veux des cadres autour d'un rendu sans lancer d'émulateur, c'est une voie raisonnable et rapide. Garde juste en tête que tu renders des widgets, pas l'app en direct, et que le « marketing » que tu obtiens se limite à un cadre d'appareil — pas de titres, pas de fonds, pas de carrousel multi-panneaux.

Option 3 : Fastlane / Codemagic pour Flutter

Fastlane est un outil d'automatisation iOS/Android, pas spécifique à Flutter, mais il s'intègre de deux façons. Les outils de capture de Fastlane (snapshot pour iOS via XCUITest, screengrab pour Android via un harnais Espresso/UI Automator) attendent des tests UI natifs, que les apps Flutter n'ont généralement pas — donc la plupart des équipes Flutter n'utilisent pas snapshot pour capturer. Là où Fastlane a toute sa valeur, c'est pour la livraison : deliver (iOS) et supply (Android) uploadent tes PNG déjà capturés et tes métadonnées vers App Store Connect / Play Console, et ils s'appuient sur une arborescence de dossiers au format Fastlane — exactement celle qu'écrit golden_screenshot et que tu peux aussi faire écrire à ton driver de l'Option 1.

Codemagic est le CI natif Flutter vers lequel la plupart des équipes se tournent ici. La recette CI réaliste : exécute ton drive de captures d'écran integration_test sur les runners macOS/Android pour les classes d'appareils dont tu as besoin, collecte les PNG comme artefacts de build, puis appelle deliver/supply pour les pousser. C'est vraiment utile quand les captures d'écran dérivent à chaque release et que tu livres souvent. Pour le compromis plus large « automatiser vs sans code » — y compris quand cette configuration est excessif — consulte fastlane snapshot vs captures d'écran App Store sans code.

Option 4 : capture manuelle (toujours tout à fait valable)

Pour beaucoup d'apps Flutter indépendantes, la réponse honnête est : prends simplement les captures d'écran à la main une fois par release. Lance l'app dans le simulateur iOS à la bonne classe d'appareil (iPhone 6.9 pouces, iPad 13 pouces), navigue vers chaque écran, et utilise la commande de capture d'écran du simulateur — elle capture à la résolution exacte de l'appareil, ce qu'App Store Connect attend. Sur Android, le bouton appareil photo de l'émulateur fait la même chose. Temps total pour une app de cinq écrans : peut-être dix minutes.

La capture manuelle n'a aucun coût de maintenance, pas de finders instables, pas de CI à surveiller. Son seul défaut : rien ne se régénère automatiquement — si tu livres un changement d'UI, tu retournes prendre les captures. Pour une équipe qui livre quelques fois par an, ce compromis est largement gagnant. L'automatisation des Options 1 à 3 se rentabilise précisément quand « reprendre à la main » n'est plus une affaire de dix minutes, parce que tu livres chaque semaine et dans beaucoup de langues.

Ce qu'aucune de ces approches ne fait : composition et localisation en 50 langues

Voici le paragraphe honnête et délimité. Chaque option ci-dessus produit des frames brutes — l'interface de ton app à la résolution d'un appareil. Les carrousels App Store et Play Store qui convertissent ne sont pas des frames brutes : c'est la frame dans une maquette d'appareil, sur un fond, sous un court titre, souvent en séquence multi-panneaux, répétée pour chaque langue ciblée. Cette étape de composition, c'est pour ça que Mokbi existe. Tu apportes les frames que tu as capturées (depuis integration_test, golden_screenshot, le simulateur, peu importe), tu les déposes dans un éditeur navigateur, tu ajoutes des cadres d'appareils, des légendes et des fonds, tu traduis les légendes en 50 langues App Store en à peu près un clic, et tu exportes en lot à toutes les dimensions requises. La conception est gratuite avec un aperçu en filigrane ; l'export sans filigrane illimité et la publication sur les stores sont inclus avec un abonnement — Solo à €29.99/mo (1 app) ou Studio à €49.99/mo (jusqu'à 5 apps), sans achat unique. Pour être explicite sur la frontière : Mokbi ne capture pas les captures d'écran sources — il ne lance pas ton app ni ne pilote tes widgets. La capture reste du côté des outils Flutter ci-dessus ; Mokbi compose et localise ce que ces outils produisent.

Un workflow Flutter combiné et réaliste

  1. Capturer les frames sources. Écris un drive de captures d'écran integration_test (Option 1) pour les écrans que tu veux dans le carrousel, ou — si tu livres rarement — capture-les simplement à la main depuis le simulateur. Lance sur un appareil de classe iPhone 6.9 pouces et un iPad de classe 13 pouces pour obtenir les bonnes résolutions.
  2. (Optionnel) Intégrer en CI. Si la dérive est un vrai problème, exécute le drive sur Codemagic et collecte les PNG comme artefacts (Option 3). Sinon, passe cette étape entièrement.
  3. Composer le carrousel marketing. Dépose les frames dans Mokbi, ajoute des cadres/légendes/fonds, construis la séquence multi-panneaux. C'est l'étape que les outils Flutter ne peuvent pas faire.
  4. Localiser et exporter. Traduis les légendes en un clic dans toutes tes langues App Store cibles, exporte en lot à chaque dimension requise, et (si tu le souhaites) dépose le résultat dans un dossier Fastlane pour que deliver/supply l'uploade.
  5. À la prochaine modification de l'UI, recapturer et rouvrir. Relance le drive (ou reprends les captures à la main), rouvre le projet Mokbi sauvegardé, échange les frames, réexporte. La composition et les traductions sont préservées.

Quand tu peux passer l'automatisation entièrement

Si tu livres une à trois releases par an, construire un harnais de captures d'écran integration_test et un pipeline CI te coûtera plus d'heures qu'il n'en économisera jamais. Capture à la main depuis le simulateur, compose et localise une fois, et passe à la suite. L'automatisation des Options 1 à 3 s'adresse aux équipes pour lesquelles la dérive des captures d'écran est un coût récurrent — releases fréquentes, nombreuses langues, ou plusieurs apps dans un portefeuille. Adapte la machinerie à la cadence de release, pas à ce qui paraît rigoureux.

Avant d'exporter quoi que ce soit, vérifie les cibles : les dimensions exactes en pixels dans le guide des tailles de captures d'écran App Store, et les règles de format (PNG/JPEG, RGB, pas de canal alpha, 1 à 10 par classe d'appareil) dans le guide des exigences de captures d'écran App Store. Apple rejette les dimensions décalées d'un pixel sans aucune tolérance — ça vaut trente secondes de vérification.

À lire ensuite

Ouvrir l'éditeur →