Developing a Test Automation Framework for Appium

Reading Time : 14min read
Developing a Test Automation Framework for Appium

Developing a test automation framework for Appium boils down to a simple 2-step process:

  1. Identify the Ui Element locator(address)
  2. Perform an action on it.

In the real world, Appium is used to automate an entire mobile application and the simple idea to put all element locators and interactions with locators into one file won’t help us. It’s just bad design. When we inevitably go back to increase automation test coverage, we would likely end up with an unmaintainable project – large project files, complex code and duplicate usage of element locators will become the bane of your daily automation life.

Moreover, even a small change in the application UI would break the existing working locators, and if we use the linear structure in our test code, it will become so difficult to fix that locator because we need to replace the invalid locator from each place in the code.

For Example, most apps or websites have a ‘home’ page, such as a dashboard, containing a number of menu options. Many automation test cases might click on these menu options to perform a specific task. Now imagine that the UI is changed/revamped and menu buttons are relocated and some of them removed. This will lead to automation tests failure because scripts will not be able to find the particular element.

So in order to reduce that pain, we need to use some kind of structure which can eliminate those difficulties. And that masterpiece code structure (or framework as it’s more commonly called!) is known as Page Object Modeling.

What is Appium Page Object Modeling (POM)?

Page Object Model is a popular and widely used Design Pattern in Appium (and Selenium) Test Automation. It is popular because it enhances test maintenance and reduces code duplication. The main logic behind the Page Object Model Appium framework is to keep Locators and Tests cases separate from each other.

Page Object is an object-oriented class that keeps all the element locators referring to a particular page of your Application Under Test and it has interaction methods for all relevant locators that have been defined. These will be used by the Test Cases in a particular order according to the test requirements of the feature being tested.

The main advantage to using this is that whenever a UI change causes a test script failure,  you only need to apply changes on Page Object classes to fix the automation script.

What does the POM automation framework look like?

The basic structure of the Page Object Model Appium framework, which is depicted below, includes Page Objects, Test Cases, Utility Class, and WebDriver categories. However, this depicts just one possible Page Object Model structure.

Page Objects flow chart
Figure-1: Page Object Modeling StructurePOM.

NOTE: The above structure just illustrates one possible Page Object Model structure – It may vary according to the needs of your app and test cases, and POM works best for multi page web application.

But the mentioned POM structure is bit heavy for the brain and especially when we are taking baby steps. So In this tutorial we will understand the modified and “lighter” version of POM illustrated below:

Page objects, Utility class, and test cases flow chart
Figure-2: Page Object Modeling Structure(Light-weight).

We have removed the WebDriver as separate entity and included it in the BaseTestCase. The reason to doing so is we want to simplify things, and WebDriverManager makes more sense when we are working with many different test execution clients(browsers).

So it is specially ideal to have the WebDriverManager class in case of Selenium, as there are many browsers out there such as Chrome, Firefox, Safari, Opera, etc., we need to automate upon and every browser needs some specific Desired Capabilities.

But here we are dealing with iOS and Android Devices only, so in lieu of creating a separate WebDriverManager class, we can include the WebDriver creation logic inside of BaseTestCase.

Follow the below steps to implement this(Steps shown below remain same for Intellij IDEA and Eclipse IDE):

  1. Create a new Java Project:
Image of creating a java project
Figure-3: Create Java Project.

  1. Give a valid GroupId and ArtifactId:
Image of GroupID and ArtifactID
Figure-4: GroupId and ArtifactId.

  1. Check the configuration and click on Next button.
Image of checking the configuration
Figure-5: Check the Configuration.

  1. Check the Project Name, Project Location and More Settings.
Image of project name and other details
Figure-6: Name of New Project and other details.

  1. When you click on the Finish button it will build the whole project and link the default dependencies.
Image of gradle build
Figure-7: Gradle Build.

  1. As we are using Gradle as a build tool (No offense to Maven), the first thing you need to do is to add the gradle dependencies and Import the changes.
