· Workflow · อ่าน 9 นาที

วิธีสร้างภาพหน้าจอ App Store สำหรับแอป Flutter: integration_test, Golden Tests และทางที่เร็วกว่า (2026)

วิธีสร้างภาพหน้าจอ App Store สำหรับแอป Flutter: integration_test, Golden Tests และทางที่เร็วกว่า (2026)
TL;DR. Flutter ไม่มี command "สร้างภาพหน้าจอ store ให้" ในตัว ตัวเลือกที่เป็นจริง ได้แก่ การถ่าย frame จริงด้วย integration_test + flutter drive, การ render widget snapshot ด้วย golden tests (เหมาะสำหรับ regression แต่ไม่ค่อยเหมาะสำหรับ store asset) หรือการครอบทั้งสองอย่างด้วย Fastlane/Codemagic CI ไม่มีตัวเลือกใดที่จัดวางคารูเซลทางการตลาดได้ — คำบรรยาย กรอบอุปกรณ์ พื้นหลัง และข้อความ 50 locale นั่นคือขั้นตอนแยกต่างหากที่ Mokbi ทำ: จัดวางและแปลภาษาภาพหน้าจอที่คุณถ่ายมา โดยไม่ได้ถ่ายเองแต่อย่างใด

ถ้าคุณสร้างแอปด้วย Flutter และเคยส่งไปที่ App Store หรือ Play Store คุณต้องเจอกับช่องว่างนี้แน่นอน: ไม่มี flutter screenshots Apple ต้องการ PNG ที่มีขนาดพิกเซลแน่นอน เช่น 1320 × 2868 (iPhone ขนาด 6.9 นิ้ว) และ 2064 × 2752 (iPad ขนาด 13 นิ้ว) Flutter มอบ rendering engine และ test harness ให้คุณ แต่ปล่อยให้ store pipeline เป็นหน้าที่ของคุณเอง โพสต์นี้คือแผนที่ตรงๆของสิ่งที่ใช้งานได้จริงในปี 2026 พร้อมโค้ดที่นำไปวางได้เลย และขอบเขตชัดเจนว่าเครื่องมือแต่ละอย่างทำอะไร ถ้าต้องการภาพรวมเฉพาะ framework ก่อน ดู Mokbi สำหรับนักพัฒนา Flutter

ขอชี้แจงก่อนเพราะจะช่วยหลีกเลี่ยงความสับสน: การถ่าย frame (ขับแอปไปยังสถานะหนึ่งแล้วบันทึก PNG ของสิ่งที่อยู่บนหน้าจอ) กับ การจัดวางภาพหน้าจอ store (frame นั้นอยู่ใน ม็อกอัพอุปกรณ์ พร้อมหัวข้อ พื้นหลัง และข้อความที่แปลแล้ว ส่งออกทุกขนาดที่ต้องการ) เป็นสองปัญหาคนละเรื่อง เครื่องมือ Flutter ด้านล่างส่วนใหญ่แก้ปัญหาแรก ไม่มีตัวไหนแก้ปัญหาที่สอง

ตัวเลือกที่ 1: integration_test + flutter drive (frame ที่ถ่ายจริง)

นี่คือสิ่งที่ Flutter มีที่ใกล้เคียง official screenshot path มากที่สุด package integration_test เปิดแอปเต็มรูปแบบบนอุปกรณ์จริงหรือ emulator ขับด้วย finder/tester API เดียวกับ widget test และ expose binding.takeScreenshot() ไว้ให้ ข้อจำกัดคือ takeScreenshot ส่งคืน bytes ไปยัง test process — ไม่ได้เขียนไฟล์ — ดังนั้นต้องใช้ extended driver เพื่อรับ bytes เหล่านั้นบน host และบันทึก

ก่อนอื่นคือ test โปรดสังเกต dance เฉพาะ Android: ต้องเรียก convertFlutterSurfaceToImage() (และ pump frame หนึ่งครั้ง) ก่อนถ่าย เพราะ Android render 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 และเขียน PNG bytes ที่ test ส่งกลับมา ไฟล์นี้อยู่ที่ test_driver/ และใช้ integration_test_driver_extended.dart (integration_test_driver.dart แบบธรรมดาไม่มี 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)
    },
  );
}

สำคัญมากคือต้องรันด้วย flutter drive ไม่ใช่ flutter test — เฉพาะ driver path เท่านั้นที่เรียก onScreenshot:

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

