// 구현 확인물 캡처 공용 도구 — 화면군마다 `capture_f*_test.dart` 가 이것을 쓴다. // // **앱 코드를 고치지 않는다.** 앱이 쓰는 위젯과 테마를 그대로 기기 위에 띄우고 // 화면마다 PNG 를 한 장씩 뽑는다. `adb shell input` 으로 41 화면을 눌러 다니는 // 방식은 이 에뮬레이터에서 한 번 누르는 데 최대 60초가 걸려 재현되지 않았다. // // 화면군을 파일로 나눈 이유 — 한 실행이 중간에 끊기면 드라이버가 그때까지 모은 // 캡처를 통째로 잃는다. 군마다 실행을 나누면 한 군이 실패해도 나머지는 남는다. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:fancake/core/theme/app_theme.dart'; /// 캡처 도구 한 벌. `main()` 첫 줄에서 만든다. class Capture { Capture() : binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); final IntegrationTestWidgetsFlutterBinding binding; /// Android 는 이걸 부르지 않으면 화면을 이미지로 뜨지 못한다. Future prepare() => binding.convertFlutterSurfaceToImage(); /// 정해진 시간만큼 프레임을 돌린다. /// /// `pumpAndSettle` 은 로딩 스피너처럼 끝나지 않는 애니메이션이 하나라도 있으면 /// 던진다. 캡처는 그런 화면에서도 나와야 하고, 저장소를 읽고 다시 그리는 /// 시간도 필요하므로 고정 프레임 수로 돌린다. Future settle(WidgetTester tester, {int frames = 16}) async { for (var i = 0; i < frames; i++) { await tester.pump(const Duration(milliseconds: 100)); } } /// 화면 하나를 앱 테마 그대로 띄운다. /// /// [tag] 로 키를 준다. **없으면 안 된다** — 같은 종류의 화면을 연달아 띄우면 /// Flutter 가 앞 화면의 State 를 그대로 재사용해 새 인자가 무시되고 /// 앞 화면이 한 번 더 찍힌다(q1 상세가 q4 상세 자리에 찍히는 식). Future open(WidgetTester tester, String tag, Widget screen) async { await tester.pumpWidget( MaterialApp( debugShowCheckedModeBanner: false, theme: buildAppTheme(), darkTheme: buildAppTheme(), themeMode: ThemeMode.dark, home: KeyedSubtree(key: ValueKey(tag), child: screen), ), ); await settle(tester); } Future shot(String name) => binding.takeScreenshot(name); /// 띄우고 찍기. Future capture(WidgetTester tester, String name, Widget screen) async { await open(tester, name, screen); await shot(name); } }