How to create page objects with Selenium WebDriver

Everything starts with a manual test case. 

Our test case is for the Vancouver Public Library site

It tests that a search done on the site’s home page returns results. 

 

 

 

The test case tell us what actions the user takes 

1. user opens home page 
2. user searches for keyword 

to verify if the result is correct

3. user verifies that results page is opened 
4. user verifies that there is at least 1 result for the search 

Our goal is to automate this test case using Selenium WebDriver, Java and the page object model.

Before writing any code, we will convert the test case to a format suitable for test automation. 

This will happen in a few iterations. 


1. PREPARE THE TEST CASE FOR AUTOMATION

1.1 GROUP TEST CASE ACTIONS BY PAGE

First, we group the test case actions and verifications by the page where they happen:

HOME PAGE 

1. open home page 
2. search for keyword 

RESULTS PAGE 

3. verify that results page is opened 
4. verify that there are results for the search 

1.2 BREAK DOWN TEST CASE ACTIONS IN SUB-ACTIONS

The test case actions tell us WHAT the user does to be able to make verifications.

Both the user actions and the verifications are defined at a high level so far. 

We will decompose each test case action in sub-actions that describe HOW the action is executed by the user. 

When decomposing actions in sub-actions, we will determine as well what is required by each sub-action. 


HOME PAGE 

1.  OPEN HOME PAGE

    Required: home page url 

2. SEARCH FOR KEYWORD 

   2.1 click search text box

         Required: search text box locator

   2.2 type keyword in search text box

         Required: search text box locator, search keyword

   2.3 click search button 

         Required: search button locator




RESULTS PAGE

3. CHECK THAT RESULTS PAGE IS DISPLAYED 

   3.1 get page title 

         Required: page title 

   3.2 verify that page title is correct 

   3.3 get page url

         Required: page url 

   3.4 verify that page url is correct 


4. CHECK THAT THE SEARCH RETURNS RESULTS

   4.1 get results count value 

         Required: result count label locator

4.2 verify that results count value is > 0

 




We have so far 3 types of sub-actions:

  • sub-actions that do something 

1.1 open page using home page url

2.1 click search text box

2.2 type keyword in search text box

2.3 click search button

  • sub-actions that provide information about page elements 

3.1 get page title

3.3 get page url

4.1 get results count value

  • actions that assert (verify) that information is correct 

3.2 verify that page title is correct

3.4 verify that page url is correct

4.2 verify that results count value is > 0




1.3 CONVERT ACTIONS AND SUB-ACTIONS INTO METHODS

Each action and sub-action will be implemented by a method as follows:

  1. If the action does something. the corresponding method does not return a value.
  2. If the action provides information, the corresponding method returns a value.
  3. If the action verifies that information is correct, an assertion is used.

Methods may have parameters (example: search() method uses a parameter for the keyword).

If a method interacts with a page element, it will use a locator variable for the element.

HOME PAGE 

1. public void openPage(String homePageUrl) 

2. public void searchForKeyword(String keyword) 

This method executes the search with the help of 2 other methods:

typeSearchKeyword(keyword);
executeSearch();

These methods use 2 locator variables:

String searchTextBoxLocator;
String searchButtonLocator;



RESULTS PAGE

3. verifyThat(getPageTitle() == expectedResultsPageTitle);

     verifyThat(resultsPageUrl == expectedResultsPageUrl); 

getPageTitle() provides current page title.

             getPageUrl() provides the current page url.

      
 4. verifyThat(resultCount() > 0); 

     resultCount() provides the count of results.

     It uses a locator variable for the results count label (resultCountLabelLocator).



We have what we need for the test automation script:

  • methods to be implemented
  • variables that the methods need
  • methods and variables are grouped by page

3 components are required for the test automation script to work: 

  • Test Class
  • Home Page Class
  • Results Page Class



2. CREATE THE TEST CLASS

Each test automation script implements one test case. 

A test script is created in a test class which may include multiple test scripts. 

If a test script is equivalent of a test case, a test class is equivalent of a suite of test cases. 




2.1 Component of a test class 

The components of the test class are

 

a) setUpEnvironment() method 

A test script needs a test environment to be executed in.

The test environment consists in

1. browser where the site is loaded

2. browser driver for driving the site in the browser

setUpEnvironment() method creates the test environment for the test script.

It executes before each test script because of the @Before JUNIT annotation.

It creates the browser driver object which has as effect opening the browser.

 

b) testScript() 

