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:
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- This is how our Home Page looks like. Notice that we have three elements that need to be located.
- 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(); } }
- 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.
- 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(); } }
- 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.
- 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(); } }
- 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.
- 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(); } }
- 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.
- 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.
TestNG Script:
Next step is to create TestNG script which will use the Page Objects to complete the execution.
- 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.
- Right click on ‘com.selenium.testcase’ package inside ‘src’ folder and select TestNG -> Create TestNG Class as shown below.
- Name the TestNG class as ‘BookFlight’. Select the following annotations – ‘@BeforeMethod’, ‘@AfterMethod’, ‘@DataProvider’, ‘@BeforeTest’.
- 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"); } }
- To run our TestNG script, right click on ‘BookFlight’ class and click Run As -> TestNG Test.
- After successful execution, below result is displayed.
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;