· Workflow · 9 min czytania

Generowanie zrzutów ekranu App Store we Flutterze: integration_test, testy golden i szybsza ścieżka (2026)

Generowanie zrzutów ekranu App Store we Flutterze: integration_test, testy golden i szybsza ścieżka (2026)
TL;DR. Flutter nie ma polecenia w stylu „wygeneruj moje zrzuty ekranu sklepowe”. Realistyczne opcje to: przechwytywanie prawdziwych klatek przez integration_test + flutter drive, renderowanie migawek widżetów testami golden (świetne do wykrywania regresji, słabsze jako materiały sklepowe) albo owinięcie jednego z tych podejść w Fastlane/Codemagic CI. Żadne z nich nie komponuje karuzeli marketingowej — podpisów, ramek, teł, treści w 50 lokalizacjach. To osobny etap i właśnie w nim mieści się Mokbi: komponuje i lokalizuje zrzuty ekranu, które już przechwyciłeś. Nie przechwytuje ich.

Jeśli budujesz aplikacje we Flutterze i wypuściłeś już coś do App Store lub Play Store, na pewno trafiłeś na tę lukę: nie istnieje flutter screenshots. Apple wymaga PNG-ów o dokładnych wymiarach, np. 1320 × 2868 (iPhone 6,9 cala) i 2064 × 2752 (iPad 13 cali); Flutter daje ci silnik renderowania i harness testowy, a resztę pipeline'u sklepowego zostawia tobie. Ten wpis to uczciwa mapa tego, co faktycznie działa w 2026 roku, z kodem do wklejenia i jasnym podziałem, które narzędzie robi jaką pracę. Jeśli chcesz najpierw przegląd konkretnie dla tego frameworka, zobacz Mokbi dla deweloperów Flutter.

Jedno rozróżnienie na start, bo oszczędza sporo zamieszania: przechwycenie klatki (doprowadzenie aplikacji do danego stanu i zapisanie PNG-a tego, co jest na ekranie) i skomponowanie zrzutu ekranu sklepowego (ta klatka wewnątrz ramki urządzenia, z nagłówkiem, tłem i przetłumaczoną treścią, wyeksportowana w każdym wymaganym wymiarze) to dwa różne problemy. Większość poniższych narzędzi Fluttera rozwiązuje ten pierwszy. Żadne nie rozwiązuje drugiego.

Opcja 1: integration_test + flutter drive (prawdziwe przechwycone klatki)

To najbliższa oficjalna ścieżka Fluttera do zrzutów ekranu. Pakiet integration_test uruchamia twoją pełną aplikację na prawdziwym urządzeniu lub emulatorze, prowadzi ją tym samym API finder/tester co testy widżetów i udostępnia binding.takeScreenshot(). Haczyk polega na tym, że takeScreenshot zwraca bajty do procesu testowego — nie zapisuje pliku — więc potrzebujesz rozszerzonego drivera, który odbierze te bajty na hoście i je zapisze.

Najpierw test. Zwróć uwagę na taniec obecny tylko na Androidzie: convertFlutterSurfaceToImage() musi zostać wywołane (a klatka odświeżona) przed przechwyceniem, bo Android renderuje Fluttera do SurfaceView, którego framebuffer nie może bezpośrednio odczytać z powrotem.

// 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');
  });
}

Następnie driver, który działa na maszynie hosta i zapisuje bajty PNG odesłane przez test. Ten plik znajduje się w test_driver/ i używa integration_test_driver_extended.dart (zwykły integration_test_driver.dart nie ma hooka 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)
    },
  );
}

Ważne: musisz to uruchomić przez flutter drive, a nie flutter test — tylko ścieżka drivera wywołuje onScreenshot:

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

Co dostajesz, a czego nie:

  • Prawdziwe piksele aplikacji. To zrzuty ekranu twoich rzeczywiście działających widżetów, a nie ponowny render — czcionki, motyw, dynamiczne dane, wszystko natywne.
  • Deterministyczne stany. Prowadzisz aplikację dokładnie do tych ekranów, których chcesz, i przechwytujesz na żądanie.
  • Klasa urządzenia = urządzenie, na którym uruchamiasz test. Rozdzielczość wyjściowa to ta, jaką ma emulator/urządzenie. Żeby trafić w dokładne 1320 × 2868 Apple'a, uruchamiasz symulator klasy iOS 6,9 cala; dla zestawu iPad 13 cali uruchamiasz ten symulator. Nie ma magicznej flagi „przeskaluj do App Store”.
  • Lokalizacja to twoje zadanie. Możesz przekazać lokalizację do aplikacji przy starcie i uruchomić ponownie dla każdego języka, ale to przechwytuje interfejs aplikacji w tej lokalizacji — nie zapisuje treści twojego nagłówka marketingowego.
  • Koszt utrzymania jest realny. Findery (find.byKey, tapnięcia) psują się przy zmianach interfejsu, tak jak w każdym teście UI.

