As we continue to automate the test cases on a daily basis using Selenium automation, the maintenance of the growing test suite parallely becomes complicated. While automating the test cases on Selenium, there would be an instance where we use the same web element in multiple test scripts. For example, while automating an E-Commerce application, in every test case we have to search for a particular item from a search field using Selenium locator i.e. using XPath or ID. For such scenarios, we add the search field locator in every test script which would eventually increase code duplicacy.
Moreover, if there is any change in the locator, we have to change the locator in every test script where a particular web element is used. To overcome such challenges, we would be further looking at the Page Object Model that would help build a framework that is easy to use and maintain.
What is the Page Object Model (POM)?
Page Object Model is a design pattern commonly used in test automation for creating an Object Repository of web elements. The primary advantage of adopting POM is to reduce code duplicacy and to reduce maintenance efforts.
To start with, a Page Object Model framework development, we create different classes for different web pages. Each class file contains a web element locator for the elements present on a particular web page. Multiple test scripts can further use these element locators to perform various web actions. Since a separate class file is created for each web page and the common locators can be used by multiple test classes, this reduces the code duplicity and improves code maintenance.
Advantages of Page Object Model
Enhances code maintainability : In case of changes in the locators, updating the code becomes very easy as web element locators are maintained separately from the test classes.
Enhances code readability : With the elimination of code duplicacy and redundancy from test classes, the code becomes more clean and easy to read.
Enhances code reusability : Creating a separate object repository allows multiple test scripts to access required locators from a single object repository based on web pages. This type of design pattern enhances code reusability.
Implementing Page Object Model with Selenium
Now since we are aware of the Page Object Model design pattern, let’s implement a basic structure of Page Object Model with Selenium framework where all the web elements of the web page and the method to perform web actions will be maintained in a class file.
Further, these web action methods will be called in selenium based test scripts and assertions will also be applied in test methods. As an example, let’s automate the below login test scenario:
Direct to pCloudy login page
Enter username and password
Click login
Validate the web page title
pCloudyLogin Page POM
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class pCloudyLogin {
WebDriver driver;
By loginMenuButton = By.xpath("//a[text()='Login']");
By userID = By.id("userId");
By passWord = By.name("password");
By loginButton = By.id("loginSubmitBtn");
public pCloudyLogin(WebDriver driver){
this.driver = driver;
}
public void clickLoginHeaderButton()
{
driver.findElement(loginMenuButton).click();
}
public void setUserID(String username)
{
driver.findElement(userID).sendKeys(username);
}
public void setPassword(String password)
{
driver.findElement(passWord).sendKeys(password);
}
public void clickLoginButton()
{
driver.findElement(loginButton).click();
}
public String getLoginTitle(){
return driver.getTitle();
}
public void loginToPCloudy(String username, String password)
{
this.setUserID(username);
this.setPassword(password);
this.clickLoginButton();
}
}
TestLogin Selenium Test Case
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import io.github.bonigarcia.wdm.WebDriverManager;
public class TestLogin {
WebDriver driver;
pCloudyLogin objLogin;
@BeforeTest
public void setup() {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
driver.manage().window().maximize();
driver.get("https://www.pcloudy.com/");
}
@test(priority=0)
public void testPCloudyLogin(){
objLogin = new pCloudyLogin(driver);
objLogin.clickLoginHeaderButton();
objLogin.loginToPCloudy("ramit.dhamija@gmail.com", "ramit9876");
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
String expectedTitle = "Mobile App Testing, Continuous Testing Cloud, Mobile Testing Tools | pCloudy";
String actualTitle = objLogin.getLoginTitle();
Assert.assertEquals(actualTitle,expectedTitle, "pCloudy Login Failed");
}
@AfterTest
public void tearDown() {
if(driver!=null)
{
driver.quit();
}
}
}
Code Walkthrough :
As per the Page Object Model design pattern, we have created a separate class named “pCloudyLogin” that behaves as an object repository for a login web page. The locators used to login to the pCloudy system have been added in this class and for usage of each locator, a different method has been created to perform web action so that the same methods can be used in multiple test scripts.
Next, we have created a test class where all the methods of web actions are called step by step in a test method. In the BeforeTest annotated method we have set up the ChromeDriver and launched the automated browser. In the test method, along with the calling of the web action method, we have passed the test data to the required methods and have added assertion to validate the login test case using the web page title. In the AfterTest annotated method, we have quit the Selenium session/browser.
Note: For webdriver setup and teardown activity, a separate class can be created to enhance code maintainability and readability.
What is the Page Factory?
Page Factory is a Selenium inbuilt class to support Page Object Design pattern. With Page Factory, the framework can be built in a more optimized form.
It provides a @FindBy annotation to find web elements without using “FindElement/s”.
The initElements method can be further used to initialize web elements in Page Class.
With the Page Factory Model, we use the same concept of maintaining the object repository separating from the test class.
@FindBy annotation accepts Selenium supported locators as parameters, for example : id, xpath, name, css, tagName, linkText, partialLinkText etc.
initElements is a static method of PageFactory class that accepts two parameters i.e. WebDriver and reference to the current class.
AjaxElementLocatorFactory is used to locate web elements only when the elements are utilized in any operation and is known as lazy load concept of Page Factory in Selenium. The web element timeout can be assigned to Object Class using AjaxElementLocatorFactory. Example of AjaxElementLocatorFactory:
AjaxElementLocatorFactory factory = new
AjaxElementLocatorFactory(driver,100);
PageFactory.initElements(driver, this);
With the above code example, If the element is not visible to perform an operation in 100 seconds, a timeout exception will appear.
Implementing Page Factory with Selenium
Now since we are clear with Page Factory, let’s have a look on implementing Page Factory with Selenium. As an example, let’s automate the below login test scenario:
Direct to pCloudy login page
Enter username and password
Click login
Validate the web page title
pCloudyLogin Page Class
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class pCloudyLogin {
WebDriver driver;
@FindBy(xpath="//a[text()='Login']")
WebElement loginMenuButton;
@FindBy(id="userId")
WebElement userID;
@FindBy(name="password")
WebElement passWord;
@FindBy(id="loginSubmitBtn")
WebElement loginButton;
public pCloudyLogin(WebDriver driver){
this.driver = driver;
PageFactory.initElements(driver, this);
}
public void clickLoginHeaderButton()
{
loginMenuButton.click();
}
public void setUserID(String username)
{
userID.sendKeys(username);
}
public void setPassword(String password)
{
passWord.sendKeys(password);
}
public void clickLoginButton()
{
loginButton.click();
}
public String getLoginTitle(){
return driver.getTitle();
}
public void loginToPCloudy(String username, String password)
{
this.setUserID(username);
this.setPassword(password);
this.clickLoginButton();
}
}
TestLogin Selenium Test Case
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import io.github.bonigarcia.wdm.WebDriverManager;
public class TestLogin {
WebDriver driver;
pCloudyLogin objLogin;
@BeforeTest
public void setup() {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
driver.manage().window().maximize();
driver.get("https://www.pcloudy.com/");
}
@test(priority=0)
public void testPCloudyLogin(){
objLogin = new pCloudyLogin(driver);
objLogin.clickLoginHeaderButton();
objLogin.loginToPCloudy("ramit.dhamija@gmail.com", "ramit9876");
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
String expectedTitle = "Mobile App Testing,
Continuous Testing Cloud, Mobile Testing Tools | pCloudy";
String actualTitle = objLogin.getLoginTitle();
Assert.assertEquals(actualTitle,expectedTitle, "pCloudy Login Failed");
}
@AfterTest
public void tearDown() {
if(driver!=null)
{
driver.quit();
}
}
}
Code Walkthrough :
In the login page class, we have defined all the web elements required to validate login test cases. @FindBy annotation provided by Selenium to support Page Factory Model is used to find defined web elements. An initElements() method has been used to initialize web elements. For each web element defined, we have created a method to perform web actions on web elements.
Next, we have created a test class where all the methods for web actions are called step by step in a test method. In the BeforeTest annotated method we have set up the ChromeDriver and launched the automated browser. In the test method, along with the calling of the web action method, we have passed the test data to the required methods and have added assertion to validate the login test case using the web page title. In the AfterTest annotated method, we have quit the Selenium session/browser.
Note: For webdriver setup and teardown activity, a separate class can be created to enhance code maintainability and readability.
Best Practices for Using Page Object Model and Page Factory:
Keep the page classes small and focused on a specific web page or functionality: When implementing the Page Object Model, it’s important to keep the page classes small and focused. Each page class should represent a single web page or a logical unit of functionality. This helps in better organization and maintainability of the codebase. By keeping the classes focused, it becomes easier to understand and update the code when necessary.
Use meaningful names for the web elements and methods in the page classes: Choose descriptive names for the web elements and methods in your page classes. This improves code readability and makes it easier for other team members to understand the purpose and functionality of each element or method. Meaningful names also help in writing clear and self-explanatory test scripts.
Avoid hardcoding locators in the page classes, use a centralized locator strategy: Instead of hardcoding locators directly into the page classes, it is recommended to use a centralized locator strategy. This can be achieved by creating a separate class or file that contains all the locators used in the application. By centralizing the locators, any changes to the locators can be made in a single place, reducing the effort required to update the tests when the application’s UI changes.
Regularly review and update the page classes to reflect any changes in the application under test: As the application under test evolves, it is important to regularly review and update the page classes to reflect any changes in the UI or functionality. This ensures that the page classes accurately represent the current state of the application and that the automation tests remain reliable and maintainable. Set up a process to review the page classes periodically and update them as needed.
Use inheritance or composition to handle common elements or functionalities across multiple pages: In scenarios where multiple pages share common elements or functionalities, consider using inheritance or composition to handle such cases. This helps in avoiding code duplication and promotes reusability. By creating a base page class or utility classes that can be extended or composed into other page classes, you can achieve a more modular and maintainable test automation framework.
Encapsulate complex interactions or workflows into higher-level methods for better readability: When dealing with complex interactions or workflows on a web page, it is beneficial to encapsulate them into higher-level methods. This improves code readability and makes the tests more maintainable. By creating methods that represent the steps of a workflow or a commonly performed action, you can simplify the test scripts and make them more focused on the test scenarios.
By following these best practices, you can ensure that your implementation of the Page Object Model and Page Factory in Selenium is efficient, maintainable, and scalable. These practices promote code readability, reusability, and adaptability, making your automation framework robust and easier to manage in the long run.
Conclusion
Knowing how to use Page Object Model and Page Factory in Selenium can be a huge benefit to many developers and automation engineers who struggle with duplicity of test cases. With the help of POM and Page factory in Selenium automation engineers can now separate out the web elements to easily locate them on test scripts to perform various web actions. This will go a long way in code reusability, maintenance and elimination of duplication for future test cases.