Automation in Selenium: Page Object Model and Page Factory
Proper test automation is a challenging yet essential ingredient for quality software applications. Explore maintainable test automation in Selenium, including Page Object model and Page Factory.
Proper test automation is a challenging yet essential ingredient for quality software applications. Explore maintainable test automation in Selenium, including Page Object model and Page Factory.
Dejan is a senior QA engineer with significant experience in web and mobile testing. He has also worked extensively on Android app development.
Expertise
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.
Writing automated tests is more than just a luxury for any agile software development team. It is an essential tool to find bugs quickly during early phases of software development cycles. When there is a new feature that is still in the development phase, developers can run automated tests and see how those changes affect other parts of the system. This article will explain how you can speed up this process with test automation in Selenium, using the Page Object model.
Through test automation, it is possible to reduce the cost of bug fixes and improve the overall software quality assurance (QA) process. With proper tests, developers can find and resolve bugs before they reach QA. Test automation further helps us to automate test cases and features that are constantly regressing. This way, QA engineers have more time to test other parts of the application. Moreover, this helps ensure quality of the product in production releases. As a result, we get products that are effectively more stable, and a QA process that is more efficient.
Although writing automated tests may seem like an easy task for developers and engineers, there is still the possibility of ending up with poorly implemented tests and poor code maintainability. Trying to constantly deliver changes or features in any agile development project can prove to be costly when tests are involved. Changing one element on a webpage that 20 tests rely on will require going through these 20 test routines and updating each one to adapt to this change. This is time consuming and discourages developers from implementing automated tests early on.
But what if we could make the change in one place only and have every relevant test routine use it? We will take a look at automated tests in Selenium, and how we can employ Page Object model best practices to write maintainable and reusable test routines.
Selenium Page Object Model
Page Object model is an object design pattern in Selenium, where webpages are represented as classes, and the various elements on the page are defined as variables on the class. All possible user interactions can then be implemented as methods on the class:
clickLoginButton();
setCredentials(user_name,user_password);
Since well-named methods in classes are easy to read, this works as an elegant way to implement test routines that are both readable and easier to maintain or update in the future. For example:
In order to support Page Object model, we use Page Factory. Page Factory in Selenium is an extension to Page Object and can be used in various ways. In this case we will use Page Factory to initialize web elements that are defined in webpage classes or Page Objects.
Webpage classes or Page Objects containing web elements need to be initialized using Page Factory before the web element variables can be used. This can be done through the use of initElements
function on Page Factory:
LoginPage page = new LoginPage(driver);
PageFactory.initElements(driver, page);
Or, even simpler:
LoginPage page = PageFactory.intElements(driver,LoginPage.class)
Or, inside the webpage class constructor:
public LoginPage(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
Page Factory will initialize every WebElement
variable with a reference to a corresponding element on the actual webpage based on configured “locators.” This is done through the use of @FindBy
annotations. With this annotation, we can define a strategy for looking up the element, along with the necessary information for identifying it:
@FindBy(how=How.NAME, using="username")
private WebElement user_name;
Every time a method is called on this WebElement
variable, the driver will first find it on the current page and then simulate the interaction. In case we are working with a simple page, we know that we will find the element on the page every time we look for it, and we also know that we will eventually navigate away from this page without returning. Here, we can cache the looked up field by using another simple annotation:
@FindBy(how=How.NAME, using="username")
@CacheLookup
private WebElement user_name;
This entire definition of the WebElement
variable can be replaced with a much more concise form:
@FindBy(name="username")
private WebElement user_name;
The @FindBy
annotation supports a handful of other strategies that make things a bit easier, including id
, name
, className
, css
, tagName
, linkText
, partialLinkText
, and xpath
.
@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 can then be used to interact with the corresponding elements on the page. For example, we can send the given sequence of keystrokes to the password field on the page:
user_password.sendKeys(password);
This is equivalent to the following:
driver.findElement(By.name(“user_password”)).sendKeys(password);
Moving on, you will often come across situations where you need to find a list of elements on a page, and that is when @FindBys
comes in handy:
@FindBys(@FindBy(css=”div[class=’yt-lockup-tile yt-lockup-video’]”)))
private List<WebElement> videoElements;
The above code will find all the div
elements having two class names, yt-lockup-tile
and yt-lockup-video
. We can simplify this even more by replacing it with the following:
@FindBy(how=How.CSS,using="div[class=’yt-lockup-tile yt-lockup-video’]")
private List<WebElement> videoElements;
Additionally, you can use @FindAll
with multiple @FindBy
annotations to look for elements that match any of the given locators:
@FindAll({@FindBy(how=How.ID, using=”username”),
@FindBy(className=”username-field”)})
private WebElement user_name;
Now that we can represent webpages as Java classes and use Page Factory to initialize WebElement
variables easily, let’s see how 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, let’s automate Toptal’s developer sign-up process, which includes the following steps:
-
Visit www.toptal.com
-
Click on the Apply As A Developer button
-
Check if the portal page is opened
-
Click on the Join Toptal button
-
Fill out the form
-
Submit the form by clicking on the Join Toptal button
To execute this automation, we must set up our Java project.
Setting Up a Project
Download and install Java JDK and InteliJ Idea, and then we can proceed:
-
Create a new Maven project
-
Link
Project SDK
to your JDK, e.g.: on WindowsC:\Program Files\Java\jdkxxx
-
Setup groupId (
<groupId>SeleniumTEST</groupId>
) and artifactId (<artifactId>Test</artifactId>
) -
Add dependencies for Selenium and JUnit Maven in your project POM file; make sure to update
selenium.version
andjunit.version
with the latest version numbers for JUnit Maven and Selenium:
<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>
At this point, if auto build is enabled, dependencies should start downloading automatically. If not, just activate Plugins > install > install:install under the Maven Projects panel on the right side of your IntelliJ Idea IDE.

