Selenium Automation: Utilizing the Page Object Model and Page Factory

Editor’s note: This article was updated on 11/18/2022 by our editorial team. It has been modified to include recent sources and to align with our current editorial standards.

Automated tests are crucial for agile software development, enabling swift bug detection early in the development cycle. When a new feature is under development, developers can execute automated tests to observe its impact on other system components. This article will demonstrate how to expedite this process using test automation in Selenium with the Page Object model.

Test automation reduces bug fix costs and enhances the overall software quality assurance (QA) process. Thorough tests allow developers to identify and address bugs before they reach QA. Test automation further facilitates the automation of regression test cases and features. This frees QA engineers to focus on other application aspects, ensuring higher-quality production releases, leading to more stable products and a more efficient QA process.

Selenium simplifies test automation for web applications.

While writing automated tests may appear straightforward for developers and engineers, poorly implemented tests and compromised code maintainability are potential pitfalls. The continuous delivery of changes or features in agile development can become expensive when tests are factored in. Modifying a webpage element on which 20 tests depend necessitates updating each of those 20 test routines, a time-consuming process that can discourage early automated testing.

But what if modifications could be made in a single location, automatically propagating to all relevant test routines? Let’s delve into automated tests in Selenium and explore how Page Object model best practices can create maintainable and reusable test routines.

Selenium Page Object Model

Page Object model is an object design pattern in Selenium that represents webpages as classes, with page elements defined as class variables. All potential user interactions can be implemented as methods within the class:

1
2
clickLoginButton();
setCredentials(user_name,user_password);

Well-named methods in classes enhance readability, providing an elegant approach to implementing test routines that are both understandable and easy to maintain or update. For instance:

To support the Page Object model, we employ Page Factory. Page Factory in Selenium, an extension of the Page Object model, offers various usage options. We will utilize Page Factory to initialize web elements defined within webpage classes or Page Objects.

Before using web element variables, we need to initialize webpage classes or Page Objects containing them using Page Factory. This initialization is achieved using the initElements function of Page Factory:

1
2
LoginPage page = new LoginPage(driver);
PageFactory.initElements(driver, page);

A simpler approach is:

1
LoginPage page = PageFactory.intElements(driver,LoginPage.class)

Or, within the webpage class constructor:

1
2
3
4
public LoginPage(WebDriver driver) {           
         this.driver = driver; 
         PageFactory.initElements(driver, this);
}

Page Factory initializes each WebElement variable by referencing a corresponding element on the actual webpage based on configured “locators,” achieved using the @FindBy annotation. This annotation defines an element lookup strategy along with the necessary identification information:

1
2
@FindBy(how=How.NAME, using="username")
private WebElement user_name;

Whenever a method is called on this WebElement variable, the driver first locates it on the current page and then simulates the interaction. For simple pages where element presence is guaranteed and page revisits are unlikely, caching the located field using a simple annotation is possible:

1
2
3
@FindBy(how=How.NAME, using="username")
@CacheLookup
private WebElement user_name;

The entire WebElement variable definition can be condensed as follows:

1
2
@FindBy(name="username")
private WebElement user_name;

The @FindBy annotation supports various strategies for simplification, including id, name, className, css, tagName, linkText, partialLinkText, and xpath.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@FindBy(id="username")
private WebElement user_name;


@FindBy(name="passsword")
private WebElement user_password;


@FindBy(className="h3")
 private WebElement label;


@FindBy(css=”#content)
private WebElement text;

Once initialized, these WebElement variables facilitate interaction with corresponding elements on the page. For instance, sending keystrokes to the password field:

1
user_password.sendKeys(password);

This is equivalent to:

1
driver.findElement(By.name(user_password)).sendKeys(password);

When identifying multiple elements on a page, the @FindBys annotation proves useful:

1
2
@FindBys(@FindBy(css=div[class=’yt-lockup-tile yt-lockup-video])))
private List<WebElement> videoElements;

This code snippet finds all div elements with both yt-lockup-tile and yt-lockup-video class names. This can be simplified further:

1
2
@FindBy(how=How.CSS,using="div[class=’yt-lockup-tile yt-lockup-video’]")
private List<WebElement> videoElements;

Furthermore, @FindAll can be used with multiple @FindBy annotations to locate elements matching any given locator:

1
2
3
@FindAll({@FindBy(how=How.ID, using=username),
	@FindBy(className=username-field)})
private WebElement user_name;

