Screenshooter
Automatically create screenshots of your flutter app.
Working Principle
Screenshooter works by building a special version of your app (with a different entrypoint) and running that on an iOS simulator. This app will execute the steps you specify and notify the host when it should take the screenshot. This way you can be sure that the screenshot is exactly what is on screen, because it actually takes a real screenshot of the simulator.
Setup
There are two steps to make your app work with Screenshooter:
- create the screenshot suite
- create the configuraton file
- (optional) add configuration for framing
Screenshot Suite
The screenshot suite is the client-side definition of how every screenshot is taken. Create a new entrypoint to your application (by default lib/screenshots.dart
) and add a ScreenshotSuite
instance and run it. A simple suite could look like this:
// lib/screenshots.dart
import 'main.dart' as app;
void main() {
ScreenshotSuite(
name: 'default',
prepare: () async {
// This is executed before everything else.
// You will need to start the real app here
app.main();
},
prepareLocale: (ScreenshotLocale locale) async {
// If you specify locales in the config later, this will be called
// for every locale. You probably want to set your locale here
setAppLocale(locale);
},
cleanupLocale: (ScreenshotLocale locale) async {
// Same as prepareLocale but obviously runs after taking
// the screenshots for the locale
clearCache();
},
cleanup: () {
removeData();
},
screenshots: [...]
).run();
}
The screenshots are each described by a Screenshot
instance, which is pretty self-explanatory:
final screenshot = Screenshot(
// a unique name for the screenshot
name: 'home',
// delay after calling prepare (e.g. waiting for images to load)
delay: const Duration(seconds: 1),
prepare: () async {
await navigateToHome();
},
cleanup: () async {
// perform some cleanup
}
);
NOTE: All methods return a Future<void>
which is awaited before proceeding so you don't have to worry about a screenshot being taken before things are ready.
Screenshot Configuration
If you want to access configuration variables at runtime you can use ScreenshotConfiguration.fromEnv
. This is a constant value so it will not slow down your application when e.g. used in if statements.
To find out if the app is currently running in screenshot mode use ScreenshotConfiguration.fromEnv.isActive
.
If you want to log in an example user you can access ScreenshotConfiguration.fromEnv.username
and ScreenshotConfiguration.fromEnv.password
which will be filled with the values passed to screenshooter. These values come from the CLI args --username
and --password
or as (recommended) fallback from the environment variables SCREENSHOOTER_USERNAME
and SCREENSHOOTER_PASSWORD
.
Configuration File
After setting up your screenshot suite you will need to specify how it will be run. Even though at this point you could just run screenshooter with CLI arguments, it is recommended to use a configuration file. By default the screenshooter.yaml
file will be used with the fallback being the screenshooter
key in the pubspec.yaml
(see Usage).
Using a config file will also enable you to efficiently take screenshots for multiple simulators.
This is an example of such a config file with all possible options - they are not needed however:
# screenshooter.yaml
bundleId: com.example.app
# This is the path for every screenshot. Placeholders:
# - {locale} will be replaced by the current locales language tag (BCP47)
# - {name} is replaced by the name specified in the `Screenshot`
# - {device} is replaced by the device identifier (as specified in `devices`)
path: screenshots/{locale}/{name}_{device}.png
# All locales to create screenshots for (in BCP47 format with `-` separators). If you omit this
# the screenshots are just taken once and the {locale} placeholder in the `path` will have the
# value `none`
locales:
- en-US
- de-DE
# This is a list of all the simulators to use. The key is the exact name of the iOS Simulator
# and the value is the identifier used
devices:
iPhone 14 Plus: iphone_65
iPhone 8 Plus: iphone_55
# The name of the file to use as the entrypoint for the app.
target: lib/screenshots.dart
Usage
To run screenshooter use dart run screenshooter
in your projects root directory. By default it will try to find a screenshooter.yaml
file to read the configuration from. If that doesn't exist it will look for a screenshooter
key in the pubspec.yaml
.
To load another configuration file (for example if you have multiple configs) use dart run screenshooter -f <name of your config.yaml>
Use dart run screenshooter --help
for info on all the CLI arguments.
Adding Device Frames
Screenshooter also includes a fast implementation for framing screenshots. This step is entirely optional and separate from taking the screenshots. Use dart run screenshooter:frame
to run this postprocessing after creating the screenshots.
In addition to adding a device frame, this tool can also add text on top of the frame using the titles
. The resulting image is kept in the same size as the original screenshot.
This uses the meta device frames because they are freely available and fairly current. For image processing ImageMagick is used as it is significantly faster than anything that purely uses Dart. The device frames are downloaded automatically into the ~/.cache/screenshooter-device-frames
directory. To find good frameSelectors
you can have a look in there.
NOTE: The download link for the device frames is not static and has to be parsed from an HTML page. The tool should do this automatically but it might fail. To circumvent this, you can download the archive manually from design.facebook.com/toolsandresources/devices/ and unpack it to ~/.cache/screenshooter-device-frames
so that the subdirectory Meta Devices
contains the folders for the devices.
Notes
How is the title selected?
Every titles key is checked against the filename of the screenshot. The first title whose key is contained in the filename is used.
How is the device frame selected?
The device name from the screenshooter configuration is used. In the config the name is the key inside the devices
map.
Configuration
The configuration is read from screenshooter.frames.yaml
. If this file does not exist, the config is loaded from the frames
key in screenshooter.yaml
or pubspec.yaml
.
These are all options that are available though not all are required:
# The text to add at the top of each screenshot. The languages correspond to the
# languages in the `locales` section of the `screenshooter.yaml` file.
# The first key from this file (e.g. "home", "account") that is found verbatim in the
# filename of the screenshot is selected.
titles:
en-US:
home: This is the home screen
account: This is the account screen
de-DE:
shop: Das ist der Home-Bildschirm
account: Das ist der Konto-Bildschirm
# Appended to the filename of the screenshot after framing it.
suffixFrame: _framed
# Appended to the filename of the screenshot after adding the text.
suffixText: _text
# Criteria to select which frames to use. If there are multiple matches in one level in the
# directory hierarchy, the shortest one is used.
frameSelectors:
- shadow
- white
- silver
- starlight
# The background color for the screenshot. Can be either a hex color or a color name
# as recognized by ImageMagick.
background: black
# The amount of padding on each side in percent. Relative to the width of
# the screenshot.
paddingPercent: 5
# Settings regarding the font used for the title.
font: ./fonts/OpenSans/OpenSans-Bold.ttf
fontSize: 75
fontColor: white