Once the project has been bootstrapped, we can start creating our test package under src/test/java
. Name the package com.toptal
, and create two more packages under it: com.toptal.webpages
and com.toptal.tests
.

We will keep our Page Object/Page Factory classes under com.toptal.webpages
and the test routines under com.toptal.tests
.
We will 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 are ready to create the Page Object items.
Selenium Page Object Model: HomePage
The very first object we need to implement is for Toptal’s homepage (www.toptal.com). Create a class under com.toptal.webpages
and name it HomePage
.
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 we are interested in one element in particular, and that is the Apply as a Developer button. We can find this element by matching the text, which is what we are doing above. While modeling webpages as Page Object classes, finding and identifying elements can often become a chore. With Firefox Developer Tools or Chrome DevTools, this task can be made easier. By right-clicking on any element on a page, you can activate the Inspect Element option from the context menu to find out detailed information about the element.
By copying the xpath
of the element, we can create a WebElement field for it in our Page Object as follows:
@FindBy(xpath = "/html/body/div[1]/div/div/header/div/h1")
WebElement heading;
Or, to keep things simple, we can use the tag name “h1” here, as long as it uniquely identifies the element we are interested in:
@FindBy(tagName = "h1")
WebElement heading;
Selenium Page Object Model: DeveloperPortalPage
Next, we need a Page Object that represents the developer portal page, the one that we can reach by clicking on the Apply As A Developer button.
On this page, we have two elements of interest. To determine if the page has loaded, we want to verify the existence of the heading. And we also want a WebElement
field for the Join Toptal button.
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
For our third and last page object for this project, we define an object that represents the page containing the developer application form. Since we have to deal with a number of form fields here, we define one WebElement
variable for every form field. We find each field by its id
, and we define special setter methods for every field that simulate keystrokes for the corresponding fields.
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 our pages, and user interactions as their methods, we can now write our test routine as a series of simple method calls and assertions.
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
At this point, your project structure should look like this:

If you want to run the test, select ApplyAsDeveloperTest from the tree, right-click on it, and select Run ‘ApplyAsDeveloperTest’.

Once the test has been run, you can see the results in the lower left corner of your IDE:

Automation in Selenium for Maintainable Test Suites
Page Object and Page Factory in Selenium make it easy to model and automatically test webpages, and make the lives of both developers and QA engineers much simpler. When done right, these Page Object classes can be reused across your entire test suite and make it possible to implement automated Selenium tests for your projects early on, without compromising agile development. By abstracting away user interactions in your page object models and keeping your test routines light and simple, you can adapt your test suite to any requirement changes with little effort.
I hope I have managed to show you how to write nice and clean test code that is easy to maintain. Keep my favorite QA quote in mind: “Think twice, code once!”
Further Reading on the Toptal Blog:
Understanding the basics
What is Page Object model in Selenium?
Page Object model is an object design pattern in Selenium. Webpages are represented as classes, and elements on the page are defined as variables on the class, so user interactions can then be implemented as methods on the class.
Why do we use Selenium?
Selenium is designed to automate web browsers, thus enabling software engineers to greatly speed up and automate testing. While test automation is its primary use, Selenium can also be used to automate certain repetitive actions, such as basic administrative tasks.
What is Selenium testing?
Selenium testing is the process of using Selenium software tools to facilitate test automation. In most cases, software engineers choose one to two Selenium tools for this task, but additional tools can be employed to meet different needs.
What's the difference between Page Object model and Page Factory?
Page Object model is a design pattern. Page Factory expands on Page Object model functionality by introducing more advanced features. It allows users to initialize specific elements within the Page Object model, using annotations.
Belgrade, Serbia
Member since September 3, 2014
About the author
Dejan is a senior QA engineer with significant experience in web and mobile testing. He has also worked extensively on Android app development.