How to Automate Appium Java Tests In Parallel using TestNG

  November 21, 2019

The beauty of Appium for mobile testing is that its tests can be written in any programming language including Python, Ruby, Java, JavaScript and C#. While we have covered extensive Appium tutorials, in this blog post we’ll walk through how to automate Appium tests in parallel against our real devices using TestNG Java sample tests (you can find our Appium JUnit sample for parallel testing here).

What is and Why to Use TestNG?

TestNG, where NG denotes Next Generation, is an automated testing framework that is inspired by both JUnit and NUnit.

It is designed to simplify a wide range of testing needs including unit testing, functional testing and integration testing. And it gives mobile developers and testers the ability to write more flexible and powerful tests.

Benefits of using TestNG

TestNG is similar to JUnit but introduces some new features that make it more powerful and easier to use. Below are some of the benefits Appium Java users can enjoy with TestNG:

  • More annotations: TestNG has an extensive choice of annotations. See a full list of annotations available in TestNG.
  • Test Grouping: The groups attribute with the @Test annotation eases the grouping of test cases.
  • Test Prioritization: Using the priority attribute to set different priorities for each test case.
  • Parameterized Tests: Using parameterized tests makes it easy to run the same test over and over using different values.
  • Parallel Testing: It’s possible and easy to parallelize test execution with the parallel attribute. We’re going to use it in our sample below.

The Basic Setup for Running Appium Tests in Java Using TestNG

First of all, to run tests simultaneously on different devices, you should use ThreadLocal in order to save several drivers at one time.

If you use only one driver instance, the test will be run on the one device only because the diver will use the last created session.

Firstly, you should create the ThreadLocal object:

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

Then, you have to add the driver in ThreadLocal using the driver.set(…) method:

driver.set(new RemoteWebDriver(new URL("remote_hub_url_here"), capabilities));

And now, if you want to get the driver which is active at the moment, you can use the driver.get() method:

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

Dealing with Appium Desired Capabilities

In order to run RemoteWebDriver, you should install the needed capabilities. They can vary depending on the device where the test has to be performed. Mostly, specialists use the basic ones, such as deviceName, platformName, browserName, platformVersion.

Here we have capabilities that are needed to run the tests on Bitbar cloud for Android devices:

DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("bitbar_apiKey", <YOUR_BITBAR_API_KEY>);
capabilities.setCapability("bitbar_testrun", "Bitbar test run");
capabilities.setCapability("bitbar_project", "Bitbar test project");
capabilities.setCapability("bitbar_device", bitbarDevice);
capabilities.setCapability("browserName", browserName);
capabilities.setCapability("platformName", platformName);
capabilities.setCapability("deviceName", deviceName);

Properties will be as follows:

bitbar_device = Google Pixel XL
browser_name = chrome
platform_name = Android
device_name = Android Phone

In the case with iOS devices, you have to add one more capability:

capabilities.setCapability("automationName", automationName);

Properties file will be like this:

bitbar_device = iPhone7 11.3.1 (ios02)
browser_name = safari
platform_name = iOS
device_name = iPhone device
automation_name = XCUITest

The final configuration of capabilities will be the following:

DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("bitbar_apiKey", <YOUR_BITBAR_API_KEY>);
capabilities.setCapability("bitbar_testrun", this.getClass().getSimpleName());
capabilities.setCapability("bitbar_project", xmlTestName);
capabilities.setCapability("bitbar_device", bitbarDevice);
capabilities.setCapability("browserName", browserName);
capabilities.setCapability("platformName", platformName);
capabilities.setCapability("deviceName", deviceName);
if (automationName != null)
   capabilities.setCapability("automationName", automationName);

driver.set(new RemoteWebDriver(new URL("https://appium.bitbar.com/wd/hub/"), capabilities));

To get more information about needed capabilities for various devices, please visit Bitbar Capabilities Creator.

Get Started with a Simple Appium Test Using Java TestNG

Now let’s create a simple Google search test:

import base.BaseTest;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.util.List;

public class SimpleGoogleTest extends BaseTest {

  private static final String GOOGLE_SEARCH_URL = "https://www.google.com/";

  private static By searchInput = By.cssSelector("[name='q']");
  private static By searchButton = By.cssSelector(".Tg7LZd");

