Appium Tests Sharding with TestNG

Hi,
I am looking to implement test execution in such a way that If I have 100 tests across different JAVA classes. I can divide them to run on 4 devices in parallel to reduce the test execution time.

Currently I have set parallel=tests in my TestNG.xml file which runs Classes inside tag in parallel but that still takes a lot of time. Here’s my testng.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="POS Smoke Test Suite" parallel="tests" thread-count="2">
	<listeners>
		<listener class-name="reporting.Listeners"></listener>
	</listeners>
	<parameter name="environment" value="${environment}"></parameter>
  <test name="DashOrder">
	<parameter name="udid" value="${udid1}"></parameter>
	<parameter name="serverPin" value="${serverPin1}"></parameter>
	<parameter name="managerPin" value="${managerPin1}"></parameter>
	<parameter name="systemPort" value="${systemPort1}"></parameter>
	<parameter name="appiumPort" value="${appiumPort1}"></parameter>
	  <groups>
		  <run>
			  <include name="Smoke"></include>
		  </run>
	  </groups>
    <classes>
		<class name="tests.OrderTest">
	  </class>
		<class name="tests.CashDrawerTest"></class>
    </classes>
  </test>
 <!-- Test -->
   <test name="Test">
	<parameter name="udid" value="${udid2}"></parameter>
	<parameter name="serverPin" value="${serverPin2}"></parameter>
	<parameter name="managerPin" value="${managerPin2}"></parameter>
	<parameter name="systemPort" value="${systemPort2}"></parameter>
	<parameter name="appiumPort" value="${appiumPort2}"></parameter>
	  <groups>
		  <run>
			  <include name="Smoke"></include>
		  </run>
	  </groups>
    <classes>
	  <class name="tests.PaymentTest">
		  <methods>
			  <exclude name="verifyAmountAddedinDrawer"></exclude>
		  </methods>
	  </class>
		<class name="tests.QSRTests"></class>
    </classes>
  </test> <!-- Test -->
</suite> <!-- Suite -->

My Base Class containing BeforeMethod config method:

protected AndroidDriver getDriver() {
	    return driverThreadLocal.get();
	}

	protected void setDriver(AndroidDriver driver) {
	    driverThreadLocal.set(driver);
	}
	
	@BeforeMethod(alwaysRun=true)
	@Parameters({"udid","serverPin","systemPort","managerPin","appiumPort"})
	public void startApp(String udid,Method method,String serverPin,int systemPort,String managerPin,int appiumPort) {
		this.serverPin=serverPin;
		this.managerPin=managerPin;
		String env=FileUtility.readEnvironmentFromFile();
		UiAutomator2Options options1=new UiAutomator2Options();
		options1.setCapability("udid",udid);
		options1.setApp(System.getProperty("user.dir")+File.separator+"src"+File.separator+"test"+File.separator+"java"+File.separator+"resources"+File.separator+"pos-"+env+"-release.apk");
		options1.setCapability("autoGrantPermissions",true);
		options1.setSystemPort(systemPort);
		try {
			driver1=new AndroidDriver(new URL("http://localhost:"+appiumPort),options1);
			setDriver(driver1);
		}
		catch(Exception e) {
			getLogger().error("Driver1 Not Started "+e);	
		}
		getDriver().manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
		getLogger().info("Starting test: " + method.getName());
	}

I just wrote mine approach - possibly helps you

  1. I run tests with some set of phones e.g. set mobile phones -Ddevice=nokia_x10_1,nokia_x10_2,nokia_g42_3,nokia_x10_4,nokia_g42_5,nokia_5.3_6,nokia_x10_7,nokia_x10_8,nokia_g42_9,nokia_g42_10,nokia_g42_11,nokia_g42_12

beforeSuite
2. now in beforeSuite first I detect number of connected phones e.g. for Android running adb devices and matching them to requested phones. Name of phone and it properties stored in json file e.g.

  "nokia_x10_1": {
    "platformName": "Android",
    "platformVersion": "13.0.0",
    "deviceName": "xxx",
    "udid": "xxx",
    "appiumPort": 5001
  },
  1. as you see phones list has port number. now in same beforeSuite I am starting ALL appium servers with needed port numbers which are unique per device.
  2. now in beforeSuite I set number of threads = active devices found
iTestContext.getSuite().getXmlSuite().setThreadCount(availableDevices.size());

beforeMethod
6. I have 2 lists: ONE is number of available devices, second list number of taken devices. In beforeMethod I grab one from using synchronization and starting driver using correct Appium port.