· Workflow · 9 menit baca

Membuat tangkapan layar App Store untuk Flutter: integration_test, golden tests, dan jalur lebih cepat (2026)

Membuat tangkapan layar App Store untuk Flutter: integration_test, golden tests, dan jalur lebih cepat (2026)
TL;DR. Flutter tidak menyediakan perintah "buat tangkapan layar toko" bawaan. Pilihan realistis yang ada adalah: mengambil frame nyata dengan integration_test + flutter drive, merender snapshot widget dengan golden tests (bagus untuk regresi, agak dipaksakan untuk aset toko), atau membungkus keduanya dalam CI Fastlane/Codemagic. Tidak satu pun dari itu yang menyusun korsel marketing — teks, bingkai, latar belakang, salinan 50 lokal. Itu adalah langkah terpisah, dan di sinilah Mokbi berperan: ia menyusun dan melokalisasi tangkapan layar yang sudah kamu ambil. Ia tidak mengambilnya.

Jika kamu membangun dengan Flutter dan sudah merilis ke App Store atau Play Store, kamu pasti sudah merasakan celahnya: tidak ada flutter screenshots. Apple menginginkan PNG piksel-tepat di ukuran seperti 1320 × 2868 (iPhone 6.9-inch) dan 2064 × 2752 (iPad 13-inch); Flutter memberimu mesin rendering dan harness pengujian lalu menyerahkan pipeline toko sepenuhnya kepadamu. Artikel ini adalah peta jujur tentang apa yang benar-benar berhasil di 2026, lengkap dengan kode yang bisa kamu tempel, dan garis jelas tentang alat mana yang mengerjakan pekerjaan mana. Jika kamu ingin gambaran umum khusus Flutter terlebih dahulu, lihat Mokbi untuk developer Flutter.

Satu framing di awal, karena ini menghemat banyak kebingungan: mengambil frame (mengarahkan aplikasi ke suatu kondisi dan menyimpan PNG dari apa yang tampil di layar) dan menyusun tangkapan layar toko (frame itu di dalam mockup perangkat, dengan judul, latar belakang, dan salinan terjemahan, diekspor di setiap dimensi yang diperlukan) adalah dua masalah yang berbeda. Sebagian besar tooling Flutter di bawah ini menyelesaikan yang pertama. Tidak ada yang menyelesaikan yang kedua.

Opsi 1: integration_test + flutter drive (frame yang benar-benar diambil)

Ini adalah jalur paling mendekati "resmi" yang dimiliki Flutter untuk tangkapan layar. Paket integration_test meluncurkan aplikasi penuhmu di perangkat nyata atau emulator, mengarahkannya dengan API finder/tester yang sama dengan widget test, dan mengekspos binding.takeScreenshot(). Tangkapannya adalah bahwa takeScreenshot mengembalikan byte ke proses test — tidak langsung menulis file — jadi kamu perlu driver yang diperluas untuk menerima byte tersebut di host dan menyimpannya.

Pertama, testnya. Perhatikan langkah khusus Android: convertFlutterSurfaceToImage() harus dipanggil (dan satu frame dipump) sebelum pengambilan, karena Android merender Flutter ke dalam SurfaceView yang tidak bisa dibaca balik langsung oleh framebuffer.

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

Kemudian drivernya, yang berjalan di mesin host dan menulis byte PNG yang dikirim kembali oleh test. File ini berada di test_driver/ dan menggunakan integration_test_driver_extended.dart (yang biasa integration_test_driver.dart tidak memiliki 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)
    },
  );
}

Yang penting, kamu harus menjalankannya dengan flutter drive, bukan flutter test — hanya jalur driver yang memanggil onScreenshot:

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

Apa yang kamu dapatkan dan apa yang tidak:

  • Piksel aplikasi nyata. Ini adalah tangkapan layar dari widget yang benar-benar berjalan, bukan render ulang — font, tema, data dinamis, semua native.
  • Kondisi yang deterministik. Kamu mengarahkan aplikasi ke tepat layar yang kamu inginkan dan mengambil gambar sesuai kebutuhan.
  • Kelas perangkat = perangkat yang kamu jalankan. Resolusi output adalah apa pun yang dimiliki emulator/perangkat. Untuk mendapatkan tepat 1320 × 2868 milik Apple, kamu menjalankan di simulator iOS kelas 6.9-inch; untuk set iPad 13-inch, kamu menjalankan simulator tersebut. Tidak ada flag ajaib "ubah ukuran ke App Store".
  • Lokal adalah tugasmu. Kamu bisa meneruskan lokal ke aplikasimu saat boot dan menjalankan ulang per bahasa, tapi itu mengambil UI aplikasi dalam lokal tersebut — tidak menulis salinan judul marketing-mu.
  • Biaya pemeliharaan nyata. Finder (find.byKey, tap) akan rusak ketika UI berubah, sama seperti UI test biasa.

