Working with Page Factory…

If we closely analyze the code in Part 1: Page Object Model…, we have not used Page Factory class anywhere in our code. Now, what is Page Factory? Page Factory is an inbuilt library inside Selenium WebDriver for supporting Page Object Model concept. One of the highlights of using Page Factory is that it reduces code duplication. Without Page Factory, we have to use the below code multiple times in the same Page Object.

// Enter username
driver.findElement(userName).sendKeys(uname);

Page Factory pattern works by first identifying web elements using ‘@FindBy’ annotation and then relying on the ‘PageFactory’ class to create an instance of the Page Object. It uses the ‘initElements’ method to initialize the Page Object, which has a constructor with ‘WebDriver’ as its sole argument. Below is the sample code of a Page Object using Page Factory pattern.

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

public class SelectFlightPage {

	//Declare WebDriver
	WebDriver driver;
	//Element Locators @FindBy annotation
	@FindBy(name = "reserveFlights")
	private WebElement reserveFlightsButton;
        
        //Constructor for initializing the driver
	public SelectFlightPage(WebDriver driver) {
		this.driver = driver;
	}

	// Reserve Flight method
	public void reserveFlight() {
		// Click reserve Flights button
		reserveFlightsButton.click();
	}
}

We will automate same test case used in Part 1: Page Object Model… but this time using Page Factory pattern.

Test Case:

  • Navigate to http://newtours.demoaut.com website and login.
  • Select an itinerary and complete the booking.
  • Log out and close the browser.

We will create five Page Objects using Page Factory pattern for five different pages. They are:

  • Home Page or Login Page
  • Flight Finder Page
  • Select Flight Page
  • Book Flight Page
  • Flight Confirmation Page

New Project Creation:

  1. Launch Eclipse. Create a new Java Project by clicking on File -> New -> Java Project. Name the Java Project as ‘NewTourPageFactory’. Click Next and then Finish to complete the project creation. Once the project is created, we will have ‘src‘ folder and ‘JRE System Library‘ by default.

    NewTourPageFactory Java Project
  2. Next step is to add the Selenium Jars. Right Click on the project name and click Properties. Click on Java Build Path -> Add External Jars. Go to the local folder where ‘selenium-server-standalone-3.0.1’ is saved. We will be using 3.0.1 version from our last project. Add the Jar and click OK to close the window. Once complete, we will see ‘Referenced Libraries‘ included in our project.

    Add selenium-server-standalone-3.0.1 jar
  3. Next step is to add the Chrome Driver. Right click on the project name and click New -> Folder. Give the folder name as ‘Chrome’ and click Finish to close the window. Now place the Chrome driver that we already downloaded in our last project into the ‘Chrome’ folder.

    Add Chrome Driver
  4. Next step is to add the TestNG libraries into our project. Right click on the project ‘FrameworkDesign’ and click Properties -> Java Build Path -> Add Library. In Add Library window, select TestNG and click on Next. Click on Finish to add the TestNG Library. Click OK to close the Properties window.

    Add TestNG
  5. Right click on the ‘src’ folder and click New -> Package. Name the package as ‘com.selenium.pages’ and click Finish to close the window. This is where we will place our page specific code and their methods.

Page Object Model with Page Factory:

