Skip to content

Instantly share code, notes, and snippets.

@aednlaxer
Created March 21, 2023 08:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aednlaxer/3a563979e653c9b9640b6a65c1205dea to your computer and use it in GitHub Desktop.
Save aednlaxer/3a563979e653c9b9640b6a65c1205dea to your computer and use it in GitHub Desktop.
Flutter widget tests to create app store graphics
import 'package:device_frame/device_frame.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
/// A widget that renders a [child] with [text] on top
class AppStoreFrame extends StatelessWidget {
const AppStoreFrame({
required this.text,
required this.child,
required this.deviceInfo,
super.key,
});
final String text;
final Widget child;
final DeviceInfo deviceInfo;
@override
Widget build(BuildContext context) {
final backgroundColor = debugBrightnessOverride == Brightness.dark
? Colors.black
: Colors.white;
final textColor = debugBrightnessOverride == Brightness.dark
? Colors.white
: Colors.black;
return MediaQuery(
data: const MediaQueryData(),
child: Directionality(
textDirection: TextDirection.ltr,
child: ColoredBox(
color: backgroundColor,
child: Stack(
children: [
Container(
height: 140,
alignment: Alignment.center,
padding: const EdgeInsets.only(
top: 24,
left: 16,
right: 16,
),
child: Text(
text,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
fontFamily: 'Fira',
color: textColor,
),
),
),
Positioned(
top: 170,
left: 16,
right: 16,
child: DeviceFrame(
device: deviceInfo,
screen: Container(
color: backgroundColor,
padding: deviceInfo.safeAreas,
child: child,
),
),
),
],
),
),
),
);
}
}
import 'package:ratiom8/model/preferences_model.dart';
/// A mock version of this app's PreferenceModel that returns the given
/// values for [getRatio] and [getBeans].
class MockPreferencesModel extends PreferencesModel {
MockPreferencesModel(this._ratio, this._beans);
final double _ratio;
final double _beans;
@override
Future<double?> getRatio() async => _ratio;
@override
Future<double?> getBeans() async => _beans;
@override
Future<void> saveRatioBeansWater(double ratio, double beans) async {
return;
}
}
import 'dart:io';
import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
const _directory = 'app-store-screenshots';
Future<void> takeWidgetScreenshot(
WidgetTester tester,
Finder finder,
String filename,
double pixelRatio,
) async {
final boundary = tester.renderObject(finder);
if (boundary is! RenderRepaintBoundary) {
throw Exception('Widget is not a RenderRepaintBoundary');
}
final image = await boundary.toImage(pixelRatio: pixelRatio);
await Directory(_directory).create();
final file = File('$_directory/$filename');
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
await file.writeAsBytes(byteData!.buffer.asUint8List());
}
import 'package:device_frame/device_frame.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:ratiom8/app/app.dart';
import 'package:ratiom8/model/preferences_model.dart';
import 'infrastructure/app_store_frame.dart';
import 'infrastructure/mock_preferences_model.dart';
import 'infrastructure/screenshot.dart';
const iphoneXsMax = Size(414, 896);
const iphone8Plus = Size(414, 736);
const iPad3G = Size(1366, 1024);
const iPad2G = Size(1366, 1024);
Future<void> main() async {
setUp(() async {
// Load app fonts so text doesn't look redacted
final font = rootBundle.load('assets/fonts/fira_code_regular.ttf');
final fontLoader = FontLoader('Fira')..addFont(font);
await fontLoader.load();
});
testWidgets(
'Create App Store screenshots',
(tester) async {
await takeScreenshots(tester, iphoneXsMax, Devices.ios.iPhone13, 3);
await takeScreenshots(tester, iphone8Plus, Devices.ios.iPhoneSE, 3);
await takeScreenshots(tester, iPad2G, Devices.ios.iPad12InchesGen2, 2);
await takeScreenshots(tester, iPad3G, Devices.ios.iPad12InchesGen4, 2);
},
);
}
/// Takes a series of 3 screenshots for the given device
Future<void> takeScreenshots(
WidgetTester tester,
Size imageSize,
DeviceInfo deviceInfo,
double pixelRatio,
) async {
await tester.runAsync(() async {
await takeAppScreenshot(
tester: tester,
imageSize: imageSize,
deviceInfo: deviceInfo,
filename: '${deviceInfo.name}-first.png',
model: MockPreferencesModel(17, 30),
brightness: Brightness.light,
text: 'Brew a perfect cup of coffee!',
pixelRatio: pixelRatio,
);
await takeAppScreenshot(
tester: tester,
imageSize: imageSize,
deviceInfo: deviceInfo,
filename: '${deviceInfo.name}-second.png',
model: MockPreferencesModel(16.5, 24),
brightness: Brightness.light,
text: 'Set ratio, beans and water',
pixelRatio: pixelRatio,
);
await takeAppScreenshot(
tester: tester,
imageSize: imageSize,
deviceInfo: deviceInfo,
filename: '${deviceInfo.name}-third.png',
model: MockPreferencesModel(17, 30),
brightness: Brightness.dark,
text: 'Dark theme for a midnight cup of Joe',
pixelRatio: pixelRatio,
);
});
}
/// Captures a screenshot of the app widget with the given parameters
Future<void> takeAppScreenshot({
required WidgetTester tester,
required Size imageSize,
required DeviceInfo deviceInfo,
required String filename,
required MockPreferencesModel model,
required Brightness brightness,
required String text,
required double pixelRatio,
}) async {
await tester.binding.setSurfaceSize(imageSize);
// A trick to create a new unique key for each function call
final rootKey = ValueKey('key-${DateTime.now()}');
debugBrightnessOverride = brightness;
await tester.pumpWidget(
RepaintBoundary(
key: rootKey,
// Add custom app store frame (text and device frame)
child: AppStoreFrame(
deviceInfo: deviceInfo,
text: text,
child: ProviderScope(
// Override state with custom value
overrides: [preferenceModelProvider.overrideWithValue(model)],
// This is the main app widget, not shared in this gist
child: const App(),
),
),
),
);
await tester.pumpAndSettle();
await takeWidgetScreenshot(tester, find.byKey(rootKey), filename, pixelRatio);
debugBrightnessOverride = null;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment