Selenium Page Object Model Tutorial
This article is an introduction to the Selenium Page Object Model and how we can leverage it for better readability and code reuse.
1. Selenium Page Object Model – Introduction
Selenium is used to automate browsers. Selenium WebDriver has found its place in test automation for web applications. It is used to create robust, browser-based regression automation suites and tests. Inherent to this flexibility, there lies a bit of complexity. Testing for large applications might result in lot of code repetition from selenium end. Page Objects promotes code re-usability and simpler structures for clarity.
Table Of Contents
We will cover first the automation of a simple page with vanilla Selenium
. We will introduce then the Page Object Model to see the benefits it brings to the table. We will extend it further with PageFactory
class which provides even more simpler code.
2. Technologies Used
- IntelliJ Idea (Any Java IDE would work)
- Java 1.8.101 (1.8.x will do fine)
- GeckoDriver (firefox)
- ChromeDriver (Chrome)
- Maven
- Selenium
- junit
- assert4j
3. Resources
To illustrate the Selenium’s capability, we will create two web pages login.html
and index.html
. login.html
will contain a username and password textbox along with Login button to submit the page.
Index.html will just display the logged in user via the sessionstorage
. sessionstorage
resides until the user closes the browser window.
Our objective is to automate the testing of these two pages via Selenium
. To automate this, we create a Maven
project with the following pom.xml
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jcg</groupId> <artifactId>pageObjectModel</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.10.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>3.13.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
- We declare
assert4j
,Selenium
andjunit
as dependencies - We define Maven compiler to assemble the jar output and configure it with Java version of 1.8
Assert4J
provides a fluent assertion library for our testing purposes. Selenium
is used to control the webdriver
and is the scope of our discussion. Junit
is used to fire our test cases. We are covering Selenium
here from the testing point of view.
Next, We will cover application.properties
. This is used to control the properties of the application which are loaded on Startup of DriverInitializer
class. This controls the behavior of our application.
application.properties
chrome.path=/usr/local/bin/chromedriver gecko.path=/usr/local/bin/geckodriver browser=chrome login.url=file:///JCG/pageObjectModel/src/main/resources/login.html
- We need to download the
geckodriver
andchromedriver
for firefox and chrome respectively. - Driver download Path is mentioned in lines 1-2 of
application.properties
. - We provide browser as
chrome
for testing our application. - Path to the webpage is provided in
login.url
.
DriverInitializer
class is used to load the default properties for our application. It is used to get the necessary driver used for testing of our application.
DriverInitializer.java
package com.jcg.PageObjectModel; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.firefox.FirefoxDriver; import java.util.Properties; public class DriverInitializer { private static Properties properties = null; private static WebDriver driver = null; static { try { properties = new Properties(); properties.load(DriverInitializer.class.getClassLoader() .getResourceAsStream("application.properties")); System.setProperty("webdriver.chrome.driver", properties.getProperty("chrome.path")); System.setProperty("webdriver.gecko.driver", properties.getProperty("gecko.path")); switch (getProperty("browser")) { case "chrome": driver = new ChromeDriver(); break; case "firefox": driver = new FirefoxDriver(); break; default: driver = new ChromeDriver(); } } catch (Exception e) { e.printStackTrace(); } } public static WebDriver getDriver() { return driver; } public static String getProperty(String key) { return properties == null ? null : properties.getProperty(key, ""); } }
The properties are read from the application.properties
available in the classpath. Based on the properties, firefoxdriver
and chromedriver
paths are set. Based on the browser configured in property, either firefox or chrome is used. This class exposes two methods:
getDriver
– Provides the appropriate driver based on the browser configured in the property file.getProperty
– Provides a convenient method to access the property value based on the provided key.
4. Vanilla Selenium
Let’s start with the plain vanilla approach of accessing the page and automating the input. Before that, We will see the needed methods for all our test cases to work.
TestLogin.java
package com.jcg.pageObjectModel.test; import com.jcg.PageObjectModel.DriverInitializer; import com.jcg.PageObjectModel.IndexPage; import com.jcg.PageObjectModel.LoginPage; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import static org.assertj.core.api.Assertions.*; public class TestLogin { static WebDriver webDriver; @BeforeClass public static void setUp() throws Exception { webDriver = DriverInitializer.getDriver(); } @AfterClass public static void tearDown() { webDriver.quit(); } @Before public void navigate() { webDriver.get(DriverInitializer.getProperty("login.url")); } }
We get the necessary web driver using DriverIntiliazer
class in setUp
which runs at the start of our test class. It has to be a static method as it is run at the class level. Before the execution of each test case, We open up the login page URL in the navigate
method. Here it is conveniently used with @Before
annotation. Ideally, each test case might open a different URL and would not be always in this manner.
After the entire test suite is completed, We use the tearDown
method to close the browser and exit the selenium session. This is to ensure that the browser does not remain open and consume resources.
We will look at the actual test case to test our functionality.
TestCase1:
@Test public void login() { WebElement webElement = webDriver.findElement(By.id("username")); webElement.sendKeys("hi"); webElement = webDriver.findElement(By.id("password")); webElement.sendKeys("hi"); webElement = webDriver.findElement(By.id("login-btn")); webElement.click(); webElement = webDriver.findElement(By.id("name")); assertThat(webElement.getText()).isEqualTo("hi"); }
- We find a
domElement
using itsid
for the three fields –username
,password
andlogin button
. - We send the value hi to username and password field.
- Subsequently, we click on the login button.
- Page navigation happens and we look up the name field by its
id
. Here it is a span element but there is no distinction on the locator. - We assert the value of
Span
is the username we provided in the login page.
5. Page Object Model
In the previous example, we were able to automate our testing. But if this locator
had to be reused again, it needs to be redefined again. There will be a lot of repetitive code involved in each test case. This lead to the concept of Page Object Model. At a high level, all elements on a page should be moved as locators in a single class. For complex applications, Page model does not equate to a single page but covers a single repeated functionality. We will transform the previous test case to the page model in iterations.
LoginPage.java
public class LoginPage { public static By usernameLocator = By.id("username"); public static By passwordLocator = By.id("password"); public static By loginButtonLocator = By.id("login-btn"); }
As the first step, We moved the locators to a class LoginPage
which will serve as the page model. Now we can convert the previous example into the below manner.
driver.findElement(LoginPage.usernameLocator).sendKeys(username); driver.findElement(LoginPage.passwordLocator).sendKeys(password); driver.findElement(LoginPage.loginButtonLocator).click();
This seems better than the previous approach but it is not complete. We are going to reuse the entire login functionality and not just the username locator etc. So it might be better to have the complete login function for reuse. At the same time, Index
has only one simple locator which can be directly used from Index page Object.
IndexPage.java
public class IndexPage { public static By usernameLocator = By.id("name"); }
This just contains the span locator to verify the username is passed onto the index page.
LoginPage.java(With Login)
public class LoginPage { public static By usernameLocator = By.id("username"); public static By passwordLocator = By.id("password"); public static By loginButtonLocator = By.id("login-btn"); public static void logInWithUsernameAndPassword (String username, String password, WebDriver driver) { driver.findElement(usernameLocator).sendKeys(username); driver.findElement(passwordLocator).sendKeys(password); driver.findElement(loginButtonLocator).click(); } }
This is an extension to the previous implementation. Here, the logInWithUsernameAndPassword
is used to abstract the login functionality as a single unit to the external world. It just needs the webDriver
to execute the test case.
TestCase 2:
@Test public void loginPageModel() { LoginPage.logInWithUsernameAndPassword("hi", "hi", webDriver); assertThat(webDriver.findElement(IndexPage.usernameLocator).getText()).isEqualTo("hi"); }
Now the test case is much simpler. LoginPage’s login method is used to execute login functionality on the page while IndexPage’s name locator is used to verify the name. This approach provides excellent reuse and less coupled code.
6. Page Factory
The previous approach provided a simpler and reusable version of the test case. Selenium provides the PageFactory
class to further streamline our test case.
LoginPage.java(Page Factory)
public LoginPage(WebDriver driver) { PageFactory.initElements(driver, this); } @FindBy(how = How.ID, using = "username") private WebElement userName; @FindBy(how = How.ID, using = "password") private WebElement password; @FindBy(how = How.ID, using = "login-btn") private WebElement login; public void logIn(String userName, String password) { this.userName.sendKeys(userName); this.password.sendKeys(password); this.login.click(); }
We initialize the PageFactory
class with WebDriver
and pass our instance to the PageFactory
. This enables the PageFactory
class to pass the webdriver to our annotated instance variables. We have three locators via the instance variables username,password and login. Using FindBy
annotation, we specify that we lookup an element by its ID and the corresponding id is username for the username element. Based on this declaration, we get a simplified element locator at class level.
The logIn implementation uses these locators to carry out the expected functionality.
TestCase3
@Test public void loginPageFactory() { new LoginPage(webDriver).logIn("hi", "hi"); assertThat(webDriver.findElement(IndexPage.usernameLocator).getText()).isEqualTo("hi"); }
In this test case, We just provide the appropriate driver to the pageModel
class during initialization itself. Once the class is initialized, We call the logIn
method to execute the login activity. Assertion is similar to the previous test case.
7. Summary
In this tutorial, we saw how the concept of Page Model
brings clarity over the plain Selenium
driven way. We extended it further with PageFactory
which had nice java annotation goodies.
8. Download the Source Code
You can download the full source code of this example here: Page Object Model