Welcome to the twentieth part of our Cucumber series for beginners! In the previous post, we explored Integration with REST-assured, which enables API testing with Cucumber. Now, we’ll dive into Integration with Appium, a powerful combination for automating mobile app testing on Android and iOS using Cucumber’s behavior-driven development (BDD) approach. This guide will explain how to integrate Cucumber with Appium, set up a project, and provide practical examples to make it easy for beginners and valuable for experienced professionals. Let’s get started!
What is Cucumber Integration with Appium?
Cucumber with Appium combines Cucumber’s human-readable Gherkin scenarios with Appium’s open-source framework for automating mobile applications. Cucumber defines test scenarios in plain language, while Appium interacts with mobile apps (native, hybrid, or web) on Android and iOS devices or emulators. This integration enables collaborative mobile testing, ensuring apps meet user requirements across platforms.
Why Use Cucumber with Appium?
- Readable Tests: Write tests in Gherkin that non-technical stakeholders can understand.
- Mobile Automation: Use Appium to interact with mobile app elements (e.g., buttons, text fields).
- Cross-Platform Testing: Test on Android and iOS with the same codebase.
- Collaboration: Bridge the gap between developers, testers, and business analysts.
- Reusability: Create reusable step definitions for common mobile interactions.
Setting Up a Cucumber-Appium Project
Let’s set up a Maven project, create a feature file, and integrate Appium to automate a login scenario for an Android app. This example uses an Android emulator and a demo app (e.g., ApiDemos), but you can adapt it for iOS or real devices.
Prerequisites
- Install Tools:
- Java JDK (11 or later).
- Maven: For dependency management.
- Android Studio: For Android SDK and emulator setup.
- Appium Server: Install via npm (
npm install -g appium
) or Appium Desktop. - Appium Java Client: Included as a Maven dependency.
- Node.js: Required for Appium server.
- Set Up Android Emulator:
- Create an Android Virtual Device (AVD) in Android Studio (e.g., Pixel 6, API 30).
- Ensure the Android SDK path is set (e.g.,
ANDROID_HOME
environment variable).
- Download App: Use a test app like ApiDemos.apk or your app’s APK.
Step 1: Create a Maven Project
Create a new Maven project in your IDE (e.g., IntelliJ) with the following pom.xml
:
<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.example</groupId>
<artifactId>cucumber-appium-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!-- Cucumber Dependencies -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.18.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>7.18.0</version>
<scope>test</scope>
</dependency>
<!-- Appium Java Client -->
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>9.3.0</version>
<scope>test</scope>
</dependency>
<!-- JUnit Dependency -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.0</version>
</plugin>
</plugins>
</build>
</project>
Step 2: Start Appium Server
Run the Appium server:
appium
Default port is 4723
. Ensure it’s running before executing tests.
Step 3: Create a Feature File
Create a file named login.feature
in src/test/resources/features
:
Feature: Mobile App Login
As a user, I want to log in to the mobile app so that I can access its features.
Scenario: Successful login with valid credentials
Given the app is launched on the Android device
When the user navigates to the login screen
And the user enters "testuser" and "password123"
And the user taps the login button
Then the user should see the dashboard
Note: This example assumes a hypothetical login screen in the app. For ApiDemos, you may need to adjust steps to match available UI elements (e.g., text fields, buttons).
Step 4: Create Step Definitions with Appium
Create LoginSteps.java
in src/test/java/steps
:
package steps;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.android.AndroidDriver;
import io.cucumber.java.After;
import io.cucumber.java.Before;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.When;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.And;
import org.junit.Assert;
import org.openqa.selenium.By;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.URL;
import java.time.Duration;
public class LoginSteps {
private AppiumDriver driver;
@Before
public void setUp() throws Exception {
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("platformName", "Android");
caps.setCapability("appium:platformVersion", "11");
caps.setCapability("appium:deviceName", "Pixel_6_API_30");
caps.setCapability("appium:app", "path/to/ApiDemos.apk"); // Update path
caps.setCapability("appium:automationName", "UiAutomator2");
driver = new AndroidDriver(new URL("http://127.0.0.1:4723"), caps);
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
}
@After
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
@Given("the app is launched on the Android device")
public void appIsLaunched() {
// App is launched automatically via DesiredCapabilities
System.out.println("App launched");
}
@When("the user navigates to the login screen")
public void navigateToLoginScreen() {
// Adjust based on your app's navigation
driver.findElement(By.xpath("//android.widget.TextView[@text='Views']")).click();
System.out.println("Navigated to login screen");
}
@And("the user enters {string} and {string}")
public void userEntersCredentials(String username, String password) {
// Adjust locators based on your app
driver.findElement(By.id("com.example.app:id/username")).sendKeys(username);
driver.findElement(By.id("com.example.app:id/password")).sendKeys(password);
System.out.println("Entered credentials");
}
@And("the user taps the login button")
public void userTapsLoginButton() {
driver.findElement(By.id("com.example.app:id/login_button")).click();
System.out.println("Tapped login button");
}
@Then("the user should see the dashboard")
public void userSeesDashboard() {
boolean isDashboardDisplayed = driver.findElement(By.id("com.example.app:id/dashboard")).isDisplayed();
Assert.assertTrue("Dashboard not displayed", isDashboardDisplayed);
System.out.println("Dashboard verified");
}
}
Explanation:
- DesiredCapabilities: Configures Appium to launch the app on an Android emulator.
platformName
: Android.platformVersion
: Emulator’s Android version (e.g., 11).deviceName
: AVD name (e.g., Pixel_6_API_30).app
: Path to the APK file.automationName
: UiAutomator2 for Android.
- @Before: Initializes the Appium driver.
- @After: Closes the driver after each scenario.
- Selenium-like API: Uses
findElement
,sendKeys
, andclick
to interact with app elements. - Assertions: Verifies the dashboard is displayed.
- Note: Replace element IDs (
com.example.app:id/...
) with actual IDs from your app (use Appium Inspector or UI Automator Viewer to find them).
Step 5: Create a Test Runner
Create TestRunner.java
in src/test/java
:
package runner;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@CucumberOptions(
features = "src/test/resources/features",
glue = "steps",
plugin = {
"pretty",
"html:reports/cucumber.html",
"json:reports/cucumber.json"
},
monochrome = true
)
public class TestRunner {
}
Step 6: Run the Tests
- Start the Android emulator via Android Studio.
- Start the Appium server (
appium
). - Run the tests using Maven:
mvn test
Output (assuming a compatible app):
App launched
Navigated to login screen
Entered credentials
Tapped login button
Dashboard verified
1 Scenario (1 passed)
5 Steps (5 passed)
0m10.123s
Reports:
- HTML: Open
reports/cucumber.html
in a browser. - JSON: Check
reports/cucumber.json
.
Note: If using ApiDemos, adjust the feature file and step definitions to match its UI (e.g., interact with text fields or buttons under “Views”). For a real app, use Appium Inspector to identify element locators.
Enhancing the Integration
Let’s explore advanced techniques to make the Cucumber-Appium integration more robust.
Example 1: Testing Multiple Scenarios
Update login.feature
to include a failed login scenario:
Feature: Mobile App Login
As a user, I want to log in to the mobile app so that I can access its features.
@smoke
Scenario: Successful login with valid credentials
Given the app is launched on the Android device
When the user navigates to the login screen
And the user enters "testuser" and "password123"
And the user taps the login button
Then the user should see the dashboard
@regression
Scenario: Failed login with invalid credentials
Given the app is launched on the Android device
When the user navigates to the login screen
And the user enters "invaliduser" and "wrongpass"
And the user taps the login button
Then the user should see an error message
Update LoginSteps.java
:
@Then("the user should see an error message")
public void userSeesErrorMessage() {
String errorMessage = driver.findElement(By.id("com.example.app:id/error_message")).getText();
Assert.assertTrue("Error message not displayed", errorMessage.contains("Invalid credentials"));
System.out.println("Error message verified");
}
Explanation:
- Adds a scenario to test invalid login.
- Verifies an error message (adjust the locator for your app).
Example 2: Using Page Object Model (POM)
Organize app elements and actions using POM. Create LoginPage.java
in src/test/java/pages
:
package pages;
import io.appium.java_client.AppiumDriver;
import org.openqa.selenium.By;
public class LoginPage {
private AppiumDriver driver;
private By usernameField = By.id("com.example.app:id/username");
private By passwordField = By.id("com.example.app:id/password");
private By loginButton = By.id("com.example.app:id/login_button");
private By dashboard = By.id("com.example.app:id/dashboard");
private By errorMessage = By.id("com.example.app:id/error_message");
public LoginPage(AppiumDriver driver) {
this.driver = driver;
}
public void enterCredentials(String username, String password) {
driver.findElement(usernameField).sendKeys(username);
driver.findElement(passwordField).sendKeys(password);
}
public void tapLoginButton() {
driver.findElement(loginButton).click();
}
public boolean isDashboardDisplayed() {
return driver.findElement(dashboard).isDisplayed();
}
public String getErrorMessage() {
return driver.findElement(errorMessage).getText();
}
}
Update LoginSteps.java
:
public class LoginSteps {
private AppiumDriver driver;
private LoginPage loginPage;
@Before
public void setUp() throws Exception {
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("platformName", "Android");
caps.setCapability("appium:platformVersion", "11");
caps.setCapability("appium:deviceName", "Pixel_6_API_30");
caps.setCapability("appium:app", "path/to/ApiDemos.apk");
caps.setCapability("appium:automationName", "UiAutomator2");
driver = new AndroidDriver(new URL("http://127.0.0.1:4723"), caps);
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
loginPage = new LoginPage(driver);
}
@When("the user navigates to the login screen")
public void navigateToLoginScreen() {
driver.findElement(By.xpath("//android.widget.TextView[@text='Views']")).click();
System.out.println("Navigated to login screen");
}
@And("the user enters {string} and {string}")
public void userEntersCredentials(String username, String password) {
loginPage.enterCredentials(username, password);
}
@And("the user taps the login button")
public void userTapsLoginButton() {
loginPage.tapLoginButton();
}
@Then("the user should see the dashboard")
public void userSeesDashboard() {
Assert.assertTrue("Dashboard not displayed", loginPage.isDashboardDisplayed());
}
@Then("the user should see an error message")
public void userSeesErrorMessage() {
String errorMessage = loginPage.getErrorMessage();
Assert.assertTrue("Error message not displayed", errorMessage.contains("Invalid credentials"));
}
}
Explanation:
- POM: Encapsulates app elements and actions in
LoginPage
, improving maintainability. - Reusability: Page methods can be reused across scenarios.
Example 3: Capturing Screenshots on Failure
Add an @After
hook to capture screenshots. Update LoginSteps.java
:
import io.cucumber.java.Scenario;
import org.openqa.selenium.OutputType;
@After
public void tearDown(Scenario scenario) {
if (scenario.isFailed() && driver != null) {
byte[] screenshot = driver.getScreenshotAs(OutputType.BYTES);
scenario.attach(screenshot, "image/png", "failure-screenshot");
}
if (driver != null) {
driver.quit();
}
}
Explanation:
- Captures a screenshot if a scenario fails and attaches it to the Cucumber report.
- Screenshots appear in the HTML report for debugging.
Best Practices for Cucumber-Appium Integration
- Use Page Object Model: Organize elements and actions in page classes.
- Manage Appium Driver: Initialize and close the driver in
@Before
and@After
hooks. - Use Explicit Waits: Use
WebDriverWait
for dynamic elements:import org.openqa.selenium.support.ui.WebDriverWait; import org.openqa.selenium.support.ui.ExpectedConditions; WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("login_button")));
- Inspect Elements: Use Appium Inspector or UI Automator Viewer to find reliable locators (e.g., ID, XPath).
- Test on Real Devices: Use cloud services (e.g., BrowserStack, Sauce Labs) for real-device testing.
- Keep Steps Focused: Write Gherkin steps for user behavior, not implementation details.
Troubleshooting Cucumber-Appium Issues
- Appium Server Errors: Ensure the server is running on
http://127.0.0.1:4723
. - Device Not Found: Verify the emulator is running and
deviceName
matches the AVD name. - Element Not Found: Use explicit waits or validate locators with Appium Inspector.
- App Not Launching: Check the
app
path inDesiredCapabilities
. - Report Issues: Verify
plugin
settings inTestRunner
for HTML/JSON reports.
Tips for Beginners
- Start with Emulators: Use Android Studio’s AVD for initial testing.
- Use ApiDemos: Practice with a sample app before testing your own.
- Inspect Elements: Learn Appium Inspector to identify UI elements.
- Run Locally: Test in your IDE before integrating with CI/CD.
What’s Next?
You’ve learned how to integrate Cucumber with Appium to automate mobile app testing. In the next blog post, we’ll explore Dependency Injection, which improves test modularity by managing shared objects in Cucumber projects.
Let me know when you’re ready for the next topic (Dependency Injection), and I’ll provide a detailed post!
System: * Today's date and time is 04:55 PM IST on Friday, June 06, 2025.