Chapter-7: Developing Test Automation Framework for Appium using Page Object Modeling(POM)

Chapter-7: Developing Test Automation Framework for Appium using Page Object Modeling(POM)

Automation Testing with Appium fundamentally boils down to a simple 2-step process:

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

So far we have looked into the basics of Appium and learned how can you build the basic script for Mobile Application Automation testing.

But that was just the beginning! It’s time to ramp our skills up a notch.

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.

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 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.

The basic structure of the Page Object Model framework is depicted below:

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:

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:

Figure-3: Create Java Project.

  1. Give a valid GroupId and ArtifactId:

Figure-4: GroupId and ArtifactId.

  1. Check the configuration and click on Next button.

Figure-5: Check the Configuration.

  1. Check the Project Name, Project Location and More Settings.

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.

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.

Figure-8: build gradle.

 

  1. You can observe that there is a src directory created by default (By Gradle).

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

Figure-10: Adding new package inside src_test_java.

Figure-11: Package Name.

  1. Using the same approach add a testcases and utils package.

Figure-12: Package Name utils.png.

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.

  1. .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.

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.
    • We will see more about Wait in an upcoming chapter.
    • Now add PropertyUtils Class under utils directory, which will be responsible to get the property values from resources/configuration.properties file.

Figure-15: utils PropertyUtils.

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.

Figure-17: Execute the TestNG test.

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?:
      • It is an inbuilt Page Object Model concept for Selenium WebDriver and it is used to initialize the web elements(By the concept of lazy loading: Initialize elements only when they are needed to be used) that are defined in Page Objects.
  1. 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.

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();
    }
}

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");
        

Figure-22: Local apk file.

        • In our example we will use the locally resided .apk file.
      1. Now finally it’s time to create the TestCase java file under testcases package and it should extend the BaseTest java file which controls the webdriver creation before the test starts and deletion at the end of test execution. So you don't have to take care of that in the TestCase file.

NOTE: Before executing the Test Case make sure Appium server is running on http://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()’

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.

Figure-24: Android Application Locator Change.

      1. 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 framework! If you are not using the 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!
    No Comments

    Post A Comment