สิ่งที่ได้และสิ่งที่ไม่ได้:

  • พิกเซลของแอปจริง ภาพหน้าจอเหล่านี้มาจาก widget ที่รันอยู่จริง ไม่ใช่การ re-render — ฟอนต์ ธีม ข้อมูล dynamic ทุกอย่างเป็น native
  • สถานะที่กำหนดได้แน่นอน คุณขับแอปไปยังหน้าจอที่ต้องการและถ่ายตามต้องการ
  • คลาสอุปกรณ์ = อุปกรณ์ที่รัน ความละเอียด output ขึ้นอยู่กับ emulator/อุปกรณ์ที่ใช้ หากต้องการให้ได้ 1320 × 2868 ของ Apple พอดี ต้องรันบน iOS simulator คลาส 6.9 นิ้ว สำหรับชุด iPad 13 นิ้วก็ต้องรันบน simulator นั้น ไม่มี flag "resize ให้เป็น App Store" สำเร็จรูป
  • Locale เป็นหน้าที่ของคุณ คุณสามารถส่ง locale เข้าแอปตอน boot แล้วรันซ้ำต่อภาษา แต่นั่นถ่าย UI แอป ในภาษานั้น — ไม่ได้เขียนข้อความ headline ทางการตลาดของคุณ
  • ต้นทุนการบำรุงรักษามีอยู่จริง finders (find.byKey, taps) จะพังเมื่อ UI เปลี่ยน เหมือน UI test ทั่วไป

ตัวเลือกที่ 2: golden tests — และทำไมถึงไม่ใช่ store asset

Flutter golden tests (matchesGoldenFile หรือ testGoldens ระดับสูงขึ้นจาก package เช่น golden_toolkit) render widget tree เป็นภาพและเปรียบเทียบ byte-for-byte กับ reference ที่ commit ไว้ มันดีมาก และสร้างมาเพื่อวัตถุประสงค์ที่ต่างจากภาพหน้าจอ store: pixel regression จุดประสงค์คือตรวจจับวันที่ padding เปลี่ยนหรือ font swap ขยับ UI โดยไม่ตั้งใจ golden คือสัญญา และ diff คือ test ที่ล้มเหลว

สองเหตุผลที่ไม่ควรใช้ raw golden tests เป็น store pipeline:

  • Golden render widget ใน headless test environment ไม่ใช่ device surface จริง Platform views, shader บางตัว และสิ่งที่ขึ้นกับ GPU compositor จริงๆ อาจ render ต่างออกไป (หรือไม่ render เลย) เทียบกับสิ่งที่ user เห็น สำหรับ regression contract ไม่เป็นไร — คุณเปรียบเทียบกับตัวเอง แต่สำหรับ store asset คุณต้องการ frame จริงซึ่งเป็นสิ่งที่ตัวเลือกที่ 1 ให้
  • การจับคู่แบบ pixel-perfect ขัดขวางคุณ Golden แบบ vanilla คาดหวังการจับคู่ที่แน่นอน ดังนั้นความแตกต่างของ antialiasing และ font-hinting ข้ามเครื่องทำให้เกิด failure แบบ flaky นั่นคือความเข้มงวดที่ถูกต้องสำหรับ regression และความเข้มงวดที่ผิดสำหรับ "สร้างภาพทางการตลาดที่ดูดี"

อย่างไรก็ตาม มีทางสายกลางที่สมเหตุสมผล package เช่น golden_screenshot นำ golden machinery มาใช้เพื่อ store output โดยตรง: ใช้ testGoldens ครอบหน้าจอด้วยกรอบอุปกรณ์จริง (โทรศัพท์/tablet/desktop) เปิด fuzzy comparator ที่ยอมรับความแตกต่างเล็กน้อย (~0.1%, ปรับได้) แทนการเรียก pixel-perfect และเขียนไฟล์ในรูปแบบ Fastlane-style directory (metadata/<locale>/images/...) ถ้าหน้าจอของคุณเป็น Flutter ล้วน (ไม่มี platform views) และต้องการกรอบรอบ render โดยไม่ต้องเปิด emulator ถือเป็นทางที่สมเหตุสมผลและเร็ว แค่ต้องรับรู้ชัดๆว่าคุณกำลัง render widget ไม่ใช่ถ่ายแอปสด และ "การตลาด" ที่ได้คือกรอบอุปกรณ์เท่านั้น — ไม่มี headline, พื้นหลัง หรือคารูเซลหลายพาเนล

