Implement Page Object Model using Appium & Java for Android Tests

The following tutorial is about Page Object Model and Page Factory, a design pattern intended to simplify tests and create a scalable solution for test automation. If you want to develop maintainable and reusable tests, prevent unwanted code duplication and make your testing life much simpler – Page Object Model is definitely one of the best ways to go!


Tutorial Overview: 

  1. Build your First Test with Appium
  2. Page Object Model and Page Factory Advantages vs. “Plain” Appium
  3. Create Page Classes by Utilizing Page Object and Page Factory
  4. Create Appium Page Object Test Based on the Page Classes 
  5. Benefits of Scheduling and Running POM Appium Tests with TestProject Framework
  6. Test Example of Implementing Page Object Model with TestProject Framework
  7. Examples of TestProject Framework Execution Reports – 
  8. Watch a Video of this Entire Tutorial Here

1. Build your First Test with Appium

Let’s start this tutorial by learning how to create a “plain” Appium automated test for Android:
This test example below automates the TestProject Demo app. You can download its APK or view the complete source code.

To start the automation, the Test prepares Android driver capabilities with all the required information about the device under test (DUT) and App under test (AUT). Then, using these capabilities, it initiates the Android driver that will be used to invoke various Appium actions on the mobile application. To perform the actions, Appium must identify the elements it interacts with. To do so, it uses various location strategies.

The most convenient location strategy is the ID (aka. resource-id in Android). For example, it searches for the login button to make sure that the login screen is displayed using By.id and the value login. I will show how to identify your element locators later in this tutorial, here.

Basic Login Test: 

package io.testproject.appium.tests;

import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.AndroidKeyCode;
import io.appium.java_client.remote.AndroidMobileCapabilityType;
import io.appium.java_client.remote.MobileCapabilityType;
import org.openqa.selenium.By;
import org.openqa.selenium.Platform;
import org.openqa.selenium.remote.DesiredCapabilities;

import java.net.URL;

public class PositiveLoginTest {

    private final static String APP_PACKAGE_NAME = "io.testproject.demo";
    private final static String APP_ACTIVITY_NAME = ".MainActivity";
    private final static String PASSWORD = "12345";

    public static void main(String[] args) throws Exception {

        // Prepare Appium session
        DesiredCapabilities capabilities = DesiredCapabilities.android();
        capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, Platform.ANDROID);
        capabilities.setCapability(MobileCapabilityType.UDID, "YOUR_DEVICE_UDID");
        capabilities.setCapability(MobileCapabilityType.NO_RESET, false);
        capabilities.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, APP_PACKAGE_NAME);
        capabilities.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, APP_ACTIVITY_NAME);

        // Initialize driver
        AndroidDriver driver = new AndroidDriver(new URL("http://0.0.0.0:4723/wd/hub"), capabilities);

        // Discard state
        driver.resetApp();

        // Will throw exception if login element missing
        driver.findElement(By.id("login"));

        // Hide keyboard if visible
        if (driver.findElements(By.className("UIAKeyboard")).size() != 0) {
            driver.pressKeyCode(AndroidKeyCode.KEYCODE_ESCAPE);
        }

        // Type Full Name
        driver.findElement(By.id("name")).sendKeys("John Smith");

        // Type Password
        driver.findElement(By.id("password")).sendKeys(PASSWORD);

        // Click Login
        driver.findElement(By.id("login")).click();

        // Verify login
        if (driver.findElement(By.id("greetings")).isDisplayed()) {
            System.out.println("Test completed successfully");
        }

        // Close session
        driver.quit();
    }
}

 

2. Page Object Model and Page Factory Advantages vs. “Plain” Appium

Now that we’ve created our first “Plain” Appium test, let’s see how it can be even better with Page Object Model (POM). Below are the advantages of utilizing POM as opposed to “plain” Appium:

“Plain” Appium POM Appium
Mix of test logic and UI actions Separation of duties to OOP classes
Coupled design Decoupling objects library from decision taking in tests
Difficult maintenance One authority to manage page elements and UI action
Code redundancy No duplicate findElement calls and UI manipulations
Unnecessary complexity Self-explanatory code, thanks to FindBy annotations

 

3. Create Page Classes by Utilizing Page Object and Page Factory

So, it’s time that we learn how to build an Appium test utilizing Page Object Model and Page Factory.

Creating our page classes 

First, let’s define our page classes. In the demo application we tested, we have two pages: Login page and Profile page.

To map the elements, POM uses the FindBy annotations. These annotations should be used to decorate the fields declared for the elements. Location strategies can be different, but the most convenient one is the ID (aka. resource-id in Android).

To find out what are the right values to use, we used TestProject inspector and element locator.

*No worries, TestProject is FREE to use forever. All you need to do in order to use the TestProject element locator is to install and register the TestProject agent (Here‘s a quick video showing just how you can do it). Then, just click on the page and select your element. With the TestProject element locator, you can copy the XPath, CSS Path, Tag name or Id (if available) of any element on the page.

