Android UI Testing with Cucumber and Appium

Vineeth G
5 min readFeb 29, 2024

--

Photo by Guido Coppa on Unsplash

In the fast-paced world of app development, ensuring a seamless user experience across devices is paramount. UI testing, facilitated by tools like Cucumber and Appium, can be a game-changer in achieving this goal. This brief guide outlines the steps to create a basic UI testing framework for Android apps using these powerful tools.

Appium: A cross-platform mobile application automation tool, Appium empowers developers to test Android apps effortlessly. Supporting multiple programming languages, it ensures a seamless testing experience.

Cucumber: Leveraging the power of behavior-driven development, Cucumber provides a user-friendly framework for test automation. It bridges the gap between technical and non-technical stakeholders with its plain-text format.

Setting up the essentials

Before diving into the world of Android automation, make sure you have the following tools installed on your device:

  1. Java Development Kit (JDK)
  2. Android Studio
  3. Maven or Gradle
  4. NodeJS
  5. Appium
  6. UiAutomator2
  7. Appium Inspector

To start Appium server, type ‘appium’ in your command prompt.

Setting up Appium Inspector

Launch Appium Inspector

Open Appium Inspector after installing it. The Inspector provides a graphical interface to inspect and interact with the elements of your mobile application.

Configure Desired Capabilities

Before starting a session, configure the desired capabilities in Appium Inspector. These capabilities include information about the device, automation settings, and the application under test. Refer the following image.

Image explaining capabilities required for Appium Inspector

Start Session

Ensure that either an Android Virtual Device (AVD) is running or a physical device is connected through ADB (Android Debug Bridge). Click on the “Start Session” button in Appium Inspector to initiate the session with the configured capabilities.

Creating Your UI Test Framework

Before we start, make sure you all the necessary plugins and dependencies configured in your project.

<dependencies>
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>9.1.0</version>
<scope>compile</scope>
</dependency>
<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>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.16.1</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-picocontainer</artifactId>
<version>7.14.0</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.masterthought</groupId>
<artifactId>cucumber-reporting</artifactId>
<version>5.7.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.0</version>
<configuration>
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>
<plugin>
<groupId>net.masterthought</groupId>
<artifactId>maven-cucumber-reporting</artifactId>
<version>2.8.0</version>
<executions>
<execution>
<id>execution</id>
<phase>verify</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<projectName>cucumber</projectName>
<outputDirectory>
${project.build.directory}/cucumber-report-html</outputDirectory>
<cucumberOutput>
${project.build.directory}/cucumber.json</cucumberOutput>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

Step 1: Define UI Elements in YAML

Create a YAML file, let’s call it LoginScreen.yml, to define your app’s UI elements. For example:

loginButton: "//android.view.Button[@content-desc='Login Button']"

Step 2: Map YAML to Java

Create a Java class, let’s call it LoginScreenOR.java, to map the YAML elements to your code:

package Repository;

public class LoginScreenOR {
public String loginButton;
}

Step 3: Constants and Paths

Create a utility class to define constants and file paths:

package Utility;

public class Constant {
public static String USER_WORK_DIR = System.getProperty("user.dir");
public static String LoginScreenORFile = "<PATH TO LOGINSCREEN YML FILE>";
}

We will use the below function to complete the mapping.

package Utility;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;

