How To Use Page Factory in Selenium Webdriver

Writing Selenium script for web automation UI testing is easy, we just need to find elements and perform the operations. This seems to be fine when the pages are less or our Application Under Test(AUT) is small. The problem starts when the pages and the elements start growing in our AUT and several different scripts use the same page and  the same elements in their respective scripts. This results in duplicated code, and the disadvantage now is that our Selenium testing project is error prone and maintainability becomes cumbersome. The developer changes an element-id in one of the page and several tests start to fail, consequently we need to go and update several selenium scripts for a single change in an element-id. This is surely time consuming and boring work for a test developer.

Introducing Page Factory in Selenium Automation Testing which will address our problems above. Below is a short Selenium tutorial on Page Factory.

Page Object Pattern/Model saves us from this maintainability issue and helps us model our test in a very robust way. In this model, we define all Web Elements of a page and the methods that operate on these elements in a single class file. The class encapsulates all the logic about how to perform certain actions on the page.
Apart from the advantage of eliminating duplicate code it has other benefits like:
- Less and optimized code, easy to maintain, since all elements of a page are stored in a single class.
- Code re-usability improved, as the test code and the page objects are separated.
- Easy adding new tests, since we already have all locators defined. Also with the help of the "methods" in the page object, anyone can write test without the knowledge of the locators.

PageFactory Class is an optimized extension of the PageObject design pattern. It is used to initialize the web elements of the PageObject with the initElements() method, we don't need to use 'FindElement' or 'FindElements'. The PageFactory.initElements() static method takes the driver instance and the class type, and returns a Page Object with it’s fields fully initialized.
Annotations are used to supply descriptive names of Web Elements to improve code readability. Annotation @FindBy is used to identify Web Elements in the page. Location strategies, like id, name, xpath or className, are all available using the @FindBy attribute.
Annotation @CacheLookup can also be used to cache the element once its located. It is best applied to Web Elements to indicate that it never changes. Otherwise every time you use a Web Element the WebDriver will go and search it again.

Let us try to write a sample test using PageFactory design.
We start by creating 'PageFactory' classes for each of the pages in our AUT(http://newtours.demoaut.com/). Methods are are also implemented relevant to our pages for certain required operations on the page.

1. Our first PageFactory Class "NewToursHomePage.java".
package com.page.factory.pages;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.How;
import org.openqa.selenium.support.PageFactory;

public class NewToursHomePage {

 private final WebDriver driver;

 @FindBy(how = How.LINK_TEXT, using = "SIGN-ON")
 private WebElement signonLink;

 @FindBy(linkText = "REGISTER")
 // Another way of writing @FindBy
 private WebElement registesLink;

 // Constructor for this page object, which includes initElements() method to
 // instantiate all Web Elements in this page
 public NewToursHomePage(WebDriver driver) {
  this.driver = driver;
  PageFactory.initElements(driver, this); //instantiating all elements 
 }

 public String getHomePageTitle() {
  return driver.getTitle();
 }

 public void clickSignOn() {
  signonLink.click();
 }

 public NewToursSignonPage clickSignOn2() {
  signonLink.click();
  return PageFactory.initElements(driver, NewToursSignonPage.class);
 }
}

2. Our next "PageFactory" class "NewToursSignonPage.java".
package com.page.factory.pages;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.CacheLookup;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

public class NewToursSignonPage {

 private final WebDriver driver;
 
 //We are using @CacheLookup annotation
 @FindBy(name="userName") @CacheLookup
 private WebElement userName;
 
 @FindBy(name="password") @CacheLookup
 private WebElement pwd;
 
 @FindBy(name="login")
 private WebElement loginButton;
 
 public NewToursSignonPage(WebDriver driver) {
  this.driver = driver;
 }
 
 public String signOnTitle() {
  return driver.getTitle();
 }
 
 public NewToursFlightFinderPage signOn(String user, String pass) {
  userName.sendKeys(user);
  pwd.sendKeys(pass);
  loginButton.click();
  return PageFactory.initElements(driver, NewToursFlightFinderPage.class); 
 }
}

3. Our last(for this example) "PageFactory" class "NewToursFlightFinderPage.java".
package com.page.factory.pages;

import org.openqa.selenium.WebDriver;

public class NewToursFlightFinderPage {
 
 private final WebDriver driver;

 public NewToursFlightFinderPage(WebDriver driver) {
  this.driver = driver;
 }
 
 public String getFltFinderTitle() {
  return driver.getTitle();
 }
}

4. Here is the sample code to test our "PageFactory" classes. This is written as a TestNg test class.
package com.page.factory.test;

import java.util.concurrent.TimeUnit;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.PageFactory;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import com.page.factory.pages.NewToursFlightFinderPage;
import com.page.factory.pages.NewToursHomePage;
import com.page.factory.pages.NewToursSignonPage;

public class TestingPageFactory {
 private WebDriver driver;
 private String testurl = "http://newtours.demoaut.com/";
 private String username= "test";
 private String password= "test";
 NewToursHomePage homePage;
 NewToursSignonPage signOnPage;
 NewToursFlightFinderPage fltFinderPage;

 @BeforeClass
 public void setup() {
  driver = new FirefoxDriver();
  driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
  driver.get(testurl);
  homePage = new NewToursHomePage(driver); //this page elements are instantiated in constructor
 }

 @Test
 public void test123() throws InterruptedException {
  System.out.println(driver.getTitle());
  System.out.println(homePage.getHomePageTitle());
  homePage.clickSignOn();
  
  //instantiate NewToursSignonPage and initialize elements of the SignOn page class.
  signOnPage = PageFactory.initElements(driver, NewToursSignonPage.class);
  //Above line can be replaced by calling clickSignOn2(), as below
        //signOnPage = homePage.clickSignOn2();
          
        System.out.println(driver.getTitle());
  System.out.println(signOnPage.signOnTitle());
  
  fltFinderPage = signOnPage.signOn(username, password);
  
  System.out.println(driver.getTitle());
  System.out.println(fltFinderPage.getFltFinderTitle()); 
  Thread.sleep(5000);
 }
 
 @AfterClass
 public void finish() {
  driver.quit();
 }
}

5. Below is the screenshot of the package structure in Eclipse:

6. Run the "TestingPageFactory.java" as "TestNG Test" in Eclipse. Below is the output:
[TestNG] Running:
  C:\xxxx\xxxx\xxxx\xxxx\testng-eclipse-1761636653\testng-customsuite.xml

Welcome: Mercury Tours
Welcome: Mercury Tours
Sign-on: Mercury Tours
Sign-on: Mercury Tours
Find a Flight: Mercury Tours:
Find a Flight: Mercury Tours:
PASSED: test123

===============================================
    Default test
    Tests run: 1, Failures: 0, Skips: 0
===============================================

===============================================
Default suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

[TestNG] Time taken by org.testng.reporters.jq.Main@b2771: 71 ms
[TestNG] Time taken by org.testng.reporters.XMLReporter@1882d18: 20 ms
[TestNG] Time taken by org.testng.reporters.EmailableReporter2@48ffbc: 20 ms
[TestNG] Time taken by org.testng.reporters.JUnitReportReporter@152e7a4: 20 ms
[TestNG] Time taken by [FailedReporter passed=0 failed=0 skipped=0]: 0 ms
[TestNG] Time taken by org.testng.reporters.SuiteHTMLReporter@135707c: 230 ms

Now, go ahead and create some Page Factory in your Selenium Automation testing project.
If you like this short tutorial on Selenium Page Factory, please put a comment below.

No comments:

Post a Comment