We will now create class files for each of the five pages.

  1. Right click on ‘com.selenium.pages’ package and click New -> Class. Name the class as ‘HomePage’. In this class we will place Home Page element locators and methods. Click Finish to close the window. Notice that we will not require static main method in any of our class files.
  2. This is how our Home Page looks like. Notice that we have three elements that need to be located.

    Home Page
  3. Copy the below code and paste into ‘HomePage’ class. Notice how the page specific Element locator code are written using ‘@FindBy‘ annotation. Unlike the methods in our last ‘NewTour’ Project, this time the methods don’t have any return types.
    package com.selenium.pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    
    public class HomePage {
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators for username textbox
    	@FindBy(name = "userName")
    	private WebElement userName;
    	
    	//Element Locators for password textbox
    	@FindBy(name = "password")
    	private WebElement password;
    	
    	//Element Locators for login button
    	@FindBy(name = "login")
    	private WebElement login;
    	
    	//Constructor. Driver Initialization
    	public HomePage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	// Login method. Notice there is no return type
    	public void loginUser(String uname, String pwd) {
    		// Enter username
    		userName.sendKeys(uname);
    		// Enter password
    		password.sendKeys(pwd);
    		// Click Login button
    		login.click();
    	}
    }
    
  4. We will create a second class for our next page. Once the login is successful, we will be taken to Flight Finder Page. Flight Finder page has many elements. For now we will be concentrating on ‘One Way’, ‘Departing From’, ‘On Date’ and ‘Continue’ elements. Below is how our Flight Finder page looks like.

    Flight Finder Page
  5. Right click on ‘com.selenium.pages’ package and click New -> Class. Name the class as ‘FlightFinderPage’. In this class we will place Flight Finder Page element locators and methods. Click Finish to close the window. Once done, copy the below code and paste into ‘FlightFinderPage’ class. Notice that the layout is similar to ‘HomePage’ class. We have one method that takes two parameters and has a return type object of ‘SelectFlightPage’ class. Notice that the method has no return type.
    package com.selenium.pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    import org.openqa.selenium.support.ui.Select;
    
    public class FlightFinderPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators for one way radio button
    	@FindBy(xpath = "//input[@value='oneway']")
    	private WebElement onewayRadio;
    	
    	//Select the location from dropdown
    	@FindBy(name = "fromPort")
    	private WebElement fromPortDrop;
    	
    	//Select the day from dropdown
    	@FindBy(name = "fromDay")
    	private WebElement fromDayDrop;
    	
    	//Click Business radio button
    	@FindBy(xpath = "//input[@value='Business']")
    	private WebElement businessRadio;
    	
    	//Click find flights button
    	@FindBy(name = "findFlights")
    	private WebElement findFlightsButton;
    	
    	//Constructor. Driver Initialization
    	public FlightFinderPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	//Find Flights method. Notice there is no return type
    	public void findFlights(String departFrom, String departDate) {
    		// Click one way radio button
    		onewayRadio.click();
    		// Select Departing From dropdown
    		Select dropFrom = new Select(fromPortDrop);
    		dropFrom.selectByValue(departFrom);
    
    		// Select Departing Day dropdown
    		Select dropDay = new Select(fromDayDrop);
    		dropDay.selectByValue(departDate);
    
    		// Click business class
    		businessRadio.click();
    
    		// Click Find Flights button
    		findFlightsButton.click();
    	}
    }
    
  6. From Flight Finder page we will be taken to Select Flight page. Next step is to create a class file for Select Flight page. We will go with the default selection of flights and click continue to go to next page. Below is how our Select Flight page looks like.

    Select Flight Page
  7. Right click on ‘com.selenium.pages’ package and click New -> Class. Name the class as ‘SelectFlightPage’. In this class we will place Select Flight Page element locators and methods. Click Finish to close the window. Once done, copy the below code and paste into ‘SelectFlightPage’ class. Notice that we have only one method that takes no parameters and there is no return type.
    package com.selenium.pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    
    public class SelectFlightPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators for reserve flight button
    	@FindBy(name = "reserveFlights")
    	private WebElement reserveFlightsButton;
    
    	//Constructor. Driver Initialization
    	public SelectFlightPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	// Reserve Flight method. Notice there is no return type
    	public void reserveFlight() {
    		// Click reserve Flights button
    		reserveFlightsButton.click();
    	}
    }
    
  8. From Select Flight page we will navigate to Book Flight page. We will create a class file for Book Flight page. Although Book Flight page has many elements but we will be concentrating on ‘First Name’, ‘Last Name’ and ‘Number’ elements. Below is how our Book Flight page looks like.

    Book Flight Page
  9. Right click on ‘com.selenium.pages’ package and click New -> Class. Name the class as ‘BookFlightPage’. In this class we will place Book Flight Page element locators and methods. Click Finish to close the window. Once done, copy the below code and paste into ‘BookFlightPage’ class. Notice that we have only one method that take three parameters and has no return type.
    package com.selenium.pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    
    public class BookFlightPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators for first name textbox
    	@FindBy(name = "passFirst0")
    	private WebElement firstNameTextBox;
    	
    	//Element Locators for last name textbox
    	@FindBy(name = "passLast0")
    	private WebElement lastNameTextBox;
    	
    	//Element Locators for credit number textbox
    	@FindBy(name = "creditnumber")
    	private WebElement ccNumberTextBox;
    	
    	//Element Locators for Buy Flights button
    	@FindBy(name = "buyFlights")
    	private WebElement buyFlightsButton;
    
    	//Constructor. Driver Initialization
    	public BookFlightPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	//Book Flight method.Notice there is no return type
    	public void bookFlight(String fname, String lname, String ccNumber) {
    		// Enter first name
    		firstNameTextBox.sendKeys(fname);
    		// Enter last name
    		lastNameTextBox.sendKeys(lname);
    		// Enter credit card number
    		ccNumberTextBox.sendKeys(ccNumber);
    		// Confirm purchase
    		buyFlightsButton.click();
    	}
    }
    
  10. Finally we will create one more class file for Flight Confirmation page. We will verify the confirmation message by using Assert statement and then log out from the page. Below is how our Flight Confirmation page looks like.

    Flight Confirmation Page
  11. Right click on ‘com.selenium.pages’ package and click New -> Class. Name the class as ‘FlightConfirmationPage’. In this class we will place Flight Confirmation Page element locators and methods. Click Finish to close the window. Notice that we have only one method that takes no parameters and has no return type. Copy the below code and paste into ‘FlightConfirmationPage’ class.
    package com.selenium.pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    import org.testng.Assert;
    
    public class FlightConfirmationPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators for confirmation text
    	@FindBy(xpath = "//font[contains(text(),'Confirmation')]")
    	private WebElement confirmationText;
    	
    	//Element Locators for logout button
    	@FindBy(xpath = "//img[@src='/images/forms/Logout.gif']")
    	private WebElement logOutButton;
    
    	//Constructor. Driver Initialization
    	public FlightConfirmationPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	//Verify and Log out method. 
    	public void clickLogOut() {
    		// Assert if Confirmation text is displayed
    		Assert.assertTrue(confirmationText.isDisplayed());
    		// Click Logout button
    		logOutButton.click();
    	}
    }
    

