Page Object Model (POM) With Page Factory | Selenium Tutorial

This In-depth Tutorial Explains All About Page Object Model (POM) With Pagefactory Using Examples. You Can Also Learn the Implementation of POM in Selenium:

In this tutorial, we will understand how to create a Page Object Model using the Page Factory approach. We will focus on :

  • Factory Class
  • How to Create a Basic POM using Page Factory Pattern
  • Different Annotations Used in Page Factory Approach

Before we see what is Pagefactory and how it can be used along with the Page object model, let us understand what is Page Object Model which is commonly known as POM.

=> Visit Here To See The Selenium Training Series For All.

Page Object Model With Page factory In Selenium

What Is Page Object Model (POM)?

Theoretical terminologies describe the Page Object Model as a design pattern used to build an object repository for the web elements available in the application under test. Few others refer to it as a framework for Selenium automation for the given application under test.

However, what I have understood about the term Page Object Model is:

#1) It is a design pattern where you have a separate Java class file corresponding to each screen or page in the application. The class file could include the object repository of the UI elements as well as methods.

#2) In case there are humongous web elements on a page, the object repository class for a page can be separated from the class that includes methods for the corresponding page.

Example: If the Register Account page has many input fields then there could be a class RegisterAccountObjects.java that forms the object repository for the UI elements on the register accounts page.

A separate class file RegisterAccount.java extending or inheriting RegisterAccountObjects that includes all the methods performing different actions on the page could be created.

#3) Besides, there could be a generic package with a {roperties file, Excel test data, and Common methods under a package.

Example: DriverFactory that could be used very easily throughout all the pages in the application

Understanding POM With Example

Check here to learn more about POM.

Below is a snapshot of the Web Page:

POM Reference Site

Clicking on each of these links will redirect the user to a new page.

Here is the snapshot of how the project structure with Selenium is built using the Page object model corresponding to each page on the website. Every Java class includes object repository and methods for performing different actions within the page.

Besides, there will be another JUNIT or TestNG or a Java class file invoking calls to class files of these pages.

CheckboxesPage.Java

Why Do We Use The Page Object Model?

There is a buzz around on the usage of this powerful Selenium framework called POM or page object model. Now, the question arises as “Why use POM?”.

The simple answer to this is that POM is a combination of data-driven, modular and hybrid frameworks. It’s an approach to systematically organizing the scripts in such a way that it makes it easy for the QA to maintain the code free of hassles and also helps to prevent redundant or duplicate code.

For instance, if there is a change in the locator value on a specific page, then it is very easy to identify and make that quick change only in the script of the respective page without impacting the code elsewhere.

We use the Page Object Model concept in Selenium Webdriver due to the following reasons:

  1. An object repository is created in this POM model. It is independent of test cases and can be reused for a different project.
  2. The naming convention of methods is very easy, understandable and more realistic.
  3. Under the Page object model, we create page classes that can be reused in another project.
  4. The Page object model is easy for the developed framework due to its several advantages.
  5. In this model, separate classes are created for different pages of a web application like login page, the home page, employee detail page, change password page, etc.
  6. If there is any change in any element of a website then we only need to make changes in one class, and not in all classes.
  7. The script designed is more reusable, readable and maintainable in the page object model approach.
  8. Its project structure is quite easy and understandable.
  9. Can use PageFactory in the page object model in order to initialize the web element and store elements in the cache.
  10. TestNG can also be integrated into the Page Object Model approach.

Implementation Of Simple POM In Selenium

#1) Scenario To Automate

Now we automate the given scenario using the Page Object Model.

The scenario is explained below:

Step 1: Launch the site “ https: //demo.vtiger.com ”.
Step 2: Enter the valid credential.
Step 3: Login to the site.
Step 4: Verify the Home page.
Step 5: Logout the site.
Step 6: Close the Browser.

#2) Selenium Scripts For The Above Scenario In POM

Now we create the POM Structure in Eclipse, as explained below:

Step 1: Create a Project in Eclipse – POM based Structure:

a) Create Project “ Page Object Model ”.

Create Project - Page Object Model

b) Create 3 Package under the project.

  • library
  • pages
  • test cases

Library: Under this, we put those codes that need to be called again and again in our test cases like Browser launch, Screenshots, etc. The user can add more classes under it based on the project need.

Pages: Under this, classes are created for each page in the web application and can add more page classes based on the number of pages in the application.

Test cases: Under this, we write the login test case and can add more test cases as required to test the whole application.

POM - SRC

c) Classes under the Packages are shown in the below image.

Classes under the Packages

Step 2: Create the following classes under the library package.

Browser.java: In this class, 3 browsers ( Firefox, Chrome and Internet Explorer ) are defined and it is called in the login test case. Based on the requirement, the user can test the application in different browsers as well.

package library;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;

public class Browser {

             static WebDriver driver;

            public static WebDriver StartBrowser(String browsername , String url)
            {
                         // If the browser is Firefox
                       if(browsername.equalsIgnoreCase("Firefox"))
                        {
                                      // Set the path for geckodriver.exe
                                     System.setProperty("webdriver.firefox.marionette"," E://Selenium//Selenium_Jars//geckodriver.exe ");
                                                                  driver = new FirefoxDriver();
                        }

                      // If the browser is Chrome
                    else if(browsername.equalsIgnoreCase("Chrome"))
                     {
                                 // Set the path for chromedriver.exe
            System.setProperty("webdriver.chrome.driver","E://Selenium//Selenium_Jars//chromedriver.exe");
                          driver = new ChromeDriver();
                     }
                    // If the browser is IE
                  else if(browsername.equalsIgnoreCase("IE"))
                  {
                                      // Set the path for IEdriver.exe
            System.setProperty("webdriver.ie.driver","E://Selenium//Selenium_Jars//IEDriverServer.exe");
                           driver = new InternetExplorerDriver();
                  }

                           driver.manage().window().maximize();
                          driver.get(url);
                         return driver;
                  }
}

ScreenShot.java: In this class, a screenshot program is written and it is called in the test case when the user wants to take a screenshot of whether the test fails or passes.

package library;
import java.io.File;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
public class ScreenShot {
 