The test script implements a test case.

It uses @Test JUNIT annotation which specifies that the method is a test script.

You can have a test script for each manual test case to be automated.


c) cleanUpEnvironment() 

After the test script finished executing, the test environment is cleaned up.

The cleanUpEnvironment() method is responsible for this.

It executes after each test script because of the @After JUNIT annotation.

It destroys the browser driver object and closes the browser.



2.2 How a test script is executed 


When executed, a test script goes through a few phases:



test environment is built by setUpEnvironment() method

the site is brought to the point where verifications can be done.

 

verifications are being done.

 

test environment is cleaned up by cleanUpEnvironment() method.


 

2.3 Create the template of the test class 

What follows is the template of the test class.

The setUpEnvironment(), cleanUpEnvironment() and searchReturnsResults() method are empty at this point.

We will add code to them very soon.

public class Tests {

@Before
public void setUpEnvironment() {
}

@Test
public void searchReturnsResults() {
}

//other test scripts

@After
public void cleanUpEnvironment() {
}
}




2.4 Add code for setUpEnvironment() and cleanUpEnvironment() methods 

3 changes are done in this phase: 

  • WebDriver variable is added to test class
  • the driver object is instantiated as FirefoxDriver() in the setUpEnvironment() method
  • the driver object is destroyed using driver.quit() in the cleanUpEnvironment() method 
public class Tests {

WebDriver driver;

@Before
public void setUpEnvironment() {
  driver = new FirefoxDriver();
}

@After
public void cleanUpEnvironment() {
  driver.quit();
}

@Test
public void searchReturnsResults() {
}

}



2.5 Add the code for the test script 

Next, we add code to the test script. 

The code should look as close as possible to the test case. 

We will use in the test script the methods for the test case actions (openPage(), searchForKeyword()). 

public class Tests {

WebDriver driver;

@Before
public void setUp() {
  driver = new FirefoxDriver();
}

@After
public void tearDown() {
  driver.quit();
}

@Test
public void searchReturnsResults() {
  homePage.open("http://www.vpl.ca");
  homePage.searchForKeyword("java");
  assertTrue(resultsPage.isOpen() == true);
  assertTrue(resultsPage.resultCount() > 0);
}

}




2.6 Create objects for Home Page and Results Page 

For the test script to work, the homePage and resultsPage objects are needed. 

They are objects of HomePage and ResultsPage classes. 

public class Tests {

WebDriver driver;

@Before
public void setUp() {
  driver = new FirefoxDriver();
}

@After
public void tearDown() {
  driver.quit();
}

@Test
public void searchReturnsResults() {

  HomePage homePage = new HomePage(driver);
  homePage.openPage("http://www.vpl.ca");
  homePage.searchForKeyword("java");

  ResultsPage resultsPage = new ResultsPage(driver);
  assertTrue(resultsPage.isOpen() == true);
  assertTrue(resultsPage.resultCount() > 0);

}

}



If you execute the code at this point, it will not work as the HomePage and ResultsPage classes do not exist yet.


What is important to note for the test script is that

  • it does not include any Selenium WebDriver method or objects; the only exception to this rule is the driver variable 
  • it uses objects of the HomePage and ResultsPage classes to interact with these pages of the site
  • it uses JUNIT assertions for verifications 




3. CREATE THE HOME PAGE CLASS 

HomePage class is the container where user actions for Home Page are implemented. 

It is a page object class. 

It includes fields and methods. 


3.1 Create the template of the home page class

public class HomePage {

/* VARIABLES

  element locators
  page title
  page url

*/

/* METHODS

   openPage(url);
   searchForKeyword(keyword);

*/

}



3.2 Add the fields and methods to the class 

public class HomePage {

//fields
String homePageUrl;
String searchTextBoxLocator;
String searchButtonLocator;

//methods
public void openPage(String url) {
}

public void searchForKeyword(String keyword) {
}

}



3.3 Add values to the class’s fields

public class HomePage {

String homePageUrl = "http://www.vpl.ca";
String searchTextBoxId = "globalQuery";
String searchButtonLocator = "//input[@class='search_button']";

public void openPage(String url) {
}

public void searchForKeyword(String keyword) {
}

}



3.4. Add the WebDriver field and constructor to the class 

The HomePage constructor executes automatically when a class object is created. 

It takes a WebDriver parameter (driver) that comes from the test script. 

The driver parameter is saved in the browserDriver class field. 
The HomePage methods use the browserDriver to interact with page elements through Selenium WebDriver methods.