Obtained values were placed in the annotations in the code.
Then, initElements method residing on the PageObject factory class should be invoked to initialize all the elements of the page.

Elements, page class fields, are now being used across the page to manipulate the UI and encapsulated in methods invoked externally.

Login Page Class:

package io.testproject.appium.pom.tests.pages;

import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.AndroidElement;
import io.appium.java_client.android.AndroidKeyCode;
import io.appium.java_client.pagefactory.AndroidFindBy;
import io.appium.java_client.pagefactory.AppiumFieldDecorator;
import org.openqa.selenium.support.PageFactory;

public class LoginPage {

    private AndroidDriver<AndroidElement> driver;

    public LoginPage() {
    }

    public LoginPage(AndroidDriver<AndroidElement> driver) {
        this.driver = driver;
        PageFactory.initElements(new AppiumFieldDecorator(driver), this);
    }

    @AndroidFindBy(className = "UIAKeyboard")
    private AndroidElement keyboard;

    @AndroidFindBy(id = "name")
    private AndroidElement nameElement;

    @AndroidFindBy(id = "password")
    private AndroidElement passwordElement;

    @AndroidFindBy(id = "login")
    private AndroidElement loginElement;

    public boolean isDisplayed() {
        return loginElement.isDisplayed();
    }

    public void typeName(String name) {
        nameElement.sendKeys(name);
    }

    public void typePassword(String password) {
        passwordElement.sendKeys(password);
    }

    public void clickLogin() {
        loginElement.click();
    }

    public void hideKeyboardIfVisible() {
        if (keyboard != null) {
            driver.pressKeyCode(AndroidKeyCode.KEYCODE_ESCAPE);
        }
    }

    public void login (String name, String password) {
        hideKeyboardIfVisible();
        typeName(name);
        typePassword(password);
        clickLogin();
    }
}

Let’s locate the elements for the profile page class on the same manner we did for the login page above.

Profile Page Class:

package io.testproject.appium.pom.tests.pages;


import io.appium.java_client.MobileBy;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.AndroidElement;
import io.appium.java_client.android.AndroidKeyCode;
import io.appium.java_client.pagefactory.AndroidFindBy;
import io.appium.java_client.pagefactory.AppiumFieldDecorator;
import org.openqa.selenium.support.PageFactory;

import java.util.concurrent.TimeUnit;

public class ProfilePage {

    private AndroidDriver driver;

    public ProfilePage() {
    }

    public ProfilePage(AndroidDriver driver) {
        this.driver = driver;
        PageFactory.initElements(new AppiumFieldDecorator(driver), this);
    }

    @AndroidFindBy(className = "UIAKeyboard")
    private AndroidElement keyboard;

    @AndroidFindBy(id = "greetings")
    private AndroidElement greetingsElement;

    @AndroidFindBy(id = "logout")
    private AndroidElement logoutElement;

    @AndroidFindBy(id = "country")
    private AndroidElement countryElement;

    @AndroidFindBy(id = "address")
    private AndroidElement addressElement;

    @AndroidFindBy(id = "email")
    private AndroidElement emailElement;

    @AndroidFindBy(id = "phone")
    private AndroidElement phoneElement;

    @AndroidFindBy(id = "save")
    private AndroidElement saveElement;

    @AndroidFindBy(id = "saved")
    private AndroidElement savedElement;

    public boolean isDisplayed() {
        return greetingsElement.isDisplayed();
    }

    public void typeAddress(String address) {
        addressElement.sendKeys(address);
    }

    public void typeEmail(String email) {
        emailElement.sendKeys(email);
    }

    public void typePhone(String phone) {
        phoneElement.sendKeys(phone);
    }

    public void hideKeyboardIfVisible() {
        if (keyboard != null) {
            driver.pressKeyCode(AndroidKeyCode.KEYCODE_ESCAPE);
        }
    }

    public void save() {
        saveElement.click();
    }

    public boolean isSaved() {
        return savedElement.isDisplayed();
    }

}

4. Create Appium Page Object Test Based on the Page Classes

This test is identical to the one above with the only difference that all the UI actions are now encapsulated into the pages classes.

Instead of searching and manipulating elements directly, this is being done using convenience methods (e.g. isDisplayed) implemented by every page.

package io.testproject.appium.pom.tests;

import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.remote.AndroidMobileCapabilityType;
import io.appium.java_client.remote.MobileCapabilityType;
import io.testproject.appium.pom.tests.pages.LoginPage;
import io.testproject.appium.pom.tests.pages.ProfilePage;
import org.openqa.selenium.Platform;
import org.openqa.selenium.remote.DesiredCapabilities;

import java.net.URL;

public class PositiveLoginTest {