              public static void captureScreenShot(WebDriver driver, String ScreenShotName)
              {
                            try {
                                                 File screenshot=((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
                                                 FileUtils.copyFile(screenshot,new File("E://Selenium//"+ScreenShotName+".jpg"));
                                     } catch (Exception e)
                                       {
                                           System.out.println(e.getMessage());
                                            e.printStackTrace();
                                        }
                  }
}

Step 3 : Create page classes under Page package.

HomePage.java: This is the Home page class, in which all the elements of the home page and methods are defined.

package pages;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class HomePage {

WebDriver driver;

By logout = By.id("p_lt_ctl03_wSOB_btnSignOutLink");
By home = By.id("p_lt_ctl02_wCU2_lblLabel");

//Constructor to initialize object
public HomePage(WebDriver dr)
              {
                             this.driver=dr;
               }
              public String pageverify()
              {
                            return driver.findElement(home).getText();
               }
              publicvoid logout()
               {
                             driver.findElement(logout).click();
                }
}

LoginPage.java: This is the Login page class, in which all the elements of the login page and methods are defined.

package pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class LoginPage {
WebDriver driver;
By UserID = By.xpath("//*[contains(@id,'Login1_UserName')]");
By password = By.xpath("//*[contains(@id,'Login1_Password')]");
By Submit = By.xpath("//*[contains(@id,'Login1_LoginButton')]");
//Constructor to initialize object
public LoginPage(WebDriver driver)
{
              this.driver = driver;
}
public void loginToSite(String Username, String Password)
{
                this.enterUsername(Username);
                this.enterPasssword(Password);
                this.clickSubmit();
}
publicvoid enterUsername(String Username)
{
                driver.findElement(UserID).sendKeys(Username);
}
publicvoid enterPasssword(String Password)
{
                driver.findElement(password).sendKeys(Password);
}
publicvoid clickSubmit()
{
                driver.findElement(Submit).click();
}
}

Step 4: Create Test Cases for the login scenario.

LoginTestCase.java: This is the LoginTestCase class, where the test case is executed. The user can also create more test cases as per the project need.

package testcases;
import java.util.concurrent.TimeUnit;
import library.Browser;
import library.ScreenShot;
import org.openqa.selenium.WebDriver;
import org.testng.Assert;
import org.testng.ITestResult;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import pages.HomePage;
import pages.LoginPage;
public class LoginTestCase {

               WebDriver driver;
               LoginPage lp;
               HomePage hp;
              int i = 0;
              // Launch of the given browser.
              @BeforeTest
              public void browserlaunch()
              {
                             driver = Browser.StartBrowser("Chrome", "http://demostore.kenticolab.com/Special-Pages/Logon.aspx");
                             driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
                             lp = new LoginPage(driver);
                             hp = new HomePage(driver);
               }
             // Login to the Site.
             @Test(priority = 1)
             public void Login()
              {
              lp.loginToSite("gaurav.3n@gmail.com","Test@123");
              }
             // Verifing the Home Page.
             @Test(priority = 2)
             public void HomePageVerify()
             {
                            String HomeText = hp.pageverify();
                            Assert.assertEquals(HomeText, "Logged on as");
              }
              // Logout the site.
             @Test(priority = 3)
             public void Logout()
              {
                             hp.logout();
               }
// Taking Screen shot on test fail
              @AfterMethod
              public void screenshot(ITestResult result)
              {
                         i = i+1;
                         String name = "ScreenShot";
                         String x = name+String.valueOf(i);
                        if(ITestResult.FAILURE == result.getStatus())
                          {
                                         ScreenShot.captureScreenShot(driver, x);
                           }
   }
  @AfterTest
   public void closeBrowser()
   {
                  driver.close();
    }
}

Step 5: Execute “ LoginTestCase.java “.

Step 6: Output of the Page Object Model:

  • Launch the Chrome browser.
  • The demo website is opened in the browser.
  • Login to the demo site.
  • Verify the home page.
  • Logout the site.
  • Close the browser.

Now, let us explore the prime concept of this tutorial that grabs the attention i.e. “Pagefactory”.

What Is Pagefactory?

PageFactory is a way of implementing the “Page Object Model”. Here, we follow the principle of separation of Page Object Repository and Test Methods. It is an inbuilt concept of Page Object Model which is very optimized.

Let us now have more clarity on the term Pagefactory.

#1) Firstly, the concept called Pagefactory, provides an alternate way in terms of syntax and semantics for creating an object repository for the web elements on a page.

#2) Secondly, it uses a slightly different strategy for the initialization of the web elements.

#3) The object repository for the UI web elements could be built using:

  • Usual ‘POM without Pagefactory’ and,
  • Alternatively, you can use ‘POM with Pagefactory’.

Given below is a pictorial representation of the same:

Object repository for the UI web elements

Now we will look at all the aspects that differentiate the usual POM from POM with Pagefactory.

a) The difference in the syntax of locating an element using usual POM vs POM with Pagefactory.

For Example, Click here to locate the search field that shows up on the page.

NSE

POM Without Pagefactory:

#1) Below is how you locate the search field using the usual POM:

WebElement searchNSETxt=driver.findElement(By.id(“searchBox”));

#2) The below step passes the value “investment” into the Search NSE field.

searchNSETxt.sendkeys(“investment”);

POM Using Pagefactory:

#1) You can locate the search field using Pagefactory as shown below.

The annotation @FindBy is used in Pagefactory to identify an element while POM without Pagefactory uses the driver.findElement() method to locate an element.

The second statement for Pagefactory after @FindBy is assigning an <element name> of type WebElement class that works exactly similar to the assignment of an element name of type WebElement class as a return type of the method driver.findElement() that is used in usual POM (searchNSETxt in this example).

We will look at the @FindBy annotations in detail in the upcoming part of this tutorial.

@FindBy(id = "searchBox")
WebElement searchNSETxt;

#2) The below step passes the value “investment” into the Search NSE field and the syntax remains the same as that of the usual POM (POM without Pagefactory).

searchNSETxt.sendkeys(“investment”);

b) The difference in the strategy of Initialization of Web Elements using usual POM vs POM with Pagefactory.

Using POM Without Pagefactory:

Given below is a code snippet to set the Chrome driver path. A WebDriver instance is created with the name driver and the ChromeDriver is assigned to the ‘driver’. The same driver object is then used to launch the National Stock Exchange website, locate the searchBox and enter the string value to the field.

The point that I wish to highlight here is that when it is POM without page factory, the driver instance is created initially and every web element is freshly initialized each time when there is a call to that web element using driver.findElement() or driver.findElements().

This is why, with a new step of driver.findElement() for an element, the DOM structure is again scanned through and refreshed identification of the element is done on that page.

System.setProperty("webdriver.chrome.driver",
"C:\\eclipse-workspace\\automationframework\\src\\test\\java\\Drivers\\chromedriver.exe");
WebDriver driver = new ChromeDriver();
driver.get("http://www.nseindia.com/");
WebElement searchNSETxt=driver.findElement(By.id(“searchBox”));
searchNSETxt.sendkeys(“investment”);