Opsi 2: golden tests — dan mengapa itu bukan aset toko

Golden test Flutter (matchesGoldenFile, atau testGoldens tingkat lebih tinggi dari paket seperti golden_toolkit) merender pohon widget ke gambar dan membandingkannya byte demi byte dengan referensi yang sudah di-commit. Ini sangat baik, dan dirancang untuk tujuan yang berbeda dari tangkapan layar toko: regresi piksel. Tujuannya adalah mendeteksi hari ketika perubahan padding atau pergantian font diam-diam menggeser tampilan UI-mu. Golden adalah kontrak; perbedaan adalah test yang gagal.

Dua alasan untuk tidak menjadikan golden test mentah sebagai pipeline toko:

  • Golden merender widget di lingkungan test headless, bukan permukaan perangkat nyata. Platform views, beberapa shader, dan apa pun yang bergantung pada GPU compositor aktual bisa dirender secara berbeda (atau tidak sama sekali) dibandingkan apa yang dilihat pengguna. Untuk kontrak regresi itu tidak masalah — kamu membandingkan dengan dirimu sendiri. Untuk aset toko kamu ingin frame nyata, yang diberikan Opsi 1.
  • Pencocokan piksel-sempurna melawanmu. Golden vanilla mengharapkan pencocokan tepat, sehingga perbedaan antialiasing dan font-hinting antar mesin menyebabkan kegagalan yang tidak konsisten. Itu adalah keketatan yang tepat untuk regresi dan keketatan yang salah untuk "hasilkan gambar marketing yang bagus".

Meski begitu, ada jalan tengah yang valid. Paket seperti golden_screenshot sengaja memanfaatkan mesin golden untuk output toko: mereka menggunakan testGoldens, membungkus layarmu dalam bingkai perangkat nyata (ponsel/tablet/desktop), mengaktifkan komparator fuzzy yang mentoleransi ketidakcocokan kecil (~0,1%, bisa dikonfigurasi) alih-alih menuntut kesempurnaan piksel, dan menulis file ke tata letak direktori bergaya Fastlane (metadata/<locale>/images/...). Jika layarmu murni-Flutter (tanpa platform views) dan kamu ingin bingkai-di-sekitar-render tanpa harus menjalankan emulator, ini adalah jalur yang masuk akal dan cepat. Hanya saja sadari bahwa kamu merender widget, bukan mengambil aplikasi langsung, dan bahwa "marketing" yang kamu dapatkan adalah bingkai perangkat — bukan judul, latar belakang, atau korsel multi-panel.

Opsi 3: Fastlane / Codemagic untuk Flutter

Fastlane adalah automasi iOS/Android, bukan khusus Flutter, tapi ia masuk dalam dua cara. Alat pengambilan Fastlane sendiri (snapshot untuk iOS via XCUITest, screengrab untuk Android via harness Espresso/UI Automator) mengharapkan UI test native, yang umumnya tidak dimiliki aplikasi Flutter — jadi sebagian besar tim Flutter tidak menggunakan snapshot untuk mengambil gambar. Di sinilah Fastlane benar-benar berguna adalah pengiriman: deliver (iOS) dan supply (Android) mengunggah PNG dan metadata yang sudah kamu ambil ke App Store Connect / Play Console, dan keduanya membaca folder berformat Fastlane — yang persis adalah tata letak yang ditulis golden_screenshot dan tata letak yang bisa kamu buat dari driver Opsi 1.

Codemagic adalah CI native Flutter yang paling banyak dipilih tim untuk ini. Resep CI yang realistis adalah: jalankan drive tangkapan layar integration_test-mu di runner macOS/Android untuk kelas perangkat yang kamu butuhkan, kumpulkan PNG sebagai artefak build, lalu panggil deliver/supply untuk mendorongnya. Ini benar-benar berguna ketika tangkapan layar berubah di setiap rilis dan kamu sering merilis. Untuk pertimbangan lebih luas soal "otomasi vs tanpa kode" — termasuk kapan setup ini berlebihan — lihat Fastlane snapshot vs tangkapan layar App Store tanpa kode.

Opsi 4: pengambilan manual (masih sepenuhnya valid)

Untuk banyak aplikasi Flutter indie, jawaban jujurnya adalah: cukup ambil tangkapan layar secara manual sekali per rilis. Jalankan aplikasi di iOS Simulator di kelas perangkat yang tepat (iPhone 6.9-inch, iPad 13-inch), arahkan ke setiap layar, dan gunakan perintah tangkapan layar simulator — hasilnya sesuai resolusi perangkat persis, yang memang diinginkan App Store Connect. Di Android, tombol kamera emulator melakukan hal yang sama. Total waktu untuk aplikasi lima layar: mungkin sepuluh menit.

