In our previous blog we have seen how to create a POM and with the help of POM and TestNG we executed our first test case in our Appium automation framework. Now we are taking our next step in the ladder that is “Page Factory”.

We all knows that POM is a pattern or style in other words it acts like a “Object Repository” i.e.  Declaring a Page and page objects/elements in our java class. Page Factory is also same like POM but in addition it comes with lot of predefined notations and attributes to help the automation tester to initiate or declare an element in more standard and robust way.

Let me show an example of POM and Page Factory for better understanding,

Imagine, I have a Login test case to be executed and using POM I have a Login class which is having the below method,

Public void verify_Login() {
//my code goes here
}

Using the above method, my intension is to verify login functionality using a valid username and Password. For this I can go with 4 types of approaches,

  1. Without defining the Locator and Locator strategy as a variable seperately, instead directly implementing the locator in driver.findElement() method.

public void verify_Login() {		 
    driver.findElement(MobileBy.AccessibilityId("username")).sendKeys("Admin");		 
    driver.findElement(MobileBy.AccessibilityId("password")).sendKeys("Admin123");
    driver.findElement(MobileBy.AccessibilityId("login_button")).click();
}

2. Defining the Locator and Locator strategy as a “String” variable

String loc_Username = "username";
String loc_Password = "password";
	
public void verify_Login() {		 
    driver.findElement(MobileBy.AccessibilityId(loc_Username)).sendKeys("Admin");		 
    driver.findElement(MobileBy.AccessibilityId(loc_Password)).sendKeys("Admin123");
    driver.findElement(MobileBy.AccessibilityId("login_button")).click();
}

3. Defining the Locator using “By” variable (‘By’ from selenium or ‘MobileBy’ from Appium using POM)

public static By loc_Username = MobileBy.AccessibilityId("username");
	public static By loc_Password = MobileBy.AccessibilityId("password");
	public static By loc_login_btn = MobileBy.AccessibilityId("login_button");
	
	public void verify_Login() {
		driver.findElement(loc_Username).sendKeys("Admin");
		driver.findElement(loc_Password).sendKeys("Admin123");
		driver.findElement(loc_login_btn).click();
	}

4. Defining each Locator directly as a “MobileElement“ with the help of Page Factory. This is what we gonna see in this session.

We have already covered till point #3 in our previous blogs, in this blog we will see point #4 which is nothing but Page Factory model.

I will give you a sample code snippet below for Page Factory to discuss and for better understanding

public class LoginPage_PF extends Testengine {
	
	private AppiumDriver<?> driver;
	
	public LoginPage_PF(AppiumDriver<?> driver) {
		this.driver = driver;
		PageFactory.initElements(new AppiumFieldDecorator(driver), this);
	}	
}

In the above code, it is very clear that we have a Constructor and using this keyword we are declaring the driver. What is the next line means?

PageFactory.initElements(new AppiumFieldDecorator(driver), this);

This is the point we initiated the Page Factory. Here PageFactory is a class which is having a static method “public static <T> T initElements(WebDriver driver, Class<T> pageClassToProxy)” this method is used to initiate the driver instance of a given class and which will return an instantiated instance of the class with WebElement.

AppiumFieldDecorator() which is one of an overridden constructor of the class “AppiumFieldDecorator”. This constructor is used to decorate/initiate each mobile element directly as a “MobileElement” variable.

In combination of PageFactory.initElements and AppiumFieldDecorator we can initiate a variable/element with the help of @FindBy annotations.

Remember, one of the main advantage using Page Factory is declaring locators/elements directly as a “MobileElement”. To do so it is important to initiate the driver as soon as the class is invoked this is one of a main reason why we are initiating page factory in Constructor.

Quick Hint: “this” keyword is a reference variable which refers to the current object, so instead of using this keyword in the below line, we can directly mention the class name as well.

PageFactory.initElements(new AppiumFieldDecorator(driver), this);
 Or
 PageFactory.initElements(new AppiumFieldDecorator(driver), LoginPage_PF.class);

Quick Hints:

  1. “PageFactory” class comes from the package “org.openqa.selenium.support”.
  2. “AppiumFieldDecorator” comes from the package “io.appium.java_client.pagefactory.”
  3. Any @ notations are internally referring to interfaces
  4. It is always a good practice to comment for each element or for a group of elements about its usage and how to use.

Tips: There is another ways of initiating PageFactory, is while creating an instance of the page, for example

LoginPage_PF login = PageFactory.initElements(driver, LoginPage_PF.class)

It is up to you to select any one of these ways in your framework.

Hope we are clear with the Constructor and initElements parts, Let’s move to Page Factory Annotations

What are Page Factory Annotations?

Page Factory annotations are used to provide the way of finding a MobileElement. Also it will make the user feel the code more readable and understandable.  

       @FindBy() is one of an annotation which helps user to declare a WebElement

Quick Hint: We are telling ‘@’ special key as an annotation, actually it is an interface with @infront. Example: public @interface Test, public @interface FindBy.   

