There are so many test automation tools available on the market. Just to name a few – Selenium, Puppeteer, Appium, Calabash, Robotium. But which one to choose?

At TestDevLab we are working with mobile and desktop web UI test automation on a daily basis and there are two problems that we constantly have to deal with. First of all – the setup. In most cases you don’t just create one test scenario for one device and call it a day. You need proper project structure for scalability, improved classes for interaction with elements, some multithreading to run the tests on multiple devices, servers and emulators to automatically start at the beginning and so on.

The second big issue is platform support. It’s easy if you know that you will only need to automate tests on Chrome, or only on real iOS devices. But what if you started with Android devices and then the system you’re testing starts to support iOS as well? Or what if you’re using Appium, but want to do something on a desktop browser in the middle of the test? For example, access that versatile admin panel to check if the data you created from the mobile device is correctly saved in the database? These are the kind of issues that inspired us to create our own framework: TestUI.

With TestUI we offer you:

  • Readable APIs for Appium
  • Easier server and emulator setup
  • Some simple but expandable parallelisation
  • Cross platform testing
  • Cleaner code for interaction with elements

The main focus is on mobile testing with additional desktop browser support, but it can be easily used the other way around, for desktop browser testing with some additional mobile testing.

TestUI is based on Appium and Selenide, two of the most powerful, all-round test automation frameworks. Appium was an obvious choice due to its long list of supported platforms. And Selenide brings us the support of desktop web browsers thanks to Selenium WebDriver. We were also familiar with these two frameworks which gave us more confidence in our choice.

Not only that, but TestUI is already integrated with Cucumber and Allure test framework for that sweet, sweet BDD (Behavior Driven Development) and report generation.

Technical details

The first iteration of TestUI project is written in Java. Let’s look at how we simplified the code to the crucial Appium setup parts.

SETUP

When automating a test scenario with Appium, you would automatically start an Appium server and set the desired capabilities. Desired capabilities consist of your device and app details. Usually, it would look something like this:

// [Code for starting server with Appium]
private static void startServer(String port, String Bootstrap) {
    AppiumServiceBuilder builder;
    DesiredCapabilities cap;
    // Set Capabilities
    cap = new DesiredCapabilities();
    cap.setCapability("noReset", "false");
    // Build the Appium service
    builder = new AppiumServiceBuilder();
    builder.withIPAddress("127.0.0.1");
    builder.usingPort(Integer.parseInt(port));
    builder.withCapabilities(cap);
    builder.withArgument(GeneralServerFlag.SESSION_OVERRIDE);
    builder.withArgument(GeneralServerFlag.LOG_LEVEL, "error");
    builder.withArgument(AndroidServerFlag.BOOTSTRAP_PORT_NUMBER, Bootstrap);
    // Start the server with the builder
    setService(AppiumDriverLocalService.buildService(builder));
    getServices().get(getServices().size() - 1).start();
}
// [Code for setting desired capabilities with Appium]
public static DesiredCapabilities setAppAndroidCapabilities() {
    if (Configuration.emulatorName.isEmpty() && !getDeviceStatus(getDevice()).equals("device")) {
        throw new Error("The device status is " + getDeviceStatus(getDevice()) +
                " to use usb, you must allow usb debugging for this device: " + getDevice());
    }
    String devModel;
    if (Configuration.emulatorName.isEmpty()) {
        devModel = (getDeviceName().equals(getDevice()) ? getDeviceModel(getDevice()) : getDeviceName());
    } else {
        devModel = Configuration.emulatorName;
    }
    String deviceVersion = getDeviceVersion(getDevice())
    putAllureParameter("Device Model", devModel);
    // Created object of DesiredCapabilities class.
    DesiredCapabilities cap = new DesiredCapabilities();
    cap.setCapability(MobileCapabilityType.DEVICE_NAME, getDevice());
    cap.setCapability(MobileCapabilityType.PLATFORM_VERSION, deviceVersion);
    cap.setCapability(MobileCapabilityType.AUTOMATION_NAME, "Appium");
    cap.setCapability(MobileCapabilityType.PLATFORM_NAME, Platform.ANDROID);
    if (Configuration.androidAppPath.isEmpty()) {
        cap.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, Configuration.appActivity);
        cap.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, Configuration.appPackage);
    } else {
        String appPath = Configuration.androidAppPath.charAt(0) == '/' ? Configuration.androidAppPath :
            System.getProperty("user.dir") +"/"+ Configuration.androidAppPath;
        cap.setCapability("androidInstallPath", appPath);
        cap.setCapability("app", appPath);
    }
}

