Edition 13

Switching Between iOS Apps During a Test

Smartphone users use on average 9 apps per day, and 30 apps per month, according to one recent report. If your app is lucky enough to be given such attention, what are the chances that it lives alone, with no integration to the rest of the user's smartphone experience? So many apps today provide value by integrating with other aspects of the mobile phone experience, even if it's just as simple as taking photos using the device's camera.

Historically, testing these kinds of multi-app integrations with iOS has been challenging, because Apple's automation model allowed automation only so long as the AUT was running. The second you switched to another app to automate another part of your user flow, the automation would die. Thankfully, with Appium's XCUITest driver (available with iOS 9.3+), we're no longer limited to this kind of sandbox.

There are so many kinds of multi-app flows we could experiment with, but let's imagine a scenario involving the camera. Let's say your app allows the user to take photos, and modifies them in some way before finally saving them to the system-wide photo library (the Camera Roll). One key verification for testing your app will be ensuring that, after performing the necessary steps in your app, the photo is newly available in the Camera Roll album of the Photos app. So, the test would look like this on a high level:

  1. Launch the Photos app
  2. Navigate to Camera Roll album
  3. Determine how many photos are present, and store that number
  4. Launch your app
  5. Add and save photo by automating your app's UI
  6. Switch back to the Photos app
  7. Navigate to Camera Roll
  8. Determine how many photos are present now
  9. Verify that the number of photos present before has increased by 1

This is all possible to encode into your Appium scripts using two handy commands: mobile: launchApp and mobile: activateApp. We've seen launchApp before, in the Appium Pro article on testing app upgrades. activateApp is just the same, only the app must already have been launched; activateApp merely brings it back to the foreground. Here are examples of how we'd use these commands, by incorporating the Bundle IDs of the apps we're dealing with (having these Bundle IDs available is a pre-requisite):

// launch the photos app (with the special bundle id seen below)
HashMap<String, Object> args = new HashMap<>();
args.put("bundleId", "com.apple.mobileslideshow");
driver.executeScript("mobile: launchApp", args);

// re-activate that AUT (in this case The App)
args.put("bundleId", "io.cloudgrey.the-app");
driver.executeScript("mobile: activateApp", args);

Simple, no? With Appium we can really pretend we are a user and do all the things a user would do in order to check the appropriate conditions. I won't go into detail in this edition about actually navigating the Photos app UI, nor does The App currently allow you to take photos and save them to the library, so filling out these specific steps will be left as an exercise to the reader. And chances are, you have a different requirement than verifying photo saving. So the thing to take away is the principle of using mobile: launchApp and friends to manage apps on the iOS device during a test.

What's nice is that this particular strategy will work not only for simulators but also for real devices! So if you need to have a user login to your app via Facebook, or some other third-party credential authority, you can do so.

As always, there's a full example up on GitHub. In this case, though, we just insert some clever Thread.sleeps instead of doing all the craziness with finding the number of photos and so on. Check it out here:

import io.appium.java_client.ios.IOSDriver;
import java.net.URL;
import java.util.HashMap;
import org.junit.Test;
import org.openqa.selenium.remote.DesiredCapabilities;

public class Edition013_iOS_Multi_App {


    private String APP = "https://github.com/cloudgrey-io/the-app/releases/download/v1.3.0/TheApp-v1.3.0.app.zip";
    private String PHOTOS_BUNDLE_ID = "com.apple.mobileslideshow";
    private String BUNDLE_ID = "io.cloudgrey.the-app";

    @Test
    public void testMultiApps() throws Exception {
        DesiredCapabilities capabilities = new DesiredCapabilities();
        capabilities.setCapability("platformName", "iOS");
        capabilities.setCapability("platformVersion", "11.3");
        capabilities.setCapability("deviceName", "iPhone X");
        capabilities.setCapability("app", APP);

        IOSDriver driver = new IOSDriver<>(new URL("http://localhost:4723/wd/hub"), capabilities);

        try {
            HashMap<String, Object> args = new HashMap<>();
            args.put("bundleId", PHOTOS_BUNDLE_ID);
            driver.executeScript("mobile: launchApp", args);

            // Here is where we would navigate the Photos UI to get the number of Camera Roll photos
            Thread.sleep(1000);

            // Now reactivate our AUT
            args.put("bundleId", BUNDLE_ID);
            driver.executeScript("mobile: activateApp", args);

            // Here is where we would cause the new photo to be taken, edited, and saved
            Thread.sleep(1000);

            // Now reactivate the Photos app
            args.put("bundleId", PHOTOS_BUNDLE_ID);
            driver.executeScript("mobile: activateApp", args);

            // Here is where we would get the new number of Camera Roll photos, and assert that it
            // has increased by the appropriate number
            Thread.sleep(1000);
        } finally {
            driver.quit();
        }
    }
}