Integration of Appium Tests with TestNG

 

To integrate Appium with TestNG for automating an Android Calculator app, you need to:


Prerequisites:

  • Java (JDK 11+)
  • Appium Desktop or Appium server installed and running
  • Android Emulator or real device connected
  • Appium Java client added to the project
  • TestNG configured in your project (typically via Maven)



Maven Dependencies:


<dependencies>
    <!-- Appium Java Client -->
    <dependency>
        <groupId>io.appium</groupId>
        <artifactId>java-client</artifactId>
        <version>8.5.1</version>
    </dependency>

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



Java Test Script with TestNG Integration:


import io.appium.java_client.MobileElement;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.remote.MobileCapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.testng.Assert;
import org.testng.annotations.*;

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

public class CalculatorTest {

    private AndroidDriver<MobileElement> driver;

    @BeforeClass
    public void setup() throws MalformedURLException {
        DesiredCapabilities caps = new DesiredCapabilities();
        caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
        caps.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator");
        caps.setCapability(MobileCapabilityType.AUTOMATION_NAME, "UiAutomator2");
        caps.setCapability("appPackage", "com.android.calculator2");
        caps.setCapability("appActivity", "com.android.calculator2.Calculator");
        caps.setCapability(MobileCapabilityType.NO_RESET, true);

        driver = new AndroidDriver<>(new URL("http://localhost:4723/wd/hub"), caps);
    }

    @Test
    public void testAddition() {
        driver.findElementById("digit_2").click();
        driver.findElementById("op_add").click();
        driver.findElementById("digit_3").click();
        driver.findElementById("eq").click();

        String result = driver.findElementById("result").getText();
        Assert.assertEquals(result, "5");
    }

    @AfterClass
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}


How to Run:

  • Ensure the Appium server is running (localhost:4723)

  • Make sure your Android device/emulator is active and unlocked

  • Right-click the test file and choose Run As → TestNG Test

DataProvider in TestNG

  

What is @DataProvider in TestNG?

@DataProvider is a feature in TestNG that allows you to run the same test method multiple times with different sets of data. This is a powerful way to implement data-driven testing.


Points to be Remember:

  • It's a method that returns a 2D Object array (Object[][])

  • Each row is treated as a separate invocation of the test method

  • It is linked to a @Test method using the dataProvider attribute

  • You can name it using @DataProvider(name = "myProvider")


Example Code Using @DataProvider

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class DataProviderDemo {

    // Test method using data from the DataProvider
    @Test(dataProvider = "loginData")
    public void loginTest(String username, String password) {
        System.out.println("Login with Username: " + username + ", Password: " + password);
    }

    // DataProvider method
    @DataProvider(name = "loginData")
    public Object[][] getData() {
        return new Object[][] {
            {"admin", "admin123"},
            {"user1", "pass1"},
            {"guest", "guest123"}
        };
    }
}


Output:

Login with Username: admin, Password: admin123
Login with Username: user1, Password: pass1
Login with Username: guest, Password: guest123



Why Use @DataProvider?

FeatureDescription
ReusabilityOne DataProvider can serve multiple test methods
FlexibilityData can be fetched from arrays, DB, Excel, etc.
Automation CoverageEasily test edge cases and large data sets



Using Multiple Parameters

You can return multiple types and parameters as needed:

@DataProvider(name = "userData")
public Object[][] getUserData() {
    return new Object[][] {
        {"Alice", 25, true},
        {"Bob", 30, false}
    };
}

@Test(dataProvider = "userData")
public void testUser(String name, int age, boolean isActive) {
    System.out.println(name + " | " + age + " | " + isActive);
}

How To Retry Failed Tests using IRetryAnalyzer in TestNG

  

The IRetryAnalyzer interface in TestNG allows us to automatically retry failed test cases. It is useful when tests may fail due to temporary issues like network problems, timeouts, or environment instability.


Steps to Implement IRetryAnalyzer

  • Create a class that implements IRetryAnalyzer.
  • Override the method boolean retry(ITestResult result).
  • Track retry count and limit the maximum number of retries.
  • Attach the RetryAnalyzer to test cases using:
    • @Test(retryAnalyzer = RetryAnalyzerClass.class), or

    • Globally using IAnnotationTransformer.




Example 1: RetryAnalyzer Implementation


import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;

public class RetryAnalyzer implements IRetryAnalyzer {
    private int count = 0;
    private static final int MAX_RETRY = 3;

    @Override
    public boolean retry(ITestResult result) {
        if (count < MAX_RETRY) {
            count++;
            return true;  // Retry the test
        }
        return false;  // Do not retry
    }
}




Example 2: Use RetryAnalyzer with Test


import org.testng.Assert;
import org.testng.annotations.Test;

public class SampleTest {

    int i = 0;

    @Test(retryAnalyzer = RetryAnalyzer.class)
    public void testMethod() {
        i++;
        System.out.println("Attempt #" + i);
        Assert.assertTrue(i >= 3, "Test failed");  // Passes on 3rd attempt
    }
}



Optional: Global Retry with IAnnotationTransformer

Instead of adding retryAnalyzer to each @Test, you can apply it globally:



RetryTransformer.java

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

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

public class RetryTransformer implements IAnnotationTransformer {
    @Override
    public void transform(ITestAnnotation annotation, Class testClass,
                          Constructor testConstructor, Method testMethod) {
        annotation.setRetryAnalyzer(RetryAnalyzer.class);
    }
}




Add Listener in testng.xml

<listeners>
    <listener class-name="RetryTransformer"/>
</listeners>



Important Points

ComponentPurpose
IRetryAnalyzerDefines retry logic for failed test cases
retry()Decides whether to retry a test
@Test(retryAnalyzer = ...)Attach retry logic to specific test
IAnnotationTransformerApply retry logic globally

