Appium Cucumber Parallel Execution with TestNG

How to run Appium Tests in Parallel with Cucumber and TestNG? Yes, in this post, I will show you how we can achieve this! I will explain to you a mobile test automation project that contains TestNG, Appium, Cucumber. In this project, you can do your modifications to write your mobile test automation tests and run them in parallel. Before starting this article, I highly suggest you, check the below appium articles. I will extend Appium Parallel Testing architecture and add the cucumber support in this article.

Cucumber IntelliJ Plugins

In order to add Appium Cucumber support to our project in IntelliJ IDEA, we need to install “Gherkin” and “Cucumber for JAVA” plugins. You will download and then go to settings -> plugins and then install these plugins.

cucumber plugin settings on intellij

cucumber for java plugin

gherking plugin for intellij

Here, you can find also details: IntelliJ Cucumber Support

Cucumber Dependencies

After this step, you need to add the below Cucumber Dependencies to your pom.xml.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>appium-parallel-tests</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <appium-java-client-version>7.5.1</appium-java-client-version>
        <selenium-java-version>3.141.59</selenium-java-version>
        <testng-version>7.4.0</testng-version>
        <lombok-version>1.18.20</lombok-version>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/io.appium/java-client -->
        <dependency>
            <groupId>io.appium</groupId>
            <artifactId>java-client</artifactId>
            <version>${appium-java-client-version}</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium-java-version}</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.testng/testng -->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>${testng-version}</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok-version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-java -->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>6.10.3</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-jvm -->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-jvm</artifactId>
            <version>6.10.3</version>
            <type>pom</type>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-core -->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-core</artifactId>
            <version>6.10.3</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.cucumber/gherkin -->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>gherkin</artifactId>
            <version>18.1.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-testng -->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-testng</artifactId>
            <version>6.10.3</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-picocontainer -->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-picocontainer</artifactId>
            <version>6.10.3</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/net.masterthought/cucumber-reporting -->
        <dependency>
            <groupId>net.masterthought</groupId>
            <artifactId>cucumber-reporting</artifactId>
            <version>5.5.3</version>
        </dependency>
    </dependencies>
</project>

Cucumber Files and Folders

Then, I created a cucumber package and under this package, I created below packages and files:

appium cucumber parallel testing with testng

  • In the features package, we will hold all of our feature files.
  • In the steps package, we will write the steps of our features. BaseSteps file is a common/base file of all steps files.
  • In the tests package, we will have cucumber test classes.

I will explain the details of the above package and files. ;)

Appium Cucumber Parallel Test Automation with TestNG Scenario 

Our test scenario is a successful login of isinolsun application.

I will share some classes’ codes below but you can find the project code here: https://github.com/swtestacademy/appium-parallel-tests/tree/appium-cucumber-testng-parallel-testing

In the feature file, we should write our scenario in Gherkin syntax.

@Candidate
  Feature: Candidate can see the job's details.

    Scenario Outline: Candidate opens a job's page and see its details.
      Given Candidate is on the jobs listing screen
      When Candidate opens a job which has index of <index>
      Then Candidate should see the jobs details
      Examples: First and Second jobs
        | index |
        | 0     |
        | 1     |

BaseSteps file is the base class of all steps files. In this class, we declared all common variables and functions for all steps files.

public class BaseSteps {
    protected SplashScreen        splashScreen;
    protected SelectionScreen     selectionScreen;
    protected CandidateMainScreen candidateMainScreen;
    protected JobScreen           jobScreen;

    public void setupScreens(AndroidDriver<MobileElement> driver) {
        splashScreen = new SplashScreen(driver);
        selectionScreen = new SelectionScreen(driver);
        candidateMainScreen = new CandidateMainScreen(driver);
        jobScreen = new JobScreen(driver);
    }
}

We need to write the detailed actions in the steps file. In the JobsDetailsSteps file, I wrote all actions related to our steps.

