Why should the Page Object Model be used
A Page Object represents different pages/sections of a website as objects within the test script. In today’s software development lifecycle, there is a constant need of the software to stay up-to-date to the ever-growing technology. Hence, many clients are opting to work on a scrum-based software development methodology.
With the Page Object Model, even if the UI implementation of the website goes through modifications, test script level changes will only be limited to the Page Objects. This model helps in enhancing the tests, makes them highly customizable, reduces the code duplication, builds a layer of abstraction and finally hides the inner implementation from tests.
In Figure 1 below, a clear flow is seen from the test cases to the webpage. Initially, the test scripts interact with the page objects. These page objects interact with the Selenium WebDriver which contains selenium functions that finally hit the webpage and perform actions on it.
By applying principles of object-oriented development, we can create a class that serves as an interface to a web page in the application & modeling its properties and behavior. This helps in creating a layer of separation between the test code and the page-specific code by hiding the technical implementation such as locators used to identify elements on the page, layout and so on.
The Page Object design pattern provides tests for an interface where a test can operate on that page in a manner similar to the user accessing the page but by hiding its internals. As an example, think of the inbox of any web-based email system. Amongst all the services that it offers, one of them is typically the ability to compose a new email, choose to read a single email and to list the subject lines of the emails in the inbox. How these are implemented shouldn’t matter to the test. The tests should use objects of a page at a high level where any change in layout or attributes used for the fields in the underlying page should not break the test.
Layers of the framework in Page Object Model
Test Case Layer
This is the first layer from where the flow of the automation framework starts. It usually contains functions which in turn call functions in the Page Object layer (described below). It is important to note that assertions are done at this level.
Page Object Model Layer
At the second layer, i.e. the page object specific later, a level of abstraction is provided by hiding the technical implementation of the test case. The code for this layer is implemented by writing functions for the services provided by a particular page.
Let us take an example of the Gmail portal. An email workflow typically consists of a user logging in, seeing the list of emails, clicking on Compose, writing the email and finally sending it. If we observe this workflow carefully, it is seen that all these actions are performed on different webpages on the same website.
The below steps explain the flow of the test case keeping the page object model in picture:
Step 1. Login page
a. Enter login credentials
b. Click Login button
Step 2. Worklist Page
a. List of emails seen
Step 3. Compose Page
a. Write email
b. Click Send
Step 4. Worklist Page
a. Verify that list of emails are seen
At the test script level for the Page Object Model, all these pages have their own class files. These class files contain the application-specific locators which identify the page objects (like username text box locator, sign-in button locator etc.) Along with locators the locators, page-related functions are also included (like click on the login button, type values in the username box etc.)
Now assume that on performing an action, the control moves to another page. In this case, the function performing that action should return an object of the new page.
For example, when a user clicks on Login button in the Gmail portal, he/she is navigated to the user’s Inbox; here the function implementing the click on the login button should return the object of the navigated page i.e. the User’s Inbox page. This is depicted in the code snippet below:
Another point worth noting is that the Page objects are commonly used for testing, but should not make assertions themselves. Their responsibility is to provide access to the state of the underlying page. It’s the responsibility of the Test Case Class to carry out the assertion logic.
Page objects are a classic example of encapsulation. They hide the details of the UI structure from the tests. It’s a good design principle to look for situations like this where you can ask yourself “how can I hide some details from the rest of the software?” As with any encapsulation this yields two benefits:
- Business logic resides at a single place which can be modified without affecting rest of the components of the system
- It provides separation between business logic and the UI details
Selenium Function Layer
This section contains the Selenium WebDriver code written in the form of wrapped functions which directly interacts with the AUT.
Page Object Model in action
Page Object Model is explained below with an example. Let us consider the simple illustration of logging into a demo website.
This portal consists of a UserName Text Box, Password Text Box and a Login button as shown below:
UML diagram of the test case workflow
The UML diagram of the entire flow is shown below starting from the Test Cases (both positive and negative). These test cases interact with the SignIn Class (implemented as Page Object Pattern) consisting of services (offered by the page) in the form of functions. This layer provides abstraction and hides the inner implementation from tests. The Page Object Class in turn communicates with the Selenium functions implemented in another file which provides another layer of abstraction to the test cases. Last but not the least, Selenium functions class interacts with the AUT (application under test), since this is what they are designed to do.
Sign-in Page modelled as Page Object Pattern
The Page Object Model for the Sign-in Page contains the representation of the application’s login page, and the services provided by the page via public methods.
The class shown below consists of object locators along with three elementary functions (enterLoginId (String), enterPassword (String), clickLoginButton ()) and a master function- login (String, String)
SignIn Page Class
As per the UML diagram, the SignInPage (implemented as POM) interacts with the Selenium functions (implemented in the BrowserActions class – defined below) which in-turn interacts with the UI components of the application under test i.e. performing actions like click, entering test data, etc.
The BrowserActions class shown below consists of wrapper functions – ‘text_Set(By, String)’ and ‘element_Click(By)’ which internally make use of Selenium WebDriver functions like findElement(By), sendKeys(String), click() etc.
Advantages of using Page Objects
1.) There is a clean separation between test code and page-specific code such as locators and layout
2.) There is a single repository for the services or operations offered by the page rather than having these services scattered throughout the tests
3.) In test automation, it is used for enhancing test maintenance and reducing code duplication
4.) It is an object oriented class which acts as an interface to the actual page of an application
5.) Tests use methods of the Page Object whenever they need to interact with the UI page. The advantage over here is that if the UI for a page changes, then there is no need to change the test code. Just the code within the Page Object needs to change. Subsequently, all changes which are required to be done due to UI changes are at one place i.e. Page Object.
In order to provide additional support for the Page Object pattern, the package ‘org.openqa.selenium’ has a PageFactory Class. One can import the PageFactory class in Java using the following statement:
- Declare fields in a java class (say, UsingPageFactory.java) that are WebElements or List<WebElement>. Also, the names of the WebElement(s) should be the same as the ID/name of the UI element in the HTML DOM.
- In order to initialize the WebElements, we need to use the PageFactory.initElements method as shown below:
- Step 1 and Step 2 can be summarized using the below code snippet:
The name of the private field i.e. ’firstNameTxtBox’ in the ‘UsingPageFactory’ Java class is assumed to be either the “id” or “name” of the element on the HTML page.
In the example above, the line:
is equivalent to either
The WebDriver’s instance that’s used in the above statements is the one that’s passed to the PageFactory’s initElements method.
If both the statements fail to find the element firstNameTxtBox on the webpage using the findElement method, then, NoSuchElementException is thrown.
Making Use of Annotations (@FindBy and @CacheLookup)
In order to give meaningful name to the fields rather than a name which is either “id” or “name” of the html element or list of elements, it is advisable to use the annotation @FindBy
You can either use this annotation by specifying both “how” and “using”
@FindBy(how = How.ID, using = “web.id.one”) WebElement userName;
or by specifying one of the location strategies (e.g. “id”) with an appropriate value to use.
@FindBy(id = “web.id.one”) WebElement userName;
Both the above statements point to the same WebElement i.e. userName and below two annotations point to the same list of elements:
The static method initElements is used to initialize each of the WebElements and List<WebElement> instance that have been declared, assuming that the field name is also the HTML element’s “id” or “name”. This means that for the Page class below, the “submit” WebElement is internally located using the XPath expression “//*[@id=’submit’]” or “//*[@name=’submit’]”.
On the other hand, if @FindBy annotation is used, then the WebElement is located using the search criteria specified by the constant of enum <How> and its value assigned to using.
In the below example, the location strategy specified is ID and its value is submitButton.unique.id
One shortcoming is that every time a method is called on the WebElement, the driver will again find it on the current page. This behavior is desired on simple webpages where we know that element is always going to be present when an action is performed. We also know that we won’t be navigating away from the page and returning. In such a case, it would be handy if we could “cache” the element once it is found.
Consider the below code snippet where the @CacheLookup annotation is being used to eliminate the searching of the searchBox WebElement every time an action is performed using it.
But because of the @CacheLookup annotation we are more likely to encounter the StaleElementExceptions since this annotation will find the element once and then keep a reference to it. On the contrary, if this annotation is not used and every time there is a reference to a WebElement, it will go and find it again so you shouldn’t see StaleElementExceptions.
- If you use PageFactory, you can assume that the fields are initialized. If you don’t use PageFactory, then NullPointerExceptions will be thrown if you make the assumption that the fields are already initialized
- List<WebElement> fields are decorated if and only if they have @FindBy or @FindBys annotation. Default search strategy “by id or name” that works for WebElement fields is hardly suitable for lists because it is rare to have several elements with the same id or name on a page.
- WebElements are evaluated lazily. It means if you never use a WebElement field in a PageObject, there will never be a call to “findElement” for it.
Using PageFactory in PageObject Model
Referring to the same code in the section “Sign-in Page modelled as Page Object Pattern” the PageFactory.initElements has been used to initialize all the private instance variables (i.e the WebElements) of the SignInPage class.
All the three private WebElements usernameTextBox, passwordTextBox and loginButton in the below code snippet are automatically located/initialized when the class is instantiated using the PageFactory.initElements (driver, SignInPage.class)
Similarly, the private WebElement welcomeMessage in the HomePage class is instantiated using the same method i.e. PageFactory.initElements(driver, this)
Once the WebElements have been initialized, they can be used to perform various actions like sendKeys(String), click(), etc on the AUT(application under test) as shown below in the below code snippet:
The Page Object is a Design Pattern which has become popular in test automation and is regarded as one of the best practices in Selenium.