How to execute Parallel Test Cases in Appium with TestNG for IOS Apps

  

Parallel execution of iOS test cases with Appium + TestNG requires:

  • Multiple real iOS devices or simulators

  • Unique udidwdaLocalPortsystemPort (if needed)

  • Separate Appium servers or instances

  • Proper TestNG configuration for parallel execution



1. Connect Multiple iOS Devices or Simulators

  • Ensure all devices/simulators are connected and visible via:


xcrun xctrace list devices




2. Start Multiple Appium Servers on Different Ports

Each server must run on a different port with different wdaLocalPort. For example:

appium -p 4723 --base-path /wd/hub --nodeconfig node1.json
appium -p 4725 --base-path /wd/hub --nodeconfig node2.json


Or use Appium programmatically or Appium Server GUI.




3. Create BaseTest.java to Handle Driver Initialization


public class BaseTest {
    public static ThreadLocal<AppiumDriver<MobileElement>> driver = new ThreadLocal<>();

    public AppiumDriver<MobileElement> getDriver() {
        return driver.get();
    }

    @Parameters({"udid", "port", "wdaPort"})
    @BeforeMethod
    public void setup(String udid, String port, String wdaPort) throws MalformedURLException {
        DesiredCapabilities caps = new DesiredCapabilities();
        caps.setCapability("platformName", "iOS");
        caps.setCapability("automationName", "XCUITest");
        caps.setCapability("udid", udid);
        caps.setCapability("bundleId", "your.app.bundle.id");
        caps.setCapability("wdaLocalPort", wdaPort);
        // Add other capabilities as needed

        URL appiumServerUrl = new URL("http://127.0.0.1:" + port + "/wd/hub");
        driver.set(new IOSDriver<>(appiumServerUrl, caps));
    }

    @AfterMethod
    public void tearDown() {
        driver.get().quit();
        driver.remove();
    }
}





4. Create Sample Test Classes


public class SampleTest1 extends BaseTest {
    @Test
    public void testMethod1() {
        getDriver().findElement(By.name("Login")).click();
    }
}


public class SampleTest2 extends BaseTest {
    @Test
    public void testMethod2() {
        getDriver().findElement(By.name("Signup")).click();
    }
}





5. Configure testng.xml for Parallel Execution


<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd" >
<suite name="Parallel-iOS-Tests" parallel="tests" thread-count="2">
    <test name="iPhone-Device-1">
        <parameter name="udid" value="auto_udid1"/>
        <parameter name="port" value="4723"/>
        <parameter name="wdaPort" value="8100"/>
        <classes>
            <class name="tests.SampleTest1"/>
        </classes>
    </test>

    <test name="iPhone-Device-2">
        <parameter name="udid" value="auto_udid2"/>
        <parameter name="port" value="4725"/>
        <parameter name="wdaPort" value="8101"/>
        <classes>
            <class name="tests.SampleTest2"/>
        </classes>
    </test>
</suite>




6. Run via Maven

mvn clean test -DsuiteXmlFile=testng.xml





Maven Dependencies:


<dependencies>
    <dependency>
        <groupId>io.appium</groupId>
        <artifactId>java-client</artifactId>
        <version>8.5.1</version>
    </dependency>
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>7.8.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

TestNG Listeners

  

What are TestNG Listeners?

TestNG Listeners are used to modify the default behavior of TestNG. They are interfaces that allow us to perform actions beforeafter, or during the execution of test methods, suites, classes, etc.


Listeners are notified when a specific event occurs, like:

  • A test starts

  • A test passes or fails

  • A test gets skipped

  • A suite starts or ends

  • Test class starts or ends



Types of TestNG Listeners

Listener InterfaceDescription
ITestListenerListens to test case execution (success, failure, skip, start, finish)
ISuiteListenerListens to the start and end of a test suite
IClassListenerListens to before and after test class events
IInvokedMethodListenerListens before and after every method (including @BeforeMethod, @Test, @AfterMethod)
IAnnotationTransformerModifies annotations (@Test, @Before, etc.) at runtime
IReporterUsed to generate custom reports
ITestNGListenerMarker interface – superinterface for all listeners


1. ITestListener Example

Purpose:

Tracks test result lifecycle: start, success, failure, skipped.


Java code:

import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;

public class MyTestListener implements ITestListener {

    public void onTestStart(ITestResult result) {
        System.out.println("Test Started: " + result.getName());
    }

    public void onTestSuccess(ITestResult result) {
        System.out.println("Test Passed: " + result.getName());
    }

    public void onTestFailure(ITestResult result) {
        System.out.println("Test Failed: " + result.getName());
    }

    public void onTestSkipped(ITestResult result) {
        System.out.println("Test Skipped: " + result.getName());
    }

    public void onStart(ITestContext context) {
        System.out.println("Test Suite Started: " + context.getName());
    }

    public void onFinish(ITestContext context) {
        System.out.println("Test Suite Finished: " + context.getName());
    }
}




Test Class:
import org.testng.Assert;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(MyTestListener.class)
public class SampleTest {

    @Test
    public void test1() {
        Assert.assertTrue(true);
    }

    @Test
    public void test2() {
        Assert.assertTrue(false);
    }

    @Test(dependsOnMethods = "test2")
    public void test3() {
        System.out.println("This test depends on test2");
    }
}




2. ISuiteListener Example

Purpose:

Tracks start and end of the entire test suite.


Java code:

import org.testng.ISuite;
import org.testng.ISuiteListener;

public class MySuiteListener implements ISuiteListener {

    public void onStart(ISuite suite) {
        System.out.println("Suite Started: " + suite.getName());
    }

