· Workflow · 9 मिनट पढ़ने का समय

Flutter App Store स्क्रीनशॉट बनाना: integration_test, Golden Tests, और एक तेज़ रास्ता (2026)

Flutter App Store स्क्रीनशॉट बनाना: integration_test, Golden Tests, और एक तेज़ रास्ता (2026)
TL;DR. Flutter में कोई first-party "मेरे स्टोर स्क्रीनशॉट बनाओ" कमांड नहीं मिलती। व्यावहारिक विकल्प हैं: integration_test + flutter drive से असली फ्रेम कैप्चर करें, golden tests से widget snapshots रेंडर करें (regression के लिए बढ़िया, स्टोर assets के लिए थोड़ा खिंचाव है), या दोनों को Fastlane/Codemagic CI में लपेटें। इनमें से कोई भी मार्केटिंग कैरोसेल नहीं बनाता — कैप्शन, फ्रेम, बैकग्राउंड, 50-locale copy। यह एक अलग चरण है, और यहीं Mokbi फिट होता है: यह आपके कैप्चर किए स्क्रीनशॉट को कंपोज़ और लोकलाइज़ करता है। यह उन्हें कैप्चर नहीं करता।

अगर आप Flutter से बिल्ड करते हैं और App Store या Play Store पर शिप कर चुके हैं, तो यह अंतर पहले ही देख चुके होंगे: कोई flutter screenshots नहीं है। Apple को 1320 × 2868 (6.9-inch iPhone) और 2064 × 2752 (13-inch iPad) जैसे आयामों पर exact-pixel PNG चाहिए; Flutter आपको एक rendering engine और एक test harness देता है और स्टोर पाइपलाइन आप पर छोड़ देता है। यह पोस्ट 2026 में जो वाकई काम करता है उसका ईमानदार नक्शा है — पेस्ट करने योग्य कोड के साथ, और इस बारे में स्पष्ट लकीर कि कौन-सा टूल कौन-सा काम करता है। अगर पहले framework-specific overview चाहते हैं, तो देखें Flutter developers के लिए Mokbi

एक बात पहले ही स्पष्ट कर लेते हैं, क्योंकि इससे बहुत भ्रम बचता है: फ्रेम कैप्चर करना (ऐप को किसी state में ले जाना और स्क्रीन पर जो है उसका PNG सेव करना) और स्टोर स्क्रीनशॉट कंपोज़ करना (वह फ्रेम एक डिवाइस मॉकअप के अंदर, हेडलाइन के साथ, बैकग्राउंड पर, और translated copy के साथ, हर ज़रूरी डाइमेंशन पर एक्सपोर्ट) — ये दो अलग-अलग समस्याएँ हैं। नीचे बताया Flutter tooling पहली समस्या हल करता है। इनमें से कोई भी दूसरी नहीं।

विकल्प 1: integration_test + flutter drive (असली कैप्चर फ्रेम)

Flutter के पास official screenshot path के सबसे करीब यही है। integration_test package आपका पूरा ऐप एक असली डिवाइस या emulator पर लॉन्च करता है, widget tests जैसे ही finder/tester API से चलाता है, और binding.takeScreenshot() expose करता है। पेच यह है कि takeScreenshot bytes test process को लौटाता है — file नहीं लिखता — इसलिए host पर उन bytes को receive कर file सेव करने के लिए extended driver चाहिए।

पहले, test। Android-only की यह बात ध्यान में रखें: कैप्चर से पहले convertFlutterSurfaceToImage() ज़रूर call करना होता है (और एक frame pump करना होता है), क्योंकि Android Flutter को एक SurfaceView में रेंडर करता है जिसे framebuffer सीधे read back नहीं कर सकता।

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

फिर driver, जो host machine पर चलता है और test के भेजे PNG bytes लिखता है। यह file test_driver/ में रहती है और integration_test_driver_extended.dart उपयोग करती है (सादे integration_test_driver.dart में onScreenshot hook नहीं होता):

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

ज़रूरी बात: इसे flutter drive से चलाएं, flutter test से नहीं — केवल driver path ही onScreenshot invoke करता है:

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

क्या मिलता है और क्या नहीं:

  • असली ऐप pixels। ये आपके वास्तव में चल रहे widgets के स्क्रीनशॉट हैं, re-render नहीं — fonts, theme, dynamic data, सब नेटिव।
  • Deterministic states। आप ऐप को ठीक उन स्क्रीन तक ले जाते हैं जो चाहते हैं और मांग पर कैप्चर करते हैं।
  • Device class = जो device चलाते हैं। आउटपुट resolution जो emulator/device है वही होगा। Apple का exact 1320 × 2868 पाने के लिए 6.9-inch-class iOS simulator पर चलाएं; 13-inch iPad set के लिए वह simulator चलाएं। कोई जादुई "App Store के लिए resize" flag नहीं है।
  • Locale आपकी ज़िम्मेदारी है। बूट पर ऐप में locale पास कर सकते हैं और प्रति भाषा दोबारा चला सकते हैं, लेकिन यह app UI को उस locale में कैप्चर करता है — आपके मार्केटिंग headline copy नहीं लिखता।
  • Maintenance की लागत असली है। Finders (find.byKey, taps) UI बदलने पर टूट जाते हैं, किसी भी UI test की तरह।

