Screenshot App Store con Flutter: integration_test, golden test e un percorso più rapido (2026)
integration_test + flutter drive, renderizzare snapshot di widget con i golden test (ottimi per la regressione, una forzatura per gli asset dello store), oppure integrare entrambi in Fastlane/Codemagic CI. Nessuna di queste opzioni compone il carosello marketing — didascalie, cornici, sfondi, copy in 50 locale. Quella è una fase separata, ed è qui che si inserisce Mokbi: compone e localizza gli screenshot che hai catturato. Non li cattura.Se sviluppi con Flutter e hai già pubblicato sull'App Store o sul Play Store, hai già incontrato il problema: non esiste un comando flutter screenshots. Apple vuole PNG pixel-perfetti in dimensioni come 1320 × 2868 (iPhone da 6.9 pollici) e 2064 × 2752 (iPad da 13 pollici); Flutter ti mette in mano un motore di rendering e un harness di test, e lascia a te la pipeline per lo store. Questo post è la mappa onesta di quello che funziona davvero nel 2026, con codice da incollare e un confine netto su quale strumento fa quale lavoro. Se vuoi prima la panoramica specifica per framework, consulta Mokbi per sviluppatori Flutter.
Una premessa, perché evita molta confusione: catturare un frame (portare l'app a uno stato e salvare un PNG di ciò che è sullo schermo) e comporre uno screenshot per lo store (quel frame dentro un mockup del dispositivo, con un titolo, uno sfondo e copy tradotto, esportato in ogni dimensione richiesta) sono due problemi distinti. La maggior parte degli strumenti Flutter qui sotto risolve il primo. Nessuno risolve il secondo.
Opzione 1: integration_test + flutter drive (frame reali catturati)
È il percorso più vicino a un approccio ufficiale per gli screenshot in Flutter. Il pacchetto integration_test avvia l'intera app su un dispositivo reale o un emulatore, la guida con la stessa API finder/tester dei widget test, ed espone binding.takeScreenshot(). L'inconveniente è che takeScreenshot restituisce i byte al processo di test — non scrive un file — quindi hai bisogno del driver esteso per ricevere quei byte sull'host e salvarli.
Prima il test. Nota il passaggio specifico per Android: convertFlutterSurfaceToImage() deve essere chiamato (e un frame eseguito) prima della cattura, perché Android renderizza Flutter in una SurfaceView che il framebuffer non può leggere direttamente.
// 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');
});
}Poi il driver, che gira sull'host e scrive i byte PNG che il test gli invia. Questo file va in test_driver/ e usa integration_test_driver_extended.dart (il semplice integration_test_driver.dart non ha l'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 punto cruciale è che devi eseguirlo con flutter drive, non con flutter test — solo il percorso driver invoca onScreenshot:
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/screenshot_test.dart \
-d emulator-5554Cosa ottieni e cosa non ottieni:
- Pixel reali dell'app. Sono screenshot dei widget realmente in esecuzione, non un re-render — font, tema, dati dinamici, tutto nativo.
- Stati deterministici. Porti l'app esattamente alle schermate che vuoi e catturi su richiesta.
- La classe del dispositivo coincide con il dispositivo su cui giri. La risoluzione dell'output dipende dall'emulatore o dispositivo usato. Per raggiungere esattamente 1320 × 2868 di Apple devi girare su un simulatore iOS da 6.9 pollici; per il set da 13 pollici serve quel simulatore specifico. Non esiste un flag magico "ridimensiona per l'App Store".
- La locale è a tuo carico. Puoi passare una locale alla tua app all'avvio e rieseguire per ogni lingua, ma questo cattura la UI dell'app in quella locale — non scrive il copy dei titoli marketing.
- Il costo di manutenzione è reale. I finder (
find.byKey, i tap) si rompono quando la UI cambia, come qualsiasi UI test.
Opzione 2: golden test — e perché non sono asset per lo store
I golden test di Flutter (matchesGoldenFile, o il testGoldens di più alto livello da pacchetti come golden_toolkit) renderizzano un albero di widget in un'immagine e la confrontano byte per byte con un riferimento registrato. Sono eccellenti, e sono pensati per uno scopo diverso dagli screenshot dello store: la regressione visiva. Il punto è rilevare il giorno in cui una modifica al padding o un cambio di font sposta silenziosamente la tua UI. Il golden è il contratto; una differenza è un test fallito.
Due motivi per non usare i golden test grezzi come pipeline per lo store:
- I golden renderizzano i widget in un ambiente di test headless, non sulla superficie reale del dispositivo. Le platform view, alcuni shader e tutto ciò che dipende dal compositor GPU reale possono renderizzarsi diversamente (o per niente) rispetto a ciò che vede un utente. Per un contratto di regressione va bene — ti confronti con te stesso. Per un asset dello store vuoi il frame reale, che è quello che ti dà l'Opzione 1.
- Il confronto pixel-perfect ti lavora contro. I golden vanilla si aspettano una corrispondenza esatta, quindi le differenze di antialiasing e font-hinting tra macchine diverse causano fallimenti instabili. Quella è la giusta severità per la regressione, e quella sbagliata per "produrre una bella immagine marketing".
Detto questo, esiste un percorso intermedio legittimo. Pacchetti come golden_screenshot ripropongono deliberatamente la macchina dei golden per l'output dello store: usano testGoldens, avvolgono la tua schermata in cornici di dispositivi reali (telefono/tablet/desktop), abilitano un comparatore fuzzy che tolera una piccola discordanza (~0,1%, configurabile) invece di esigere la perfezione pixel, e scrivono i file in una struttura di directory Fastlane-style (metadata/<locale>/images/...). Se le tue schermate sono pure Flutter (nessuna platform view) e vuoi cornici attorno a un render senza avviare un emulatore, è un percorso ragionevole e veloce. Sii però consapevole che stai renderizzando widget, non catturando l'app viva, e che il "marketing" che ottieni è una cornice del dispositivo — non titoli, sfondi o un carosello multi-pannello.
Opzione 3: Fastlane / Codemagic per Flutter
Fastlane è automazione iOS/Android, non specifica per Flutter, ma si integra in due modi. Gli strumenti di cattura di Fastlane (snapshot per iOS via XCUITest, screengrab per Android via un harness Espresso/UI Automator) si aspettano UI test nativi, che le app Flutter in genere non hanno — quindi la maggior parte dei team Flutter non usa snapshot per catturare. Dove Fastlane guadagna il suo posto è nella distribuzione: deliver (iOS) e supply (Android) caricano i tuoi PNG già catturati e i metadati su App Store Connect / Play Console, e leggono una struttura di cartelle Fastlane-style — che è esattamente il layout che golden_screenshot scrive e che puoi far scrivere anche al driver dell'Opzione 1.
Codemagic è il CI nativo per Flutter a cui la maggior parte dei team ricorre in questo contesto. La ricetta CI realistica è: eseguire il drive di screenshot integration_test sui runner macOS/Android per le classi di dispositivo necessarie, raccogliere i PNG come artefatti di build, poi chiamare deliver/supply per inviarli. È genuinamente utile quando gli screenshot si sfasano a ogni release e rilasci spesso. Per il bilanciamento generale tra automazione e no-code — incluso quando questa configurazione è eccessiva — consulta Fastlane snapshot vs screenshot App Store no-code.
Opzione 4: cattura manuale (ancora del tutto valida)
Per molte app Flutter indie, la risposta onesta è: cattura gli screenshot a mano una volta per release. Avvia l'app nel simulatore iOS sulla classe di dispositivo giusta (iPhone da 6.9 pollici, iPad da 13 pollici), naviga a ogni schermata e usa il comando screenshot del simulatore — cattura alla risoluzione esatta del dispositivo, che è quello che vuole App Store Connect. Su Android, il pulsante fotocamera dell'emulatore fa lo stesso. Tempo totale per un'app con cinque schermate: forse dieci minuti.
La cattura manuale non ha costi di manutenzione, nessun finder instabile e nessuna CI da sorvegliare. Il suo unico limite è che nulla si rigenera automaticamente — se rilasci un cambiamento alla UI, rifai le catture. Per un team che rilascia poche volte all'anno, questo compromesso è nettamente vantaggioso. L'automazione delle Opzioni 1–3 si ripaga esattamente quando "rifare le catture a mano" smette di essere un lavoro da dieci minuti perché rilasci ogni settimana e in molte locale.
La parte che nessuna delle opzioni precedenti copre: composizione + localizzazione in 50 lingue
Ecco il paragrafo onesto e preciso. Ogni opzione qui sopra produce frame grezzi — la UI dell'app alla risoluzione del dispositivo. I caroselli App Store e Play Store che convertono non sono frame grezzi: sono il frame dentro un mockup del dispositivo, su uno sfondo, sotto un breve titolo, spesso come sequenza multi-pannello, e ripetuto per ogni locale che vuoi raggiungere. Quella fase di composizione è la funzione di Mokbi. Porti i frame che hai catturato (da integration_test, golden_screenshot, dal simulatore, ovunque), li inserisci in un editor nel browser, aggiungi cornici del dispositivo, didascalie e sfondi, traduci le didascalie in fino a 50 lingue dell'App Store con circa un clic ed esporti in batch a ogni dimensione richiesta. Progettare è gratuito con anteprima con watermark; esportazione e pubblicazione illimitate arrivano con un abbonamento — Solo a €29.99/mo (1 app) o Studio a €49.99/mo (fino a 5 app), senza acquisto una tantum. Per essere espliciti sul confine: Mokbi non cattura screenshot sorgente — non avvia la tua app né guida i tuoi widget. La cattura rimane agli strumenti Flutter qui sopra; Mokbi compone e localizza ciò che quegli strumenti producono.
Un workflow Flutter combinato realistico
- Cattura i frame sorgente. Scrivi un drive di screenshot con
integration_test(Opzione 1) per le schermate che vuoi nel carosello, oppure — se rilasci poco spesso — catturale semplicemente a mano dal simulatore. Esegui su un dispositivo iPhone da 6.9 pollici e su un iPad da 13 pollici così le risoluzioni sono corrette. - Integra in CI (facoltativo). Se lo sfasamento è un problema reale, esegui il drive su Codemagic e raccogli i PNG come artefatti (Opzione 3). Se non lo è, salta del tutto questo passaggio.
- Componi il carosello marketing. Inserisci i frame in Mokbi, aggiungi cornici/didascalie/sfondi, costruisci la sequenza multi-pannello. Questo è il passaggio che gli strumenti Flutter non possono fare.
- Localizza ed esporta. Traduci le didascalie con un clic per le locale App Store target, esporta in batch ogni dimensione richiesta, e — se vuoi — inserisci l'output in una cartella Fastlane così
deliver/supplyla carica. - Al prossimo cambiamento UI, ricattura e riapri. Riesegui il drive (o rifai le catture), riapri il progetto Mokbi salvato, sostituisci i frame, riesporta. La composizione e le traduzioni vengono conservate.
Quando saltare l'automazione del tutto
Se rilasci da uno a tre volte all'anno, costruire un harness di screenshot con integration_test e una pipeline CI ti costerà più ore di quante ne risparmierà mai. Cattura a mano dal simulatore, componi e localizza una volta, e vai avanti. L'automazione delle Opzioni 1–3 è per i team in cui lo sfasamento degli screenshot è una tassa ricorrente — release frequenti, molte locale o più app in un portfolio. Adatta il livello di automazione alla cadenza di rilascio, non a ciò che sembra più rigoroso.
Prima di esportare qualsiasi cosa, verifica gli obiettivi: prendi le dimensioni esatte in pixel dalla guida alle dimensioni degli screenshot App Store e le regole di formato (PNG/JPEG, RGB, nessun canale alpha, da 1 a 10 per classe di dispositivo) dalla guida ai requisiti degli screenshot App Store. Apple respinge dimensioni sbagliate anche di un solo pixel senza alcuna tolleranza, quindi vale i trenta secondi necessari.