Edition 112

How to Specify Screenshot Orientation for iOS Devices

If you do a lot of iOS testing using Appium, and have requirements especially for user flows that involve rotating the device to a different screen orientation, you might have noticed that screenshots retrieved by Appium in these cases might be incorrectly rotated. That is, the screenshot might not match the visible orientation of the device. This can be especially troublesome if you are using image-based testing, because Appium uses the coordinates from the screenshot to enable you to interact with found image regions!

Unfortunately, some of the time the screenshot comes back rotated incorrectly, Appium is not at fault; in these cases, XCUITest is just doing its thing, and Appium doesn't know whether the image is correct or not (it's not peeking at the "contents" of the screenshot to check that it's correct). But if you find yourself in this situation as a test author, you know what the screenshot is supposed to look like, you know which orientation your device is in, and you can tell Appium which orientation you want it to use for screenshots!

The screenshotOrientation setting

To accomplish this we make use of Appium's Settings API, and specifically a setting (released in Appium 1.17) called screenshotOrientation. There are 5 possible values this setting can take:

  • auto: don't enforce any screenshot orientation. Just pass along the screenshot given by XCUITest.
  • portrait: enforce portrait orientation for the screenshot.
  • portraitUpsideDown: enforce the "upside-down" orientation for the screenshot.
  • landscapeRight: enforce a landscape (turned to the right) orientation.
  • landscapeLeft: enforce a landscape (turned to the left) orientation.

The way we tell Appium to use a particular value is (in Java) with the driver.setSetting command:

driver.setSetting("screenshotOrientation", "landscapeRight")

Calling this will set the screenshot orientation value for the duration of the Appium session (or until I choose to call setSetting again with a different value).

Usage example

I was able to see this setting in action by writing a test that first turns a device to landscape mode, and then attempts to take a screenshot. If, before I start the test, I rotated the device manually, but in the opposite direction to the one Appium would normally rotate it (i.e., if I rotate it left), then I noticed that Appium's screenshots came back upside down!

Anyway, in this case, including the setting of the screenshotOrientation value enabled me to guarantee the screenshot would always come back in the correct orientation. Check out the full example below (and as always, the code is up on GitHub too:

public class Edition112_iOS_Screenshot_Orientation {
    private IOSDriver<WebElement> driver;
    private final static String PHOTOS = "com.apple.mobileslideshow";

    @Before
    public void setUp() throws MalformedURLException {
        DesiredCapabilities capabilities = new DesiredCapabilities();
        capabilities.setCapability("platformName", "iOS");
        capabilities.setCapability("platformVersion", "13.3");
        capabilities.setCapability("deviceName", "iPad Pro (12.9-inch) (3rd generation)");
        capabilities.setCapability("app", PHOTOS);
        capabilities.setCapability("simulatorTracePointer", true);
        driver = new IOSDriver<WebElement>(new URL("http://localhost:4723/wd/hub"), capabilities);
    }

    @After
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }

    @Test
    public void testScreenshots() throws Exception {
        Thread.sleep(2000);
        String desktop = System.getenv("HOME") + "/Desktop";
        driver.rotate(ScreenOrientation.LANDSCAPE);

        // this screenshot might be correct or it might be upside down
        File regularScreenshot = driver.getScreenshotAs(OutputType.FILE);

        // this screenshot should always be correct
        driver.setSetting("screenshotOrientation", "landscapeRight");
        File adjustedScreenshot = driver.getScreenshotAs(OutputType.FILE);
        FileUtils.copyFile(regularScreenshot, new File(desktop + "/screen1.png"));
        FileUtils.copyFile(adjustedScreenshot, new File(desktop + "/screen2.png"));
    }
}