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

  1. 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.
  2. 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).
  3. 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, and click 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

  1. Start the Android emulator via Android Studio.
  2. Start the Appium server (appium).
  3. 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

  1. Use Page Object Model: Organize elements and actions in page classes.
  2. Manage Appium Driver: Initialize and close the driver in @Before and @After hooks.
  3. 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")));
    
  4. Inspect Elements: Use Appium Inspector or UI Automator Viewer to find reliable locators (e.g., ID, XPath).
  5. Test on Real Devices: Use cloud services (e.g., BrowserStack, Sauce Labs) for real-device testing.
  6. 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 in DesiredCapabilities.
  • Report Issues: Verify plugin settings in TestRunner 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.