Pengambilan manual tidak ada biaya pemeliharaan, tidak ada finder yang tidak stabil, dan tidak ada CI yang harus dijaga. Kelemahannya hanya satu: tidak ada yang meregenerasi secara otomatis — jika kamu merilis perubahan UI, kamu perlu mengambil ulang. Untuk tim yang merilis beberapa kali setahun, pertukaran itu sangat layak. Otomasi di Opsi 1–3 baru terbayar ketika "ambil ulang secara manual" berhenti menjadi pekerjaan sepuluh menit karena kamu merilis mingguan dan dalam banyak lokal.

Bagian yang tidak dilakukan satu pun dari opsi di atas: komposisi + lokalisasi 50 bahasa

Inilah paragraf yang jujur dan terbatas. Setiap opsi di atas menghasilkan frame mentah — UI aplikasimu di resolusi perangkat. Korsel App Store dan Play Store yang mengkonversi bukan frame mentah: itu adalah frame di dalam mockup perangkat, di atas latar belakang, di bawah judul singkat, sering sebagai urutan multi-panel, dan diulang di setiap lokal yang kamu targetkan. Langkah komposisi itulah yang menjadi tujuan Mokbi. Kamu membawa frame yang kamu ambil (dari integration_test, golden_screenshot, simulator, dari mana saja), menjatuhkannya ke editor berbasis browser, menambahkan bingkai perangkat dan teks dan latar belakang, menerjemahkan teks ke hingga 50 bahasa App Store hanya dalam satu klik, dan mengekspor massal di setiap ukuran yang diperlukan. Mendesain gratis dengan pratinjau bertanda air; ekspor tanpa batas dan publikasi ke toko hadir bersama langganan — Solo €29.99/mo (1 aplikasi) atau Studio €49.99/mo (hingga 5 aplikasi), tanpa pembelian sekali bayar. Untuk memperjelas batasannya: Mokbi tidak mengambil tangkapan layar sumber — ia tidak menjalankan aplikasimu atau mengarahkan widget-mu. Pengambilan tetap menggunakan tooling Flutter di atas; Mokbi menyusun dan melokalisasi apa yang dihasilkan tooling tersebut.

Alur kerja Flutter kombinasi yang realistis

  1. Ambil frame sumber. Tulis drive tangkapan layar integration_test (Opsi 1) untuk layar yang ingin kamu tampilkan di korsel, atau — jika kamu merilis jarang — cukup ambil secara manual dari simulator. Jalankan di perangkat kelas iPhone 6.9-inch dan perangkat kelas iPad 13-inch agar resolusinya tepat.
  2. (Opsional) Integrasikan ke CI. Jika drift adalah masalah nyata, jalankan drive di Codemagic dan kumpulkan PNG sebagai artefak (Opsi 3). Jika tidak, lewati langkah ini sepenuhnya.
  3. Susun korsel marketing. Masukkan frame ke Mokbi, tambahkan bingkai/teks/latar belakang, bangun urutan multi-panel. Ini adalah langkah yang tidak bisa dilakukan tooling Flutter.
  4. Lokalisasi dan ekspor. Terjemahkan teks satu klik ke lokal App Store targetmu, ekspor massal setiap dimensi yang diperlukan, dan (jika mau) masukkan output ke folder Fastlane agar deliver/supply bisa mengunggahnya.
  5. Saat ada perubahan UI berikutnya, ambil ulang dan buka kembali. Jalankan ulang drive (atau ambil ulang secara manual), buka kembali proyek Mokbi yang tersimpan, ganti frame, ekspor ulang. Komposisi dan terjemahan tetap tersimpan.

Kapan kamu bisa melewati otomasi sepenuhnya

Jika kamu merilis satu hingga tiga kali setahun, membangun harness tangkapan layar integration_test dan pipeline CI akan memakan lebih banyak jam daripada yang pernah dihemat. Ambil secara manual dari simulator, susun dan lokalisasi sekali, lalu lanjutkan. Otomasi di Opsi 1–3 diperuntukkan bagi tim yang drift tangkapan layar menjadi pajak berulang — rilis sering, banyak lokal, atau beberapa aplikasi dalam portofolio. Sesuaikan alat dengan frekuensi rilis, bukan dengan apa yang terlihat paling ketat.

Sebelum mengekspor apa pun, periksa targetnya: dapatkan dimensi piksel yang tepat dari panduan ukuran tangkapan layar App Store dan aturan format (PNG/JPEG, RGB, tanpa alpha channel, 1–10 per kelas perangkat) dari panduan persyaratan tangkapan layar App Store. Apple menolak dimensi yang meleset satu piksel pun tanpa toleransi, jadi ini layak dilakukan dalam tiga puluh detik.

Bacaan selanjutnya

Buka editor →