How to Automatically Take Screenshots With Fastlane Snapshot & Screengrab.

Grabbing screenshots for store display can become an exhausting work. Clicking button X to open screen B and take a screenshot, or input a username & password then wait for the login process or taking an exact screen which deeply nested. Multiply it by how many localization that’s supported. Also, don’t forget the revision or new design for next month. Now if you didn’t automate your way to do all this, then you’re doing it wrong.

Introducing Fastlane Snapshot (for iOS & tvOS), tools for automating all this hard work for taking app snapshots.

Setup

To install Fastlane, you need to have Ruby installed first (which is installed by default in MacOS) and then run the command

gem install fastlane

This will install all the Fastlane tools including Snapshot and Screengrab.

iOS Project Setup

For this tutorial, we’ll be using project that we’ve created. You can clone it here or download the zip file here. This app contains 2 screens with en-US & fr-FR localization.
To start with Fastlane snapshot, open your terminal and type

fastlane snapshot init

This command will add 2 files, SnapshotHelper.swift is a class that contains helper methods to get snapshots. Snapfile is a configuration file for Fastlane Snapshot.

Now open the app project and add SnapshotHelper.swift inside HelloAppUITests folder. Next, open HelloAppUITests.swift and remove everything inside setUp method except,super.setUp()

then add this lines to setup Snapshot

let app = XCUIApplication()
setupSnapshot(app)
app.launch()

Next, we will add Localization.strings and a helper method to get Localization strings inside UI Tests. Click on HelloApp project, head to HelloAppUITests target. Go to Build Phase, expand Copy Bundle Resources and press + button then choose Localizable.strings.

Now open HelloAppUITests.swift and add this method :

func localizedString(key:String) -> String
{
    let localizationBundle = Bundle(for: HelloAppUITests.self)        

    // handle "en-US" localisation
    if let path = localizationBundle.path(forResource: deviceLanguage, ofType: "lproj") {
        let deviceBundle = Bundle(path: path)
        let result = NSLocalizedString(key, bundle: deviceBundle!, comment: "")
        return result
    }

    // handle "Base.lproj" localization
    if let path = localizationBundle.path(forResource: "Base", ofType: "lproj") {
        let deviceBundle = Bundle(path: path)
        let result = NSLocalizedString(key, bundle: deviceBundle!, comment: "")
        return result
    }

    // handle "en" localization
    if let path = localizationBundle.path(forResource: NSLocale.current.languageCode, ofType: "lproj") {
        let deviceBundle = Bundle(path: path)
        let result = NSLocalizedString(key, bundle: deviceBundle!, comment: "")
        return result
    }

    return "?"
}

This method helps us to locate the Localizable strings inside UI Test and return the localized string from the defined key. Now move your cursor inside testExample method, this will activate the Record button on the bottom left.

Next, Click that button (this will build and run your app on emulator). Then click on “Continue” and in Product Detail page, click on “ADD TO CART“, and press Stop Record button. Above actions will add UI test scenarios inside the testExample method.

For our Snapshot localization to work, we need to change the hardcoded strings into its localized version. On the “Continue” change it into

localizedString(key: "Continue")

And on the “Add To Cart” change it into

localizedString(key: "Add To Cart").uppercased()

Next, we will add codes to take & save our snapshots. Modify the code inside testExample() to be like this :

let app = XCUIApplication()
snapshot("01Welcome")

app.buttons[localizedString(key: "Continue")].tap()
snapshot("02Home")

app.buttons[localizedString(key: "Add to cart").uppercased()].tap()
snapshot("03Alert", timeWaitingForIdle: 2)

snapshot("FileName") is to take a snapshot and then save it with “FileName.png”. For our alert after tapping “Add to card”, because there’s a 2 seconds delay before an alert is shown, we use snapshot("FileName", timeWaitingForIdle: n_seconds)

Next, open Snapfile with your text editor. As you can see, there are configurations for device types to be used, languages and others. For optimal results, we want iPhone 8 Plus and iPhone X. For other iPhone sizes, AppStore Connect will resize the screenshots from iPhone 8 Plus, and even though iPhone X screenshots is optional, it’s nice to have those, especially with snapshot automation. Next, for the language, we will add “en-US” and “fr-FR”. Then we set clear_previous_screenshots to true because we don’t need the older screenshots. The final setting for Snapfile is like this :

devices(["iPhone 8 Plus", "iPhone X"])
languages(["en-US", "fr-FR"])
clear_previous_screenshots(true)

Now open the Terminal and type :

fastlane snapshot

This command will build your app, run it inside the simulators matching the devices setting, run your UI test, take and save snapshots. After done, you should see these results.

Fastlane also generates a nice HTML page containing all your screenshots for selected devices & localizations.

Android Project Setup

Before we start with fastlane screengrab, make sure you have a phone with Nougat (SDK 25) at most or create a new virtual device with SDK 25 or below. Because screengrab sometimes has issues when running on device/simulator with SDK 26 and above. Now, let’s get started by opening your terminal or command line and run

fastlane screengrab init

Above command will generate a new file named Screengrabfile inside Fastlane folder. This file is where we configure the fastlane screengrab. Open Screengrabfile and make sure that there’s already a configuration for apk path and test apk path. If the config is commented, uncomment it. Also, add locales configuration if your app supports multi-language.

locales(['en-US', 'fr-FR'])

Next, open build.gradle inside app module and add the required dependencies:

androidTestImplementation 'tools.fastlane:screengrab:1.0.0'
androidTestImplementation 'com.android.support.test:rules:1.0.2'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

Sync your Gradle then open ExampleInstrumentedTest inside androidTest folder. You can use this example or create a new instrumentation test file.

Add some setup code:

@ClassRule
public static final LocaleTestRule localeTestRule = new LocaleTestRule();

@Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);

@BeforeClass
public static void beforeAll() {
    Screengrab.setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy());
}
  • localeTestRule is to handle automatic localization switching.
  • mActivityTestRule is to the test runner that this test is meant to run on MainActivity
  • The last line of code is a Screengrab configuration.

Next, we add codes to control the UI and take screenshots:

@Test
public void mainActivityTest() {
    Screengrab.screenshot("01Main");

    ViewInteraction appCompatButton = onView(withId(R.id.btn_continue));
    appCompatButton.perform(click());

    Screengrab.screenshot("02Second");

    ViewInteraction appCompatButton2 = onView(withId(R.id.button2));
    appCompatButton2.perform(click());
}
  • To capture a screenshot we use Screengrab.screenshot("screenshot_name")
  • To get a reference of UI component inside an Activity use onView(withId(R.id.component_name))
  • To perform an action use viewReference.perform(action())

Next, run the instrumentation test by clicking the play button beside the class name.

After it finished. Open your terminal and run

fastlane screengrab

After the end of processing your default browser will pop and show an HTML page with the generated screenshots. You can clone this example project here: https://github.com/hidrodixtion/HelloAppAndroid