Page object model is a pattern where we write object oriented classes that serve as an interface to a particular view of web page. We use the methods of that page class to perform the required action. Few years back, we were manipulating the HTML code of webpage in test classes directly which was very difficult to maintain along with brittle to changes in UI.
However, having your code organized in a way of Page Object Pattern provides an application specific API, allowing you to manipulate the page elements without digging around the HTML. The basic Rue of thumb says, your page object should have everything which a human can do on that webpage. For example, to access the text field on a webpage you should a method there to get the text and return string after doing all the modifications.
Few important points you should keep in mind while designing the page objects:
Page object usually should not build only for pages, but you should prefer to build it for significant elements of page. For example, a page having multiple tabs to show different charts of your academics should have same number of pages as the count of tabs.
Navigating from one view to other should return the instance of page classes.
The utility methods which are required to be there only for a specific view or webpage should belong to that page class only.
The assertion methods shouldn't be taken care by page classes, you can have methods to return boolean but don't verify them there. For example, to verify user full name you can have method to get boolean value:
public boolean hasDisplayedUserFullName (String userFullName) {
return driver.findElement(By.xpath("xpathExpressionUsingFullName")).isDisplayed();
}
If your webpage is based on iframe, prefer to have page classes for iframes too.
Advantages of Page Object Pattern:
An example to perform login test based on Page object pattern:
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
/**
* Class which models the view of Sign-In page
*/
public class SignInPage {
@FindBy(id="username")
private usernameInput;
@FindBy(id="password")
private passwordInput;
@FindBy(id="signin")
private signInButton;
private WebDriver driver;
public SignInPage(WebDriver driver) {
this.driver = driver;
}
/**
* Method to perform login
*/
public HomePage performLogin(String username, String password) {
usernameInput.sendKeys(username);
passwordInput.sendKeys(password);
signInButton.click();
return PageFactory.initElements(driver, HomePage.class);
}
}
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
/**
* Class which models the view of home page
*/
public class HomePage {
@FindBy(id="logout")
private logoutLink;
private WebDriver driver;
public HomePage(WebDriver driver) {
this.driver = driver;
}
/**
* Method to log out
*/
public SignInPage logout() {
logoutLink.click();
wait.ForPageToLoad();
return PageFactory.initElements(driver, SignInPage.class);
}
}
/**
* Login test class
*/
public class LoginTest {
public void testLogin() {
SignInPage signInPage = new SignInPage(driver);
HomePage homePage = signInPage.login(username, password);
signInPage = homePage.logout();
}
}
Page Objects should contain behavior, return info for assertions and possibly a method for page ready state method on initialization. Selenium supports Page Objects using annotations. In C# it's as follows:
using OpenQA.Selenium;
using OpenQA.Selenium.Support.PageObjects;
using OpenQA.Selenium.Support.UI;
using System;
using System.Collections.Generic;
public class WikipediaHomePage
{
private IWebDriver driver;
private int timeout = 10;
private By pageLoadedElement = By.ClassName("central-featured-logo");
[FindsBy(How = How.Id, Using = "searchInput")]
[CacheLookup]
private IWebElement searchInput;
[FindsBy(How = How.CssSelector, Using = ".pure-button.pure-button-primary-progressive")]
[CacheLookup]
private IWebElement searchButton;
public ResultsPage Search(string query)
{
searchInput.SendKeys(query);
searchButton.Click();
}
public WikipediaHomePage VerifyPageLoaded()
{
new WebDriverWait(driver, TimeSpan.FromSeconds(timeout)).Until<bool>((drv) => return drv.ExpectedConditions.ElementExists(pageLoadedElement));
return this;
}
}
notes:
CacheLookup
saves the element in the cache and saves returning a new element each call. This improves performance but isn't good for dynamically changing elements.searchButton
has 2 class names and no ID, that's why I can't use class name or id.Search()
method returns another page object (ResultsPage
) as the search click redirects you to another page.