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.

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.

Check out this step-by-step tutorial for more details on configuring your iOS certificate and provisioning profile: https://blog.testproject.io/2019/01/29/setup-ios-test-automation-windows-without-mac/

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 iOS Test executed on TestProject Demo App:

package io.testproject.examples.sdk.tests;

import io.testproject.examples.sdk.pages.LoginPage;
import io.testproject.examples.sdk.pages.ProfilePage;
import io.testproject.java.sdk.v2.drivers.IOSDriver;
import io.testproject.java.sdk.v2.enums.ExecutionResult;
import io.testproject.java.sdk.v2.exceptions.FailureException;
import io.testproject.java.sdk.v2.tests.IOSTest;
import io.testproject.java.sdk.v2.tests.helpers.IOSTestHelper;

public class BasicTest implements IOSTest {

    public String name = "John Smith";
    public String password = "12345";
    public String country = "USA";
    public String address = "Street number and name";
    public String email = "[email protected]";
    public String phone = "+1 555 555 55";

    public ExecutionResult execute(IOSTestHelper helper) throws FailureException {
        IOSDriver driver = helper.getDriver();

        driver.resetApp();

        LoginPage loginPage = new LoginPage(driver);
        loginPage.login(name, password);

        ProfilePage profilePage = new ProfilePage(driver);
        profilePage.updateProfile(country, address, email, phone);

        return profilePage.isSaved() ? ExecutionResult.PASSED : ExecutionResult.FAILED;
    }
}

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.pages;

import io.appium.java_client.ios.IOSElement;
import io.appium.java_client.pagefactory.AppiumFieldDecorator;
import io.appium.java_client.pagefactory.iOSFindBy;
import io.testproject.java.sdk.v2.drivers.IOSDriver;
import io.testproject.java.sdk.v2.support.PageFactory;

public class LoginPage {

    private IOSDriver driver;
    @iOSFindBy(id = "name")
    private IOSElement nameElement;
    @iOSFindBy(id = "password")
    private IOSElement passwordElement;
    @iOSFindBy(id = "login")
    private IOSElement loginElement;

    public LoginPage() {
    }

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

    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 login(String name, String password) {
        typeName(name);
        typePassword(password);
        clickLogin();
    }
}

Profile Page:

package io.testproject.examples.sdk.pages;

import io.appium.java_client.ios.IOSElement;
import io.appium.java_client.pagefactory.AppiumFieldDecorator;
import io.appium.java_client.pagefactory.iOSFindBy;
import io.testproject.java.sdk.v2.drivers.IOSDriver;
import io.testproject.java.sdk.v2.support.PageFactory;
import org.openqa.selenium.By;

import java.util.concurrent.TimeUnit;

public class ProfilePage {

    private IOSDriver driver;
    @iOSFindBy(id = "greetings")
    private IOSElement greetingsElement;
    @iOSFindBy(id = "logout")
    private IOSElement logoutElement;
    @iOSFindBy(id = "country")
    private IOSElement countryElement;
    @iOSFindBy(id = "address")
    private IOSElement addressElement;
    @iOSFindBy(id = "email")
    private IOSElement emailElement;
    @iOSFindBy(id = "phone")
    private IOSElement phoneElement;
    @iOSFindBy(id = "save")
    private IOSElement saveElement;
    @iOSFindBy(id = "saved")
    private IOSElement savedElement;

    public ProfilePage() {
    }

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

    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 hideKeyboard() {
        driver.hideKeyboard();
    }

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

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

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

    public By getPhoneElement() {
        return By.id("phone");
    }
}
  • You can find a full source code including Maven/Gradle files and 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  😉