Selenium

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.
 
 

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.

Selenium Page Object Model - Login Page
Login Page

Index.html will just display the logged in user via the sessionstorage. sessionstorage resides until the user closes the browser window.

Selenium Page Object Model - Index Page
Index Page

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 and junit 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 and chromedriver 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 its id for the three fields – username, password and login 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

Download
You can download the full source code of this example here: Page Object Model

Rajagopal ParthaSarathi

Rajagopal works in software industry solving enterprise-scale problems for customers across geographies specializing in distributed platforms. He holds a masters in computer science with focus on cloud computing from Illinois Institute of Technology. His current interests include data science and distributed computing.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button