public class JobsDetailsSteps extends BaseSteps {

    @Before
    public void setupLoginSteps() {
        setupScreens(ThreadLocalDriver.getTLDriver());
    }

    @Given("Candidate is on the jobs listing screen")
    public void candidateIsOnTheJobsListingScreen() {
        splashScreen.skipSplashScreen();
        selectionScreen.clickIamSearchingJob();
        candidateMainScreen.allowNotification();
    }

    @When("Candidate opens a job which has index of {int}")
    public void candidateOpensTheIndexOfJobScreen(int index) {
        candidateMainScreen.clickToJob(index);
    }

    @Then("Candidate should see the jobs details")
    public void candidateShouldSeeTheJobsDetails() {
        jobScreen.assertToolBarTitleIsExpected();
    }
}

and now it is time to write our test runner file. In the below test file, we will run all features and generate cucumber reports. If you want, you can also modify this class to run specific cucumber features.

@CucumberOptions(
    monochrome = true,
    tags = "@Candidate",
    features = "src/test/java/cucumber/features",
    glue = "cucumber.steps",
    publish = true
)
public class TestRunner extends BaseTest {
    private TestNGCucumberRunner testNGCucumberRunner;

    @BeforeClass(alwaysRun = true)
    public void setUpClass() {
        testNGCucumberRunner = new TestNGCucumberRunner(this.getClass());
    }

    @Test(groups = "cucumber", description = "Run Cucumber Features.", dataProvider = "scenarios")
    public void scenario(PickleWrapper pickleWrapper, FeatureWrapper featureWrapper) {
        testNGCucumberRunner.runScenario(pickleWrapper.getPickle());
    }

    @DataProvider
    public Object[][] scenarios() {
        return testNGCucumberRunner.provideScenarios();
    }

    @AfterClass(alwaysRun = true)
    public void tearDownClass() {
        testNGCucumberRunner.finish();
    }
}

In BaseTest class, we do test setup and teardown operations.

public class BaseTest {
    private final DesiredCapabilitiesUtil desiredCapabilitiesUtil = new DesiredCapabilitiesUtil();

    @BeforeMethod
    @Parameters({ "udid", "platformVersion" })
    public void setup(String udid, String platformVersion) throws IOException {
        DesiredCapabilities caps = desiredCapabilitiesUtil.getDesiredCapabilities(udid, platformVersion);
        ThreadLocalDriver.setTLDriver(new AndroidDriver<>(new URL("http://127.0.0.1:4444/wd/hub"), caps));
    }

    @AfterMethod
    public synchronized void teardown() {
        ThreadLocalDriver.getTLDriver().quit();
    }
}

We have also two utility classes. One of them is for DesiredCapabilities.

public class DesiredCapabilitiesUtil {
    public DesiredCapabilities getDesiredCapabilities(String udid, String platformVersion) {
        DesiredCapabilities desiredCapabilities = new DesiredCapabilities();
        desiredCapabilities.setCapability("udid", udid);
        desiredCapabilities.setCapability("platformVersion", platformVersion);
        desiredCapabilities.setCapability("platformName", "Android");
        desiredCapabilities.setCapability("appPackage", "com.isinolsun.app");
        desiredCapabilities.setCapability("appActivity", "com.isinolsun.app.activities.SplashActivity");
        desiredCapabilities.setCapability("skipUnlock", "true");
        desiredCapabilities.setCapability("noReset", "false");
        return desiredCapabilities;
    }
}

and one is for ThreadLocal Driver.

public class ThreadLocalDriver {
    private static final ThreadLocal<AndroidDriver<MobileElement>> tlDriver = new ThreadLocal<>();

    public static synchronized void setTLDriver(AndroidDriver<MobileElement> driver) { tlDriver.set(driver); }

    public static synchronized AndroidDriver<MobileElement> getTLDriver() {
        return tlDriver.get();
    }
}

