Using Gradle to switch between local and Cloud Appium Java runs

In the past I was tasked with being the first person on a new project. This project was to implement an end to end UI automation testing framework for an anonymous company’s native iOS and Android apps.

The choice of tooling was Cucumber JVM using the Appium Java library underneath, and Gradle was the chosen build management system. (If you wish to know more details about this, we have an article detailing the setup and it can be found here)

At the start of the project I was also informed that once we had been able to source more people to join, we would need to be able to make the framework cater for local and cloud test runs. This was to make use of a device farm which the business was excited to see in action.

Running tests in a cloud setup is very a different ball game so we were left scratching our heads as to how we could achieve this. We came up with the below implementation which we thought should be shared.

  1. We used an @before hook to start our tests

We stored our drivers as getters and setters in their own class, and they were set during our app launch method. This method was stored in an InitaliseApp class which we invoked with the use of a@before hook. What the hook did before each test is check to see if the main driver getter returned null. If it did return null we would then call the launchApp method in the InitaliseApp class. An example of our hook and driver is below:

Before Hook

public class Hooks {

AppLogger logger = new AppLogger();
InitialiseApp initialiseApp = new InitialiseApp();
AppiumActions appiumActions = new AppiumActions();

@Before()
public void checkForDriver() {

if (InitialiseDriver.getDriver() == null) {
try {
initialiseApp.launchApp();
} catch (Exception e) {
logger.getLogger().fatal("FAILED TO LAUNCH APP");
e.printStackTrace();
}
} else {
appiumActions.resetApp();
}
}

InitialiseDriver class

public class InitialiseDriver {

private static ThreadLocal<WebDriver> driver = new ThreadLocal<>();

public static WebDriver getDriver() {
return driver.get();
}

public static void setDriver(WebDriver Driver) {
driver.set(Driver);
}
}

2. We created separate methods for local and cloud launches

When running in the cloud Appium needs a separate set of capabilites. We therefore created 2 methods to retrieve capabilities. One retrieved the capabilties for local runs, and the other for cloud runs.

Local capabilities retrieval

public DesiredCapabilities loadLocalAppiumCapabilities() {
DesiredCapabilities appiumCapabilities;
appiumCapabilities = DevicePropertiesCache.getInstance().getAppiumCapabilities();

String platformVersion = System.getProperty(APPIUM_PLATFORM_VERSION_CAPABILITY);

if (!(platformVersion == null)) {
appiumCapabilities.setCapability(APPIUM_PLATFORM_VERSION_CAPABILITY, platformVersion);
}

try {
switch (appiumCapabilities.getPlatform().toString().toLowerCase()) {
case ANDROID_PLATFORM:
appiumCapabilities.setCapability(MobileCapabilityType.APP, getCurrentWorkingDirectory() + "/src/main/resources/apps/androidApp.apk");
appiumCapabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, ANDROID_AUTOMATION_DRIVER);
appiumCapabilities.setCapability("appPackage", ANDROID_PACKAGE_NAME);
appiumCapabilities.setCapability("appActivity", INITIAL_ANDROID_APP_ACTIVITY);
break;
case IOS_PLATFORM:
appiumCapabilities.setCapability(MobileCapabilityType.APP, getCurrentWorkingDirectory() + "/src/main/resources/apps/iPhoneApp.ipa");
appiumCapabilities.setCapability("xcodeSigningId", IosConstants.XCODE_SIGNING_ID);
appiumCapabilities.setCapability("xcodeOrgId", IosConstants.XCODE_ORG_ID);
appiumCapabilities.setCapability("automationName", IosConstants.IOS_AUTOMATION_DRIVER);
break;
}

} catch (Exception e) {
e.printStackTrace();
}
return appiumCapabilities;
}

Cloud capabilties

public DesiredCapabilities loadCloudAppiumCapabilities() {
DesiredCapabilities appiumCapabilities;
appiumCapabilities = DevicePropertiesCache.getInstance().getAppiumCapabilities();

String platformName = appiumCapabilities.getPlatform().toString().toLowerCase();
switch (platformName) {
case ANDROID_PLATFORM:
<android capabilities>
break;
case IOS_PLATFORM:
<iOS capabilities>
break;
}
return appiumCapabilities;
}

3. We implemented a System variable to select which type of run we require

Using Gradle’s offerings we introduced a system variable called type . This allowed us to state if we wished to run our tests locally or in the cloud at runtime. The routing was handled by our parent launchApp method which was responsible for putting together the correct capabilities, server path and making the connection to Appium. The method is shown below:

public void launchApp() {
try {
DesiredCapabilities deviceCapabilities;
AppiumDriver<MobileElement> driver = null;
URL url;

String deviceType = System.getProperty(DEVICE_TYPE_ARGUMENT_NAME);

if (deviceType.equalsIgnoreCase(EMULATOR_DEVICE_TYPE)) {
checkForEmulatorPlatformVersion();
}

if (System.getProperty(DEVICE_TYPE_ARGUMENT_NAME).equalsIgnoreCase(CLOUD_DEVICE_TYPE)) {
logger.getLogger().info("Starting Testing session on Cloud");
url = getAppiumCloudUrl();
deviceCapabilities = loadCloudAppiumCapabilities();
} else {
logger.getLogger().info("Starting Testing session Locally");
url = new URL(APPIUM_LOCAL_LAUNCH_URL);
deviceCapabilities = loadLocalAppiumCapabilities();
}

String platformName = Objects.requireNonNull(deviceCapabilities).getPlatform().toString().toLowerCase();
switch (platformName) {
case ANDROID_PLATFORM:
driver = new AndroidDriver<>(url, deviceCapabilities);
InitialiseDriver.setAndroidDriver((AndroidDriver<MobileElement>) driver);
break;
case IOS_PLATFORM:
driver = new IOSDriver<>(url, deviceCapabilities);

InitialiseDriver.setIosDriver((IOSDriver<MobileElement>) driver);
break;
}

logger.getLogger().info("Launched " + platformName + "Appium testing session with the following details:\r\n" +
"Appium URL: " + url + "\r\nAppium Capabilities: "
+ deviceCapabilities);

InitialiseDriver.setDriver(driver);
} catch (Exception e) {
quitAppiumSession(DevicePropertiesCache.getInstance().getPlatformName());
e.printStackTrace();
System.exit(SYSTEM_FAILURE_EXIT_CODE);
}
}

Many thanks for reading this article. We hope that it may aid you in any projects which are either working on currently, or in the future.

My additional thanks also go to my peers Laxmikant Somni and Shekhar Majhanov who helped with all of the thrills and spills of this implementation.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store