image of building gradle
Figure-8: build gradle.

  1. You can observe that there is a src directory created by default (By Gradle).
image of gradle directories
Figure-9: Directories created by Gradle.

  1. After adding the Appium and TestNG dependencies, we will create the Page Object package inside src > test > java
image of adding new package inside src_test_java
Figure-10: Adding new package inside src_test_java.

image of naming new package: pageobject
Figure-11: Package Name.

  1. Using the same approach, add a testcases and utils package.
image of naming new package: utils
Figure-12: Package Name utils.png.

image of naming new package: testcases
Figure-13: Package Name testcases.

  1. The first step is to add the configuration.properties file under resources/ directory. Now what do we mean by this? According to the Page Object Model, we need to put all the configuration related values in one file and in Java, we can use a .properties file for it.
  2. .properties file contains the key value pair, using the key we can get the value, so if we want to change some configuration like change the connected device name, we just need to change the value of android.device.name key.
image of configured properties
Figure-14: configuration properties.

But to get the value of key from .properties file, we need to implement methods and we will use the PropertyUtils class for that.

  1. Now we need to add Utility classes such as PropertyUtils, WaitUtils etc.
    • In test automation, Wait has got a vital role. In application automation, when you navigate to another screen or page, there will be a delay due to resource loading and if we don’t apply the wait, then Automation test scripts can break. So to avoid that, we need to tell Appium’s webdriver object that we are expecting some delay to get the mobile element on the new screen/page and this phenomena known as Wait.
    • There are basically 3 Types of waits in Appium (or Selenium) and we need to use each one of them periodically, so we can create useful methods on the wait and put them together in the WaitUtility Class. And whenever we need to use the wait, we will use the WaitUtility class.
    • Now add PropertyUtils Class under utils directory, which will be responsible to get the property values from resources/configuration.properties file.
image of utils PropertyUtils code
Figure-15: utils PropertyUtils.

image of  utils WaitUtils code
Figure-16: utils WaitUtils.

  1. Now the most important step is to create our BaseTestCase class and include the logic of WebDriver creation, which would be the responsible to manage the WebDriver object throughout the automation project.
    • Create BaseTestCase class file under the testcases package.
    • Add TestNG(Test Framework for Java) default methods such as @BeforeSuite, @BeforeClass, @BeforeTest, @AfterClass and @AfterTest methods.
    • The @BeforeMethod will be executed every time before test case starts – Let’s look at some simple code regarding its usage.
package testcases;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class TestCases {
    @BeforeMethod
    public void setUp() {
        System.out.println("Before Method executed..!");
    }
    @Test
    public void test() {
        System.out.println("Test");
    }
    @AfterMethod
    public void tearDown() {
        System.out.println("After Method executed..!");
    }
}
  • It’s pretty straight-forward code. @Test method is the actual testing method you will use for your test cases.
  • @BeforeMethod will be executed before the execution of your @test method and likewise @AfterMethod will be executed after the test execution everytime it does not matter if you are calling these methods or not.
  • So when you execute the above test by selecting the test method > Right Click > Run ‘test()’ it will execute the setUp() method before test executes → test() method → tearDown() method after test executes.
  • Have a look at below screenshot to get the idea how to execute the test case and refer to the second screenshot for output of the above program.
image of executing the TestNG test
Figure-17: Execute the TestNG test.

image of executing default suite
Figure-18: Execution of Simple Program.

@BeforeMethod
public void setUpAppium() throws MalformedURLException {
    DesiredCapabilities capabilities = new DesiredCapabilities();
    setDesiredCapabilitiesForAndroid(capabilities);
    driver = new AppiumDriver(new URL(APPIUM_SERVER_URL), capabilities);
}
/**
 * It will set the DesiredCapabilities for the local execution
 *
 * @param desiredCapabilities
 */