TestUI simplifies the setup process. Appium server is started and stopped automatically so you don’t have to worry about that ever again. As for the desired capabilities, we have shortened the required list by getting a few things automatically.

On Android and iOS devices we get the device name and version automatically. To use a specific device you have to add the device identifier (UDID for iOS / Serial Number for Android real devices / AVD name for Android emulators). If you don’t, it will just take one randomly.

Additionally for real iOS devices you have to specify your developer team details, “xcodeOrgId” and “xcodeSigningId”. If you’re not familiar with this kind of setup for Appium tests, more info is available here. As for the app, just provide the name of the app and store it in project root folder.

// [Code for Android setup with TestUI]
//@Test
@DisplayName("Android minimalistic desired capabilities")
public void testAndroidApp1() {
    Configuration.androidAppPath = "cornHub.apk";
    open();
}

@DisplayName("Android desired capabilities for specific device")
public void testAndroidApp2() {
    Configuration.androidAppPath = "cornHub.apk";
    Configuration.androidDeviceName = "ab1231231ab123123cde";
    open();
}

@DisplayName("Android emulator desired capabilities")
public void testAndroidApp3() {
    Configuration.androidAppPath = "cornHub.apk";
    Configuration.emulatorName = "Nexus_6_API_26";
    open();
}
// [Code for iOS setup with TestUI]
@DisplayName("iOS minimalistic desired capabilities for real device")
public void testIOSApp1() {
    Configuration.iOSTesting = true;
    Configuration.iOSAppPath = "cornHub.app";
    Configuration.xcodeOrgId = "G2348JAA24";
    Configuration.xcodeSigningId = "iPhone Developer";
    open();
}

@DisplayName("iOS desired capabilities for real specific device")
public void testIOSApp2() {
    Configuration.iOSTesting = true;
    Configuration.iOSAppPath = "cornHub.app";
    Configuration.xcodeOrgId = "G2348JAA24";
    Configuration.xcodeSigningId = "iPhone Developer";
    Configuration.UDID = "abd123efg123abc123egh456wgib54giternl4i5";
    open();
}

@DisplayName("iOS desired capabilities for random simulator")
public void testIOSApp3() {
    Configuration.iOSTesting = true;
    Configuration.iOSAppPath = "cornHub.app";
    open();
}

But of course, you are still able to specify all the desired capabilities or any additional optional ones by yourself!

// [Code for specifying all desired capabilities
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("app", "cornHub.apk");
capabilities.setCapability("deviceName", "ab1231231ab123123cde");
capabilities.setCapability("noReset", true);
Configuration.addMobileDesiredCapabilities = capabilities;

DRIVERS

For any test you can choose what kind of platform you would like to test on. TestUI will create Appium driver or Selenium WebDriver accordingly.

// [Code for open()]
open();
// or
open("https://www.facebook.com");

And at any point you can add more devices to the test.

// [Code for openNew()]
openNew();
// or
openNew("https://www.facebook.com");

A new driver instance will be assigned for every new added test device and multiple screenshots will be available in the report in case of test failure.

ELEMENTS

After you’ve set up everything, most of the automation will consist of interacting with elements on the app or web browser screen. Therefore, to make the code more readable we improved interaction with elements by providing you with our refined, built-in UIElements and UICollections classes.

// [Code for Elements & Collections]
// Definition
private UIElement catering = Ex("//android.widget.Button[@text=\"Catering\"]");
private UICollection nearMeCollection = EE(byId("lv.lattelecombpo.yellowpages:id/label"));
// [Code for Elements & Collections]
// Usage
landingPage.getCatering().given().waitFor(10).untilIsVisible().then().click();
landingPage.getNearMeCollection().get(1).then().waitFor(5).untilIsVisible().and().getText();

// Usage without optional methods given()/when()/then()/and()
landingPage.getCatering().waitFor(10).untilIsVisible().click();
landingPage.getNearMeCollection().get(1).waitFor(5).untilIsVisible().getText();

For individual elements you would use “UIElements” class and for lists – “UICollections”.

Conclusion

TestUI framework was developed by TestDevLab engineer Alvaro Laserna and is already being used by some of our colleagues at TestDevLab, who were struggling with the same issues that we were – annoying setup and insufficient platform support. And now you can try it yourself as well!

We are giving it away as an open source project. Open source projects have made our lives easier so we are giving back to the community.

Click here to check out the project: github.com/testdevlab/TestUI,  and we would be happy to hear your feedback, that you can send to our email testui@testdevlab.com.

We have big plans for this project, including other language support and image recognition. And of course improving the base to be as stable as possible, while keeping the simplicity and versatility! Ruby support coming this year!