Java Test Setup with Maven and TestNG

9 min read

Appium tests in Java need a project structure, a build tool, and a test framework. Maven handles dependencies and builds. TestNG handles test lifecycle, parallel execution, and reporting. This lesson walks through creating a project from scratch, adding the right dependencies, and wiring everything together so you can write your first test in the next lesson.

Prerequisites

Ensure you have JDK 11 or later installed:

java -version
javac -version

And Maven:

mvn -version

If Maven is missing, install it via Homebrew on macOS (brew install maven) or download from maven.apache.org.

Create the Maven project

mvn archetype:generate \
  -DgroupId=com.qa \
  -DartifactId=appium-tests \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DarchetypeVersion=1.4 \
  -DinteractiveMode=false

This creates the standard Maven directory layout:

appium-tests/
├── pom.xml
└── src/
    ├── main/java/com/qa/
    └── test/java/com/qa/

All your test code goes in src/test/java/.

pom.xml dependencies

Replace the contents of pom.xml with:

<?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>com.qa</groupId>
  <artifactId>appium-tests</artifactId>
  <version>1.0-SNAPSHOT</version>
 
  <properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
    <appium.version>9.2.2</appium.version>
    <testng.version>7.9.0</testng.version>
  </properties>
 
  <dependencies>
    <!-- Appium Java Client -->
    <dependency>
      <groupId>io.appium</groupId>
      <artifactId>java-client</artifactId>
      <version>${appium.version}</version>
    </dependency>
 
    <!-- TestNG -->
    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>${testng.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
 
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.2.5</version>
        <configuration>
          <suiteXmlFiles>
            <suiteXmlFile>testng.xml</suiteXmlFile>
          </suiteXmlFiles>
        </configuration>
      </plugin>
    </plugins>
  </build>
 
</project>

Key dependency: io.appium:java-client pulls in AndroidDriver, IOSDriver, AppiumBy, all capability options classes, and the Selenium dependency transitively. You do not need to add Selenium explicitly.

TestNG suite file

Create testng.xml in the project root:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Appium Tests" verbose="1">
  <test name="Android Tests">
    <classes>
      <class name="com.qa.tests.LoginTest" />
    </classes>
  </test>
</suite>

Base test class

Create src/test/java/com/qa/base/BaseTest.java:

package com.qa.base;
 
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
 
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
 
public class BaseTest {
 
    protected AndroidDriver driver;
 
    @BeforeClass
    public void setUp() throws MalformedURLException {
        UiAutomator2Options options = new UiAutomator2Options()
            .setDeviceName("emulator-5554")
            .setPlatformVersion("14")
            .setApp(System.getProperty("user.dir") + "/src/test/resources/app.apk")
            .setNewCommandTimeout(Duration.ofSeconds(60));
 
        driver = new AndroidDriver(new URL("http://127.0.0.1:4723"), options);
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
    }
 
    @AfterClass
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

Notes on this structure:

  • @BeforeClass runs once before all tests in the class — one driver per test class
  • @AfterClass always runs after all tests, even on failure — guarantees session cleanup
  • implicitlyWait(10) gives Appium 10 seconds to find elements before throwing NoSuchElementException
  • The APK path uses System.getProperty("user.dir") to build an absolute path portably

First test class

Create src/test/java/com/qa/tests/LoginTest.java:

package com.qa.tests;
 
import com.qa.base.BaseTest;
import io.appium.java_client.AppiumBy;
import org.testng.Assert;
import org.testng.annotations.Test;
 
public class LoginTest extends BaseTest {
 
    @Test
    public void verifyLoginScreenLoads() {
        String title = driver
            .findElement(AppiumBy.accessibilityId("login_title"))
            .getText();
        Assert.assertEquals(title, "Sign In");
    }
}

App resource

Put the APK under src/test/resources/. For now, a demo APK works fine — the ApiDemos APK from the Appium GitHub repository is a good starting point:

mkdir -p src/test/resources
# Download ApiDemos-debug.apk from Appium's GitHub samples

Running tests

mvn test

Maven compiles your code, reads testng.xml, and runs the test classes listed there. The Surefire plugin generates reports in target/surefire-reports/.

To run a specific test class without updating testng.xml:

mvn test -Dtest=LoginTest

Project structure at this point

appium-tests/
├── pom.xml
├── testng.xml
└── src/
    └── test/
        ├── java/com/qa/
        │   ├── base/BaseTest.java
        │   └── tests/LoginTest.java
        └── resources/
            └── app.apk

This is the scaffold every subsequent chapter builds on.

// tip to track lessons you complete and pick up where you left off across devices.