Page Factory and Page Generator in Page Object Model

Hi all, in this article, I will improve my Page Object Model (POM) example by using Page Factory, Page Generator, Chain of Invocations, and JAVA Generics techniques and patterns. These techniques will reduce code repetition and duplicated code blocks. In this way, we can easy to understand, easy to read, and easy to maintain our automation projects.

The PageFactory class helps us to write more clean and maintainable codes. First, we need to find web elements by using below format (@FindBy annotation) in page classes.

//*********Web Elements by using Page Factory*********
@FindBy(how = How.ID, using = "email")
public WebElement username;

@FindBy(how = How.ID, using = "password")
public WebElement password;

@FindBy(how = How.ID, using = "loginButton")
public WebElement loginButton;

@FindBy(how = How.XPATH, using = "//*[@id=\"loginForm\"]/div[1]/div/div")
public WebElement errorMessageUsername;

@FindBy(how = How.XPATH, using = "//*[@id=\"loginForm\"]/div[2]/div/div")
public WebElement errorMessagePassword;

You can use, ID, XPATH, CSS, CLASS_NAME and all kind of locator strategies with PageFactory.

Then, we need to initialize our elements when we are instantiating our page class with below code.

PageFactory.initElements(driver,  pageClass);

In this way,  we don’t need to write “driver.findElement(By.ByCssSelector(“.logo”));” statements. PageFactory class will handle this operations.

Our project is to test login of n11.com website. The details were described in my former POM article.

Ok, now we can start to write our PageGenerator. This is one of our base class and it generates (instantiates) our pages. I instantiate the pages with “PageFactory.initElements(driver, pageClass);” line because I have to initialize (find) all elements when I instantiate (initialize) the page class. Also, I used JAVA generics to get the class of a page and return the generated (instantiated) pages which extend from BasePage class. BasePage class is the base class of all pages.

package pages;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.PageFactory;

//Note (OB):
//Without Page Factory we can generate and return a new instance of a page by using below line.
//return pageClass.getDeclaredConstructor(WebDriver.class, WebDriverWait.class).newInstance(this.driver, this.wait);

public class PageGenerator {

    public WebDriver driver;

    //Constructor
    public PageGenerator(WebDriver driver){
        this.driver = driver;
    }

    //JAVA Generics to Create and return a New Page
    public  <TPage extends BasePage> TPage GetInstance (Class<TPage> pageClass) {
        try {
            //Initialize the Page with its elements and return it.
            return PageFactory.initElements(driver,  pageClass);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }
}

and we can instantiate pages in test classes as follows.

page.GetInstance(HomePage.class).goToN11();

Above statement instantiates the page and instantiates it’s all elements and then executes its goToN11() method.

Now, Let’s write BasePage Class. In this class, we should write all common features and attributes of all pages. Here, I used method level JAVA generics to writeText, readText, and click methods. In this way, you can either send parameter as By type and Webelement type.

package pages;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.util.List;

public class BasePage extends PageGenerator {

    public BasePage(WebDriver driver) {
        super(driver);
    }

    //If we need we can use custom wait in BasePage and all page classes
    WebDriverWait wait = new WebDriverWait(this.driver,20);

    //Click Method by using JAVA Generics (You can use both By or Webelement)
    public <T> void click (T elementAttr) {
        if(elementAttr.getClass().getName().contains("By")) {
            driver.findElement((By) elementAttr).click();
        } else {
            ((WebElement) elementAttr).click();
        }
    }

    //Write Text by using JAVA Generics (You can use both By or Webelement)
    public <T> void writeText (T elementAttr, String text) {
        if(elementAttr.getClass().getName().contains("By")) {
            driver.findElement((By) elementAttr).sendKeys(text);
        } else {
            ((WebElement) elementAttr).sendKeys(text);
        }
    }

    //Read Text by using JAVA Generics (You can use both By or Webelement)
    public <T> String readText (T elementAttr) {
        if(elementAttr.getClass().getName().contains("By")) {
            return driver.findElement((By) elementAttr).getText();
        } else {
            return ((WebElement) elementAttr).getText();
        }
    }