Page Object Model with Appium, Cucumber and TestNG for IOS App

 

Below is the code for page object model (POM) with Appium, Cucumber and Testng for IOS app. Please have a look.


Maven Dependencies:

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

    <!-- TestNG -->
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>7.9.0</version>
    </dependency>

    <!-- Appium Java client -->
    <dependency>
        <groupId>io.appium</groupId>
        <artifactId>java-client</artifactId>
        <version>9.0.0</version>
    </dependency>

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





Base Setup for Appium


// hooks/BaseTest.java
package hooks;

import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.remote.IOSMobileCapabilityType;
import io.appium.java_client.remote.MobileCapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.URL;

public class BaseTest {
    protected static IOSDriver driver;

    public void initializeApp() throws Exception {
        DesiredCapabilities caps = new DesiredCapabilities();
        caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "iOS");
        caps.setCapability(MobileCapabilityType.PLATFORM_VERSION, "17.0");
        caps.setCapability(MobileCapabilityType.DEVICE_NAME, "iPhone 14 Pro");
        caps.setCapability(MobileCapabilityType.APP, "/path/to/your/app.app");
        caps.setCapability(IOSMobileCapabilityType.AUTO_ACCEPT_ALERTS, true);

        driver = new IOSDriver(new URL("http://127.0.0.1:4723/wd/hub"), caps);
    }

    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}





Hooks for Cucumber


// hooks/Hooks.java
package hooks;

import io.cucumber.java.After;
import io.cucumber.java.Before;

public class Hooks extends BaseTest {

    @Before
    public void setUp() throws Exception {
        initializeApp();
    }

    @After
    public void cleanUp() {
        tearDown();
    }
}





Feature File


# resources/features/login.feature
Feature: Login

  Scenario: Successful login
    Given User is on Login screen
    When User enters "testuser" and "password123"
    Then User is logged in






Step Definition file:


// stepdefinitions/LoginSteps.java
package stepdefinitions;

import hooks.BaseTest;
import pages.LoginPage;
import io.cucumber.java.en.*;

public class LoginSteps extends BaseTest {
    LoginPage loginPage;

    @Given("User is on Login screen")
    public void userIsOnLoginScreen() {
        loginPage = new LoginPage(driver);
    }

    @When("User enters {string} and {string}")
    public void userEntersCredentials(String username, String password) {
        loginPage.login(username, password);
    }

    @Then("User is logged in")
    public void userIsLoggedIn() {
        // Add validation logic here
    }
}






Page Class:


// pages/LoginPage.java
package pages;

import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.pagefactory.AppiumFieldDecorator;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

public class LoginPage {
    IOSDriver driver;

    public LoginPage(IOSDriver driver) {
        this.driver = driver;
        PageFactory.initElements(new AppiumFieldDecorator(driver), this);
    }

    @FindBy(accessibility = "username")
    WebElement usernameField;

    @FindBy(accessibility = "password")
    WebElement passwordField;

    @FindBy(accessibility = "loginButton")
    WebElement loginButton;

    public void login(String username, String password) {
        usernameField.sendKeys(username);
        passwordField.sendKeys(password);
        loginButton.click();
    }
}





Test runner:


// testrunner/TestRunner.java
package testrunner;

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

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





Testng.xml

<!-- testng.xml -->
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="iOS Appium Test Suite">
    <test name="Cucumber Test">
        <classes>
            <class name="testrunner.TestRunner" />
        </classes>
    </test>
</suite>





Run Tests


mvn clean test

Integration of Appium with Cucumber and Testng for Android App

 


Below is the code which is showing integration of Appium with Cucumber and Testng for Android app. Please have a look.



Maven Dependencies:

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

    <!-- Appium -->
    <dependency>
        <groupId>io.appium</groupId>
        <artifactId>java-client</artifactId>
        <version>8.5.1</version>
    </dependency>

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

    <!-- WebDriver -->
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>4.22.0</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <!-- Maven Surefire Plugin to run tests -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.2.5</version>
            <configuration>
                <includes>
                    <include>**/TestRunner.java</include>
                </includes>
            </configuration>
        </plugin>
    </plugins>
</build>




Feature File

src/test/resources/features/login.feature


Feature: Login

  Scenario: Valid Login
    Given App is launched
    When I enter username "admin" and password "admin123"
    Then I should see the homepage






Create Step Definitions

src/test/java/stepDefinitions/LoginSteps.java


package stepDefinitions;

import io.appium.java_client.android.AndroidDriver;
import io.cucumber.java.en.*;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.URL;

public class LoginSteps {
    AndroidDriver driver;

    @Given("App is launched")
    public void app_is_launched() throws Exception {
        DesiredCapabilities caps = new DesiredCapabilities();
        caps.setCapability("deviceName", "Android Emulator");
        caps.setCapability("platformName", "Android");
        caps.setCapability("appPackage", "com.example");
        caps.setCapability("appActivity", "com.example.MainActivity");
        caps.setCapability("automationName", "UiAutomator2");

        driver = new AndroidDriver(new URL("http://localhost:4723/wd/hub"), caps);
    }

    @When("I enter username {string} and password {string}")
    public void enter_credentials(String user, String pass) {
        // use driver.findElement() to locate and input values
    }

    @Then("I should see the homepage")
    public void verify_homepage() {
        // assertion logic here
    }
}





Create Test Runner (Using TestNG)

src/test/java/runners/TestRunner.java


package runners;

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

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





testng.xml


<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Cucumber-TestNG Suite">
    <test name="Cucumber Tests">
        <classes>
            <class name="runners.TestRunner"/>
        </classes>
    </test>
</suite>





Run via Maven:

mvn clean test