Opcja 2: testy golden — i dlaczego to nie są materiały sklepowe

Testy golden we Flutterze (matchesGoldenFile albo wyższopoziomowe testGoldens z pakietów takich jak golden_toolkit) renderują drzewo widżetów do obrazu i porównują je bajt w bajt z zatwierdzonym wzorcem. Są świetne i zostały zbudowane do innego celu niż zrzuty ekranu sklepowe: regresji pikselowej. Chodzi o wychwycenie dnia, w którym zmiana paddingu albo podmiana czcionki po cichu przesuwa interfejs. Golden jest kontraktem; różnica to niezaliczony test.

Dwa powody, żeby nie sięgać po surowe testy golden jako pipeline sklepowy:

  • Goldeny renderują widżety w headless środowisku testowym, nie na prawdziwej powierzchni urządzenia. Platform views, niektóre shadery i wszystko, co zależy od rzeczywistego kompozytora GPU, mogą renderować się inaczej (albo wcale) niż to, co widzi użytkownik. Dla kontraktu regresyjnego to bez znaczenia — porównujesz się sam ze sobą. Dla materiału sklepowego chcesz prawdziwej klatki, a to daje opcja 1.
  • Dopasowanie piksel w piksel walczy z tobą. Domyślne goldeny oczekują dokładnego dopasowania, więc różnice w antyaliasingu i hintingu czcionek między maszynami powodują niestabilne błędy. To odpowiedni rygor dla regresji i zły rygor dla „stwórz ładny obraz marketingowy”.

To powiedziawszy, istnieje uzasadniona droga pośrednia. Pakiety takie jak golden_screenshot celowo przekierowują mechanizm golden do generowania materiałów sklepowych: używają testGoldens, owijają twój ekran w prawdziwe ramki urządzeń (telefon/tablet/desktop), włączają rozmyty komparator tolerujący niewielką (~0,1%, konfigurowalną) niezgodność zamiast wymagać pikselowej perfekcji i zapisują pliki w strukturze katalogów w stylu Fastlane (metadata/<locale>/images/...). Jeśli twoje ekrany są czystym Flutterem (bez platform views) i chcesz ramek wokół renderu bez odpalania emulatora, to rozsądna i szybka droga. Miej jednak świadomość, że renderujesz widżety, a nie przechwytujesz działającą aplikację, i że „marketingowa” część, którą dostajesz, to ramka urządzenia — nie nagłówki, tła ani wielopanelowa karuzela.

Opcja 3: Fastlane / Codemagic dla Fluttera

Fastlane to automatyzacja iOS/Android, nie coś specyficznego dla Fluttera, ale pasuje na dwa sposoby. Własne narzędzia Fastlane do przechwytywania (snapshot dla iOS przez XCUITest, screengrab dla Androida przez harness Espresso/UI Automator) oczekują natywnych testów UI, których aplikacje Flutter zwykle nie mają — więc większość zespołów Flutter nie używa snapshot do przechwytywania. Miejsce, w którym Fastlane zarabia na swoje, to dostarczanie: deliver (iOS) i supply (Android) wysyłają już przechwycone PNG-i i metadane do App Store Connect / Play Console i odczytują folder w kształcie Fastlane — czyli dokładnie taki układ, jaki zapisuje golden_screenshot, i który może zapisywać też driver z opcji 1.

Codemagic to CI natywny dla Fluttera, po który sięga tu większość zespołów. Realistyczny przepis dla CI: uruchom drive testu przechwytującego integration_test na runnerach macOS/Android we wszystkich potrzebnych klasach urządzeń, zbierz PNG-i jako artefakty builda, a potem wywołaj deliver/supply, żeby je wypchnąć. To naprawdę przydatne, gdy zrzuty ekranu odbiegają od aplikacji przy każdym wydaniu i wypuszczasz często. Szerszy kompromis „automatyzacja kontra no-code” — w tym sytuacje, gdy ta konfiguracja to przesada — opisujemy w Fastlane snapshot a narzędzia no-code dla zrzutów ekranu App Store.

Opcja 4: przechwytywanie ręczne (nadal całkowicie uzasadnione)

Dla wielu niezależnych aplikacji we Flutterze uczciwa odpowiedź brzmi: po prostu zrób zrzuty ekranu ręcznie, raz na wydanie. Uruchom aplikację w Symulatorze iOS w odpowiedniej klasie urządzenia (iPhone 6,9 cala, iPad 13 cali), przejdź do każdego ekranu i użyj polecenia zrzutu ekranu symulatora — przechwytuje dokładnie w rozdzielczości urządzenia, czego chce App Store Connect. Na Androidzie ten sam efekt daje przycisk aparatu emulatora. Łączny czas dla aplikacji z pięcioma ekranami: może dziesięć minut.