About @FindBy in java docs

@FindBy is used to find and initiate WebElements, and to define MobileElements we need to use annotations like  @iOSXCUITFindBy and @AndroidFindBy

@iOSXCUITFindBy is used to declare variable as “IOSElement” which is for Apple idevices

@AndroidFindBy is used to declare variable as “AndroidElement” which is for Android based devices

List of locators available for @iOSXCUITFindBy

List of locators available for @AndroidFindBy

Sample code snippet below on @FindBy

@AndroidFindBy(id = "username")
 AndroidElement loc_Username_android;

 @iOSXCUITFindBy(accessibility = "username")
 AndroidElement loc_Username_ios;

Here, we are instructing Appium to use @AndroidFindBy to find an element using the strategy “id” and look for the text “username” in DOM, same for @iOSXCUITFindBy

Hence we already initialized our variable as a MobileElement, no need to use driver.findElement() method every time to access any element, instead we can directly use the variable name followed by selenium/Appium operations to be performed, for example

loc_Username_android.sendKeys(“Admin”);

Cool right!!! the background is, “driver.findElement(By.ID(“username”))” will convert any given locator as a MobileElement/WebElement in runtime and uses the selenium/Appium operations like click(), sendKeys() etc.. Since we already declared our variables as a MobileElement no need to use the findElement() method.

Please find below for the code snippet, which includes explicit wait just to let you know how to use the MobileElement.

package com.invoiceapp.pages;

import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import invoiceapp.base.Testengine;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.android.AndroidElement;
import io.appium.java_client.pagefactory.AndroidFindBy;
import io.appium.java_client.pagefactory.AppiumFieldDecorator;

public class LoginPage_PF extends Testengine {
	
	protected AppiumDriver<?> driver;
	public WebDriverWait wait;
	
	
	/**
	 * Constructor with a parameter 
	 * @param driver as an instance of AppiumDriver<?>
	 */
	public LoginPage_PF(AppiumDriver<?> driver) {
		this.driver = driver;
		PageFactory.initElements(new AppiumFieldDecorator(driver), this);
	}
	
	// Looking for the text field "Username" in Login page
	@AndroidFindBy(id = "username")
	AndroidElement loc_Username_txt;

	// Looking for the text field "Password" in Login page
	@AndroidFindBy(id = "password")
	AndroidElement loc_Password_txt;
	
	// Looking for the Button field "Username" in Login page
	@AndroidFindBy(id = "login_button")
	AndroidElement loc_login_btn;
	
	public void verify_Login() {
		wait = new WebDriverWait(driver, 10);
		wait.until(ExpectedConditions.visibilityOf(loc_Username_txt));
		loc_Username_txt.sendKeys("Admin");
		
		wait.until(ExpectedConditions.visibilityOf(loc_Password_txt));
		loc_Password_txt.sendKeys("Admin123");
		
		wait.until(ExpectedConditions.visibilityOf(loc_login_btn));
		loc_login_btn.click();
	}

}

Hope we are very clear till this level. Now the question comes, How to call this class from our Test class?

Okay, so we are using constructor in our Page class i.e. “PageFactory_Example” which expect an argument to be passed. Please find the code below to understand how to pass the driver as an argument for the PageFactory_Example.class

package test;

import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.invoiceapp.pages.LoginPage_PF;
import invoiceapp.base.Testengine;

public class PageFactoryTest extends Testengine {
	
	public static LoginPage_PF login;
	
	@BeforeClass
	public static void initiateDrivers() {
		// Passing the driver as an argument to LoginPage_PF constructor 
		login = new LoginPage_PF(driver);
	}

	@Test
	public void test_TC001_verifyAppLaunch() {		
		login.verify_Login();
	}
}

That’s all folks!!! We are now good to go with the PageFactory model in our framework. In our next blog we will see how to create a separate class for PageFactory and to get the instance of that to initiate all our elements instead of initiating everytime in our Page classes.

Also we will see how to utilize Page Factory annotations to define a MobileElement in such a way it which works for both Android and iOS, also how to create a common method for both OS, and few additional topics around Page Factory annotations like @CatcheLookup and using “how” keyword ect..

Stay focused!!!

Note: Whatever code used here are basic level and its only to understand the concept of Page Factory. We are not going to use the same code in our Framework. In our next blog I will definitely demonstrate the same using our AUT.

Hope this helps understanding the concept of Page Factory. Please post your suggestion or questions in comments sections if you have any.


I will commit the codes discussed here in my Github repo. You can clone it and start modifying as per your idea. 

https://github.com/Karthickragu/Appium_Blog

Cheers!

Naveen AutomationLab

Blog Contributors:

Author:  Ragavendran

Ragav, having 10+ years of testing experience on which 7 years of rich experience in automation using UFT, Selenium (Java/Python). Expertise in Web and Mobile automation testing using Appium.

https://www.linkedin.com/in/Ragavendran-Ragav

Reviewer: Naveen Khunteta 

https://www.linkedin.com/in/naveenkhunteta