This is how our Project structure looks like. We have successfully implemented Page Object Model using Page Factory pattern in our framework. We created five class files for five different pages and each class file have its own page specific code.

Project Structure

 

TestNG Script:

Next step is to create TestNG script which will use the Page Objects to complete the execution.

  1. Inside ‘NewTourPageFactory’ project, right click ‘src’ folder and click New -> Package. Name the package as ‘com.selenium.testcase’ and click Finish to close the window. This is where we will place our test case logic.
  2. Right click on ‘com.selenium.testcase’ package inside ‘src’ folder and select TestNG -> Create TestNG Class as shown below.

    Create TestNG Class
  3. Name the TestNG class as ‘BookFlight’. Select the following annotations – ‘@BeforeMethod’, ‘@AfterMethod’, ‘@DataProvider’, ‘@BeforeTest’.
  4. Copy the below code and paste into ‘BookFlight’ class. Notice that inside ‘@BeforeMethod’, ‘PageFactory’ class uses the ‘initElements’ method to initialize the Page Object and pass the driver information.
    package com.selenium.testcase;
    
    import org.testng.annotations.Test;
    import com.selenium.pages.BookFlightPage;
    import com.selenium.pages.FlightConfirmationPage;
    import com.selenium.pages.FlightFinderPage;
    import com.selenium.pages.HomePage;
    import com.selenium.pages.SelectFlightPage;
    import org.testng.annotations.BeforeMethod;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.openqa.selenium.support.PageFactory;
    import org.testng.annotations.AfterMethod;
    import org.testng.annotations.DataProvider;
    import org.testng.annotations.BeforeTest;
    
    public class BookFlight {
    
    	WebDriver driver;
    	HomePage homePage;
    	FlightFinderPage flightFinderPage;
    	SelectFlightPage selectFlightPage;
    	BookFlightPage bookFlightPage;
    	FlightConfirmationPage flightConfirmationPage;
    
    	// Test Case
    	@Test(dataProvider = "newTourData")
    	public void bookFlight(String uname, String pwd, String departFrom, String departDate, String fname, String lname,
    			String ccNum) {
    
    		/*
    		 * Test case logic.
    		 * 
    		 */
    		homePage.loginUser(uname, pwd);
    		flightFinderPage.findFlights(departFrom, departDate);
    		selectFlightPage.reserveFlight();
    		bookFlightPage.bookFlight(fname, lname, ccNum);
    		flightConfirmationPage.clickLogOut();
    
    	}
    
    	// Driver and Page Objects Initialization
    	@BeforeMethod
    	public void beforeMethod() {
    		// Initialize driver
    		driver = new ChromeDriver();
    		// Maximize window
    		driver.manage().window().maximize();
    
    		// Page Factory Initialization for all page objects
    		homePage = PageFactory.initElements(driver, HomePage.class);
    		flightFinderPage = PageFactory.initElements(driver, FlightFinderPage.class);
    		selectFlightPage = PageFactory.initElements(driver, SelectFlightPage.class);
    		bookFlightPage = PageFactory.initElements(driver, BookFlightPage.class);
    		flightConfirmationPage = PageFactory.initElements(driver, FlightConfirmationPage.class);
    
    		// Nvaigate to URL
    		driver.get("http://newtours.demoaut.com");
    
    	}
    
    	// Driver closure
    	@AfterMethod
    	public void afterMethod() {
    		// Close and quit the driver to close the Browser
    		driver.close();
    		driver.quit();
    	}
    
    	// Create test data
    	@DataProvider
    	public Object[][] newTourData() {
    		return new Object[][] { { "demo", "demo", "London", "7", "john", "Doe", "56465465" } };
    	}
    
    	// Setting System property
    	@BeforeTest
    	public void beforeTest() {
    		// Set System Property
    		System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "/Chrome/chromedriver");
    	}
    }
    
  5. To run our TestNG script, right click on ‘BookFlight’ class and click Run As -> TestNG Test.

    Run As TestNG Test
  6. After successful execution, below result is displayed.

    Test Results