ตัวเลือกที่ 3: Fastlane / Codemagic สำหรับ Flutter

Fastlane คือ automation สำหรับ iOS/Android ไม่ใช่เฉพาะ Flutter แต่เข้ามาได้สองทาง เครื่องมือถ่ายภาพของ Fastlane เอง (snapshot สำหรับ iOS ผ่าน XCUITest, screengrab สำหรับ Android ผ่าน Espresso/UI Automator harness) คาดหวัง native UI test ซึ่งแอป Flutter ส่วนใหญ่ไม่มี — ดังนั้นทีม Flutter ส่วนใหญ่จึงไม่ใช้ snapshot เพื่อถ่าย จุดที่ Fastlane มีคุณค่าคือ การ deliver: deliver (iOS) และ supply (Android) อัปโหลด PNG และ metadata ที่ถ่ายไว้แล้วไปยัง App Store Connect / Play Console และอ่าน folder แบบ Fastlane-shaped — ซึ่งตรงกับรูปแบบที่ golden_screenshot เขียนและที่คุณให้ driver ตัวเลือกที่ 1 เขียนได้เช่นกัน

Codemagic คือ CI ที่ทีม Flutter นิยมใช้มากที่สุด สูตร CI ที่เป็นจริงคือ: รัน integration_test screenshot drive บน macOS/Android runner ข้ามคลาสอุปกรณ์ที่ต้องการ เก็บ PNG เป็น build artifact จากนั้นเรียก deliver/supply เพื่อ push ขึ้นไป วิธีนี้มีประโยชน์จริงเมื่อภาพหน้าจอเปลี่ยนทุก release และคุณส่งบ่อย สำหรับการชั่งน้ำหนักระหว่าง "automate vs no-code" — รวมถึงกรณีที่ setup นี้ไม่คุ้ม — ดู Fastlane snapshot vs การสร้างภาพหน้าจอ App Store แบบ no-code

ตัวเลือกที่ 4: การถ่ายด้วยตนเอง (ยังใช้ได้ดีอยู่)

สำหรับแอป Flutter indie จำนวนมาก คำตอบตรงๆคือ: ถ่ายภาพหน้าจอด้วยมือครั้งละ release รัน iOS Simulator ที่คลาสอุปกรณ์ที่ถูกต้อง (iPhone 6.9 นิ้ว, iPad 13 นิ้ว) ไปยังแต่ละหน้าจอ แล้วใช้ screenshot command ของ simulator — มันถ่ายที่ความละเอียดอุปกรณ์แน่นอน ซึ่งเป็นสิ่งที่ App Store Connect ต้องการ บน Android ปุ่ม camera ของ emulator ทำแบบเดียวกัน เวลาทั้งหมดสำหรับแอปห้าหน้าจอ: ประมาณสิบนาที

การถ่ายด้วยตนเองไม่มีต้นทุนการบำรุงรักษา ไม่มี finder แบบ flaky และไม่มี CI ที่ต้องคอยดูแล ข้อเสียเดียวคือไม่มีอะไร regenerate อัตโนมัติ — ถ้าส่ง UI change คุณต้องถ่ายใหม่ สำหรับทีมที่ release ปีละสองสามครั้ง การแลกเปลี่ยนนี้คุ้มค่ามาก automation ในตัวเลือก 1–3 คุ้มก็ต่อเมื่อ "ถ่ายด้วยมือ" หยุดเป็นงานสิบนาที เพราะส่งรายสัปดาห์และหลาย locale

สิ่งที่ไม่มีตัวเลือกใดทำได้: การจัดวาง + การแปลภาษา 50 ภาษา

