Page Object Model (POM) – Why and When Should You Use It?
What is a Page Object Model?
Overview of Page Object Model
Page Object Model (or POM), is a design pattern in Selenium where we create an object repository for storing and organizing the page elements or objects. It is a very popular design pattern in web/mobile automation. The advantage of using POM is that it reduces code redundancy and complexity—as well as makes code more extensible, and improves test script maintenance by acting as an interface for the page under test. To simplify the concept of POM, we create a class file for each web page. The class file contains web elements that are available on the web page, which can be later utilized by test scripts to execute different operations.
Explanation with an example
To understand this concept better, let’s take a look at the below line of code that we often use in Selenium without the Page Object Model applied. Here there is no separation between the locators and actions on web elements.
driver.findElement(By.id(“user_email_login”)).sendKeys(“[email protected]”);
In Page Object Model, there are two separate sections created for the above, one for Identification methods and the second for Operational methods. So, the above statement is divided into separate sections within the class. Let’s take an example of an application with these web pages: login, home, customer, and transaction. So in real time, we create a separate class like loginClass, homeClass, and so on for every page, with all the web elements locators defined in one section; and operations to be performed on them in another section. We can also choose to create separate classes – one for Identification methods and the second is for Operational methods for each web page, like loginLocatorsClass and loginClassMethods.
By emailId = By.id("user_email_login"); public void enterEmailId() { driver.findElement(emailId).sendKeys("[email protected]"); }
What if you avoid using POM?
driver.findElement(By.id(“user_email_login”)).sendKeys(“[email protected]”);
- There is no clear separation between the test method and the application under test (AUT)’s locators (id’s in this example); both are used together in a single method. If the AUT’s UI changes its identifiers, layout, or how a login is input and processed, the test itself must also be changed.
- If we need to use the same web element in another place, we have to locate it again. Essentially this means developing the same code again and again. The code is not reusable, leading to duplicate and unreadable code.
- Suppose the same locator is used in multiple script files, and this locator gets changed at some point. It is then a tedious task to update this locator in all script files, especially if the project is large and complex. Imagine if multiple locators are changed? It leads to a lot of wasted time and effort.
The page object model is explicitly used to handle the above scenarios – that we may encounter quite frequently.
Advantages of the Page Object Model
- POM enables the creation of object repositories in such a way that web elements can be added, modified, and reused easily. This object repository has the web element’s name and locators to find it at the same place whenever the code is revisited.
- In POM, each web page is represented as a separate class. If any new web elements are added to a web page, the addition of new elements becomes easy by simply navigating to a class that has the same name as the webpage. For example: To add a new web element in the customer page object repository, you will search from the customerClass.java file and can add the new element to it. Similar is the case for modifying the locators if there is any change in the web element for a web page.
- The object repository is made independent of any test scripts. Whatever objects are needed to develop test scripts, call from that particular object repository.
- POM is a lazy object locator. It will not find web elements until the element is used. Lazy initialization helps in performance optimization, where object creation can be deferred until just before we need it. The key reason for doing this is that you can often avoid creating the object if you don’t need it.
- With POM, abstraction, and encapsulation are achievable.
-
Using POM, there is better coordination between pages which helps in a concept called Page Chaining to make test steps more readable and, therefore, maintainable. Suppose after login, you are moved to another web page named AccountDetails. Using the login method, you can return an object of AccountDetailsPage. See example below:
public class LoginPage { public WebDriver driver; public LoginPage(WebDriver driver) { this.driver = driver; } public AccountDetailsPage login(String username, String password) { driver.findElement(By.id(“username”)).sendKeys(username); driver.findElement(By.id(“pwd”)).sendKeys(password); driver.findElement(By.id(“loginBtn”)).click(); return new AccountDetailsPage(driver); } }
Sample Project Structure for POM
Below is a sample project structure of the page object model. Here, each web page is represented as a Java class file.
What is Page Factory?
Overview of Page Factory
Page Factory in Selenium is an extension of Page Objects. Page Factory class in Selenium makes it easier and simpler to use Page Objects and is a more optimized form of POM. In order to support the PageObject pattern, WebDriver’s support library contains a factory class.
In Page Factory, we use @FindBy annotation with different locator strategies to find web elements and perform actions on them. The initElements method is used to initialize web elements. To guarantee that relevant actions are carried out only after web elements are initialized, it is recommended in Selenium’s best practices with Page Factory to create all web element variables at the start of the class and initialize them upon page loading. See below for more details:
@FindBy annotation in Page Factory:
Method 1: @FindBy
@FindBy(how = How.ID, using= "element-id") private WebElement element-name;
‘using’ is used for assigning value to the static variable.
Method 2: @FindBy
@FindBy(id= "element-id") private WebElement element-name;
In the syntax shown above, the ID property is used for locating the element ‘element-id.’ Apart from ID, other locators can also be used, like Selenium CSS Selectors, Name locators, Class Name locators, XPath locators, and more.
Method 3: @FindAll
@FindAll ( { @FindBy(how = How.ID, using = "element"), @FindBy(className = "element-field") } ) private WebElement element_name;
If multiple elements are matching given locators, @FindAll annotation with multiple @FindBy annotations can be used.
initElements method in Page Factory:
Discussed below are how the web element variables can be initialized using the initElements method of Page Factory in Selenium:
Method 1: initElements
Type Parameters: T – Class of the PageObject
Input Parameters: driver – Selenium WebDriver used for locating the web elements,
pageClassToProxy – Class that will be initialized
Return Type: Method returns an instantiated instance of the class with WebElement and List<WebElement> fields that are proxied.
public static <T> T initElements(WebDriver driver, Class<T> pageClassToProxy) { T page = instantiatePage(driver, pageClassToProxy); initElements(driver, page); return page; }
Method 2: initElements
It is very similar to the initElements(WebDriver, Class) method, except that it will replace the fields of an already instantiated object.
Input Parameters: driver – Selenium WebDriver used for locating the web elements, page – Object with WebElement and List<WebElement> fields that have to be proxied.
public static void initElements(SearchContext searchContext, Object page) { initElements((ElementLocatorFactory)(new DefaultElementLocatorFactory(searchContext)), (Object)page); }
Method 3: initElements
It is very similar to other initElements methods; the major difference is that it takes ElementLocatorFactory (refer here) which provides the mechanism for finding the respective elements.
public static void initElements(ElementLocatorFactory factory, Object page) { initElements((FieldDecorator)(new DefaultFieldDecorator(factory)), (Object)page); }
Method 4: initElements
This one is very similar to other initElements methods; the major difference is that it takes a FieldDecorator (refer here) used for decorating the fields.
public static void initElements(FieldDecorator decorator, Object page) { for(Class<?> proxyIn = page.getClass(); proxyIn != Object.class; proxyIn = proxyIn.getSuperclass()) { proxyFields(decorator, page, proxyIn); } }