With this we came to an end on Page Factory pattern. It is very important that we use the Page Factory pattern in Page Object Model as it reduces code duplication to large extent.

Many more interesting topics to follow. Till then, Happy Learning!

Additional Information:

  • There is also another way of initializing the Page Objects using Page Factory. Instead of initializing all the Page Objects inside ‘@BeforeMethod’ of TestNG script, we can initialize the Page Object of next page inside a method of current Page. Below code is an example of ‘SelectFlightPage’ class. Notice that ‘reserveFlight()’ method is having a return type of ‘BookFlightPage’.
package com.selenium.pages;

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

public class SelectFlightPage {

	//Declare WebDriver
	WebDriver driver;
	//Element Locators for reserve flight button
	@FindBy(name = "reserveFlights")
	private WebElement reserveFlightsButton;

	//Constructor. Driver Initialization
	public SelectFlightPage(WebDriver driver) {
		this.driver = driver;
	}

	// Reserve Flight method. Notice there is no return type
	public BookFlightPage reserveFlight() {
		// Click reserve Flights button
		reserveFlightsButton.click();
                return PageFactory.initElements(driver, BookFlightPage.class);

	}
}
  • There is also another way of using ‘@FindBy‘ annotation. For this we have to import ‘org.openqa.selenium.support.How’. This is to make code more readable.
            @FindBy(how = How.NAME, using = "reserveFlights")
    	private WebElement reserveFlightsButton;