public class YmlReader {

public static LinkedHashMap < String, String > getReportEntries(String reportEntriesFilePath, String sectionName,
String tabName) {
Yaml yaml = new Yaml();
InputStream inputStream = null;
try {
inputStream = new FileInputStream(reportEntriesFilePath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
LinkedHashMap < String, LinkedHashMap < String, LinkedHashMap < String, String >>> mappings = yaml.load(inputStream);
return mappings.get(sectionName).get(tabName);
}

public static < T > T getObjectRepository(String objectRepositoryFilePath, Class << ? > objectRepositoryClass) {
Yaml yaml = new Yaml(new Constructor(objectRepositoryClass));
InputStream inputStream = null;
try {
inputStream = new FileInputStream(objectRepositoryFilePath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return yaml.load(inputStream);
}

}

Step 4: Appium Driver Setup

Create a class to set up your Appium driver:

package Driver;

import io.appium.java_client.AppiumDriver;
import io.appium.java_client.android.options.UiAutomator2Options;
import java.net.MalformedURLException;
import java.net.URL;

public class Driver {

private AppiumDriver driver;

public AppiumDriver createDriver() throws MalformedURLException {
UiAutomator2Options capabilities = new UiAutomator2Options();
capabilities.setCapability("automationName", "UiAutomator2");
capabilities.setCapability("deviceName", "<YOUR DEVICE NAME>");
capabilities.setCapability("platformName", "Android");
capabilities.setCapability("platformVersion", "<ANDROID PLATFORM VERSION>");
capabilities.setCapability("app", "<PATH TO THE APK>");
capabilities.setCapability("autoGrantPermissions", true);

URL appiumServerUrl = new URL("http://127.0.0.1:4723");
driver = new AppiumDriver(appiumServerUrl, capabilities);
return driver;
}
}

Step 5: Appium Context

Create a class to manage the Appium context, ensuring proper initialization and cleanup of resources:

package Uility;

import Actions.AppiumActions;
import Factory.AppiumFactory;
import io.appium.java_client.AppiumDriver;
import java.io.IOException;

public class AppiumContext {

public AppiumDriver driver;

public AppiumActions appiumActions;

public AppiumFactory appiumFactory;

public AppiumContext() throws IOException {
appiumFactory = new AppiumFactory();
driver = appiumFactory.createDriver();
appiumActions = new AppiumActions(driver);
}
}

Step 6: Appium Actions

Define actions you want to perform in your app using Appium:

package AppiumActions;

import org.openqa.selenium.By;
import io.appium.java_client.AppiumDriver;
import org.openqa.selenium.WebElement;

public class AppiumActions {

private final AppiumDriver driver;

public AppiumActions(AppiumDriver driver) {
this.driver = driver;
}

/*DEFINE YOUR ACTIONS HERE
.
.
.
*/

// Example:
public void clickElement(String selector) {
try {
WebElement element = driver.findElement(By.xpath(selector));
if (element.isDisplayed()) {
element.click();
} else {
System.out.println("Failed to find the UI element");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

Step 7: Screen Class

Create a class for each screen in your app:

package Screens;

import Actions.AppiumActions;
import io.appium.java_client.AppiumDriver;
import Repository.InitialScreenOR;

import Uility.YmlReader;
import static Uility.Constant.*;

public class LoginScreen {

AppiumDriver driver;

AppiumActions actions;

public LoginScreen(AppiumDriver driver) {
this.driver = driver;
actions = new AppiumActions(driver);
}

private static final LoginScreenOR loginScreenOR =
YmlReader.getObjectRepository(USER_WORK_DIR + LoginScreenORFile, LoginScreenOR.class);

public void clickButton() {
actions.clickElement(loginScreenOR.loginButton);
}

}

Step 8: Feature File

Write a Cucumber feature file to describe your test scenarios:

@demo
Feature: Demonstration of Appium with Cucumber

@Login
Scenario: click a login button on screen
Given user clicks login button

Step 9: Cucumber Steps

Create step definitions for your Cucumber scenarios:

package steps;

import Uility.AppiumContext;
import Screens.LoginScreen;
import io.cucumber.java.en.Given;

public class LoginScreen_steps {

AppiumContext context;

LoginScreen loginScreen;

public LoginScreen_steps(AppiumContext context) {
this.context = context;
loginScreen = new LoginScreen(context.driver);
}

@Given("user clicks login button")
public void user_clicks_login_button() {
screen.clickButton();
}

}

Step 10: Test Runner

Finally, set up your Cucumber test runner class:

package Runner;

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

@CucumberOptions(features="src/test/java/Features",glue= "Steps",monochrome=true,tags="@demo", plugin= {"json:target/cucumber.json","pretty"})
public class TestRunner extends AbstractTestNGCucumberTests {}

Run the command ‘mvn test’ to start executing the tests.

By following these steps, you’ve created a basic Android automation framework using Cucumber and Appium. This framework allows you to write and execute UI tests for your Android app seamlessly. Feel free to customize and expand upon this foundation as you explore the vast possibilities of automated testing in the Android ecosystem.

Happy testing!

--

--