    private final static String APP_PACKAGE_NAME = "io.testproject.demo";
    private final static String APP_ACTIVITY_NAME = ".MainActivity";
    private final static String PASSWORD = "12345";

    public static void main(String[] args) throws Exception {

        // Prepare Appium session
        DesiredCapabilities capabilities = DesiredCapabilities.android();
        capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, Platform.ANDROID);
        capabilities.setCapability(MobileCapabilityType.UDID, "YOUR_DEVICE_UDID");
        capabilities.setCapability(MobileCapabilityType.NO_RESET, false);
        capabilities.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, APP_PACKAGE_NAME);
        capabilities.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, APP_ACTIVITY_NAME);

        // Initialize driver
        AndroidDriver driver = new AndroidDriver(new URL("http://0.0.0.0:4723/wd/hub"), capabilities);

        driver.resetApp();

        LoginPage loginPage = new LoginPage(driver);
        if (!loginPage.isDisplayed()) {
            return;
        }

        loginPage.login("John Smith", PASSWORD);
        ProfilePage profilePage = new ProfilePage(driver);
        if (!profilePage.isDisplayed()) {
            return;
        }

        System.out.println("Test completed successfully");

        // Close session
        driver.quit();
    }
}

 

5. Benefits of Scheduling and Running Appium Tests with TestProject Framework

By now we’ve built a test with Appium and POM.
Now, let’s see how we can enhance this test even further while utilizing the TestProject framework. But first, check out all of TestProject’s cool benefits:

  • You don’t need to setup Appium/make any configurations.
  • You don’t need to worry about setting up your entire automation framework.
  • You get a beautiful dashboard with test automation reports including screenshots.
  • Once the tests are uploaded to TestProject, you can schedule to run your test anywhere across your account.
  • No special proxy configuration is required, everything is running locally across your organization.
  • You get tools such as element locators and recorders out of the box as part of the TestProject agent installation.
  • 98% of TestProject users use the free forever plan.

6. Test Example of Implementing Page Object Model with TestProject Framework

This is an example of an Appium & Page Object Model test while utilizing the TestProject framework: The test logic stays identical to the previous examples, however, you may notice a few enhancements:

  • The pages classes remain the same.
  • Test class is annotated with @Test annotation providing the test with a friendlier name.
  • Input fields declarations annotated with @TestParameter allowing the test to be parameterized when executed and supplying default values.
  • Calls to report.step allowing to report test milestones providing better progress granulation by reviewing them in the results and reporting dashboard.
  • Call to report.result allowing to provide final statement for the test execution before it passes or fails.

Here you can find more documentation for TestProject SDK: https://github.com/testproject-io/java-sdk-examples.

 

package io.testproject.appium.tests;

import io.testproject.examples.sdk.java.pages.LoginPage;
import io.testproject.examples.sdk.java.pages.ProfilePage;
import io.testproject.java.annotations.v2.Test;
import io.testproject.java.annotations.v2.TestParameter;
import io.testproject.java.enums.TakeScreenshotConditionType;
import io.testproject.java.sdk.v2.drivers.AndroidDriver;
import io.testproject.java.sdk.v2.enums.ExecutionResult;
import io.testproject.java.sdk.v2.exceptions.FailureException;
import io.testproject.java.sdk.v2.reporters.TestReporter;
import io.testproject.java.sdk.v2.tests.AndroidTest;
import io.testproject.java.sdk.v2.tests.helpers.AndroidTestHelper;

@Test(name = "Demo Test with Defaults")
public class DemoTestWithDefaults implements AndroidTest {

    @TestParameter(defaultValue = "John Smith")
    public String name;

    @TestParameter(defaultValue = "12345")
    public String password;

    @TestParameter(defaultValue = "Canada")
    public String country;

    @TestParameter(defaultValue = "8 Ness Ave")
    public String address;

    @TestParameter(defaultValue = "[email protected]")
    public String email;

    @TestParameter(defaultValue = "+1 555 555")
    public String phone;

    public ExecutionResult execute(AndroidTestHelper helper) throws FailureException {
        AndroidDriver driver = helper.getDriver();
        TestReporter report = helper.getReporter();

        driver.resetApp();

        LoginPage loginPage = new LoginPage(driver);
        report.step("Launched TestProject Demo app", loginPage.isDisplayed());

        loginPage.login(name, password);
        ProfilePage profilePage = new ProfilePage(driver);
        report.step(String.format("Logged in with %s:%s", name, password), profilePage.isDisplayed(), TakeScreenshotConditionType.Always);

        profilePage.updateProfile(country, address, email, phone);
        report.step(String.format("Profile information saved"), profilePage.isSaved(), TakeScreenshotConditionType.Always);

        report.result("Test completed successfully");
        return ExecutionResult.PASSED;
    }
}

 

7. Examples of TestProject Framework Execution Reports

Below are screenshot examples of detailed reports you can receive while using TestProject:

What is your opinion? Have you experienced Appium with Page Object Model?