विकल्प 2: golden tests — और ये स्टोर assets क्यों नहीं हैं

Flutter के golden tests (matchesGoldenFile, या golden_toolkit जैसे packages का higher-level testGoldens) एक widget tree को image में रेंडर करते हैं और committed reference से byte-for-byte compare करते हैं। ये बढ़िया हैं, और इनका मकसद स्टोर स्क्रीनशॉट से अलग है: pixel regression। इनका उद्देश्य उस दिन को पकड़ना है जब कोई padding बदलाव या font swap चुपके से आपका UI खिसका दे। Golden contract है; diff का मतलब failed test।

दो कारण कि raw golden tests को स्टोर पाइपलाइन के रूप में न लें:

  • Goldens widgets को headless test environment में रेंडर करते हैं, असली device surface पर नहीं। Platform views, कुछ shaders, और जो actual GPU compositor पर निर्भर हैं, वे user को जो दिखता है उससे अलग (या बिल्कुल नहीं) रेंडर हो सकते हैं। Regression contract के लिए यह ठीक है — आप खुद से compare करते हैं। स्टोर asset के लिए असली फ्रेम चाहिए, जो विकल्प 1 देता है।
  • Pixel-perfect matching आपसे लड़ता है। Vanilla goldens exact match चाहते हैं, इसलिए machines में antialiasing और font-hinting के फ़र्क से flaky failures आते हैं। Regression के लिए यह strictness सही है और "एक अच्छी मार्केटिंग image बनाओ" के लिए गलत।

हालाँकि, एक उचित बीच का रास्ता भी है। golden_screenshot जैसे packages जानबूझकर golden machinery को स्टोर output के लिए reuse करते हैं: वे testGoldens उपयोग करते हैं, आपकी स्क्रीन को असली device frames (phone/tablet/desktop) में लपेटते हैं, एक fuzzy comparator enable करते हैं जो pixel-perfection की जगह छोटे (~0.1%, configurable) mismatch को सहन करता है, और files Fastlane-style directory layout में लिखते हैं (metadata/<locale>/images/...)। अगर आपकी screens pure-Flutter हैं (कोई platform views नहीं) और emulator spin up किए बिना render-around-a-frame चाहते हैं, तो यह एक उचित और तेज़ रास्ता है। बस स्पष्ट रहें कि आप widgets रेंडर कर रहे हैं, live app कैप्चर नहीं — और जो "marketing" मिलती है वह एक device frame है, न headline, background, या multi-panel कैरोसेल।

विकल्प 3: Fastlane / Codemagic Flutter के लिए

Fastlane iOS/Android automation है, Flutter-specific नहीं, लेकिन यह दो तरीकों से काम आता है। Fastlane के अपने capture tools (snapshot iOS के लिए XCUITest के ज़रिए, screengrab Android के लिए Espresso/UI Automator harness के ज़रिए) native UI tests उम्मीद करते हैं, जो Flutter apps में आमतौर पर नहीं होते — इसलिए अधिकांश Flutter teams capture के लिए snapshot उपयोग नहीं करतीं। Fastlane जहाँ अपनी जगह बनाता है वह है delivery: deliver (iOS) और supply (Android) आपके already-captured PNGs और metadata App Store Connect / Play Console पर upload करते हैं, और एक Fastlane-shaped folder read करते हैं — जो ठीक वही layout है जो golden_screenshot लिखता है और जो आप Option 1 driver को भी लिखवा सकते हैं।

Codemagic Flutter-native CI है जिसे अधिकांश teams यहाँ पसंद करती हैं। व्यावहारिक CI recipe है: macOS/Android runners पर ज़रूरी device classes के लिए integration_test screenshot drive चलाएं, PNGs को build artifacts के रूप में collect करें, फिर उन्हें push करने के लिए deliver/supply call करें। यह सच में उपयोगी है जब हर release पर screenshots drift होते हैं और आप अक्सर शिप करते हैं। व्यापक "automate vs no-code" trade-off के लिए — जिसमें यह सेटअप कब overkill है — देखें Fastlane snapshot बनाम no-code App Store स्क्रीनशॉट

विकल्प 4: मैनुअल कैप्चर (अभी भी पूरी तरह सही)

बहुत से indie Flutter apps के लिए ईमानदार जवाब यह है: हर release पर एक बार हाथ से स्क्रीनशॉट लें। iOS Simulator में सही device class (6.9-inch iPhone, 13-inch iPad) पर ऐप चलाएं, हर स्क्रीन पर जाएं, और simulator की screenshot command उपयोग करें — यह exact device resolution पर कैप्चर करती है, जो App Store Connect चाहता है। Android पर emulator का camera बटन यही काम करता है। पाँच-स्क्रीन ऐप के लिए कुल समय: शायद दस मिनट।