Having established the representation of webpages as Java classes and the utilization of Page Factory for streamlined WebElement variable initialization, let’s proceed to write simple tests using the Page Object pattern and Page Factory in Selenium.

Simple Selenium Test Automation Project in Java

For our Selenium Page Object model tutorial, we’ll automate Toptal’s developer sign-up process, which involves the following:

  • Visiting www.toptal.com

  • Clicking the Apply As A Developer button

  • Verifying portal page opening

  • Clicking the Join Toptal button

  • Completing the form

  • Submitting the form by clicking the Join Toptal button

To execute this automation, we need to set up our Java project.

Setting Up a Project

After downloading and installing Java JDK and InteliJ Idea, we can proceed:

  • Creating a new Maven project

  • Linking Project SDK to the JDK, e.g., C:\Program Files\Java\jdkxxx on Windows

  • Setting up groupId (<groupId>SeleniumTEST</groupId>) and artifactId (<artifactId>Test</artifactId>)

  • Adding Selenium and JUnit Maven dependencies to the project POM file, ensuring selenium.version and junit.version are updated with the latest version numbers:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   <dependencies>
        <!-- JUnit -->         
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- Selenium -->

        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-firefox-driver</artifactId>
            <version>${selenium.version}</version>
        </dependency>

        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-support</artifactId>
            <version>${selenium.version}</version>
        </dependency>

        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium.version}</version>
        </dependency>

    </dependencies>

If auto-build is enabled, dependency downloads should start automatically. Otherwise, activate Plugins > install > install:install under the Maven Projects panel in IntelliJ Idea.

selenium testing tutorial IDE screenshot

With the project bootstrapped, we create our test package under src/test/java, naming it com.toptal. Under this package, two more are created: com.toptal.webpages and com.toptal.tests.

selenium testing tutorial screenshot

Page Object/Page Factory classes will reside under com.toptal.webpages, while test routines will be placed under com.toptal.tests.

We’ll have three Page Object classes:

Class

Description

HomePage

Represents Toptal's homepage, www.toptal.com

DeveloperPortalPage

Represents Toptal's developer portal page

DeveloperApplyPage

Represents Toptal's developer application form

Now, we can create the Page Object items.

Selenium Page Object Model: HomePage

The initial object represents Toptal’s homepage (www.toptal.com). We create a class named HomePage under com.toptal.webpages:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.toptal.webpages;

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

public class HomePage {
   private WebDriver driver;

   //Page URL
   private static String PAGE_URL="https://www.toptal.com";

   //Locators

   //Apply as Developer Button
   @FindBy(how = How.LINK_TEXT, using = "APPLY AS A DEVELOPER")
   private WebElement developerApplyButton;

   //Constructor
   public HomePage(WebDriver driver){
       this.driver=driver;
       driver.get(PAGE_URL);
       //Initialise Elements
       PageFactory.initElements(driver, this);
   }

   public void clickOnDeveloperApplyButton(){

       developerApplyButton.click();

   }
}

Determining Element Locators

On Toptal’s homepage, our focus is on the Apply as a Developer button, which we can locate using its text. Finding and identifying elements while modeling webpages as Page Object classes can be tedious. Tools like Firefox Developer Tools or Chrome DevTools simplify this task. Right-clicking an element and selecting Inspect Element from the context menu reveals detailed element information.

By copying the element’s xpath, we create a WebElement field for it in our Page Object:

1
2
@FindBy(xpath = "/html/body/div[1]/div/div/header/div/h1")
WebElement heading;

Alternatively, using the tag name “h1” simplifies things if it uniquely identifies the target element:

1
2
@FindBy(tagName = "h1")
WebElement heading;

Selenium Page Object Model: DeveloperPortalPage

Next, we need a Page Object for the developer portal page accessed by clicking Apply As A Developer.

We’re interested in two elements on this page. To confirm page load, we’ll verify the heading’s existence. We also need a WebElement field for the Join Toptal button:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.toptal.webpages;

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

public class DeveloperPortalPage {
   private WebDriver driver;

   @FindBy(xpath = "/html/body/div[1]/div/div/header/div/h1")
   private WebElement heading;

   @FindBy(linkText = "JOIN TOPTAL")
   private WebElement joinToptalButton;

   //Constructor
   public DeveloperPortalPage (WebDriver driver){
       this.driver=driver;

       //Initialise Elements
       PageFactory.initElements(driver, this);
   }

   //We will use this boolean for assertion. To check if page is opened
   public boolean isPageOpened(){
       return heading.getText().toString().contains("Developer portal");
   }

