There's nothing more frustrating when running an iOS test than to have a system dialog pop up right in the middle of your test. And it only makes matters worse when that dialog is asking whether to grant a certain permission to your app. You're testing the app, so obviously you want it granted! (Unless of course you're testing what happens when it's not granted, in which case, dialog away.)
Luckily, recent versions of Appium (the latest 1.9.2-beta.2 and up) have a new capability that makes it possible to do away with both of these problems simultaneously. The new capability is permissions
, and it lets you set any permission that iOS knows about, from location to notifications to photos. One caveat: the new capability only works for simulators, so those of you running on real devices will have to sit tight and keep manually automating those popups.
Because Appium relies on some newfangled 3rd-party software to make the permissions magic happen, we need to get said software on our machines before we try and run our test. Here's what to do (assuming you're on a Mac and have Homebrew installed; and if you're not using a Mac this guide won't be very useful to you anyway!):
brew tap wix/brew
brew install wix/brew/applesimutils
This gets the applesimutils
utility program onto your machine so Appium can take advantage of it. At this point, you can restart Appium and prepare to run your test.
permissions
CapabilityThere are no new commands tied to this feature, only the permissions
capability. It should be a string, in valid JSON format, with the following structure:
{
"your-bundle-id": {
"permission-1-name": "permission-1-value",
"permission-2-name": "permission-2-value",
...
}
}
Of course, replace all these fake values with ones that make sense for your app. How do you know what permission names and values are available? You can always check out the applesimutils documentation for the full list, but I will reproduce them here:
Available Permissions:
calendar=YES|NO|unset
camera=YES|NO|unset
contacts=YES|NO|unset
health=YES|NO|unset
homekit=YES|NO|unset
location=always|inuse|never|unset
medialibrary=YES|NO|unset
microphone=YES|NO|unset
motion=YES|NO|unset
notifications=YES|NO|unset
photos=YES|NO|unset
reminders=YES|NO|unset
siri=YES|NO|unset
speech=YES|NO|unset
So let's say we wanted to allow our typical test app to access the GPS data while the app is in use. In that case, our JSON object should look like:
{
"io.cloudgrey.the-app": {
"location": "inuse"
}
}
But of course we need to include this as the capability, so we have to stringify it and include it in our Java client's capability builder, like so:
caps.setCapability("permissions", "{\"io.cloudgrey.the-app\": {\"location\": \"inuse\"}}");
(Notice that I've had to escape the inner quotes).
This is all we need to do to ensure that, when my app launches and before my test even begins, the permissions I need will already be set, so that I don't have to worry about any popups in the course of my test. I can also experiment with setting the values differently for different tests, if I want to see how my app will behave if a user changes the permission to something else, or turns it off entirely.
I've incorporated this example into a complete code sample, which navigates to a geolocation demo view on The App, and attempts to read the latitude and longitude from the view. This of course requires location permissions to be granted, and will only work with the new permissions
capability (try removing the capability, and you'll see the popup asking for authorization).
import io.appium.java_client.MobileBy;
import io.appium.java_client.MobileElement;
import io.appium.java_client.ios.IOSDriver;
import java.io.IOException;
import java.net.URL;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.html5.Location;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
public class Edition043_iOS_Permissions {
private String APP = "https://github.com/cloudgrey-io/the-app/releases/download/v1.8.0/TheApp-v1.8.0.app.zip";
private IOSDriver driver;
private WebDriverWait wait;
private By geolocation = MobileBy.AccessibilityId("Geolocation Demo");
@Before
public void setUp() throws IOException {
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("platformName", "iOS");
caps.setCapability("platformVersion", "12.1");
caps.setCapability("deviceName", "iPhone 8");
caps.setCapability("app", APP);
caps.setCapability("permissions", "{\"io.cloudgrey.the-app\": {\"location\": \"inuse\"}}");
driver = new IOSDriver<MobileElement>(new URL("http://localhost:4723/wd/hub"), caps);
wait = new WebDriverWait(driver, 10);
}
@After
public void tearDown() {
try {
driver.quit();
} catch (Exception ign) {}
}
@Test
public void testLocationPermissions() {
// first, set the geolocation to something arbitrary
double newLat = 49.2827, newLong = 123.1207;
driver.setLocation(new Location(newLat, newLong, 0));
// now navigate to the location demo
wait.until(ExpectedConditions.presenceOfElementLocated(geolocation)).click();
// if permissions were set correctly, we should get no popup and instead be
// able to read the latitude and longitude that were previously set
By newLatEl = MobileBy.AccessibilityId("Latitude: " + newLat);
By newLongEl = MobileBy.AccessibilityId("Longitude: " + newLong);
wait.until(ExpectedConditions.presenceOfElementLocated(newLatEl));
wait.until(ExpectedConditions.presenceOfElementLocated(newLongEl));
}
}
Don't forget to also have a look at the sample on GitHub.