Using POM With Pagefactory:

Besides using @FindBy annotation instead of the driver.findElement() method, the below code snippet is used additionally for Pagefactory. The static initElements() method of PageFactory class is used to initialize all the UI elements on the page as soon as the page loads.

public PagefactoryClass(WebDriver driver) 
{
this.driver = driver;
PageFactory.initElements(driver, this);
}

The above strategy makes the PageFactory approach slightly different from the usual POM. In the usual POM, the web element has to be explicitly initialized while in the Pagefactory approach all the elements are initialized with initElements() without explicitly initializing each web element.

For Example: If the WebElement was declared but not initialized in the usual POM, then “initialize variable” error or NullPointerException is thrown. Hence in the usual POM, each WebElement has to be explicitly initialized. PageFactory comes with an advantage over the usual POM in this case.

Let us not initialize the web element BDate (POM without Pagefactory), you can see that the error’ Initialize variable’ displays and prompts the user to initialize it to null, hence, you cannot assume that the elements get initialized implicitly on locating them.

Error in POM - Initialize Variable

Element BDate explicitly initialized (POM without Pagefactory):

Explicitly_Initialized

Now, let’s look at a couple of instances of a complete program using PageFactory to rule out any ambiguity in understanding the implementation aspect.

Example 1:

  • Go to ‘http://www.nseindia.com/’
  • From the dropdown next to the search field, select ‘Currency Derivatives’.
  • Search for ‘USDINR’. Verify the text ‘US Dollar-Indian Rupee – USDINR’ on the resulting page.

Program Structure:

  • PagefactoryClass.java that includes an object repository using page factory concept for nseindia.com that is a constructor for initializing all web elements is created, method selectCurrentDerivative() to select value from the Searchbox dropdown field, selectSymbol() to select a symbol on the page that shows up next and verifytext() to verify if the page header is as expected or not.
  • NSE_MainClass.java is the main class file that calls all the above methods and performs the respective actions on the NSE site.

PagefactoryClass.java

package com.pagefactory.knowledge;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.Select;
public class PagefactoryClass {
	WebDriver driver;
	@FindBy(id = "QuoteSearch")
	WebElement Searchbox;
	@FindBy(id = "cidkeyword")
	WebElement Symbol;
	@FindBy(id = "companyName")
	WebElement pageText;
	public PagefactoryClass(WebDriver driver) {
		this.driver = driver;
		PageFactory.initElements(driver, this);
	}
	public void selectCurrentDerivative(String derivative) {
		Select select = new Select(Searchbox);
		select.selectByVisibleText(derivative); // "Currency Derivatives"
	}
	public void selectSymbol(String symbol) {
		Symbol.sendKeys(symbol);
	}
	public void verifytext() {
		if (pageText.getText().equalsIgnoreCase("U S Dollar-Indian Rupee - USDINR")) {
			System.out.println("Page Header is as expected");
		} else
			System.out.println("Page Header is NOT as expected");
	}
}

NSE_MainClass.java

package com.pagefactory.knowledge;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
public class NSE_MainClass {
	static PagefactoryClass page;
	static WebDriver driver;
	public static void main(String[] args) {
		System.setProperty("webdriver.chrome.driver",
"C:\\Users\\eclipse-workspace\\automation-framework\\src\\test\\java\\Drivers\\chromedriver.exe");
		driver = new ChromeDriver();
		driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
		driver.get("https://www.nseindia.com/");
		driver.manage().window().maximize();
		test_Home_Page_ofNSE();
	}
	public static void test_Home_Page_ofNSE() throws StaleElementReferenceException {
		page = new PagefactoryClass(driver);
		
		page.selectCurrentDerivative("Currency Derivatives");
		page.selectSymbol("USD");
		List<WebElement> Options = driver.findElements(By.xpath("//span[contains(.,'USD')]"));
		int count = Options.size();
		for (int i = 0; i < count; i++) {
			System.out.println(i);
			System.out.println(Options.get(i).getText());
			System.out.println("---------------------------------------");
			
			if (i == 3) {
				System.out.println(Options.get(3).getText()+" clicked");
				Options.get(3).click();
				
				break;
			}
		}
		try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			
			e.printStackTrace();
		}
		page.verifytext();
	}
}

Example 2:

  • Go to ‘https://www.shoppersstop.com/brands’
  • Navigate to Haute curry link.
  • Verify if the Haute Curry page contains the text “Start New Something”.

Program structure 

  • shopperstopPagefactory.java that includes an object repository using pagefactory concept for shoppersstop.com that is a constructor for initializing all web elements is created, methods closeExtraPopup() to handle an alert pop up box that opens up, clickOnHauteCurryLink() to click on Haute Curry Link and verifyStartNewSomething() to verify if the Haute Curry page contains the text “Start new something”.
  • Shopperstop_CallPagefactory.java is the main class file that calls all the above methods and performs the respective actions on the NSE site.

shopperstopPagefactory.java