public class HomePage {

WebDriver browserDriver;

String homePageUrl = "http://www.vpl.ca"; 
String searchTextBoxId = "globalQuery";
String searchButtonLocator = "//input[@class='search_button']";

public HomePage (WebDriver driver) {
  browserDriver = driver;
}

public void openPage(String url) {
}

public void searchForKeyword(String keyword) {
}

}



3.5. Implement the methods 

HomePage class uses 2 types of methods: 

PUBLIC

        These methods correspond to test case actions and are used in the test script:

            openPage(String url) 

            searchForKeyword(String keyword) 


PRIVATE

       These methods are helper methods for the public methods.

       They correspond to sub-actions and are used in the HomePage class only:

           typeSearchKeyword(String keyword) 

           executeSearch() 

public class HomePage {

WebDriver browserDriver;

String homePageUrl = "http://www.vpl.ca";
String searchTextBoxId = "globalQuery";
String searchButtonLocator = "//input[@class='search_button']";

public HomePage (WebDriver driver) {
  browserDriver = driver;
}

public void openPage(String url) {
  browserDriver.get(homePageUrl);
}

public void searchForKeyword(String keyword) {
 typeSearchKeyword(keyword);
 executeSearch();
}

private void typeSearchKeyword(String keyword) {
 WebElement searchTextBox = browserDriver.findElement(
                              By.id(searchTextBoxId));
 searchTextBox.click();
 searchTextBox.clear();
 searchTextBox.sendKeys(keyword);
}

private void executeSearch() {
 WebElement searchTextButton = browserDriver.findElement(
                                 By.xpath(searchButtonLocator));
 searchTextButton.click();
}

}


Quick explanation of how the methods work:

openPage(url)

  1. opens the url in the browser

typeSearchKeyword(String keyword) 

  1. finds search textbox element using its locator
  2. clicks search textbox element
  3. clears existing value from the textbox (if any)
  4. types keyword in the textbox

executeSearch()

  1. finds search button element using its locator
  2. clicks search button element

searchForKeyword(keyword)

  1. types keyword in search text box using the typeSearchKeyword() method
  2. executes the search using the executeSearch() method





4. CREATE THE RESULTS PAGE CLASS 

ResultsPage class is the container where all user actions for Results Page are implemented. 

It is a page object class. 

It includes fields and methods. 



4.1. Create the template of the results page

public class ResultsPage {

/* FIELDS

   element locators
   page title
   page url

*/

/* METHODS

   isOpen()
   resultCount()

*/

}



4.2. Add the fields and methods to the class 

public class ResultsPage {

//fields
String resultCountLocator;
String resultsPageTitle;
String resultsPageUrl;

//methods
public boolean isOpen() {
}

public int resultCount() {
}

}



4.3. Add values to the fields of the class 

public class ResultsPage {

String resultCountLocator = "//span[@class='items_showing_count']";
String resultsPageTitle = "Search | Vancouver Public Library | BiblioCommons";
String resultsPageUrl = "https://vpl.bibliocommons.com/search?q=java&t=keyword";

public boolean isOpen() {
}

public int resultCount() {
}

}



4.4. Add the WebDriver field and constructor to the class 

ResultsPage constructor executes automatically when a class object is created. 

It takes a WebDriver parameter (driver) that comes from the test script. 

The driver parameter is saved in the browserDriver class field. 

ResultsPage methods use browserDriver to interact with page elements through Selenium WebDriver methods. 

public class ResultsPage {

WebDriver browserDriver;

String resultCountLocator = "//span[@class='items_showing_count']";
String resultsPageTitle = "Search | Vancouver Public Library | BiblioCommons";
String resultsPageUrl = "https://vpl.bibliocommons.com/search?q=java&t=keyword";

public ResultsPage(WebDriver driver) {
  browserDriver = driver;
}

public boolean isOpen() {
}

public int resultCount() {
}

}



4.5. Implement the methods 

