Flutter App Store 스크린샷 만들기: integration_test, 골든 테스트, 더 빠른 방법 (2026)
integration_test + flutter drive로 실제 프레임 캡처, 골든 테스트로 위젯 스냅샷 렌더링(회귀 테스트에는 탁월하지만 스토어 에셋으로는 한계 있음), 또는 이 둘을 Fastlane/Codemagic CI로 감싸는 것이에요. 이 방법들은 어느 것도 마케팅 캐러셀 — 캡션, 기기 프레임, 배경, 50개 로케일 카피 — 을 합성해주지 않아요. 그건 별도의 단계이고, 그게 바로 Mokbi가 맡는 부분이에요: 캡처한 스크린샷을 합성하고 현지화해요. 캡처 자체는 하지 않아요.Flutter로 App Store나 Play Store에 앱을 출시해봤다면 이미 그 공백을 느꼈을 거예요: flutter screenshots 명령어가 없어요. Apple은 1320 × 2868(6.9인치 iPhone)이나 2064 × 2752(13인치 iPad) 같은 정확한 픽셀 크기의 PNG를 요구하는데, Flutter는 렌더링 엔진과 테스트 도구만 제공하고 스토어 파이프라인은 직접 만들어야 해요. 이 글은 2026년 기준 실제로 작동하는 방법들을 코드와 함께 솔직하게 정리하고, 어떤 도구가 어떤 역할을 하는지 명확한 선을 그어요. 프레임워크별 개요를 먼저 보고 싶다면 Flutter 개발자를 위한 Mokbi를 참고하세요.
먼저 짚어두고 싶은 것이 있어요. 혼란을 많이 줄여주는 구분이에요: 프레임 캡처(앱을 특정 상태로 조작해 화면 PNG를 저장하는 것)와 스토어 스크린샷 합성(그 프레임을 기기 목업 안에 배치하고, 헤드라인·배경·현지화된 카피와 함께 필요한 모든 크기로 내보내는 것)은 서로 다른 문제예요. 아래 Flutter 도구 대부분은 첫 번째 문제를 해결해요. 두 번째 문제를 해결하는 도구는 없어요.
옵션 1 — integration_test + flutter drive (실제 프레임 캡처)
Flutter에서 공식 스크린샷 경로에 가장 가까운 방법이에요. integration_test 패키지는 실제 기기나 에뮬레이터에서 전체 앱을 실행하고, 위젯 테스트와 같은 finder/tester API로 앱을 조작하며, binding.takeScreenshot()을 제공해요. 단, takeScreenshot은 바이트를 테스트 프로세스로 반환할 뿐 파일로 저장하지 않아요. 따라서 그 바이트를 호스트에서 받아 저장하는 확장 드라이버가 필요해요.
먼저 테스트 코드예요. Android 전용 처리에 주의하세요: Android는 Flutter를 SurfaceView에 렌더링하기 때문에 프레임버퍼에서 직접 읽어올 수 없어요. 캡처 전에 convertFlutterSurfaceToImage()를 호출하고 프레임을 한 번 더 펌프해야 해요.
// 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');
});
}다음은 드라이버 코드예요. 호스트 머신에서 실행되며 테스트가 전송한 PNG 바이트를 파일로 저장해요. 이 파일은 test_driver/에 위치하며 integration_test_driver_extended.dart를 사용해요(onScreenshot 훅이 없는 일반 integration_test_driver.dart가 아닌 확장 버전이어야 해요):
// 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 test가 아닌 flutter drive로 실행해야 해요 — 드라이버 경로에서만 onScreenshot이 호출돼요:
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/screenshot_test.dart \
-d emulator-5554얻을 수 있는 것과 없는 것:
- 실제 앱 픽셀. 재렌더링이 아닌 실제 실행 중인 위젯의 스크린샷 — 폰트, 테마, 동적 데이터가 모두 네이티브예요.
- 결정론적 상태. 원하는 화면으로 앱을 정확히 조작한 뒤 원하는 시점에 캡처해요.
- 기기 클래스 = 실행하는 기기. 출력 해상도는 에뮬레이터/기기의 해상도예요. Apple의 정확한 1320 × 2868 크기를 얻으려면 6.9인치 클래스 iOS 시뮬레이터에서, 13인치 iPad 세트는 해당 시뮬레이터에서 실행해야 해요. "App Store 크기로 자동 조정"하는 옵션은 없어요.
- 로케일 처리는 직접 해야 해요. 앱 실행 시 로케일을 주입하고 언어별로 재실행할 수 있지만, 그렇게 해도 앱 UI만 해당 언어로 바뀌는 거예요 — 마케팅 헤드라인 카피는 별도예요.
- 유지 비용이 있어요. finder(
find.byKey, 탭)는 UI가 바뀌면 깨져요 — 다른 UI 테스트와 마찬가지예요.
옵션 2 — 골든 테스트, 그리고 스토어 에셋이 아닌 이유
Flutter의 골든 테스트(matchesGoldenFile, 또는 golden_toolkit 같은 패키지의 상위 레벨 testGoldens)는 위젯 트리를 이미지로 렌더링하고 커밋된 기준 이미지와 바이트 단위로 비교해요. 훌륭한 도구이지만, 스토어 스크린샷이 아닌 다른 목적을 위해 만들어졌어요: 픽셀 회귀 감지예요. 패딩 변경이나 폰트 교체가 UI를 조용히 바꿔버리는 날을 잡아내는 게 핵심이에요. 골든이 계약이고, 차이가 생기면 테스트가 실패해요.
날것의 골든 테스트를 스토어 파이프라인으로 쓰지 않는 이유 두 가지:
- 골든은 헤드리스 테스트 환경에서 위젯을 렌더링해요, 실제 기기 화면이 아니에요. 플랫폼 뷰, 일부 셰이더, 실제 GPU 컴포지터에 의존하는 요소들이 사용자가 보는 것과 다르게 렌더링될 수 있어요(또는 전혀 렌더링 안 될 수도). 회귀 계약으로는 괜찮아요 — 자기 자신과 비교하니까요. 스토어 에셋으로는 옵션 1이 제공하는 실제 프레임이 필요해요.
- 픽셀 퍼펙트 매칭이 발목을 잡아요. 바닐라 골든은 정확한 일치를 요구하기 때문에 머신 간 안티앨리어싱과 폰트 힌팅 차이로 불안정한 실패가 발생해요. 회귀 감지에는 적절한 엄격함이지만 "멋진 마케팅 이미지 만들기"에는 맞지 않아요.
그렇더라도 정당한 중간 경로가 있어요. golden_screenshot 같은 패키지는 골든 메커니즘을 스토어 출력 목적으로 의도적으로 재활용해요: testGoldens를 사용하고, 화면을 실제 기기 프레임(폰/태블릿/데스크톱)으로 감싸고, 픽셀 퍼펙션 대신 작은 오차(~0.1%, 조정 가능)를 허용하는 퍼지 비교기를 쓰며, Fastlane 스타일 디렉터리 구조(metadata/<locale>/images/...)로 파일을 저장해요. 화면이 순수 Flutter(플랫폼 뷰 없음)이고 에뮬레이터를 돌리지 않고 빠르게 기기 프레임 렌더링을 원한다면 합리적인 선택이에요. 단, 눈을 크게 뜨고 봐야 할 점이 있어요: 라이브 앱을 캡처하는 게 아니라 위젯을 렌더링하는 것이고, 얻는 "마케팅" 요소는 기기 프레임뿐이에요 — 헤드라인, 배경, 멀티 패널 캐러셀은 없어요.
옵션 3 — Flutter용 Fastlane / Codemagic
Fastlane은 Flutter 전용이 아닌 iOS/Android 자동화 도구이지만 두 가지 방식으로 활용할 수 있어요. Fastlane의 자체 캡처 도구(snapshot: XCUITest 기반 iOS, screengrab: Espresso/UI Automator 기반 Android)는 네이티브 UI 테스트를 전제로 하는데, Flutter 앱은 그게 없는 경우가 대부분이에요 — 그래서 대부분의 Flutter 팀은 캡처에 snapshot을 쓰지 않아요. Fastlane이 진가를 발휘하는 건 배포예요: deliver(iOS)와 supply(Android)는 이미 캡처된 PNG와 메타데이터를 App Store Connect / Play Console에 업로드하며, Fastlane 폴더 구조를 읽어요 — golden_screenshot이 저장하는 구조, 또는 옵션 1의 드라이버가 쓰도록 설정할 수 있는 구조와 동일해요.
Codemagic은 이 용도로 대부분의 팀이 선택하는 Flutter 전용 CI예요. 현실적인 CI 레시피는: macOS/Android 러너에서 필요한 기기 클래스별로 integration_test 스크린샷 드라이브를 실행하고, PNG를 빌드 아티팩트로 수집한 뒤 deliver/supply로 업로드해요. 릴리즈마다 스크린샷이 변하고 출시 빈도가 높을 때 진정으로 유용해요. "자동화 vs 노코드" 트레이드오프 — 이 설정이 과한 경우를 포함해 — 에 대한 더 자세한 내용은 Fastlane snapshot vs 노코드 App Store 스크린샷을 참고하세요.
옵션 4 — 수동 캡처 (여전히 완전히 유효한 방법)
인디 Flutter 앱 상당수에는 솔직히 말해서 이게 정답이에요: 릴리즈마다 스크린샷을 직접 찍는 것. iOS 시뮬레이터를 올바른 기기 클래스(6.9인치 iPhone, 13인치 iPad)로 실행하고, 각 화면으로 이동한 뒤 시뮬레이터의 스크린샷 명령어를 사용하면 돼요 — 정확한 기기 해상도로 캡처되는데, 이게 App Store Connect가 요구하는 바로 그것이에요. Android에서는 에뮬레이터의 카메라 버튼이 동일한 역할을 해요. 화면 5개짜리 앱이라면 총 소요 시간이 10분 정도예요.
수동 캡처는 유지 비용이 전혀 없고, 불안정한 finder도 없고, 신경 써야 할 CI도 없어요. 유일한 단점은 자동으로 재생성되지 않는다는 것 — UI를 바꾸면 다시 촬영해야 해요. 연 몇 차례 릴리즈하는 팀에게는 이 트레이드오프가 압도적으로 유리해요. 옵션 1~3의 자동화가 빛을 발하는 건 정확히 "직접 다시 찍기"가 주 단위 출시와 많은 로케일 탓에 10분짜리 작업이 아니게 될 때예요.
위 방법 모두가 못 하는 것: 합성 + 50개 언어 현지화
솔직하고 명확하게 말씀드릴게요. 위의 모든 선택지는 날것의 프레임을 만들어요 — 기기 해상도의 앱 UI. 실제로 전환을 이끄는 App Store와 Play Store 캐러셀은 날것의 프레임이 아니에요: 기기 목업 안의 프레임, 배경 위에, 짧은 헤드라인 아래, 흔히 멀티 패널 시퀀스로, 타겟하는 모든 로케일에 반복된 구성이에요. 그 합성 단계가 바로 Mokbi의 역할이에요. 캡처한 프레임(integration_test, golden_screenshot, 시뮬레이터 등 어디서 가져왔든)을 브라우저 에디터에 드롭하고, 기기 프레임과 캡션, 배경을 추가하고, 최대 50개 App Store 언어로 캡션을 원클릭 번역하고, 필요한 모든 크기를 일괄 내보내기해요. 워터마크 미리보기로 무료 디자인이 가능하고, 워터마크 없는 무제한 내보내기와 스토어 게시는 구독으로 제공돼요 — Solo €29.99/mo(앱 1개) 또는 Studio €49.99/mo(앱 최대 5개), 한 번 결제는 없어요. 경계를 명확히 하자면: Mokbi는 소스 스크린샷을 캡처하지 않아요 — 앱을 실행하거나 위젯을 조작하지 않아요. 캡처는 위의 Flutter 도구가, 합성과 현지화는 Mokbi가 담당해요.
현실적인 통합 Flutter 워크플로
- 소스 프레임 캡처. 하나를 선택하세요. 캐러셀에 넣을 화면을 위한
integration_test스크린샷 드라이브(옵션 1)를 작성하거나, 출시 빈도가 낮다면 시뮬레이터에서 수동으로 캡처하세요. 6.9인치 iPhone 클래스 기기와 13인치 iPad 클래스 기기에서 실행해 올바른 해상도를 확보하세요. - CI 연결 (선택사항). 스크린샷 변화가 실제 문제라면 Codemagic에서 드라이브를 실행하고 PNG를 아티팩트로 수집하세요(옵션 3). 그렇지 않다면 건너뛰세요.
- 마케팅 캐러셀 합성. 프레임을 Mokbi에 드롭하고, 기기 프레임·캡션·배경을 추가하고, 멀티 패널 시퀀스를 구성하세요. 이 단계는 Flutter 도구로 할 수 없는 부분이에요.
- 현지화 및 내보내기. 대상 App Store 로케일 전체로 캡션을 원클릭 번역하고, 필요한 모든 크기를 일괄 내보내기하세요. 원한다면 출력을 Fastlane 폴더에 넣어
deliver/supply로 업로드할 수 있어요. - 다음 UI 변경 시 재캡처 후 열기. 드라이브를 다시 실행(또는 수동 재촬영)하고, 저장된 Mokbi 프로젝트를 열어 프레임을 교체한 뒤 다시 내보내세요. 합성과 번역 작업은 그대로 유지돼요.
자동화를 완전히 건너뛸 수 있는 경우
연 1~3회 출시한다면, integration_test 스크린샷 하네스와 CI 파이프라인을 구축하는 데 드는 시간이 절대 회수되지 않아요. 시뮬레이터에서 수동으로 캡처하고, 한 번 합성과 현지화를 한 뒤 그냥 넘어가세요. 옵션 1~3의 자동화가 가치 있는 건 스크린샷 변화가 반복적인 비용이 될 때 — 잦은 릴리즈, 많은 로케일, 또는 여러 앱 포트폴리오일 때예요. 얼마나 정교해 보이냐가 아니라 출시 주기에 맞는 도구를 선택하세요.
내보내기 전에 꼭 확인하세요: App Store 스크린샷 크기 가이드에서 정확한 픽셀 크기를, App Store 스크린샷 요구사항 가이드에서 형식 규칙(PNG/JPEG, RGB, 알파 채널 없음, 기기 클래스당 1~10장)을 확인해두세요. Apple은 픽셀 하나라도 어긋나면 무조건 거절하므로 30초의 가치가 있는 확인이에요.