And we should change our TestNG XML file as shown below for parallel execution.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">

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

    <test name="emulator-5554">
        <parameter name="udid" value="emulator-5554"/>
        <parameter name="platformVersion" value="11"/>
        <classes>
            <class name="cucumber.tests.TestRunner"/>
        </classes>
    </test>

    <test name="emulator-5556">
        <parameter name="udid" value="emulator-5556"/>
        <parameter name="platformVersion" value="11"/>
        <classes>
            <class name="cucumber.tests.TestRunner"/>
        </classes>
    </test>
</suite>

When you run the TestNG.xml file, your mobile tests run in parallel. In this framework, you can both use Page Object Model and Cucumber together with TestNG. You can find the other files on the GitHub project page.

And you can go and see the reports after the test execution.

cucumber report on console

cucumber html report

Youtube Demo Video

Github Project

https://github.com/swtestacademy/appium-parallel-tests/tree/appium-cucumber-testng-parallel-testing

I hope you like this post. I would like to hear your feedback. Please share your comments with us.

Thanks.
Onur Baskirt

15 thoughts on “Appium Cucumber Parallel Execution with TestNG”

  1. Hi Onur,
    Such a great tutorial by u. I need your help regarding jira xray integration. Can u help me out by, i want to know how to integrate this with jira and execute it from jira.Plz help me.

    Reply
  2. Hi Onur,
    Thank you for the tutorial!
    I have one problem, my tests are not really running parallel… One phone just goes through a step and then after that one the next phone does the step..
    Do I have to work with threads or something? I’m a student and I have to work this out for a project, but I can’t really wrap my head around it….
    I would really appreciate your help… !

    Reply
    • Hi Milan, actually they were working very well. I also post its working video on youtube. However, selenium, appium, testNG always changing. Maybe in newer versions, there are some incompatibilities exist. Next week, I will be at work, I will check this. Please try to do below items and rerun your tests:
      1- Remove “synchronized” keyword at ThreadLocalDriver.java setTLDriver and getTLDriver methods.
      2- Try to get driver as follows in test classes
      Webdriver driver = ThreadLocalDriver.getTLDriver();
      Then instantiate screen classes with this local driver variable.

      Maybe these tricks will fix your problem.

      Reply
  3. setup(cucumber.tests.TestRunner)
    org.testng.TestNGException:
    Parameter ‘udid’ is required by BeforeMethod on method setup but has not been marked @Optional or defined

    Reply
  4. hello, how to run 2 scenario? i already add 2 tags (candidate) and 2 scenario on folder feature, but when i run the code, my emulator not close after success run 1 scenario.. i want after success run 1 scenario, the emulator close and run again the next scenario.. please help me

    Reply
  5. I’m trying to use your example for browser testing, but the constructor call
    new TestNGCucumberRunner(this.getClass())
    returns null , so the resulting runner is also null. Am I missing something obvious?

    Reply
  6. I am trying to get this to work across different OS, so instead of two android devices I have one iPhone and one Android with each under their own tag. It works for the most part but they keep grabbing eachothers locators. I built my screen objects to find elements using Annotions
    ex:
    @iOSXCUITFindBy(accessibility = “toolbarTitle”)
    @AndroidFindBy(id = “com.isinolsun.app:id/toolbarTitle”)
    private WebElement toolBarTitleBy;

    So sometimes the iOS test will use the Android Id locator and vise versa.

    Also I get this error randomly, usually when one thread runs its fails a test method
    java.lang.RuntimeException: java.io.IOException: Stream closed

    Reply
    • I figured out my Stream closed problem, I had the TestNGCucumberRunner variable set as static.

      Quick Note you no longer need to write out your own @BeforeClass, @Test, @DataProvider in the runner class or even set the private TestNGCucumberRunner testNGCucumberRunner. Just have BaseTest extend AbstractTestNGCucumberTests. That class has all the necessary java methods and annotations per configured.

      Doing that you can pretty much leave the TestRunner Class empty.

      Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.