public class ResultsPage {

WebDriver browserDriver;

String resultCountLocator = "//span[@class='items_showing_count']";
String expectedResultsPageTitle = "Search | Vancouver Public Library | BiblioCommons";
String expectedResultsPageUrl = "https://vpl.bibliocommons.com/search?q=java&t=keyword";

public ResultsPage(WebDriver driver) {
  browserDriver = driver;
}

public boolean isOpen() {
  boolean isTitleCorrect = browserDriver.getTitle()
                                        .equalsIgnoreCase(
                                            expectedResultsPageTitle);
  boolean isUrlCorrect = browserDriver.getCurrentUrl()
                                      .equalsIgnoreCase(
                                            expectedResultsPageUrl);

  return isTitleCorrect && isUrlCorrect;
}

public int resultCount() {
  WebElement resultCountLabel = browserDriver.findElement(
                                   By.xpath(resultCountLocator));    

  String resultCountText = resultCountLabel.getText();
  return extractNumberFromResultCountText(resultCountText);
}

private int extractNumberFromResultCountText(String resultCountText) {
  int startIndex = resultCountText.indexOf("of") + 3;
  int endIndex = resultCountText.indexOf(" items");
  return Integer.parseInt(resultCountText
                            .substring(startIndex, endIndex));
}

}



Quick explanation of how the methods work:

isOpen()

  1. gets the page title and compares it with the expected value
  2. gets the page url and compares it with the expected value
extractNumberFromResultCountText(resultCountText)
  1. extracts the number from the result count label text
resultCount()
  1. finds the result count label element
  2. gets the value of the result count label element
  3. extracts the number from the result count label
  4. returns the number
 
 This is it!
 You should have a good idea now about how to use the page object model to create page object classes.

Questions or feedback?
Please leave them in the Comments section!

What is next?

Page object model is much more than creating classes for all pages of the site.

For good test automation code, you should also learn about

  1. returning page objects from page methods
  2. creating page element classes
  3. creating a base class for a generic page
  4. using Page Factory
  5. using the LoadableComponent class
  6. using the SlowLoadableComponent class

 

I am using all these in my daily automation work and I think that you should use them too.

Read more here.

Advertisement

Correct and incorrect ways of creating page object classes

Let’s say that you want to automate test scenarios for the login page of a web site.

The login page has 3 elements:

– username textbox

– password textbox

– sign in button

 

login

 

Some of the test cases to be automated for this page are

  1. user can sign in with correct username and password
  2. user cannot sign in if username or password are incorrect
  3. password is masked
  4. user can create a new account

 

In this article, we will focus on the first test case only.

Since the test cases uses 2 pages (login page and the page the user sees after a successful sign in), we will need 2 page object classes:

  1. LoginPage class
  2. MainPage class

One way of implementing the LoginPage.java class is

public class LoginPage {

private WebDriver driver;

private WebDriverWait wait;

private By userNameLocator = By.id("userNameId");

private By passwordLocator = By.id("passwordId");

private By signInLocator   = By.id("signInId");

 

public LoginPage(WebDriver driver) {

   this.driver = driver;

   this.wait = new WebDriverWait(driver, 30);

}

public void clickUserName() {

   WebElement userName = findElement(userNameLocator);

   userName.click();

}

public void clickPassword() {

   WebElement password = findElement(passwordLocator);

   password.click();

}

public void clickSignIn() {

   WebElement signIn = findElement(signInLocator);

   signIn.click();

}

public void typeUserName(String value) {

   WebElement userName = findElement(userNameLocator);

   userName.sendKeys(value);

}

public void typePassword(String value) {

   WebElement password = findElement(passwordLocator);

   password.sendKeys(value);

}

private WebElement findElement(By locator) {

  return

  wait.until(visibilityOfElementLocated(locator));

}

 

The LoginPage class has multiple methods for clicking elements and typing text into them.

Using this LoginPage class, we can now build a test script (JUNIT):

@Test

public void testSuccessfullLogin() {

  LoginPage loginPage = new LoginPage(driver);

  loginPage.clickUserName();

  loginPage.typeUserName("admin");

  loginPage.clickPassword();

  loginPage.typePassword("abcdef");

  loginPage.clickSignIn();

  MainPage mainPage = new MainPage(driver);

  assertTrue(mainPage.isDisplayed() == true);

}

 

Building a page object class like this is not correct.

It is not correct because what we do is creating an API that interacts with HTML elements.

For a page that has only 3 elements, we have 5 methods.

Imagine how many methods you will need for a page with 100 elements.

 

The correct approach for page object classes is explained in the following link:

https://martinfowler.com/bliki/PageObject.html

We should not create page object classes and APIs that interact with HTML elements.

Instead, we should create page classes and APIs that interacts with the site similar with how a user does it.

 

 

pageobject

 

See below the LoginPage class changed to interact with the site:

public class LoginPage {

private WebDriver driver;

private WebDriverWait wait;

private By userNameLocator = By.id("userNameId");

private By passwordLocator = By.id("passwordId");

private By signInLocator = By.id("signInId");

 

public LoginPage(WebDriver driver) {

 this.driver = driver;

 this.wait = new WebDriverWait(driver, 30);

}

public void signInWith(String userName, String password) {

 WebElement userName = findElement(userNameLocator);

 userName.click();

 userName.sendKeys(value);

 WebElement password = findElement(passwordLocator);

 password.click();

 password.sendKeys(value);

 WebElement signIn = findElement(signInLocator);

 signIn.click();

}

private WebElement findElement(By locator) {

  return

  wait.until(visibilityOfElementLocated(locator));

}

}

The class has now 1 method only instead of 5.

This method is about interacting with the site like a user by signing in.

Using the LoginPage class, the test script becomes

@Test

public void testSuccessfullLogin() {

  LoginPage loginPage = new LoginPage(driver);

  loginPage.signInWith("admin", "abcdef");

  MainPage mainPage = new MainPage(driver); 

  assertTrue(mainPage.isDisplayed() == true);

}

 

The test script is significantly shorter now.

It also looks like English and is similar to a test case.

We should always aim to