private void setDesiredCapabilitiesForAndroid(DesiredCapabilities desiredCapabilities) {
	String PLATFORM_NAME = PropertyUtils.getProperty("android.platform");
	String PLATFORM_VERSION = PropertyUtils.getProperty("android.platform.version");
	String APP_NAME = PropertyUtils.getProperty("android.app.name");
	String APP_RELATIVE_PATH = PropertyUtils.getProperty("android.app.location") + APP_NAME;
	String APP_PATH = getAbsolutePath(APP_RELATIVE_PATH);
	String DEVICE_NAME = PropertyUtils.getProperty("android.device.name");
	String APP_PACKAGE_NAME = PropertyUtils.getProperty("android.app.packageName");
	String APP_ACTIVITY_NAME = PropertyUtils.getProperty("android.app.activityName");
	String APP_FULL_RESET = PropertyUtils.getProperty("android.app.full.reset");
	String APP_NO_RESET = PropertyUtils.getProperty("android.app.no.reset");
	desiredCapabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, "uiautomator2");
	desiredCapabilities.setCapability(MobileCapabilityType.DEVICE_NAME, DEVICE_NAME);
	desiredCapabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, PLATFORM_NAME);
	desiredCapabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, PLATFORM_VERSION);
	desiredCapabilities.setCapability(MobileCapabilityType.APP, APP_PATH);
	desiredCapabilities.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, APP_PACKAGE_NAME);
	desiredCapabilities.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, APP_ACTIVITY_NAME);
	desiredCapabilities.setCapability(MobileCapabilityType.FULL_RESET, APP_FULL_RESET);
	desiredCapabilities.setCapability(MobileCapabilityType.NO_RESET, APP_NO_RESET);
        desiredCapabilities.setCapability(AndroidMobileCapabilityType.AUTO_GRANT_PERMISSIONS, true);
}
// To get Absolute Path from Relative Path
private static String getAbsolutePath(String appRelativePath){
    File file = new File(appRelativePath);
    return file.getAbsolutePath();
}
/**
* This will quite the android driver instance
*/
private void quitDriver() {
    try {
    	this.driver.quit();
	} catch (Exception e) {
		e.printStackTrace();
	}
}
  • This code illustrates how you can leverage the @BeforeMethod and @AfterMethod to leverage WebDriver object creation and deletion.
  • Every Test Case class file extends the Above BaseTest Class file, so all the methods in the BaseTest class file are visible and applicable to extending classes(TestCases) and eventually you don’t need to provide any extra code at your TestCase level to manage the WebDriver creation and deletion. You just have to focus on Test methods creation on TestCases.java file.
  1. Add a BasePO Class under the pageobject package.
    • BasePO is the class containing the PageFactory method.
    • What is Page Factory?:
  2. Below code is responsible to initialize the web elements:
private void initElements() {
   PageFactory.initElements(new AppiumFieldDecorator(driver, Duration.ofSeconds(IMPLICIT_WAIT)), this);
}
  • Now every other Page Object class like LoginPO will extend the BasePO class, so the constructor of BasePO is always called first and initElements method will be called on BasePO constructor. In simple language, initElements will always called first whenever any Page Object class gets called.
Figure-19: BasePO.


Here you can also see some objects and variables defined. IMPLICIT_WAIT is getting the values defined in properties file of java which will be stored under resources/ dir.

  • In the Page Object class, you need to define the element’s locators using the below approach:

For iOS:

@iOSFindBy(xpath = “//XCUIElementTypeTextField”)
IOSElement emailTextField;

For Android:

@AndroidFindBy(xpath = "//android.widget.TextView[@text='Login Screen']")
AndroidElement loginScreenTextView;
  • In this example, we will work with the sample Android application and we will use the UiAutomatorViewer inspection tool as it is a speedy way to get the element’s locator.
  • In below screenshot we get the locator for Login Screen Textview.
image of  UiAutomatorViewer for Android Application
Figure-20: UiAutomatorViewer for Android Application.

  • And you can create methods in the Page Object class (like in LoginPO) for the element locators, for example, if you want to Tap on Login TextView, then you can create method such as tapOnLoginScreenTextView().

HomeScreenPO.java

package pageobject;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.android.AndroidElement;
import io.appium.java_client.pagefactory.AndroidFindBy;
public class HomeScreenPO extends BasePO {
   
    public HomeScreenPO(AppiumDriver driver) {
        super(driver);
    }
    @AndroidFindBy(xpath = "//android.widget.TextView[@text='Login Screen']")
    AndroidElement loginScreenTextView;
    /**
     * This method will click on Login Screen textview.
     */
    public void tapOnLoginScreenTextView(){
        loginScreenTextView.click();
    }
}
image of  HomeScreen page object
Figure-21: HomeScreenPO.png

  1. Now that we have created our first Page Object class and added our first locator into it, we are ready to create a very basic simple test on TestCase
  2. Before creating the test case you need to provide the correct path to the application. You can either:
    1. Provide the application locally in the code.
      desiredCapabilities.setCapability(MobileCapabilityType.APP, “path/to/.apk or .ipa(or .app) file”);
    2. Provide the URL of the application.
      desiredCapabilities.setCapability(MobileCapabilityType.APP, “https://github.com/cloudgrey-io/the-app/releases/download/v1.7.0/TheApp-v1.7.0.apk”);
image of coping local apk file
Figure-22: Local apk file.

In our example we will use the locally resided .apk file.

NOTE: Before executing the Test Case make sure that the Appium mobile automation test server is running on https://127.0.0.1:4723/wd/hub

TestCases.java

package testcases;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import pageobject.HomeScreenPO;

/**
 * Year: 2018-19
 *
 * @author Prat3ik on 23/11/18
 * @project POM_Automation_Framework
 */
public class TestCases extends BaseTest{
    @Test
    public void test() {
        HomeScreenPO homeScreenPO = new HomeScreenPO(driver);
        homeScreenPO.tapOnLoginScreenTextView();
    }
    @BeforeTest
    @Override
    public void setUpPage() {
    }
}

Execute the above test by selecting the test method > Right Click > Run ‘test()’

image of Test Case successful execution
Figure-23: Test Case successful execution.

  • If you can execute the test case successfully, then you will get the above screenshot.
  • You can get the code of above explained framework from: [todo: replace with final github url prior to publishing] https://github.com/appiumbook/appiumbook/tree/master/Chapter7-Developing%20test%20automation%20framework%20using%20appium
  • Phew! That was a ride! Right now you may be thinking that was a lot of work to create a simple test case – but that simple test case is misleading. You’ve done all the groundwork – adding more test cases reusing those elements are much quicker.
  • Now the question is how can you benefit from all of this work you just did? Think about your next release when the element locator of the application changes.
  • You might be thinking that it will take too much effort to get that issue fixed. But that is absolutely incorrect. You just need to change the locator in the PageObject class.
  • As easy as 1-2-3:
    1. Open the particular Page Object class: Let say the locator of Login TextView changed in a new iteration(version) of the application and Login TextView is part of the Home Screen so you need to move to HomeScreenPO.java file.
    2. Get the new Locator: Using Appium Inspector, UiAutomator(Android) or Accessibility Inspector(iOS) you can get the Locator. So move to the particular screen on application where needed element is located and fetch the correct locator.
image of android application login screen locator change
Figure-24: Android Application Locator Change.

Change the Locator: After getting the right locator you just need to change it with old and incorrect locator, so In HomeScreenPO.java file you just need to replace the old locator with the new locator.

@AndroidFindBy(xpath = "//android.widget.Button[@text='Login Screen']")
AndroidElement loginScreenTextView;

That’s it! As you can see, we just need to change the locator in the Page Object class and everything works normally again – that is the beauty of using this Appium framework! If you are not using the Appium framework, then you might need to change the locator from every affected place in code, which is not advisable and can also break something else.

Structuring your test cases like this will make for a far more maintainable test automation suite. Learning the rigors to do it following this design pattern will benefit you and your organization for years to come!

Appium eBook