Automated Testing Using Page Objects and WebDriver

When writing your automated test scripts it is helpful to abstract your interface from the assertions. One helpful method to do this is the the Page Objects pattern. Essentially, your interface is mapped into a class, with each object field representing a UI element on your page. With all your locators in one place, you have a single repository to update if your UI ever changes.

Page Objects are a popular pattern when using Selenium RC, a great resource for guidance is the Selenium Page Object Pattern post on The Automated Tester’s blog. If you’d like some more quality reading on basic Page Object implementation take a look at the Page Objects page on the Selenium Google Code Wiki.

In Selenium 1.x Page Object implementation was simply a nice compliment to the tool, but outside the scope of the project itself. With Selenium2/WebDriver, you get the PageFactory class which takes your custom class, and gives you a usable page. [Note: At the time of writing, September 2010, the support package was only available in the Java library. Hopefully other languages will be supported by the 2.0 release]

WebDriver does this by a combination of clever conventions, and magic. Mostly magic. Following the example on the PageFactory wiki page mentioned above, I’ll demonstrate a quick test on the Bing home page. First you want your search page class. You want this class to support both the elements you’ll work with, and the services it provides. The minimum elements you’ll need to support on a search page are the search box and submit button. The process of searching is a ‘service’ the page provides, it will be provided as a method. To separate concerns, your test should have access to the services, but no knowledge of the underlying HTML. For this reason elements are private members and services are public methods. Services should return information about the page, or new Page Objects. Your search page may look something like this, notes below.

package com.PeterNewhook;

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

public class SearchPage extends Page{

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

	private WebElement q; //Search box
	private WebElement go; //Search button

	public ResultsPage search(String searchStatement){
		sb_form_q.sendKeys(searchStatement);
		sb_form_go.click();
		return PageFactory.initElements(_driver, ResultsPage.class);
	}
}

Notice a few things a few things about this class

  • It extends Page
  • search returns a ResultsPage object
  • q and go fields are used without using being instantiated.

Page is my own class with a single constructor that requires a WedDriver object. Inheriting this class makes it easy for the PageObject class to instantiate your object and associate a driver with it. Unfortunately, I missed this in the WebDriver documentation and had to check the PageFactory source after seeing this in a few examples. The Page class would look something like this:

package com.PeterNewhook;

import org.openqa.selenium.WebDriver;

public class Page {

	WebDriver _driver;
	public Page(WebDriver driver){
		this._driver=driver;
	}
}

By returning a ResultsPage object, the search method (or ‘service’ in Page Object parlance) allows our tests to navigate through the application without any reliance of the structure. This page would be instantiated with all the services a search results page would have. It might look something like this.

package com.PeterNewhook;

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

public class ResultsPage extends Page{

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

	private WebElement count;

	public String getPagesReturned(){
		return count.getText();
	}
}

Ordinarily you would expect using q and go this way to throw a Null Pointer exception. If you’re familiar with the WebDriver API you may expect to see something like driver.findElements(By.id(“q”);. But therein lies the magic of the PageObject class. Take a look at how the SearchPage could be used by a full WebDriver program.

package com.PeterNewhook;

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

public class SearchRunner {

	public static void main(String[] args) {
		WebDriver driver = new FirefoxDriver();
		driver.get("http://bing.com/");
		SearchPage bingHome = PageFactory.initElements(driver, SearchPage.class);
		ResultsPage searchResults = bingHome.search("Page Object Pattern");
		System.out.println(searchResults.getPagesReturned());
		driver.close();
	}
}

The PageFactory.initElements static method takes your driver instance and the class type you want returned, and returns a Page Object with it’s fields fully initialized. By default, the PageFactory will search for elements on the page with a matching id. If that fails, it will search by the name attribute. Because q is the name of the search box, the element is found automatically, however it could have also been defined using the FindBy attribute

@FindBy(how = How.NAME, using = "q")
private WebElement searchBox;

or even more simply

@FindBy(name "q")
private WebElement searchBox;

Other location strategies, like xpath or className, are also available using the FindBy attribute. I generally prefer using descriptive name for my field names, so I like to explicitly declare the FindBy method. This also gives me the flexibility to change the field name at a later date without needing to hunt down variables used throughout the class.

Now that you have a basic understanding of the Page Object pattern, I strongly suggest reading Simon Stewart’s wiki page mentioned earlier. It gives great detail and goes into depth on the nature of the PageFactory helper class.