  1.  making the test scripts as short as possible
  2.  moving code from the test script to the page object class
  3.  not using any webdriver object or method in the test scripts
  4.  use only page objects and page methods in the test scripts
  5.  not creating any methods that interact with an element in the page object classes
  6.  keep page object classes short (less than 250 lines of code)
  7.  do not create methods that include in their names the following words: click, type, getText, etc as these are most probably interacting with single elements

Do not store element locators in property files

You have a small test automation project with about 50 test scripts and 10 page object classes.

Each page object class uses approximately 10 web elements.

The project has around 100 web element locators.

Where should the elements’ locators be stored?

Should they be stored in the page object classes, in enumerations, classes with static members or property files?

The last option, property files, seems the best since every time more locators are needed, you just change a text file and not the project’s code.

So for each page object class, you create a property text file and save all element locators in it.

The page classes become smaller this way.

What can be wrong in this case?

The project grew from the initial size to

  • 1000 test scripts
  • 100 page object classes
  • 3000 element locators

Lets say that we are working on a class such as the next one:

public class HomePage {

  WebDriver driver;
  Properties properties;

  public HomePage(WebDriver driver) {
    this.driver = driver;
    this.properties = new Properties();
    InputStream input = new FileInputStream("home.properties");
    this.properties.load(input);
  }

  public void search(String keyword) {

   By searchBoxXpath = By.xpath(properties.get("searchTextBox"));
   WebElement searchTextBox =  driver.findElement(searchBoxXpath);

   searchTextBox.clear();
   searchTextBox.sendKeys(keyword);

   String searchButtonXpath = By.xpath(properties.get("searchButton"))

   WebElement searchButton = driver.findElement(searchButtonXpath);

   searchButton.click();

  }
}

The constructor loads the content of the home.properties file in the properties variable.

The search() method uses the name of the element which is then searched in the properties file.

The home.properties file looks as follows:

searchTextBox = //input[@elementid = 'abc']
searchButton = //input[@elementid = 'def']

Now, we need to change the locators for both elements used in the HomePage class.

How do we do this?

  1. Copy the element name (“searchTextBox”) from the HomePage class
  2. Locate the home.properties file in the project
  3. Open the home.properties file
  4. Search for the element name (“searchTextBox”)
  5. Modify the xpath expression
  6. Save and close the property file

6 steps and a few minutes for changing the value of a locator!
Remember that the project has 3000 locators at this moment.

1st problem of using property files for locators:
it is very time consuming to change the value of a locator.

 

Lets look at the locators situation from a different perspective.

Lots of code has been added, changed and removed from the project in the last year.

How do you know which locators are no longer needed?

With the locators in property files, this is impossible to see immediately.

2nd problem of using property files for locators:
it is not possible to see which locators are no longer used by the page classes.

 

Finally, we are writing test automation code using a language such as Java or C#.

The code that implements the user interactions with each page is organized in classes.

A class should group data and methods together.

By having the locators in the property files, this rule is broken.

3rd problem of using property files for locators:
classes are used incorrectly as they have methods that do not work on class members.

What is the correct approach for saving locators?

Save the locators in the page classes together with the methods.

Doing this allows

  • easy change of a locator in a minimum of steps
  • easy identification of locators not used
  • page classes include locators which are used by class methods