iOS Test Automation on Windows with Appium & TestProject!

The Challenge

One of the major obstacles with testing iOS apps is the need for iOS devices or Xcode to run the app on a Simulator.
Apple doesn’t support running Xcode (an Objective-C and Swift IDE) on any other operating system or hardware rather than macOS on Macs.
When it comes to Appium tests, Xcode is also required to build, sign, provision and run a special project that operates as a driver, executing the automation.

The Solution

TestProject framework enhances Appium driver and now enables you to run Appium tests on Windows operating system without the need for macOS.
The first thing you have to do is to get a TestProject account, no worries – it’s free!

Downloads

Then you need the TestProject Agent to be installed and running on your computer.
You will also require Apple’s iTunes application as it installs the required USB drivers for iOS devices.

Configuration

TestProject makes the driver signing and provisioning procedure easy.
First, download and install TestProject agent, you have to configure your TestProject account with your iOS certificate and provisioning profile.
You can do this in the iOS section under account settings in the TestProject application website.

Team ID

The first thing you need to do is to provide your Apple’s Developer Program Team ID.
Team ID is a 10-character string that’s generated by Apple to uniquely identify your team.
You can find your Team ID here: https://developer.apple.com/account/#/membership

 

Team ID

Certificate Signing Request / Certificate

In order to allow us signing the driver with your certificate, you need to create a Certificate Signing Request (CSR).
On the iOS setting page, generate a new one and download a certificate signing request file.

Open https://developer.apple.com/account/ios/certificate/create to upload the CSR and download the generated certificate.

Upload the certificate to the appropriate section in the iOS setting page on the TestProject application website.

Mobile Provisioning Profile

The provisioning profile is used by Apple to list the eligible devices where the driver can be installed.

Navigate to https://developer.apple.com/account/ios/profile/limited/create and create an iOS App Development provisioning profile.
Continue the wizard and select the XC Wildcard when prompted for App ID.
Continue the wizard and select the certificate you have created earlier.
Select all devices you would like to allow being used for automated testing.
Give this profile a meaningful name.
Download the newly generated profile to your computer

Upload the profile to the appropriate section in the iOS setting page on the TestProject application website.

Usage

Now you have 2 options:

  1. Developing Appium tests for iOS by using TestProject SDK.
  2. Recording your iOS test from Windows using TestProject recording studio.

**In both options the tests will be running locally on your device by TestProject agent.

TestProject SDK

Download TestProject SDK.
Below is a short example of how to develop a simple test:

package io.testproject.appium.tests;

import io.testproject.examples.sdk.java.pages.LoginPage;
import io.testproject.examples.sdk.java.pages.ProfilePage;
import io.testproject.java.annotations.v2.Test;
import io.testproject.java.annotations.v2.TestParameter;
import io.testproject.java.sdk.v2.drivers.AndroidDriver;
import io.testproject.java.sdk.v2.enums.ExecutionResult;
import io.testproject.java.sdk.v2.exceptions.FailureException;
import io.testproject.java.sdk.v2.tests.AndroidTest;
import io.testproject.java.sdk.v2.tests.helpers.AndroidTestHelper;

@Test(name = "Demo Test")
public class DemoTest implements AndroidTest {

    @TestParameter
    public String name;

    @TestParameter
    public String password;

    @TestParameter
    public String country;

    @TestParameter
    public String address;

    @TestParameter
    public String email;

    @TestParameter
    public String phone;

    public ExecutionResult execute(AndroidTestHelper helper) throws FailureException {
        AndroidDriver driver = helper.getDriver();

        driver.resetApp();

        LoginPage loginPage = new LoginPage(driver);
        if (!loginPage.isDisplayed()) {
            return ExecutionResult.FAILED;
        }

        loginPage.login(name, password);
        ProfilePage profilePage = new ProfilePage(driver);
        if (!profilePage.isDisplayed()) {
            return ExecutionResult.FAILED;
        }

        profilePage.updateProfile(country, address, email, phone);
        if (!profilePage.isSaved()) {
            return ExecutionResult.FAILED;
        }

        return ExecutionResult.PASSED;
    }
}

This Test is implemented using the Page Object Model (POM) pattern that addresses several issues:

  • Mix of test logic and UI actions are resolved by separation of duties to OOP classes (Login and Profile pages).
  • Coupled design is treated to decoupling objects library from decision taking in tests.
  • Implementing POM makes it easy to maintain the tests having one authority to manage page elements and UI actions.
  • There is no more code redundancy such as duplicate findElement calls and UI manipulations.
  • POM removes an unnecessary complexity by implementing self-explanatory code, thanks to FindBy annotations.

