TestNG | Parallel tests (each test case executed only once)

The approach is the same as in parallel tests with NUnit, here translated to Java TestNG.

It is applicable when we have several test cases in a class, and we wish to execute those in parallel on available devices of specific type - with each test case executed only once, as in the following table.

device # 1

test # 1

test # 4

device # 2

test # 2

test # 5

device # 3

test # 3


To achieve this, we need to set the parallel scope to "methods" level in the testng.xml file (as opposed to Parallel Execution with TestNG where the parallel scope is "tests" level and all the test cases in a class run once per device.)

Following is a sample example of such a testng.xml file, where we specify the type of devices on which to execute the test cases via the device model parameter.

Copy
<?xml version="1.0" encoding="UTF-8"?>
<suite name="Suite" parallel="methods">
    <test name="MyTest">
    <parameter name="deviceModel" value="Galaxy S8" />
        <classes>
            <class name="testng.MyTest"/>
        </classes>
    </test> <!-- Test -->
</suite> <!-- Suite -->

As explained in the article about NUnit, we need to create a separate Test Base class. This is where we define the driver and create/destroy the driver session, and this way, we can separate the driver session thread per test case level, not per class level as in Parallel Execution with TestNG.

Following is a very simple example of such Test Base class, with Reportium included.

Copy
package testng;

import java.net.MalformedURLException;
import java.net.URL;

import org.openqa.selenium.remote.DesiredCapabilities;

import com.perfecto.reportium.client.ReportiumClient;
import com.perfecto.reportium.client.ReportiumClientFactory;
import com.perfecto.reportium.model.PerfectoExecutionContext;
import com.perfecto.reportium.model.Project;
import com.perfecto.reportium.test.TestContext;
import com.perfecto.reportium.test.result.TestResultFactory;

import io.appium.java_client.AppiumDriver;
import io.appium.java_client.android.AndroidDriver;

public class TestSetup {
    
    private AppiumDriver driver;
    private ReportiumClient reportiumClient;
    
    //replace <your_security_token> with your security token for the cloud
    //see ../Perfecto/automation-testing/generate_security_tokens.htm
    private final String securityToken = "<your_security_token>";
    
    //replace <your_cloud_name> with the name of your cloud
    private final String cloudName = "<your_cloud_name>";
    
    //create the driver session with the Constructor of the TestSetup class
    public TestSetup(String deviceModel) {
        DesiredCapabilities capabilities = new DesiredCapabilities();
        
        capabilities.setCapability("securityToken", this.securityToken);
        
        //run the tests on devices of the specified model
        capabilities.setCapability("model", deviceModel);
        
        //create driver session in 60 retries per 1 second interval
        //modify the values according to your tests' expected duration
        int retry = 60;
        int interval = 1000;
        while(retry > 0 && driver==null) {
              try {
                  driver = new AndroidDriver(new URL("https://" + this.cloudName + ".perfectomobile.com/nexperience/perfectomobile/wd/hub"), capabilities);
              } catch (Exception e) {
                /* Decrement Retry */
                  retry--;
                  System.out.println("Attempt: " + (60-retry) + ". Failure to Acquire device, Retrying....." + e);
                  try {
                    Thread.sleep(interval);
                } catch (InterruptedException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
              }
        }
        
        // Reporting client. For more details, see ../Perfecto/test-analysis/test_analysis_with_smart_reporting.htm
        PerfectoExecutionContext perfectoExecutionContext = new PerfectoExecutionContext.PerfectoExecutionContextBuilder()
                .withProject(new Project("My Project", "1.0"))
                .withContextTags("tag1")
                .withWebDriver(driver)
                .build();
        reportiumClient = new ReportiumClientFactory().createPerfectoReportiumClient(perfectoExecutionContext);
    }
    
    public AppiumDriver getDriver() {
        return this.driver;
    }
    
    public void reportStart(String testName, TestContext context) {
        reportiumClient.testStart(testName, context);
    }
    
    public void reportSuccess() {
        reportiumClient.testStop(TestResultFactory.createSuccess());
    }
    
    public void reportFailure(Exception e) {
        reportiumClient.testStop(TestResultFactory.createFailure(e.getMessage(), e));
    }
    
    public void stop() {
        try {
            //close the driver session
            driver.close();
            driver.quit();
            
            //retrieve the URL of the Single Test Report and print it to the console
            String reportURL = reportiumClient.getReportUrl();
            System.out.println(reportURL);
            
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

}

In the following example, we see a TestNG class with 3 test cases, with a separate instance of the TestSetup class for each of the test cases. This way, the test cases can start in parallel on available devices of the specified type (via device model in this example).

Copy
package testng;

import java.util.HashMap;
import java.util.Map;

import org.testng.annotations.Test;
import org.testng.annotations.Parameters;
import org.testng.annotations.BeforeClass;

import com.perfecto.reportium.test.TestContext;

public class MyTest {
    
    private String deviceModel;
        
    @Parameters({"deviceModel"})
    @BeforeClass
    public void beforeClass(String deviceModel) {
              
        this.deviceModel = deviceModel;
    }
        
    //sample test to open the browser, navigate to Perfecto.io web page, and find the heading (Web)
    @Test
    public void test1() {
          
    TestSetup test = new TestSetup(this.deviceModel);
          
        try {
            test.reportStart("Test #1", new TestContext("Sample"));
                  
            test.getDriver().get("https://www.perfecto.io");
                
            test.getDriver().context("WEBVIEW");
                
            test.getDriver().findElementsByXPath("//*[text()='Web & Mobile App Testing']");
                
            test.reportSuccess();
                  
        } catch (Exception e) {
                
            test.reportFailure(e);
            e.printStackTrace();
        }
          
        test.stop();
    }
     
    //sample test to open the Android Settings app and find the 'Connections' element (Native)
    @Test
    public void test2() {
          
        TestSetup test = new TestSetup(this.deviceModel);
          
        try {
            test.reportStart("Test #2", new TestContext("Sample"));
                  
            Map<String, Object> params = new HashMap<>();
            params.put("name", "Settings");
            test.getDriver().executeScript("mobile:application:open", params);
                
            test.getDriver().context("NATIVE_APP");
                
            test.getDriver().findElementsByXPath("//*[@text='Connections']");
                
            test.reportSuccess();
            
        } catch (Exception e) {
            
            test.reportFailure(e);
            e.printStackTrace();
        }
          
        test.stop();
    }
      
    //sample test to go to device Home screen and check whether the 'Internet' icon is present
    @Test
    public void test3() {
          
        TestSetup test = new TestSetup(this.deviceModel);
          
        try {
            test.reportStart("Test #3", new TestContext("Sample"));
                  
            Map<String, Object> params5 = new HashMap<>();
            test.getDriver().executeScript("mobile:handset:ready", params5);
                
            Map<String, Object> params6 = new HashMap<>();
            params6.put("content", "Internet");
            params6.put("timeout", "20");
            test.getDriver().executeScript("mobile:checkpoint:text", params6);
                
            test.reportSuccess();
            
        } catch (Exception e) {
            
            test.reportFailure(e);
            e.printStackTrace();
        }
        
        test.stop();
    }
}