Selenium: Design Patterns
Join the DZone community and get the full member experience.
Join For FreeSelenium WebDriver is widely used as a first-choice framework when it comes to testing web applications. In this article I would like to introduce you to two design patterns, that work great with selenium tests. In this post we will dig into Page Object pattern and combine it with Page Factory. As always, whole code for project is available on my github.
Page Object Pattern
Page Object is a concept that helps to reduce the number of duplicated code, and helps with test maintenance, resulting from changes in the structure and functionality of the web application. The main idea behind page object is to place the code relating to the functionality of subpages in separate classes. In a very simplified way, if your web application includes pages:
- home
- about
- contact
We should create three separate page object classes:
- Home.java
- About.java
- Contact.java
Each class should contain only those methods that support functionality for the corresponding subpage, and define selectors only for this subpage. We should also remember, that public methods in page object class are only those, that represents user’s workflow.
One important point is that each public method in page object class should return object of page that user is on. For example, if a button on the page does not gets you to different subpage, this method should return this. Moreover, if a button is a link to another page, the method should return page object class of this subpage. Here is a code snippet of this approach using an example from previous article, where we wrote tests for Facebook login:
public class LoginPage {
private static By userEmailLoginInput = By.id("email");
private static By userPasswordLoginInput = By.id("pass");
private static By loginSubmitBtn = By.id("u_0_n");
private WebDriver driver;
public LoginPage(WebDriver driver) {
this.driver = driver;
driver.get("http://facebook.com");
}
public LoginPage enterUserLogin(String login) {
WebElement emailBox = driver.findElement(userEmailLoginInput);
emailBox.click();
emailBox.sendKeys(login);
return this;
}
public LoginPage enterUserPassword(String password) {
WebElement passwordBox = driver.findElement(userPasswordLoginInput);
passwordBox.click();
passwordBox.sendKeys(password);
return this;
}
public HomePage submitLoginCredentials() {
WebElement submitBtn = driver.findElement(loginSubmitBtn);
submitBtn.click();
return new HomePage(driver);
}
}
In above example, methods enterUserLogin() and enterUserPassword() don’t transfer user to another subpage, but perform activities on the login page, so return type is object of LoginPage class (this). On the other hand, submitLoginCredentials() method moves user to home page (or to the page informing about the login failure), so it returns home page class object. In real life example, in HomePage.class we would have had methods that perform actions on the home page, but since it’s only an example code demonstrating pattern usage, we have only checkIfLoginSucceed() method here:
public class HomePage {
private WebDriver driver;
public HomePage(WebDriver driver) {
this.driver = driver;
}
public boolean checkIfLoginSucceed() {
return driver.getPageSource().contains("fbxWelcomeBoxName");
}
}
…and test looks as follows:
@Test
public void shouldNotLoginWithIncorrectCreds() {
LoginPage loginPage = new LoginPage(driver);
loginPage.enterUserLogin("wrong@login.com");
loginPage.enterUserPassword("wrongPassword");
HomePage homePage = loginPage.submitLoginCredentials();
assert (!homePage.checkIfLoginSucceed());
}
Page Factory pattern
The main idea behind this pattern is to support page object classes, and to allow better page selectors management. Page Factory provide us with a set of annotations, which work great with selectors and enhance code readability. To understand this, let’s look at a standard initialization webelement through the selector:
private static By email = By.id(“email”);
WebElement emailBox = driver.findElement(email);
Unfortunately, the readability leaves much to be desired. The problem is also that variable with selector and web element object require a separate initialization. Along with Page Factory, this is greatly simplified:
private WebElement email;
That’s all! Page Factory search page source code for element with id=”email” by default and assigns it to declared webelement. Of course, this minimalism can introduce some confusion, and therefore I recommend the use of @FindBy annotation:
@FindBy(id = “email”)
private WebElement userEmailLoginInput;
We can also search selectors by other attributes, such as xpath, name, className, etc. Everything we have to do to use this pattern, is to initialize PageFactory in the page object class constructor:
PageFactory.initElements(driver, this);
Our LoginPage.class with page factory pattern will look like this:
public class LoginPage {
@FindBy(id = "email")
private WebElement userEmailLoginInput;
@FindBy(id = "pass")
private WebElement userPasswordLoginInput;
@FindBy(id = "u_0_n")
private WebElement loginSubmitBtn;
private WebDriver driver;
public LoginPage(WebDriver driver) {
this.driver = driver;
driver.get("http://facebook.com");
PageFactory.initElements(driver, this);
}
public LoginPage enterUserLogin(String login) {
userEmailLoginInput.click();
userEmailLoginInput.sendKeys(login);
return this;
}
public LoginPage enterUserPassword(String password) {
userPasswordLoginInput.click();
userPasswordLoginInput.sendKeys(password);
return this;
}
public HomePage submitLoginCredentials() {
loginSubmitBtn.click();
return new HomePage(driver);
}
}
Important thing to notice is that every time we call a method on web element, page factory search for our element all over again through page sources. If page isn’t AJAX-based, we can use page factory cache to search an element only with page factory initialization, and then retrieve it from cache:
@FindBy(id = “email”)
@CacheLookup
private WebElement userEmailLoginInput;
Summary
As in every aspect of software development, design patterns in test automations help us to develop tests faster and more efficient. Page Object and Page Factory are two simple patterns, that significantly improve maintenance and readability of selenium tests. For complete sources of examples in this article please visit my github. If you have any questions, feel free to leave a comment!
Opinions expressed by DZone contributors are their own.
Comments