นี่คือย่อหน้าที่พูดตรงและชัดเจน ทุกตัวเลือกข้างต้นผลิต frame เปล่าๆ — UI แอปของคุณที่ความละเอียดอุปกรณ์ คารูเซลของ App Store และ Play Store ที่เพิ่ม conversion ไม่ใช่ frame เปล่า: มันคือ frame ใน ม็อกอัพอุปกรณ์ บนพื้นหลัง ใต้ headline สั้นๆ มักจะเป็นลำดับหลายพาเนล และทำซ้ำข้ามทุก locale ที่คุณเจาะ ขั้นตอนการจัดวางนั้นคือสิ่งที่ Mokbi ทำ คุณนำ frame ที่ถ่ายไว้ (จาก integration_test, golden_screenshot, simulator หรือที่ไหนก็ตาม) มาวางลงใน browser editor เพิ่มกรอบอุปกรณ์ คำบรรยาย และพื้นหลัง แปลคำบรรยายข้าม 50 ภาษาของ App Store ด้วยคลิกเดียว และ batch export ทุกขนาดที่ต้องการ ออกแบบฟรีพร้อม watermark ตัวอย่าง ส่วน export และการเผยแพร่ขึ้น store แบบไม่จำกัดมาพร้อมกับการสมัครสมาชิก — Solo ที่ €29.99/mo (1 แอป) หรือ Studio ที่ €49.99/mo (สูงสุด 5 แอป) ไม่มีการซื้อแบบครั้งเดียว เพื่อพูดให้ชัดถึงขอบเขต: Mokbi ไม่ถ่ายภาพหน้าจอต้นฉบับ — มันไม่ได้รันแอปหรือขับ widget ของคุณ การถ่ายเป็นหน้าที่ของเครื่องมือ Flutter ข้างต้น Mokbi จัดวางและแปลภาษาสิ่งที่เครื่องมือเหล่านั้นผลิต

เวิร์กโฟลว์ Flutter ผสมผสานที่เป็นจริง

  1. ถ่าย source frame เขียน integration_test screenshot drive (ตัวเลือกที่ 1) สำหรับหน้าจอที่ต้องการในคารูเซล หรือ — ถ้าส่งไม่บ่อย — ถ่ายด้วยตนเองจาก simulator รันบนอุปกรณ์คลาส iPhone 6.9 นิ้ว และ iPad 13 นิ้ว เพื่อให้ความละเอียดถูกต้อง
  2. (ตัวเลือก) เชื่อมต่อเข้า CI ถ้า drift เป็นปัญหาจริง รัน drive บน Codemagic และเก็บ PNG เป็น artifact (ตัวเลือกที่ 3) ถ้าไม่ใช่ปัญหา ข้ามขั้นตอนนี้ได้เลย
  3. จัดวางคารูเซลทางการตลาด วาง frame ลงใน Mokbi เพิ่มกรอบ/คำบรรยาย/พื้นหลัง สร้างลำดับหลายพาเนล นี่คือขั้นตอนที่เครื่องมือ Flutter ทำไม่ได้
  4. แปลภาษาและส่งออก แปล batch คำบรรยายข้าม App Store locale ที่เป็นเป้าหมายด้วยคลิกเดียว batch export ทุกขนาดที่ต้องการ และ (ถ้าต้องการ) วาง output ลงใน Fastlane folder เพื่อให้ deliver/supply อัปโหลด
  5. เมื่อ UI เปลี่ยนครั้งต่อไป ถ่ายใหม่แล้วเปิด project รัน drive ใหม่ (หรือถ่ายใหม่) เปิดโปรเจกต์ Mokbi ที่บันทึกไว้ เปลี่ยน frame แล้ว export ใหม่ การจัดวางและการแปลยังคงอยู่

เมื่อไหร่ที่ข้าม automation ได้เลย

ถ้าส่งหนึ่งถึงสาม release ต่อปี การสร้าง integration_test screenshot harness และ CI pipeline จะเสียเวลามากกว่าที่ประหยัดได้ ถ่ายด้วยตนเองจาก simulator จัดวางและแปลภาษาครั้งเดียว แล้วไปต่อ automation ในตัวเลือก 1–3 เหมาะกับทีมที่ screenshot drift เป็นค่าใช้จ่ายซ้ำๆ — release บ่อย, หลาย locale, หรือดูแลแอปหลายตัวในพอร์ตโฟลิโอ เลือกเครื่องมือให้ตรงกับความถี่การ release ไม่ใช่กับสิ่งที่ดูเป็นระบบ

ก่อน export อะไร ตรวจ target ให้ดีก่อน: ดูขนาดพิกเซลที่แน่นอนจาก คู่มือขนาดภาพหน้าจอ App Store และกฎเรื่องรูปแบบ (PNG/JPEG, RGB, ไม่มี alpha channel, 1–10 ภาพต่อคลาสอุปกรณ์) จาก คู่มือข้อกำหนดภาพหน้าจอ App Store Apple ปฏิเสธขนาดที่ผิดแม้แต่พิกเซลเดียวโดยไม่มีการยืดหยุ่น ดังนั้นตรวจให้ดีสักสามสิบวินาที

อ่านต่อ

เปิดเครื่องมือแก้ไข →