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