   public void clikOnJoin(){
       joinToptalButton.click();
   }
}

Selenium Page Object Model: DeveloperApplyPage

Our third and final page object represents the page containing the developer application form. Due to multiple form fields, we define a WebElement variable for each, finding them by their id and defining specific setter methods to simulate keystrokes for each field:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.toptal.webpages;

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

public class DeveloperApplyPage {
   private WebDriver driver;

   @FindBy(tagName = "h1")
   WebElement heading;

   @FindBy(id="developer_email")
   WebElement developer_email;

   @FindBy(id = "developer_password")
   WebElement developer_password;

   @FindBy(id = "developer_password_confirmation")
   WebElement developer_password_confirmation;

   @FindBy(id = "developer_full_name")
   WebElement developer_full_name;

   @FindBy(id = "developer_skype")
   WebElement developer_skype;

   @FindBy(id ="save_new_developer")
   WebElement join_toptal_button;


   //Constructor
   public DeveloperApplyPage(WebDriver driver){
       this.driver=driver;

       //Initialise Elements
       PageFactory.initElements(driver, this);
   }

   public void setDeveloper_email(String email){
       developer_email.clear();
       developer_email.sendKeys(email);
   }

   public void setDeveloper_password(String password){
       developer_password.clear();
       developer_password.sendKeys(password);
   }

public void  setDeveloper_password_confirmation(String password_confirmation){
       developer_password_confirmation.clear();
       developer_password_confirmation.sendKeys(password_confirmation);
   }

   public void setDeveloper_full_name (String fullname){
       developer_full_name.clear();
       developer_full_name.sendKeys(fullname);
   }

   public void setDeveloper_skype (String skype){
       developer_skype.clear();
       developer_skype.sendKeys(skype);
   }

   public void clickOnJoin(){
       join_toptal_button.click();
   }
   public boolean isPageOpened(){
       //Assertion
       return heading.getText().toString().contains("Apply to join our network as a developer");
   }
}

Writing a Simple Selenium Test

With Page Object classes representing pages and user interactions as their methods, we can now write our test routine as a series of straightforward method calls and assertions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.toptal.tests;

import com.toptal.webpages.DeveloperApplyPage;
import com.toptal.webpages.DeveloperPortalPage;
import com.toptal.webpages.HomePage;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;

import java.net.URL;
import java.util.concurrent.TimeUnit;

public class ApplyAsDeveloperTest {
   WebDriver driver;

   @Before
   public void setup(){
       //use FF Driver
       driver = new FirefoxDriver();
       driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
   }

   @Test
   public void applyAsDeveloper() {
       //Create object of HomePage Class
       HomePage home = new HomePage(driver);
       home.clickOnDeveloperApplyButton();

       //Create object of DeveloperPortalPage
       DeveloperPortalPage devportal= new DeveloperPortalPage(driver);

       //Check if page is opened
       Assert.assertTrue(devportal.isPageOpened());

       //Click on Join Toptal
       devportal.clikOnJoin();

       //Create object of DeveloperApplyPage
       DeveloperApplyPage applyPage =new DeveloperApplyPage(driver);

       //Check if page is opened
       Assert.assertTrue(applyPage.isPageOpened());

       //Fill up data
       applyPage.setDeveloper_email("dejan@toptal.com");
       applyPage.setDeveloper_full_name("Dejan Zivanovic Automated Test");
       applyPage.setDeveloper_password("password123");
       applyPage.setDeveloper_password_confirmation("password123");
       applyPage.setDeveloper_skype("automated_test_skype");

       //Click on join
       //applyPage.clickOnJoin(); 
   }

    @After
    public void close(){
          driver.close();
       }
   }

Running the Test

Your project structure should now resemble this:

selenium testing example

To run the test, right-click on ApplyAsDeveloperTest in the tree and select Run ‘ApplyAsDeveloperTest’.

selenium testing example

Test results will be displayed in the lower left corner of your IDE:

selenium testing example

Automation in Selenium for Maintainable Test Suites

Selenium’s Page Object and Page Factory simplify webpage modeling and automated testing, greatly benefiting both developers and QA engineers. When implemented effectively, these Page Object classes promote reusability throughout the test suite and enable early implementation of automated Selenium tests in projects without compromising agile development. By abstracting user interactions within page object models and maintaining concise test routines, your test suite can readily adapt to changing requirements with minimal effort.

I hope this effectively demonstrates how to write clean, maintainable test code. Always remember the golden rule of QA: “Think twice, code once!”

Licensed under CC BY-NC-SA 4.0