Ręczne przechwytywanie nie ma kosztu utrzymania, żadnych niestabilnych finderów i żadnego CI do pilnowania. Jego jedyną słabością jest to, że nic nie regeneruje się automatycznie — jeśli wypuszczasz zmianę interfejsu, robisz zrzuty od nowa. Dla zespołu wydającego kilka razy w roku ten kompromis zdecydowanie się opłaca. Automatyzacja z opcji 1–3 zwraca się dokładnie wtedy, gdy „zrób zrzuty od nowa ręcznie” przestaje być dziesięciominutowym zadaniem, bo wypuszczasz aplikację co tydzień i w wielu lokalizacjach.

Czego nie robi żadna z powyższych opcji: kompozycja + lokalizacja na 50 języków

Oto ograniczony, uczciwy akapit. Każda z powyższych opcji produkuje gołe klatki — interfejs twojej aplikacji w rozdzielczości urządzenia. Karuzele w App Store i Play Store, które konwertują, to nie gołe klatki: to klatka wewnątrz ramki urządzenia, na tle, pod krótkim nagłówkiem, często jako sekwencja wielopanelowa, powtórzona w każdej lokalizacji, na którą celujesz. Ten etap kompozycji to zadanie dla Mokbi. Przynosisz klatki, które przechwyciłeś (z integration_test, golden_screenshot, symulatora, skądkolwiek), wrzucasz je do edytora w przeglądarce, dodajesz ramki urządzeń, podpisy i tła, tłumaczysz podpisy w niemal jednym kliknięciu na maksymalnie 50 języków App Store i eksportujesz wsadowo w każdym wymaganym rozmiarze. Projektowanie z podglądem z watermarkiem jest darmowe; nieograniczony eksport i publikowanie są dostępne w ramach subskrypcji — Solo za €29.99/mo (1 aplikacja) lub Studio za €49.99/mo (do 5 aplikacji), bez jednorazowego zakupu. Żeby jasno określić granicę: Mokbi nie przechwytuje źródłowych zrzutów ekranu — nie uruchamia twojej aplikacji ani nie prowadzi twoich widżetów. Przechwytywanie zostaje po stronie narzędzi Fluttera opisanych powyżej; Mokbi komponuje i lokalizuje to, co te narzędzia wyprodukują.

Realistyczny połączony przepływ pracy we Flutterze

  1. Przechwyć źródłowe klatki. Napisz drive testu przechwytującego integration_test (opcja 1) dla ekranów, które chcesz mieć w karuzeli, albo — jeśli wypuszczasz aplikację rzadko — po prostu przechwyć je ręcznie z symulatora. Uruchom na urządzeniu klasy iPhone 6,9 cala i klasy iPad 13 cali, żeby rozdzielczości się zgadzały.
  2. (Opcjonalnie) Podłącz to do CI. Jeśli rozbieżność to realny problem, uruchom drive na Codemagic i zbierz PNG-i jako artefakty (opcja 3). Jeśli nie, pomiń ten krok całkowicie.
  3. Skomponuj karuzelę marketingową. Wrzuć klatki do Mokbi, dodaj ramki/podpisy/tła, zbuduj sekwencję wielopanelową. To etap, którego narzędzia Fluttera nie potrafią.
  4. Zlokalizuj i wyeksportuj. Przetłumacz podpisy jednym kliknięciem na docelowe lokalizacje App Store, wyeksportuj wsadowo każdy wymagany wymiar i (jeśli chcesz) wrzuć wynik do folderu Fastlane, żeby deliver/supply mógł go przesłać.
  5. Przy kolejnej zmianie interfejsu przechwyć ponownie i otwórz projekt jeszcze raz. Uruchom drive ponownie (albo zrób zrzuty od nowa), otwórz zapisany projekt Mokbi, podmień klatki, wyeksportuj ponownie. Kompozycja i tłumaczenia zostają zachowane.

Kiedy całkowicie pominąć automatyzację

Jeśli wypuszczasz jedno do trzech wydań rocznie, budowa harnessu do zrzutów ekranu integration_test i pipeline'u CI zajmie ci więcej godzin, niż kiedykolwiek zaoszczędzi. Przechwytuj ręcznie z symulatora, skomponuj i zlokalizuj raz i idź dalej. Automatyzacja z opcji 1–3 jest dla zespołów, gdzie rozbieżność zrzutów ekranu to powtarzający się podatek — częste wydania, wiele lokalizacji albo kilka aplikacji w portfolio. Dopasuj narzędzia do tempa wydań, nie do tego, co wygląda solidnie.

Zanim cokolwiek wyeksportujesz, sprawdź cele: dokładne wymiary pikselowe znajdziesz w przewodniku po rozmiarach zrzutów ekranu App Store, a zasady formatu (PNG/JPEG, RGB, bez kanału alfa, 1–10 na klasę urządzenia) w przewodniku po wymaganiach zrzutów ekranu App Store. Apple odrzuca wymiary błędne choćby o jeden piksel, bez żadnej tolerancji, więc to trzydzieści sekund dobrze zainwestowanych.

Co przeczytać dalej

Otwórz edytor →