मैनुअल कैप्चर में कोई maintenance लागत नहीं, कोई flaky finders नहीं, और कोई CI babysit करना नहीं। इसकी एकमात्र कमज़ोरी यह है कि कुछ भी अपने आप रीजेनरेट नहीं होता — UI बदलाव शिप करने पर दोबारा लेना पड़ता है। साल में कुछ बार release करने वाली team के लिए यह trade बेहद फ़ायदेमंद है। Options 1–3 का automation ठीक तब काम आता है जब "हाथ से दोबारा लो" दस मिनट का काम नहीं रहता, क्योंकि आप साप्ताहिक और कई locales में शिप करते हैं।

जो हिस्सा ऊपर में से कोई भी नहीं करता: कंपोज़िशन + 50-language लोकलाइज़ेशन

यहाँ सटीक और ईमानदार बात। ऊपर बताया हर विकल्प bare frames देता है — device resolution पर आपका app UI। App Store और Play Store के कैरोसेल जो convert करते हैं, वे bare frames नहीं होते: वे frame एक डिवाइस मॉकअप के अंदर होता है, बैकग्राउंड पर, एक छोटी headline के नीचे, अक्सर multi-panel sequence के रूप में, और हर उस locale में repeat होता है जिसे आप target करते हैं। यह कंपोज़िशन चरण Mokbi के लिए है। आप जो frames कैप्चर किए हैं (integration_test, golden_screenshot, simulator, कहीं से भी) वे browser editor में लाएं, डिवाइस फ़्रेम, कैप्शन और बैकग्राउंड जोड़ें, लगभग एक क्लिक में 50 App Store languages में कैप्शन translate करें, और हर ज़रूरी size पर batch-export करें। डिज़ाइन watermarked preview के साथ मुफ्त है; unlimited export और publishing एक subscription के साथ आते हैं — Solo €29.99/mo (1 app) या Studio €49.99/mo (5 apps तक), कोई one-time purchase नहीं। सीमा के बारे में स्पष्ट रहें: Mokbi source screenshots कैप्चर नहीं करता — यह आपका ऐप नहीं चलाता या आपके widgets drive नहीं करता। Capture ऊपर बताए Flutter tooling के साथ रहता है; Mokbi उस tooling के output को कंपोज़ और लोकलाइज़ करता है।

एक व्यावहारिक संयुक्त Flutter वर्कफ़्लो

  1. सोर्स फ्रेम कैप्चर करें। कैरोसेल में जो स्क्रीन चाहते हैं उनके लिए integration_test screenshot drive (विकल्प 1) लिखें, या — अगर कम बार शिप करते हैं — simulator से मैनुअल कैप्चर लें। 6.9-inch iPhone class device और 13-inch iPad class device पर चलाएं ताकि resolutions सही रहें।
  2. CI में जोड़ें (वैकल्पिक)। अगर drift एक असली समस्या है, तो Codemagic पर drive चलाएं और PNGs artifacts के रूप में collect करें (विकल्प 3)। अगर नहीं है, तो इसे पूरी तरह छोड़ें।
  3. मार्केटिंग कैरोसेल कंपोज़ करें। Frames Mokbi में डालें, frames/captions/backgrounds जोड़ें, multi-panel sequence बनाएं। यह वह चरण है जो Flutter tooling नहीं कर सकती।
  4. लोकलाइज़ करें और एक्सपोर्ट करें। अपने target App Store locales में captions one-click translate करें, हर ज़रूरी dimension batch-export करें, और (अगर पसंद हो) output एक Fastlane folder में डालें ताकि deliver/supply इसे upload करे।
  5. अगले UI बदलाव पर, दोबारा कैप्चर करें और दोबारा खोलें। Drive फिर चलाएं (या दोबारा शूट करें), सेव किया हुआ Mokbi project फिर खोलें, frames बदलें, दोबारा एक्सपोर्ट करें। कंपोज़िशन और translations सुरक्षित रहते हैं।

ऑटोमेशन पूरी तरह कब छोड़ सकते हैं

अगर आप साल में एक से तीन release करते हैं, तो integration_test screenshot harness और CI pipeline बनाना जितने घंटे लगाएगा उससे कभी नहीं बचाएगा। Simulator से मैनुअल कैप्चर करें, एक बार कंपोज़ और लोकलाइज़ करें, और आगे बढ़ें। Options 1–3 का automation उन teams के लिए है जहाँ screenshot drift बार-बार आने वाला बोझ है — frequent releases, कई locales, या portfolio में कई apps। Machinery को release cadence के हिसाब से चुनें, न कि जो rigorous लगे उसके हिसाब से।

कुछ भी एक्सपोर्ट करने से पहले targets की पुष्टि करें: App Store स्क्रीनशॉट sizes guide से exact pixel dimensions लें और format rules (PNG/JPEG, RGB, no alpha channel, 1–10 per device class) App Store स्क्रीनशॉट requirements guide से। Apple एक pixel के फ़र्क पर dimensions reject कर देता है बिना किसी tolerance के, इसलिए यह तीस सेकंड लगाने लायक है।

आगे क्या पढ़ें

एडिटर खोलें →