    //Close popup if exists
    public void handlePopup (By by) throws InterruptedException {
        List<WebElement> popup = driver.findElements(by);
        if(!popup.isEmpty()){
            popup.get(0).click();
            Thread.sleep(200);
        }
    }
}

Now, let’s change HomePage class. I used Chain of Invocation technique for goToLoginPage() method. I returned the LoginPage class in this method. In this way, I can use LoginPage Class’s methods after goToLoginPage() method in test classes as shown below.

//Chain of Invocation (Go to Login Page and then LoginToN11)
page.GetInstance(HomePage.class).goToLoginPage().loginToN11("[email protected]", "11223344");

Also, I used PageFactory’s @FindBy annotation to find signInButton. HomePage.java implementation is as follows.

package 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 HomePage extends BasePage {

    //*********Constructor*********
    public HomePage (WebDriver driver) {
        super(driver);
    }

    //*********Page Variables*********
    String baseURL = "http://www.n11.com/";

    //*********Web Elements By Using Page Factory*********
    @FindBy(how = How.CLASS_NAME, using = "btnSignIn")
    public WebElement signInButton;

    //*********Page Methods*********
    //Go to Homepage
    public void goToN11 (){
        driver.get(baseURL);
    }

    //Go to LoginPage
    public LoginPage goToLoginPage (){
        click(signInButton);
        //I want to chain LoginPage's methods so I return LoginPage by initializing its elements
        return new PageFactory().initElements(driver,LoginPage.class);
    }
}

Now, let’s change LoginPage class. It is shown as follows.

package pages;

import org.openqa.selenium.By;
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;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;

public class LoginPage extends BasePage {

    //*********Constructor*********
    public LoginPage(WebDriver driver) {
        super(driver);
    }

    //*********Web Elements by using Page Factory*********
    @FindBy(how = How.ID, using = "email")
    public WebElement username;

    @FindBy(how = How.ID, using = "password")
    public WebElement password;

    @FindBy(how = How.ID, using = "loginButton")
    public WebElement loginButton;

    @FindBy(how = How.XPATH, using = "//*[@id=\"loginForm\"]/div[1]/div/div")
    public WebElement errorMessageUsername;

    @FindBy(how = How.XPATH, using = "//*[@id=\"loginForm\"]/div[2]/div/div")
    public WebElement errorMessagePassword;


    //*********Page Methods*********
    public void loginToN11 (String pusername, String ppassword){
        //Enter Username(Email)
        writeText(username,pusername);
        //Enter Password
        writeText(password, ppassword);
        //Click Login Button
        click(loginButton);
    }

    //Verify Username Condition
    public void verifyLoginUserName (String expectedText) {
        Assert.assertEquals(readText(errorMessageUsername), expectedText);
    }

    //Verify Password Condition
    public void verifyLoginPassword (String expectedText) {
        Assert.assertEquals(readText(errorMessagePassword), expectedText);
    }
}

Our page classes implementations finished. Now Let’s write BaseTest and LoginTests classes.

In BaseTest Class, before each test method execution, I did setup operations. They are:

  • Creating a ChromeDriver.
  • Declaring a wait for test methods.
  • Maximize the browser.
  • Instantiate the PageGenerator class.

and at teardown, I quit (kill) the browser.

package tests;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import pages.PageGenerator;

public class BaseTest {
    public WebDriver driver;
    public WebDriverWait wait;
    public PageGenerator page;

    @BeforeMethod
    public void setup () {
        //Create a Chrome driver. All test classes use this.
        driver = new ChromeDriver();

        //Create a wait. All test classes use this.
        wait = new WebDriverWait(driver,15);

        //Maximize Window
        driver.manage().window().maximize();

        //Instantiate the Page Class
        page = new PageGenerator(driver);
    }

    @AfterMethod
    public void teardown () {
        driver.quit();
    }

OPTIONAL: If you are doing single threaded test execution, you can increase your test execution time with below implementation. This time, I only created one driver instance at Class level, not at Method level. This implementation opens only one browser for each test class.

package tests;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import pages.PageGenerator;

public class BaseTest {
    public WebDriver driver;
    public WebDriverWait wait;
    public PageGenerator page;

    @BeforeClass
    public void classLevelSetup() {
        //Create a Chrome driver. All test classes use this.
        driver = new ChromeDriver();

        //Create a wait. All test classes use this.
        wait = new WebDriverWait(driver,15);

        //Maximize Window
        driver.manage().window().maximize();
    }