Below is the source code of two pages used in the test implementing POM.

Login Page:

package io.testproject.examples.sdk.java.pages;

import io.appium.java_client.android.AndroidElement;
import io.appium.java_client.android.AndroidKeyCode;
import io.appium.java_client.pagefactory.AndroidFindBy;
import io.appium.java_client.pagefactory.AppiumFieldDecorator;
import io.testproject.java.sdk.v2.support.PageFactory;
import io.testproject.java.sdk.v2.drivers.AndroidDriver;

public class LoginPage {

    private AndroidDriver driver;

    public LoginPage() {
    }

    public LoginPage(AndroidDriver driver) {
        this.driver = driver;
        PageFactory.initElements(new AppiumFieldDecorator(driver), this);
    }

    @AndroidFindBy(className = "UIAKeyboard")
    private AndroidElement keyboard;

    @AndroidFindBy(id = "name")
    private AndroidElement nameElement;

    @AndroidFindBy(id = "password")
    private AndroidElement passwordElement;

    @AndroidFindBy(id = "login")
    private AndroidElement loginElement;

    public boolean isDisplayed() {
        return loginElement.isDisplayed();
    }

    public void typeName(String name) {
        nameElement.sendKeys(name);
    }

    public void typePassword(String password) {
        passwordElement.sendKeys(password);
    }

    public void clickLogin() {
        loginElement.click();
    }

    public void hideKeyboardIfVisible() {
        if (keyboard != null) {
            driver.pressKeyCode(AndroidKeyCode.KEYCODE_ESCAPE);
        }
    }

    public void login (String name, String password) {
        hideKeyboardIfVisible();
        typeName(name);
        typePassword(password);
        clickLogin();
    }
}

Profile Page:

package io.testproject.examples.sdk.java.pages;


import io.appium.java_client.MobileBy;
import io.appium.java_client.android.AndroidElement;
import io.appium.java_client.android.AndroidKeyCode;
import io.appium.java_client.pagefactory.AndroidFindBy;
import io.appium.java_client.pagefactory.AppiumFieldDecorator;
import io.testproject.java.sdk.v2.support.PageFactory;
import io.testproject.java.sdk.v2.drivers.AndroidDriver;

import java.util.concurrent.TimeUnit;

public class ProfilePage {

    private AndroidDriver driver;

    public ProfilePage() {
    }

    public ProfilePage(AndroidDriver driver) {
        this.driver = driver;
        PageFactory.initElements(new AppiumFieldDecorator(driver, 10, TimeUnit.SECONDS), this);
    }

    @AndroidFindBy(className = "UIAKeyboard")
    private AndroidElement keyboard;

    @AndroidFindBy(id = "greetings")
    private AndroidElement greetingsElement;

    @AndroidFindBy(id = "logout")
    private AndroidElement logoutElement;

    @AndroidFindBy(id = "country")
    private AndroidElement countryElement;

    @AndroidFindBy(id = "address")
    private AndroidElement addressElement;

    @AndroidFindBy(id = "email")
    private AndroidElement emailElement;

    @AndroidFindBy(id = "phone")
    private AndroidElement phoneElement;

    @AndroidFindBy(id = "save")
    private AndroidElement saveElement;

    @AndroidFindBy(id = "saved")
    private AndroidElement savedElement;

    public boolean isDisplayed() {
        return greetingsElement.isDisplayed();
    }

    public void typeCountry(String country) {
        countryElement.sendKeys(country);

    }

    public void typeAddress(String address) {
        addressElement.sendKeys(address);
    }

    public void typeEmail(String email) {
        emailElement.sendKeys(email);
    }

    public void typePhone(String phone) {
        phoneElement.sendKeys(phone);
    }

    public void hideKeyboardIfVisible() {
        if (keyboard != null) {
            driver.pressKeyCode(AndroidKeyCode.KEYCODE_ESCAPE);
        }
    }

    public void updateProfile(String country, String address, String email, String phone) {
        hideKeyboardIfVisible();
        typeCountry(country);
        typeAddress(address);
        typeEmail(email);
        typePhone(phone);
        save();
    }

    public void save() {
        saveElement.click();
    }

    public boolean isSaved() {
        return savedElement.isDisplayed();
    }

}

You can find more examples on TestProject GitHub.

Compile and upload the JAR to TestProject account.

TestProject_Create Test

Execute the Test.

Recording on Windows

You can record your iOS test in a live app from Windows using TestProject recording studio:

TestProject recording studio

During recording you can conveniently use the TestProject Elements Inspector and Elements Locator to verify your location strategies:

 

Go ahead and try it out and share your experience in the comment section below  😉