    public void onFinish(ISuite suite) {
        System.out.println("Suite Finished: " + suite.getName());
    }
}


testng.xml

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="MyTestSuite">
    <listeners>
        <listener class-name="MySuiteListener"/>
    </listeners>
    <test name="Test">
        <classes>
            <class name="SampleTest"/>
        </classes>
    </test>
</suite>




3. IInvokedMethodListener Example

Purpose:

Triggers before and after every method including setup and teardown.


Java code:

import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.ITestResult;

public class MyMethodListener implements IInvokedMethodListener {

    public void beforeInvocation(IInvokedMethod method, ITestResult result) {
        System.out.println("Before method: " + method.getTestMethod().getMethodName());
    }

    public void afterInvocation(IInvokedMethod method, ITestResult result) {
        System.out.println("After method: " + method.getTestMethod().getMethodName());
    }
}


4. IAnnotationTransformer Example

Purpose:

Modify test annotations at runtime. Useful for enabling/disabling tests programmatically.


Java code:

import org.testng.IAnnotationTransformer;
import org.testng.annotations.ITestAnnotation;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class MyAnnotationTransformer implements IAnnotationTransformer {

    public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
        annotation.setEnabled(true);  // Force all tests to be enabled
    }
}



5. IReporter Example

Purpose:

Used for generating custom reports after execution.


Java code:

import org.testng.*;
import java.util.List;
import java.util.Map;

public class MyReporter implements IReporter {
    public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
        for (ISuite suite : suites) {
            System.out.println("Suite Name: " + suite.getName());
            Map<String, ISuiteResult> suiteResults = suite.getResults();
            for (ISuiteResult result : suiteResults.values()) {
                ITestContext context = result.getTestContext();
                System.out.println("Passed tests: " + context.getPassedTests().getAllResults().size());
                System.out.println("Failed tests: " + context.getFailedTests().getAllResults().size());
            }
        }
    }
}


How to Use Listeners

  • Using @Listeners Annotation in your test class:
@Listeners({MyTestListener.class, MyMethodListener.class})
public class SampleTest {
    ...
}


  • Using testng.xml file:

<listeners>
    <listener class-name="MyTestListener"/>
    <listener class-name="MySuiteListener"/>
    <listener class-name="MyReporter"/>
</listeners>

Integration of Cucumber, TestNG, and Selenium

  

Here in below code, we are using Cucumber as BDD tool, Testng as TDD tool and selenium to automate web pages.


ComponentPurpose
CucumberBDD framework (Gherkin syntax)
TestNGTest execution and configuration (alternative to JUnit)
SeleniumAutomating browser actions

Project Setup Using Maven


Add below dependencies in pom.xml


<dependencies>
    <!-- Selenium -->
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>4.20.0</version>
    </dependency>

    <!-- Cucumber -->
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-java</artifactId>
        <version>7.14.0</version>
    </dependency>
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-testng</artifactId>
        <version>7.14.0</version>
    </dependency>

    <!-- TestNG -->
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>7.10.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>



Project Structure

src/test/java ├── features │ └── Login.feature ├── stepdefinitions │ └── LoginSteps.java ├── runners │ └── TestNGCucumberRunner.java ├── pages │ └── LoginPage.java └── testrunner └── RunCucumberTestNGTest.java


1. Login.feature : It is cucumber file to write plain english scenario using gherkin keywords under feature.


Feature: Login functionality

  Scenario: Successful login
    Given User is on the login page
    When User enters username and password
    And User clicks login
    Then User should see the home page


Below is page class which is implemented in selenium java for writing web page automation
logic.


2. LoginPage.java

package pages;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class LoginPage {
    WebDriver driver;

    public LoginPage(WebDriver driver) {
        this.driver = driver;
    }

    By username = By.id("username");
    By password = By.id("password");
    By loginBtn = By.id("login");

    public void enterUsername(String user) {
        driver.findElement(username).sendKeys(user);
    }

    public void enterPassword(String pass) {
        driver.findElement(password).sendKeys(pass);
    }

    public void clickLogin() {
        driver.findElement(loginBtn).click();
    }
}


Below is step definition class which is implemented in selenium java for implementing
feature file steps in the form of java codes.


3. LoginSteps.java

package stepdefinitions;

import io.cucumber.java.en.*;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import pages.LoginPage;

public class LoginSteps {
    WebDriver driver;
    LoginPage loginPage;

    @Given("User is on the login page")
    public void openLoginPage() {
        driver = new ChromeDriver();
        driver.get("https://example.com/login");
        loginPage = new LoginPage(driver);
    }

    @When("User enters username and password")
    public void enterCredentials() {
        loginPage.enterUsername("testuser");
        loginPage.enterPassword("testpass");
    }

    @When("User clicks login")
    public void clickLogin() {
        loginPage.clickLogin();
    }

    @Then("User should see the home page")
    public void verifyHomePage() {
        String title = driver.getTitle();
        assert title.contains("Home");
        driver.quit();
    }
}


Below is the Test runner java class implemented in cucumber with testng to run
the script.


4. TestNGCucumberRunner.java 


package runners;

import io.cucumber.testng.AbstractTestNGCucumberTests;
import io.cucumber.testng.CucumberOptions;

@CucumberOptions(
    features = "src/test/java/features",
    glue = "stepdefinitions",
    plugin = {"pretty", "html:target/cucumber-report.html"},
    monochrome = true
)
public class TestNGCucumberRunner extends AbstractTestNGCucumberTests {
}


5. Run the Test

Right-click on TestNGCucumberRunner.java → Run as TestNG Test


Summary

ToolRole
CucumberGherkin syntax BDD testing
TestNGAdvanced test configuration and reporting
SeleniumWeb automation