    @BeforeMethod
    public void methodLevelSetup () {
        //Instantiate the Page Class
        page = new PageGenerator(driver);
    }

    @AfterClass
    public void teardown () {
        driver.quit();
    }
}

and now, it’s time to write the test method. I used Page Generator, Method Invocation techniques in test methods as I described before. Here is the code:

package tests;

import org.openqa.selenium.By;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.testng.annotations.Test;
import pages.HomePage;
import pages.LoginPage;

public class LoginTests extends BaseTest {

    @Test (priority = 0)
    public void invalidLoginTest_InvalidUserNameInvalidPassword () throws InterruptedException {
        //*************PAGE METHODS WITH JAVA GENERICS********************
        //Open N11 HomePage
        //Initialize elements by using PageFactory
        page.GetInstance(HomePage.class).goToN11();

        //Chain of Invocation (Go to Login Page and then LoginToN11)
        page.GetInstance(HomePage.class).
                goToLoginPage().loginToN11("[email protected]", "11223344");

        //*************ASSERTIONS***********************
        //Thread.sleep(500); //It is better to use explicit wait here.
        wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[text()='E-posta adresiniz veya şifreniz hatalı']")));
        page.GetInstance(LoginPage.class).verifyLoginPassword(("E-posta adresiniz veya şifreniz hatalı"));
    }

    @Test (priority = 1)
    public void invalidLoginTest_EmptyUserEmptyPassword () throws InterruptedException {
        //*************PAGE METHODS WITH JAVA GENERICS********************
        //Open N11 HomePage
        page.GetInstance(HomePage.class).goToN11();

        //Method Chaining (Go to Login Page and then LoginToN11)
        page.GetInstance(HomePage.class).goToLoginPage().loginToN11("","");

        //*************ASSERTIONS***********************
        //Thread.sleep(500); //It is better to use explicit wait here.
        wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[text()='Lütfen e-posta adresinizi girin.']")));
        page.GetInstance(LoginPage.class).verifyLoginUserName("Lütfen e-posta adresinizi girin.");

        wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[text()='Bu alanın doldurulması zorunludur.']")));
        page.GetInstance(LoginPage.class).verifyLoginPassword("Bu alanın doldurulması zorunludur.");
    }
}

and that’s all. :)

GitHub Link: https://github.com/swtestacademy/PageFactory 

Selenium Webdriver Tutorial Series

[fusion_widget_area name=”avada-custom-sidebar-seleniumwidget” title_size=”” title_color=”” background_color=”” padding_top=”” padding_right=”” padding_bottom=”” padding_left=”” hide_on_mobile=”small-visibility,medium-visibility,large-visibility” class=”” id=””][/fusion_widget_area]

Thanks for reading and happy testing!
-Onur

9 thoughts on “Page Factory and Page Generator in Page Object Model”

  1. greate selenium tutorial!
    i was wondering in which cases is the if-else part of the BasePage methods necessary? wouldn’t be “((WebElement) elementAttr).click();” be enough if all WebElements are defined with @FindBy annotation?

    Reply
    • With the GetInstance method you need to get the instance of the page class along with the driver. In BaseTest you are sending the instantiated driver to pagegenerator class with this line:

      @BeforeMethod
      public void methodLevelSetup () {
      //Instantiate the Page Class
      page = new PageGenerator(driver);
      }

      and this diver goes here:

      //Constructor
      public PageGenerator(WebDriver driver){
      this.driver = driver;
      }

      After this, the driver is sent to page generator class and when you use GetInstance you will instantiate a page with this driver.

      /Initialize the Page with its elements and return it.
      return PageFactory.initElements(driver, pageClass);

      This is better way: https://www.swtestacademy.com/pagegenerator-loadablecomponent-page-object-model-selenium/

      Also, you can use Spring and dependency injection with @Autowired annotation.

      Reply
  2. hey, I recived this exception after running your test

    java.lang.IllegalStateException: The path to the driver executable The path to the driver executable must be set by the webdriver.chrome.driver system property

    public void setup () {
    //Create a Chrome driver. All test classes use this.
    driver = new ChromeDriver();

    Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.