The Screenshot factory — Illustration by Jeremy Paul

Automate iOS Screenshots With fastlane

App Store screenshots are crucial elements in the decision to buy or download an application. So, we want it to be perfect! To make these screenshots, we know two options: making it manually (with Photoshop, Sketch, whatever …) or take real screen copies of the running app. If we want it to be neat, it would take a lot of work. But let’s see how we can optimize this.


Manual, takes time

The main benefit of creating screenshots manually is that we can entrust this job to the designer. So, we’ll have very neat pictures. But the user will not see a real preview of the app, just a view of the designer. Indeed, there is always differences between the designs and the reality of the developed application. Furthermore, if it’s a universal app (iPhone & iPad) in several languages, the work becomes titanic! 5 images, in 2 languages and 6 sizes (3.5", 4", 4.7", 5.5", 9.7" and 12.9") makes 60 pictures! On each new language, you add 30 images…

Hard work …

Using real screenshots allows us to give a real preview of the application. But it does not really reduce the work charge. Even if it’s easier to make a screen capture than to generate it in an image editor, it remains a tedious task. So, we often choose discount solutions. We make screenshots for only one language (almost always English) and we use the same pictures for all screen sizes.

Indeed, iTunes Connect allows us to use screenshots in the biggest size (5.5" for the iPhone and 12.9" for the iPad) and downsize if for smaller screens. Even if it seems a good idea, it gives some unsatisfactory results. This method does not take care of your layout adaptations to the screen size. Here are the results of this method on the iOS Photo app.

Comparison between real and downscaled screenshots.

The downscaled image is much less clear. Furthermore, you can notice that UI elements do not correspond to the reality (texts size, icons in TabBar, disclosure indicators). If you want to compare in detail, you can check the real screenshots and the resized ones.

Machines, to serve us

However, there is another option: capture real screenshots of the running application, using automation with fastlane. This open-source software is an automation tools suite for iOS developers (Android support is added step by step). Let’s focus on one of the tools: snapshot. It allows to automatically take screenshots in all screen sizes and all languages. Concretely, it’s nothing more than playing a scenario (based on simple actions) and recording the screen state at key moments. Technically, snapshot relies on Xcode UI Tests. If you are new to UI Tests, and want to know more about it, you can have a look at theses resources:

Not this kind of UI Automation …

Let’s code

To illustrate the use of snapshots, we’ll use a demo project. It’s a very simple (fake) application for a swift conference: FrenchKit. It’s built around a TabBar with 3 elements:

  • Home will display the conference logo and a quick presentation,
  • Map will show where the venue is,
  • Twitter will display the last tweets from the conference account.
The 3 screens of the demo app

This demo app is developed as simple as possible. The only purpose is to show you how snapshot works. You can download it on this Github repository.

UI Tests

The first step is to write UI Tests that show the screens we want to capture. So, let’s create a new iOS UI Testing Bundle and write the test(s). In our case, here’s what we get:

func testScreenshots() {
let app = XCUIApplication()
    XCUIDevice.shared().orientation = .portrait

let tabBarsQuery = XCUIApplication().tabBars
    // Home
tabBarsQuery.buttons.element(boundBy: 0).tap()
// Here, we'll take the first screenshot

// Map
tabBarsQuery.buttons.element(boundBy: 1).tap()
app.otherElements["eventlocation"].tap()
// Here, we'll take the second screenshot
// After pressing the "eventlocation" annotation on the map.

// Twitter
tabBarsQuery.buttons.element(boundBy: 2).tap()
// Here, we'll take the third screenshot
}

If we do Product -> Test (⌘U), we see the application running by itself in all different desired states. Now, all we have to do is to trigger screenshots at the key moments.

UITest in action.

Fastlane

That’s where fastlane comes in. If it’s not already done, we should install it: everything is explained here. Once it’s OK, open a terminal into the application’s fastlane snapshot init. This command will create a file SnapshotHelper.swift (or SnapshotHelper2-3.swift if the project is in Swift2.3) and explain us how to setup the snapshot system. First, we should add the SnapshotHelper.swift file to the UI Tests target. Then, we’ll edit the test file to add snapshot setup. Here goes:

override func setUp() {
super.setUp()
let app = XCUIApplication()
setupSnapshot(app)
app.launch()
}

Now, we should indicate to snapshot where we want him to take screenshots. We simply have to call snapshot(name:). At the end, we have:

func testScreenshots() {
let app = XCUIApplication()
    XCUIDevice.shared().orientation = .portrait

let tabBarsQuery = XCUIApplication().tabBars
    // Home
tabBarsQuery.buttons.element(boundBy: 0).tap()
snapshot("0-Home")

// Map
tabBarsQuery.buttons.element(boundBy: 1).tap()
app.otherElements["eventlocation"].tap()
snapshot("1-Map")

// Twitter
tabBarsQuery.buttons.element(boundBy: 2).tap()
snapshot("2-Twiter")
}

During its initialization, snapshot has created a configuration file called Snapfile. This is where we list all the devices and languages we should handle. We can also specify where the screenshots should be stored and if we should delete results of a previous run. In our case, we get:

# Let's take all iOS form factors
devices([
"iPhone 6",
"iPhone 6 Plus",
"iPhone 5",
"iPad Pro (12.9 inch)",
"iPad Pro (9.7 inch)"
])
# In English and French
languages([
"en",
"fr"
])
# Put it into ./screenshots
output_directory "./screenshots"
# But before each run, we clear previous screenshots
clear_previous_screenshots true

Once it’s done, we just have to run fastlane snapshot and wait! Fastlane will build and launch our application and run the tests for each pair of device and language that we specified. Potentially, it could be a very long operation. It all depends on how long compilation and tests time are. But that’s clearly the right moment to go for a coffee!

Excerpts of the snapshot result.

At the end, snapshot generates a report with the result of each scenario. It opens an HTML page with all the different screenshots. You can see a real life example here.

Would you like to know more ?

This is just an excerpt on what snapshot and fastlane can do. In future posts, I’ll explain how to:

  • Have a neat status bar on all screenshots (no more « Carrier »),
  • Handle some complex cases (like: waiting for the end of a loading),
  • Setup snapshot in a project with multiple targets (1 project for n applications),
  • Automatically integrate the generated screenshots into designs.

You could also take a look at the snapshot official documentation (which is pretty well made). There also is this interesting post: “Automating App Store screenshots generation with Fastlane snapshot and Sketch” by Pavel Tisunov.


Thank you for reading! If you have any question or comment on this: feel free to send me a mail (julien@sinplicity.fr), a tweet (@juli1Quere) or a comment here.

A big thank to Jérémy Paul for his proofreading work and the top illustration !