package com.inportia.automation_framework;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class shopperstopPagefactory {
	 WebDriver driver;
	@FindBy(id="firstVisit")
	
	WebElement extrapopup;
	
	@FindBy(xpath="//img[@src='https://sslimages.shoppersstop.com
/sys-master/root/haf/h3a/9519787376670/brandMedia_HauteCurry_logo.png']")
	
	WebElement HCLink;
	
	@FindBy(xpath="/html/body/main/footer/div[1]/p")
	
	WebElement Startnew;
	
	public  shopperstopPagefactory(WebDriver driver) {
		this.driver=driver;
		PageFactory.initElements(driver, this);
		
	}
	
	public  void closeExtraPopup() {
		extrapopup.click();
	}
	
	public  void clickOnHauteCurryLink() {
		JavascriptExecutor js = (JavascriptExecutor) driver;  
		js.executeScript("arguments[0].click();",HCLink);
		js.executeAsyncScript("window.setTimeout(arguments[arguments.length - 1], 10000);");
		if(driver.getCurrentUrl().equals("https://www.shoppersstop.com/haute-curry")) {
			System.out.println("We are on the Haute Curry page");
		}
		else {
			System.out.println("We are NOT on the Haute Curry page");	
	}
}
public  void verifyStartNewSomething() {
	if (Startnew.getText().equalsIgnoreCase("Start Something New")) {
		System.out.println("Start new something text exists");
	} else
		System.out.println("Start new something text DOESNOT exists");
}
}

Shopperstop_CallPagefactory.java

package com.inportia.automation_framework;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class Shopperstop_CallPagefactory extends shopperstopPagefactory {
	
	public Shopperstop_CallPagefactory(WebDriver driver) {
		super(driver);
		
		
		// TODO Auto-generated constructor stub
	}
	 static WebDriver driver;
	 
	public static void main(String[] args) {
		System.setProperty("webdriver.chrome.driver",
                                                    "C:\\eclipse-workspace\\automation-framework\\src\\test\\java\\Drivers\\chromedriver.exe");
		driver = new ChromeDriver();
		Shopperstop_CallPagefactory s1=new Shopperstop_CallPagefactory(driver);
       	 driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
       	 driver.get("https://www.shoppersstop.com/brands");
       	 s1.clickOnHauteCurryLink();
        	s1.verifyStartNewSomething();
        
	}
}

 

POM Using Page Factory

Video Tutorials – POM With Page Factory

Part I


Part II

A Factory class is used to make using Page Objects simpler and easier.

  • First, we need to find the web elements by annotation @FindBy in page classes.
  • Then initialize the elements using initElements() when instantiating the page class.

#1) @FindBy:

@FindBy annotation is used in PageFactory to locate and declare the web elements using different locators. Here, we pass the attribute as well as its value used for locating the web element to the @FindBy annotation and then the WebElement is declared.

There are 2 ways in which the annotation can be used.

For Example:

@FindBy(how = How.ID, using="EmailAddress") 
WebElement Email;

@FindBy(id="EmailAddress") 
WebElement Email;

However, the former is the standard way of declaring WebElements.

‘How’ is a class and it has static variables like ID, XPATH, CLASSNAME, LINKTEXT, etc.
‘using’ – To assign a value to a static variable.

In the above example, we have used the ‘id' attribute to locate the web element ‘Email’. Similarly, we can use the following locators with the @FindBy annotations:

  • className
  • css
  • name
  • xpath
  • tagName
  • linkText
  • partialLinkText

#2) initElements():

The initElements is a static method of PageFactory class which is used to initialize all the web elements located by @FindBy annotation. Thus, instantiating the Page classes easily.

initElements(WebDriver driver, java.lang.Class pageObjectClass)

We should also understand that POM follows OOPS principles.

  • WebElements are declared as private member variables (Data Hiding).
  • Binding WebElements with corresponding methods (Encapsulation).

Steps To Create POM Using Page Factory Pattern

#1) Create a separate Java class file for each webpage.

#2) In each Class, all the WebElements should be declared as variables(using annotation – @FindBy) and initialized using initElement() method. WebElements declared have to be initialized to be used in the action methods.

#3) Define corresponding methods acting on those variables.

Let's take an example of a simple scenario:

  • Open the URL of an application.
  • Type Email Address and Password data.
  • Click on the Login button.
  • Verify successful login message on the Search Page.

Page Layer

Here we have 2 pages,

  1. HomePage – The page that opens when the URL is entered and where we enter the data for login.
  2. SearchPage – A page that gets displayed after a successful login.

In Page Layer, each page in the Web Application is declared as a separate Java Class and its locators and actions are mentioned there.

Steps To Create POM With Real-Time Example

#1) Create a Java Class for each page:

In this example, we will access 2 web pages, “Home” and “Search” pages.

Hence, we will create 2 Java classes in Page Layer (or in a package say, com.automation.pages).

Package Name :com.automation.pages
 HomePage.java
 SearchPage.java

#2) Define WebElements as variables using Annotation @FindBy:

We would be interacting with:

  • Email, Password, Login button field on the Home Page.
  • Successful message on the Search Page.

So we will define WebElements using @FindBy

For Example: If we are going to identify the EmailAddress using attribute id, then its variable declaration is

//Locator for EmailId field

@FindBy(how=How.ID,using="EmailId")
 private WebElementEmailIdAddress;

#3) Create methods for actions performed on WebElements.

Below actions are performed on WebElements:

  • Type action on the Email Address field.
  • Type action in the Password field.
  • Click action on the Login Button.

For Example, User-defined methods are created for each action on the WebElement as,

public void typeEmailId(String Id){
driver.findElement(EmailAddress).sendKeys(Id)
}

Here, the Id is passed as a parameter in the method, since input will be sent by the user from the main test case.

Note: A constructor has to be created in each of the class in the Page Layer, in order to get the driver instance from the Main class in Test Layer and also to initialize WebElements(Page Objects) declared in the page class using PageFactory.InitElement().

We do not initiate the driver here, rather its instance is received from the Main Class when the object of the Page Layer class is created.

InitElement() – is used to initialize the WebElements declared, using driver instance from the main class. In other words, WebElements are created using the driver instance. Only after the WebElements are initialized, they can be used in the methods to perform actions.

Two Java Classes are created for each page as shown below:

HomePage.java

//package

com.automation.pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class HomePage {

WebDriver driver;
		
	// Locator for Email Address
@FindBy(how=How.ID,using="EmailId")
 private WebElement EmailIdAddress;

	// Locator for Password field
@FindBy(how=How.ID,using="Password ")
 private WebElement Password;

	// Locator for SignIn Button
@FindBy(how=How.ID,using="SignInButton")
 private WebElement SignInButton;

	// Method to type EmailId
	public void typeEmailId(String Id){
		driver.findElement(EmailAddress).sendKeys(Id)
	}

	// Method to type Password
	public void typePassword(String PasswordValue){
		driver.findElement(Password).sendKeys(PasswordValue)
	}

	// Method to click SignIn Button
	public void clickSignIn(){
		driver.findElement(SignInButton).click()
	}

	// Constructor
	// Gets called when object of this page is created in MainClass.java
	public HomePage(WebDriver driver)
	{
		// "this" keyword is used here to distinguish global and local variable "driver"
		//gets driver as parameter from MainClass.java and assigns to the driver instance in this class 

		this.driver=driver;

		PageFactory.initElements(driver,this); 
// Initialises WebElements declared in this class using driver instance.
			
}
}

SearchPage.Java

//package
com.automation.pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class SearchPage{

	WebDriver driver;
		
	// Locator for Success Message
@FindBy(how=How.ID,using="Message")
 private WebElement SuccessMessage;

	// Method that return True or False depending on whether the message is displayed
public Boolean MessageDisplayed(){
		Boolean status = driver.findElement(SuccessMessage).isDisplayed();
		return status;
	}

	// Constructor
	// This constructor is invoked when object of this page is created in MainClass.java
public SearchPage(WebDriver driver)
	{
		// "this" keyword is used here to distinguish global and local variable "driver"
		//gets driver as parameter from MainClass.java and assigns to the driver instance in this class 

		this.driver=driver;

		PageFactory.initElements(driver,this); 
// Initialises WebElements declared in this class using driver instance.
		
}
}

Test Layer

Test Cases are implemented in this class. We create a separate package say, com.automation.test and then create a Java Class here (MainClass.java)

Steps To Create Test Cases:

  • Initialize the driver and open the application.
  • Create an object of the PageLayer Class(for each webpage) and pass the driver instance as a parameter.
  • Using the object created, make a call to the methods in the PageLayer Class(for each webpage) in order to perform actions/verification.
  • Repeat step 3 until all the actions are performed and then close the driver.
 
//package
com.automation.test;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class MainClass {

public static void main(String[] args) {
		
	System.setProperty("webdriver.chrome.driver","./exefiles/chromedriver.exe");
	WebDriver driver= new ChromeDriver();
	driver.manage().window().maximize();
	
driver.get("URL mentioned here");
		
// Creating object of HomePage and driver instance is passed as parameter to constructor of Homepage.Java
		HomePage homePage= new HomePage(driver);
		
// Type EmailAddress
	homePage.typeEmailId("abc@ymail.com"); 
// EmailId value is passed as paramter which in turn will be assigned to the method in HomePage.Java

// Type Password Value
	homePage.typePassword("password123"); 
// Password value is passed as paramter which in turn will be assigned to the method in HomePage.Java

// Click on SignIn Button
	homePage.clickSignIn();
		
// Creating an object of LoginPage and driver instance is passed as parameter to constructor of SearchPage.Java
	SearchPage searchPage= new SearchPage(driver);
		
//Verify that Success Message is displayed
	Assert.assertTrue(searchPage.MessageDisplayed());
		
//Quit browser
driver.quit();
		
}
}

Annotation Type Hierarchy Used For Declaring WebElements

Annotations are used to help construct a location strategy for the UI Elements.

#1) @FindBy

When it comes to Pagefactory, @FindBy acts as a magical wand. It adds all the power to the concept. You are now aware that @FindBy annotation in Pagefactory performs the same as that of the driver.findElement() in the usual page object model. It is used to locate WebElement/WebElements with one criterion.

#2) @FindBys

It is used to locate WebElement with more than one criteria and need to match all of the given criteria. These criteria should be mentioned in a parent-child relationship. In other words, this uses AND conditional relationship to locate the WebElements using the criteria specified. It uses multiple @FindBy to define each criterion.

For Example:

HTML source code of a WebElement:

<div id =”searchId_1>
	<button id=”1” , name=”search_field”/>
	<input id =”2”/>
</div>

In POM:

@FindBys({
 @FindBy(id = "searchId_1"),
 @FindBy(name = "search_field")
 })

WebElementSearchButton;

In the above example, the WebElement ‘SearchButton’ is located only if it matches both the criteria whose id value is “searchId_1” and the name value is “search_field”. Please note that the first criteria belong to a parent tag<div> and the second criteria for a child tag<button>.

#3) @FindAll

It is used to locate WebElement with more than one criteria and it needs to match at least one of the given criteria. This uses OR conditional relationships in order to locate WebElements. It uses multiple @FindBy to define all the criteria.

For Example:

HTML SourceCode:

<input id="”Username”," class="”Username_r”/" name="”User_Id”," type="“text”," />

In POM:

@FindBys({
@FindBy(id = "UsernameNameField_1"), // doesn’t match
 @FindBy(name = "User_Id") //matches
@FindBy(className = “UserName_r”) //matches
})
WebElementUserName;

In the above example, the WebElement ‘Username is located if it matches at least one of the criteria mentioned.

#4) @CacheLookUp

When the WebElement is more often used in test cases, Selenium looks up for the WebElement each time when the test script is run. In those cases, wherein certain WebElements are globally used for all TC (For Example, Login scenario happens for each TC), this annotation can be used to maintain those WebElements in cache memory once it is read for the first time.

This, in turn, helps the code to execute faster because each time it doesn’t have to search for the WebElement in the page, rather it can get its reference from the memory.

This can be as a prefix with any of @FindBy, @FindBys and @FindAll.

For Example:

@CacheLookUp
@FindBys({
@FindBy(id = "UsernameNameField_1"), 
 @FindBy(name = "User_Id") 
@FindBy(className = “UserName_r”) 
})
WebElementUserName;

Also note that this annotation should be used only to WebElements whose attribute value(like xpath , id name, class name, etc.) doesn’t change quite often. Once the WebElement is located for the first time, it maintains its reference in the cache memory.

So, then there happens a change in the WebElement’s attribute after few days, Selenium will not be able to locate the element, because it already has its old reference in its cache memory and will not consider the recent change in WebElement.

More On PageFactory.initElements()

Now that we understand the strategy of Pagefactory on initializing the web elements using InitElements(), let’s try to understand the different versions of the method.

The method as we know takes the driver object and the current class object as the input parameters and returns the page object by implicitly and proactively initializing all the elements on the page.

In practice, the use of the constructor as shown in the above section is more preferable over the other ways of its usage.

Alternative Ways Of Calling The Method Is:

#1) Instead of using “this” pointer, you may create the current class object, pass the driver instance to it and call the static method initElements with parameters i.e. the driver object and the class object that was just created.

public PagefactoryClass(WebDriver driver) 
{
//version 2
PagefactoryClass page=new PagefactoryClass(driver);
PageFactory.initElements(driver, page);
}

#2) The third way to initialize elements using the Pagefactory class is by using the api called “reflection”. Yes, instead of creating a class object with a “new” keyword, classname.class can be passed as part of the initElements() input parameter.

public PagefactoryClass(WebDriver driver) {
//version 3
PagefactoryClass page=PageFactory.initElements(driver, PagefactoryClass.class);
}

Frequently Asked Questions

Q #1) What are the different locator strategies that are used for @FindBy?

Answer: The simple answer to this is there are no different locator strategies that are used for @FindBy.

They use the same 8 locator strategies that the findElement() method in the usual POM uses :

  1. id
  2. name
  3. className
  4. xpath
  5. css
  6. tagName
  7. linkText
  8. partialLinkText

Q #2) Are there different versions to the usage of @FindBy annotations too?

Answer: When there is a web element to be searched, we use the annotation @FindBy. We will elaborate on the alternative ways of using the @FindBy along with the different locator strategies as well.

We have already seen how to use version 1 of @FindBy:

@FindBy(id = "cidkeyword")
WebElement Symbol;

Version 2 of @FindBy is by passing the input parameter as How and Using.

How looks for the locator strategy using which the webelement would be identified. The keyword using defines the locator value.

See below for better understanding,

  • How.ID searches the element using id strategy and the element it tries to identify has id=cidkeyword.
@FindBy(how = How.ID, using = " cidkeyword")
WebElement Symbol;
  • How.CLASS_NAME searches the element using className strategy and the element it tries to identify has class=newclass.
@FindBy(how = How.CLASS_NAME, using = "newclass")
WebElement Symbol;

Q #3) Is there a difference between the two versions of @FindBy?

Answer: The answer is No, there is no difference between the two versions. It is just that the first version is the shorter and easier when compared to the second version.

Q #4) What do I use in the pagefactory in case there is a list of web elements to be located?

Answer: In the usual page object design pattern, we have driver.findElements() to locate multiple elements belonging to the same class or tag name but how do we locate such elements in the case of page object model with Pagefactory? The easiest way to achieve such elements is to use the same annotation @FindBy.

I understand that this line seems to be a head-scratcher for many of you. But yes, it is the answer to the question.

Let us look at the below example:

Using the usual page object model without Pagefactory, you use driver.findElements to locate multiple elements as shown below:

private List<WebElement> multipleelements_driver_findelements
=driver.findElements(By.class(“last”));

The same can be achieved using the page object model with Pagefactory as given below:

@FindBy(how = How.CLASS_NAME, using = "last")
private List<WebElement> multipleelements_FindBy;

Basically, assigning the elements to a list of type WebElement does the trick irrespective of whether Pagefactory is been used or not while identifying and locating the elements.

Q #5) Can both the Page object design without pagefactory and with Pagefactory be used in the same program?

Answer: Yes, both the page object design without Pagefactory and with Pagefactory can be used in the same program. You may go through the program given below in the Answer for Question #6 to see how both are used in the program.

One thing to remember is that the Pagefactory concept with the cached feature should be avoided on dynamic elements whereas page object design works well for dynamic elements. However, Pagefactory suits static elements only.

Q #6) Are there alternative ways of identifying elements based on multiple criteria?

Answer: The alternative for identifying elements based on multiple criteria is using the annotations @FindAll and @FindBys. These annotations help to identify single or the multiple elements depending on the values fetched from the criteria passed in it.

#1) @FindAll:

@FindAll can contain multiple @FindBy and will return all the elements that match any @FindBy in a single list. @FindAll is used to mark a field on a Page Object to indicate that the lookup should use a series of @FindBy tags. It will then search for all elements that match any of the FindBy criteria.

Note that the elements are not guaranteed to be in document order.

The syntax to use @FindAll is as below:

@FindAll(
                 {
                                 @FindBy(how = How.ID, using = "foo"),
                 @FindBy(className = "bar")
                }
                )

Explanation: @FindAll will search and identify separate elements conforming to each of the @FindBy criteria and list them out. In the above example, it will first search an element whose id=” foo” and then, will identify the second element with className=” bar”.

Assuming that there was one element identified for each FindBy criteria, @FindAll will result in listing out 2 elements, respectively. Remember, there could be multiple elements identified for each criterion. Thus, in simple words, @FindAll acts equivalent to the OR operator on the @FindBy criteria passed.

#2) @FindBys:

FindBys is used to mark a field on a Page Object to indicate that lookup should use a series of @FindBy tags in a chain as described in ByChained. When the required WebElement objects need to match all of the given criteria use @FindBys annotation.

The syntax to use @FindBys is as below:

@FindBys(
                   {
                                    @FindBy(name=”foo”)
                   @FindBy(className = "bar")
                  }
                  )

Explanation: @FindBys will search and identify elements conforming to all of the @FindBy criteria and list them out. In the above example, it will search elements whose name=”foo” and className=” bar”.

@FindAll will result in listing out 1 element if we assume there was one element identified with the name and the className in the given criteria.

If there is not one element satisfying all the FindBy conditions passed, then the resultant of @FindBys will be zero elements. There could be a list of web elements identified if all the conditions satisfy multiple elements. In simple words, @FindBys acts equivalent to the AND operator on the @FindBy criteria passed.

Let us see the implementation of all the above annotation through a detailed program :

We will modify the www.nseindia.com program given in the previous section to understand the implementation of the annotations @FindBy, @FindBys and @FindAll

#1) The object repository of PagefactoryClass is updated as below:

List <WebElement> newlist= driver.findElements(By.tagName(“a”));

@FindBy(how = How.TAG_NAME, using = “a”)
private List<WebElement> findbyvalue;
@FindAll({ @FindBy(className = “sel”), @FindBy(xpath=”//a[@id='tab5′]”)})
private List<WebElement> findallvalue;
@FindBys({ @FindBy(className = “sel”), @FindBy(xpath=”//a[@id='tab5′]”)})
private List<WebElement> findbysvalue;

#2) A new method seeHowFindWorks() is written in the PagefactoryClass and is invoked as the last method in the Main class.

The method is as below:

private void seeHowFindWorks() 
{
			
			System.out.println("driver.findElements(By.tagName()) "+newlist.size());
			System.out.println("count of @FindBy- list elements "+findbyvalue.size());
			System.out.println("count of @FindAll elements "+findallvalue.size());
			for(int i=0;i<findallvalue.size();i++) 
                       {
                                       System.out.println("@FindAll values - "+i+" "+findallvalue.get(i).getText());
			}
			System.out.println("\n\ncount of @FindBys elements "+findbysvalue.size());
			for(int i=0;i<findbysvalue.size();i++) 
                        {
			System.out.println("@FindBys values - "+i+" "+findbysvalue.get(i).getText());
			}
	}

Given below is the result shown on the console window post-execution of the program:

Result Post Execution of the Program

Let us now try to understand the code in detail:

#1) Through the page object design pattern, the element ‘newlist’ identifies all the tags with anchor ‘a’. In other words, we get a count of all the links on the page.

We learned that the pagefactory @FindBy does the same job as that of driver.findElement(). The element findbyvalue is created to get the count of all links on the page through a search strategy having a pagefactory concept.

It proves correct that both driver.findElement() and @FindBy does the same job and identify the same elements. If you look at the screenshot of the resultant console window above, the count of links identified with the element newlist and that of findbyvalue are equal i.e. 299 links found on the page.

The result showed as below:

driver.findElements(By.tagName()) 299
count of @FindBy- list elements 299

#2) Here we elaborate on the working of the @FindAll annotation that will be pertaining to the list of the web elements with the name findallvalue.

Keenly looking at each @FindBy criteria within the @FindAll annotation, the first @FindBy criteria search for elements with the className='sel' and the second @FindBy criteria searches for a specific element with XPath = “//a[@id=’tab5’]

Let us now press F12 to inspect the elements on the page nseindia.com and get certain clarities on elements corresponding to the @FindBy criteria.

There are two elements on the page corresponding to the className =”sel”:

a) The element “Fundamentals” has the list tag i.e. <li> with className=”sel”.

See Snapshot Below

Fundamentals

b) Another element “Order Book” has an XPath with an anchor tag that has the class name as ‘sel’.

Order Book

c) The second @FindBy with XPath has an anchor tag whose id is “tab5”. There is just one element identified in response to the search which is Fundamentals.

See The Snapshot Below:

XPath has an anchor tag whose id is “tab5”

When the nseindia.com test was executed, we got the count of elements searched by.

@FindAll as 3. The elements for findallvalue when displayed were: Fundamentals as the 0th index element, Order Book as the 1st index element and Fundamentals again as the 2nd index element. We already learned that @FindAll identifies elements for each @FindBy criteria separately.

Per the same protocol, for the first criterion search i.e. className =”sel”, it identified two elements satisfying the condition and it fetched ‘Fundamentals’ and ‘Order Book’.

Then it moved to the next @FindBy criteria and per the xpath given for the second @FindBy, it could fetch the element ‘Fundamentals’. This is why, it finally identified 3 elements, respectively.

Thus, it doesn’t get the elements satisfying either of the @FindBy conditions but it deals separately with each of the @FindBy and identifies the elements likewise. Additionally, in the current example, we also did see, that it doesn’t watch if the elements are unique (E.g. The element “Fundamentals” in this case that displayed twice as part of the result of the two @FindBy criteria)

#3) Here we elaborate on the working of the @FindBys annotation that will be pertaining to the list of the web elements with the name findbysvalue. Here as well, the first @FindBy criteria search for elements with the className='sel' and the second @FindBy criteria searches for a specific element with xpath = “//a[@id=”tab5”).

Now that we know, the elements identified for the first @FindBy condition are “Fundamentals” and “Order Book” and that of the second @FindBy criteria is “Fundamentals”.

So, how is @FindBys resultant going to be different than the @FindAll? We learned in the previous section that @FindBys is equivalent to the AND conditional operator and hence it looks for an element or the list of elements that satisfies all the @FindBy condition.

As per our current example, the value “Fundamentals” is the only element that has class=” sel” and id=”tab5” thereby, satisfying both the conditions. This is why @FindBys size in out testcase is 1 and it displays the value as “Fundamentals”.

Caching The Elements In Pagefactory

Every time a page is loaded, all the elements on the page are looked up again by invoking a call through @FindBy or driver.findElement() and there is a fresh search for the elements on the page.

Most of the time when the elements are dynamic or keep changing during runtime especially if they are AJAX elements, it certainly makes sense that with every page load there is a fresh search for all the elements on the page.

When the webpage has static elements, caching the element can help in multiple ways. When the elements are cached, it doesn’t have to locate the elements again on loading the page, instead, it can reference the cached element repository. This saves a lot of time and elevates better performance.

Pagefactory provides this feature of caching the elements using an annotation @CacheLookUp.

The annotation tells the driver to use the same instance of the locator from the DOM for the elements and not to search them again while the initElements method of the pagefactory prominently contributes to storing the cached static element. The initElements do the elements’ caching job.

This makes the pagefactory concept special over the regular page object design pattern. It comes with its own pros and cons which we will discuss a little later. For instance, the login button on the Facebook home page is a static element, that can be cached and is an ideal element to be cached.

Let us now look at how to implement the annotation @CacheLookUp

You will need to first import a package for Cachelookup as below:

import org.openqa.selenium.support.CacheLookup

Below is the snippet displaying the definition of an element using @CacheLookUp. As soon the UniqueElement is searched for the first time, the initElement() stores the cached version of the element so that next time the driver doesn’t look for the element instead it refers to the same cache and performs the action on the element right away.

@FindBy(id = "unique")
@CacheLookup
private WebElement UniqueElement;

Let us now see through an actual program of how actions on the cached web element are faster than that on the non-cached web element:

Enhancing the nseindia.com program further I have written another new method monitorPerformance() in which I create a cached element for the Search box and a non-cached element for the same Search Box.

Then I try to get the tagname of the element 3000 times for both the cached and the non-cached element and try to gauge the time taken to complete the task by both the cached and non-cached element.

I have considered 3000 times so that we are able to see a visible difference in the timings for the two. I shall expect that the cached element should complete getting the tagname 3000 times in lesser time when compared to that of the non-cached element.

We now know why the cached element should work faster i.e. the driver is instructed not to look up the element after the first lookup but directly continue working on it and that is not the case with the non-cached element where the element lookup is done for all 3000 times and then the action is performed on it.

Below is the code for the method monitorPerformance():

private void monitorPerformance() {
			//non cached element 
                        long NoCache_StartTime = System.currentTimeMillis();
			for(int i = 0; i < 3000; i ++)
			{
				         Searchbox.getTagName();
			 }
			 long NoCache_EndTime = System.currentTimeMillis();
			 long NoCache_TotalTime=(NoCache_EndTime-NoCache_StartTime)/1000;
                         System.out.println("Response time without caching Searchbox " + NoCache_TotalTime+ " seconds");
			
			//cached element 
			 long Cached_StartTime = System.currentTimeMillis();
			 for(int i = 0; i < 3000; i ++)
			 {
			 cachedSearchbox.getTagName();
			 }
			 long Cached_EndTime = System.currentTimeMillis();
			 long Cached_TotalTime=(Cached_EndTime - Cached_StartTime)/1000;
                         System.out.println("Response time by caching Searchbox  " + Cached_TotalTime+ " seconds");
		}

On execution, we will see the below result in the console window:

As per the result, the task on the non-cached element is completed in 82 seconds while the time taken to complete the task on the cached element was only 37 seconds. This is indeed a visible difference in the response time of both the cached and non-cached element.

CachingSpeed

Q #7) What are the Pros and Cons of the annotation @CacheLookUp in the Pagefactory concept?

Answer:

Pros @CacheLookUp and situations feasible for its usage:

@CacheLookUp is feasible when the elements are static or do not change at all while the page is loaded. Such elements do not change run time. In such cases, it is advisable to use the annotation to improve the overall speed of the test execution.

Cons of the annotation @CacheLookUp:

The greatest downside of having elements cached with the annotation is the fear of getting StaleElementReferenceExceptions frequently.

Dynamic elements are refreshed quite often with those that are susceptible to change quickly over a few seconds or minutes of the time interval.

Below are few such instances of the dynamic elements:

  • Having a stopwatch on the web page that keeps timer updating every second.
  • A frame that constantly updates the weather report.
  • A page reporting the live Sensex updates.

These are not ideal or feasible for the usage of the annotation @CacheLookUp at all. If you do, you are at the risk of getting the exception of StaleElementReferenceExceptions.

On caching such elements, during test execution, the elements’ DOM is changed however the driver looks for the version of DOM that was already stored while caching. This makes the stale element to be looked up by the driver which no longer exists on the web page. This is why StaleElementReferenceException is thrown.

Factory Classes:

Pagefactory is a concept built on multiple factory classes and interfaces. We will learn about a few factory classes and interfaces here in this section. Few of which we will look at are AjaxElementLocatorFactory , ElementLocatorFactory and DefaultElementFactory.

Have we ever wondered if Pagefactory provides any way to incorporate Implicit or Explicit wait for the element until a certain condition is satisfied (Example: Until an element is visible, enabled, clickable, etc.)? If yes, here is an appropriate answer to it.

AjaxElementLocatorFactory is one of the significant contributors among all the factory classes. The advantage of AjaxElementLocatorFactory is that you can assign a time out value for a web element to the Object page class.

Though Pagefactory doesn’t provide an explicit wait feature, however, there is a variant to implicit wait using the class AjaxElementLocatorFactory. This class can be used incorporated when the application uses Ajax components and elements.

Here is how you implement it in the code. Within the constructor, when we use the initElements() method, we can use AjaxElementLocatorFactory to provide an implicit wait on the elements.

PageFactory.initElements(driver, this); can be replaced with
PageFactory.initElements(new AjaxElementLocatorFactory(driver, 20), this);

The above second line of the code implies that driver shall set a timeout of 20 seconds for all the elements on the page when each of its loads and if any of the element is not found after a wait of 20 seconds, ‘NoSuchElementException' is thrown for that missing element.

You may also define the wait as below:

public pageFactoryClass(WebDriver driver)
{ 
 ElementLocatorFactory locateMe = new AjaxElementLocatorFactory(driver, 30); 
        		PageFactory.initElements(locateMe, this); 
        		this.driver = driver; 
}

The above code works perfectly because the class AjaxElementLocatorFactory implements the interface ElementLocatorFactory.

Here, the parent interface (ElementLocatorFactory ) refers to the object of the child class (AjaxElementLocatorFactory). Hence, the Java concept of “upcasting” or “runtime polymorphism” is used while assigning a timeout using AjaxElementLocatorFactory.

With respect to how it works technically, the AjaxElementLocatorFactory first creates an AjaxElementLocator using a SlowLoadableComponent that might not have finished loading when the load() returns. After a call to load(), the isLoaded() method should continue to fail until the component has fully loaded.

In other words, all the elements will be looked up freshly every time when an element is accessed in the code by invoking a call to locator.findElement() from the AjaxElementLocator class which then applies a timeout until loading through SlowLoadableComponent class.

Additionally, after assigning timeout via AjaxElementLocatorFactory, the elements with @CacheLookUp annotation will no longer be cached as the annotation will be ignored.

There is also a variation to how you can call the initElements() method and how you should not call the AjaxElementLocatorFactory to assign timeout for an element.

#1) You may also specify an element name instead of the driver object as shown below in the initElements() method:

PageFactory.initElements(<elementname>, this);

initElements() method in the above variant internally invokes a call to the DefaultElementFactory class and DefaultElementFactory’s constructor accepts the SearchContext interface object as an input parameter. Web driver object and a web element both belong to the SearchContext interface.

In this case, the initElements() method will upfront initialize only to the mentioned element and not all elements on the webpage will be initialized.

#2) However, here is an interesting twist to this fact which states how you should not call AjaxElementLocatorFactory object in a specific way. If I use the above variant of initElements() along with AjaxElementLocatorFactory, then it will fail.

Example: The below code i.e. passing element name instead of driver object to the AjaxElementLocatorFactory definition will fail to work as the constructor for the AjaxElementLocatorFactory class takes only Web driver object as input parameter and hence, the SearchContext object with web element would not work for it.

PageFactory.initElements(new AjaxElementLocatorFactory(<elementname>, 10), this);

Q #8) Is using the pagefactory a feasible option over the regular page object design pattern?

Answer: This is the most important question that people have and that is why I thought of addressing it at the end of the tutorial. We now know the ‘in and out’ about Pagefactory starting from its concepts, annotations used, additional features it supports, implementation via code, the pros, and cons.

Yet, we remain with this essential question that if pagefactory has so many good things, why should we not stick with its usage.

Pagefactory comes with the concept of CacheLookUp which we saw is not feasible for dynamic elements like values of the element getting updated often. So, pagefactory without CacheLookUp, is it a good to go option? Yes, if the xpaths are static.

However, the downfall is that the modern age application is filled with heavy dynamic elements where we know the page object design without pagefactory works ultimately well but does the pagefactory concept works equally well with dynamic xpaths? Maybe not. Here is a quick example:

On the nseindia.com webpage, we see a table as given below.

Dynamic xpath

The xpath of the table is

"//*[@id='tab9Content']/table/tbody/tr[+count+]/td[1]"

We want to retrieve values from each row for the first column ‘Buy Qty’. To do this we will need to increment the row counter but the column index will remain 1. There is no way that we can pass this dynamic XPath in the @FindBy annotation as the annotation accepts values that are static and no variable can be passed on it.

Here is where the pagefactory fails entirely while the usual POM works great with it. You can easily use a for loop to increment row index using such dynamic xpaths in the driver.findElement() method.

Conclusion

Page Object Model is a design concept or pattern used in the Selenium automation framework.

Naming convection of methods is user-friendly in the Page Object Model. The Code in POM is easy to understand, reusable and maintainable. In POM, if there is any change in the web element then, it is enough to make the changes in its respective class, rather than editing all the classes.

Pagefactory just like the usual POM is a wonderful concept to apply. However, we need to know where the usual POM is feasible and where Pagefactory suits well. In the static applications (where both XPath and elements are static), Pagefactory can be liberally implemented with added benefits of better performance too.

Alternatively, when the application involves both dynamic and static elements, you may have a mixed implementation of the pom with Pagefactory and that without Pagefactory as per the feasibility for each web element.

Author: This tutorial has been written by Shobha D. She works as a Project Lead and comes with 9+ years of experience in manual, automation (Selenium, IBM Rational Functional Tester, Java) and API Testing (SOAPUI and Rest assured in Java).

Now over to you, for further implementation of Pagefactory.

Happy Exploring!!!

=> Visit Here To Learn Selenium From Scratch.