“Think twice, code once.” - Anonymous. Quite clearly, whoever said this so aptly, was well and truly aware that while software runs on code, code itself runs on (or only after) being tested well enough. Within testing, test automation plays an important role, especially in agile projects. Page Object Model (POM) and Page Factory are among the most popular design patterns in test automation. These solutions are widely used in UI test automation and help develop excellent project frameworks. This design pattern:
- makes it easier to build automation test scripts
- makes automation code much cleaner and easier to read
- reduces maintenance effort and redundant code
Can you even imagine the arduous task of maintaining 50K-class scripts without any automation design? It would be a nightmare for the company and the engineer. This article explains what Page Factory is, how it works in enterprise-level test automation, and how to build the foundation to customize it.
What are Page Object and Page Factory?
“If you automate a mess, you get an automated mess.” – Rod Michael
Question: You need to pick a shirt out of many clothes. How would you prefer to select it?
Pic 1: PO vs. No PO
Scenario 1
The picture on your right: This is a pile of clothes. All the clothes are in a mess. You might need to spend some time finding the shirt.
Scenario 2
The picture on your left: The clothes are organized in a wardrobe. You can find the shirt you want with just a glance.
In an automation project, the messy clothes are the methods and classes. Without a good wardrobe (design pattern), the code would be a mess like the clothes, and it would be difficult to read or maintain your project.
Page Object (PO) is the wardrobe that helps keep the code organized (readable and maintainable). With readable and maintainable scripts, the automation case will be much more robust, and it helps save effort on code understanding and maintenance.
Page Factory is a class (provided by Selenium WebDriver) to implement the Page Object Model (POM). It provides annotations instead of FindElement and FindElements to initialize elements, making them descriptive and readable.
What are the advantages of using Page Factory?
- Clean code
The defined web element is separated from the methods to make the class clean and tidy in a PO class. - Readable and descriptive
A web element is declared as a member variable (known as Object Field), and the field annotation is used to describe the element name, type, and location. In this way, we can easily identify any defined web element by its annotations like name, type, etc. - Maintainable
The defined web element can be used without re-definition anywhere in the PO class and subclasses. This means that a specific web element may be used many times but is defined at only one place. This means maintaining it at one place will affect all places.
For example:
Here is a well-designed PO class. You can easily know what it is doing if you know the basics of coding.
Pic 2-1: Login Page
Pic 2-1 is a login web page with 3 web elements: username input box, password input box, and a login button.
Now, let’s look at the next image:
Pic 2-2: PO class for the Login page
Pic 2-2 is the PO class for the login page. Web elements on the login web page (Pic 2-1) are defined as member variables with readable annotations (known as fields).
What does Page Factory do?
In the example (Pic 2-2), 3 WebElement objects (txtUserName, txtPassword, btnLogin) are defined as member variables without being initialized in the PO class. These objects are called directly by the public member method login. Then we can log in to the website by a simple demo (Pic 3-1).
Pic 3-1: Test Login
Based on basic knowledge of Java, “NullPointerException” should be thrown out if a variable is not initialized. It seems the WebElement objects are not initialized in the PO class. How does the demo (Pic 3-1) work as expected, without any error?
If the variable txtUserName is null, why not throw NullPointerException?
Something must initialize it silently.
Let us look back into the PO class (Pic 2-2) again. A static method in PageFactory class is called by its construction method. This static method is how Page Factory works on a PO class. Page Factory initializes the declared field (WebElement object) in a PO class (Pic 3-2).
Pic 3-2: Page Factory initializes WebElement object
How does Page Factory work?
We know that Page Factory initializes a declared variable of the WebElement object so that these defined WebElement objects can be called directly in the PO class and subclasses.
Page Factory is helpful but not enough. A live project often requires additional operations to initialize a WebElement object. Here are some examples:
- State verification
To get the WebElement with an expected state. E.g., before clicking a button, the button should be visible and enabled. - Time out (synchronization)
Selenium has a configuration of implicitlyWait to ensure that a specific web element appears within a global time out. But if it is not specific, it makes no sense to use the largest wait configuration for all elements.
The test code will become messy if many additional operations are added inline. Is there a way to avoid this situation? Can we get a well-prepared WebElement after Page Factory initializes it? To be able to that, it is necessary to understand how Page Factory works.
Page Factory converts and assigns the object WebElement as follows: (the sequences marked in the flow chart are mapped to the key classes in Pic 4-2).
- Page Factory defines the WebElement and its annotation as an object Field (Pic 4-1) in the PO class.
- Object Field is divided into two objects, object Annotation and the non-initialized object WebElement.
- Object Annotation (@FindBy) generates the object By in the Annotations class (Pic 4-1, flow ①),
- The object By generates the object ElementLocator in the DefaultElementLocator class (Pic 4-1, flow ②) and object ElementLocator is converted to an initialized object WebElement by DefaultFieldDecorator class (Pic 4-1, flow ③).
- At last, the initialized object WebElement that ElementLocator generates is assigned to the non-initialized WebElement, which is separated from object Field (Pic 4-1, flow ④).
- The PO class will be ready after all declared fields go through the same process.
Pic 4-1: Page Factory - flow chart
- org.openqa.selenium.support.pagefactory.Annotations
- org.openqa.selenium.support.pagefactory.DefaultElementLocator
- org.openqa.selenium.support.pagefactory.DefaultFieldDecorator
- org.openqa.selenium.support.PageFactory
Pic 4-2: Page Factory – key classes for the flow chart
Let’s dive into the code details to see how the objects described in the flow chart are generated.
Object Annotation -> object By
- org.openqa.selenium.support.pagefactory.Annotations
Method: buildByFromShortFindBy, buildByFromLongFindBy
Input: Object Annotation, e.g. @FindBy(locateType = StringLocator)
Output: Object By
Based on the input annotation, the method will go through all the Locators to find out the non-empty string (Defined locateType in the annotation) and then build the object By, based on the annotation locate type and value.
Object By -> object ElementLocator
- org.openqa.selenium.support.pagefactory.DefaultElementLocator
Method: DefaultElementLocator (construction method)
Input: Object By, object SearchContext (context, e.g. initialized WebDriver), Boolean variable shoudCache (optional annotation, not include in this article)
Output: Object ElementLocator
Object ElementLocator -> object FieldDecorator
- org.openqa.selenium.support.pagefactory.DefaultFieldDecorator
Method: proxyForLocator, proxyForListLocator
Input: ClassLoader for PO class, object ElementLocator
Output: proxy for object WebElement
The instance creation of WebElement or List<WebElement> is using a proxy. Each proxy has its invocationHandler.
Selenium comprises two invocationHandlers:
- LocatingElementHandler is for object WebElement
- LocatingElementListHandler is for object List<WebElement>
By using a proxy, the object WebElement is created regardless of whether the element exists or not. When a method of object WebElement is called, it automatically searches for the element. In this case, the case execution will be more stable and help avoid exceptions such as the StaleElementReferenceException.
Object FieldDecorator -> Initialized WebElement
- org.openqa.selenium.support.PageFactory
Method: proxyFields
Input: Object FieldDecorator, PO object, PO class
Output: All the defined object WebElement in the PO class is initialized and assigned with a proxy object
Method ProxyFields goes through all the Field objects (Defined object WebElement) within a PO class and the superclasses by a for loop. Each object field uses a method decorator (Method in object FieldDecorator) to get the initialized object WebElement.
Bottom line
A healthy framework should be clear and well-designed in framework layer and logic. It should also be readable and maintainable in a page object class.
There are always some complex web pages or some common additional operations that must be performed on all WebElement objects. Familiarity with the Page Factory mechanism and appropriate customizations can avoid messy definitions and make the PO classes much tidier and readable.
Customized Page Factory also improves the velocity of test automation in many Nagarro projects. In one of our projects, only six engineers covered the execution and maintenance work of 20,000 scripts that run daily.
Page Factory is one of the best practices to build automation frameworks, especially for enterprise-level, complex web applications. Are you looking to develop automation frameworks based on Page Factory? Our experts can help you! Explore our offerings and get in touch with us.