  private static By searchResultTable = By.id("cnt");
  private static By searchResultItemHeader = By.cssSelector("#rso div.zlBHuf");

  @Test
  public void searchForBitbar() throws InterruptedException {

    // Get driver instance
    WebDriver driver = BaseTest.getDriver();

    // Open main Google Search page
    driver.get(GOOGLE_SEARCH_URL);
    Assert.assertEquals(driver.getCurrentUrl(), GOOGLE_SEARCH_URL);

    // Input "Bitbar" search term, click on search button and wait for page to be loaded
    driver.findElement(searchInput).sendKeys("Bitbar");
    driver.findElement(searchButton).click();
    Thread.sleep(2000);
    // Check that Search result table present with search results
    Assert.assertTrue(driver.findElements(searchResultTable).size() > 0);

    // Check that at least one search result item contains word "Bitbar"
    List<WebElement> resultItems = driver.findElements(searchResultItemHeader);
    boolean isPresent = false;
    for (WebElement item : resultItems) {
      if (item.getText().contains("Bitbar"))
        isPresent = true;
    }
  Assert.assertTrue(isPresent);
  }
}

Also, we will create a test suite to run two tests simultaneously:

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">

<suite name="Test Suite" thread-count="2" parallel="tests">

  <test name="Simple Google Test Google Pixel XL">
    <parameter name="Device" value="GooglePixelXL"/>
    <classes>
      <class name="SimpleGoogleTest"/>
    </classes>
  </test>

  <test name="Simple Google Test iPhone7 11.3.1">
    <parameter name="Device" value="iPhone7_11.3.1"/>
    <classes>
      <class name="SimpleGoogleTest"/>
    </classes>
  </test>

</suite>

File with driver configurations will be as follows:

package base;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testng.ITestContext;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Parameters;
import utils.Constants;
import utils.Reporter;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

public class BaseTest {

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

  private String deviceName;
  private String bitbarDevice;
  private String browserName;
  private String platformName;
  private String automationName;

  @BeforeMethod
  @Parameters(value = {"Device"})
  public void setUp(String device, ITestContext context) throws MalformedURLException {
    setupRemoteDriver(device, context.getCurrentXmlTest().getName());

    getDriver().manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
  }

  private void setupRemoteDriver(String device, String xmlTestName) throws MalformedURLException {
    loadProperties(device);

    DesiredCapabilities capabilities = new DesiredCapabilities();
    capabilities.setCapability("bitbar_apiKey", <YOUR_BITBAR_API_KEY>);
    capabilities.setCapability("bitbar_testrun", this.getClass().getSimpleName());
    capabilities.setCapability("bitbar_project", xmlTestName);
    capabilities.setCapability("bitbar_device", bitbarDevice);
    capabilities.setCapability("browserName", browserName);
    capabilities.setCapability("platformName", platformName);
    capabilities.setCapability("deviceName", deviceName);
    if (automationName != null)
      capabilities.setCapability("automationName", automationName);

    driver.set(new RemoteWebDriver(new URL("https://appium.bitbar.com/wd/hub/"), capabilities));
  }

  private void loadProperties(String device) {
    FileInputStream propertiesFIS;
    Properties properties = new Properties();
    String propertiesFilePath = String.format("src/test/resources/properties/%s.properties", device);

    try {
      propertiesFIS = new FileInputStream(propertiesFilePath);
      properties.load(propertiesFIS);

      bitbarDevice = properties.getProperty("bitbar_device");
      browserName = properties.getProperty("browser_name");
      platformName = properties.getProperty("platform_name");
      deviceName = properties.getProperty("device_name");
      if (properties.getProperty("automation_name") != null)
      automationName = properties.getProperty("automation_name");

    } catch (IOException e) {
      Reporter.err("Properties file is missing or invalid! Check path to file: " + propertiesFilePath);
      System.exit(0);
    }
  }

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

  @AfterMethod
  public void tearDown() {
    if (getDriver() != null)
      getDriver().quit();
  }
}

Conclusion

TestNG is a great testing framework of choice for Appium tests written in Java. In case you have implemented your Appium TestNG tests and are looking for a mobile app testing platform for automating it in parallel, try to run your tests on Bitbar Cloud.