WAF#3 - Implementing POM design pattern with an Object repository

WAF#3 - Implementing POM design pattern with an Object repository

Introduction

Before discussing POM, first, let's understand what the problem is if POM is not used.

Below is the test written for validLoginTest-

@Test
public void validLoginTest(){
    driver.findElement(By.name("username")).sendKeys("Admin");
    driver.findElement(By.name("password")).sendKeys("admin");
    driver.findElement(By.xpath("//button[@type='submit' and text()=' Login ']")).click();
    String actualDashboardHeader = driver.findElement(By.xpath("//span[@class='oxd-topbar-header-breadcrumb']/h6")).getText();
    Assert.assertEquals(actualDashboardHeader,"Dashboard");
}

What's the problem with the above code?

  • At the test layer, we should not write code to interact with AUT. Because your tests will be less maintainable. This is because the logic for interacting with the web page will be scattered throughout your test code. This can make it difficult to find and fix bugs, and it can also make it difficult to add new features to your tests.

  • Your tests will be less reusable. This is because your tests will be tightly coupled to the specific implementation of the web page. This means that if the web page changes, you will need to update your tests.

UI automation projects consist of three crucial components:

• UI objects,

• the code or actions performed on those objects,

• and the test data associated with the automation process.

Due to the significance of preserving code related to the interaction with the User Acceptance Testing (UAT) environment, it is vital to develop maintainable code for this purpose. One widely employed design pattern in UI automation is the Page-Object model.


What is the POM design pattern?

POM stands for "Page Object Model." It is a design pattern commonly used in test automation frameworks to enhance the maintainability and reusability of automation code. The Page Object Model aims to create an object-oriented representation of web pages or user interface (UI) components in a software application.

In the Page Object Model, each web page or UI component is represented by a separate class called a "Page Object." These Page Objects encapsulate the elements and behaviours of the corresponding web page or UI component, providing a higher level of abstraction and allowing for easier test case development and maintenance.

The Page Objects expose methods or actions that can be performed on the UI elements within the page, such as clicking a button, entering text into a field, or submitting a form. The underlying implementation details and locators (such as XPath or CSS selectors) are encapsulated within the Page Objects, abstracting them from the test scripts.


Adding locators in Object Repository

I will be storing UI locators in an external json file which will act as Object Repository and will help to separate code interacting with UI and locators.

Below is an example of adding locators for username field -

"userName": {
  "type": "text",
  "locateBy": "name",
  "name": "username",
  "explicitWait": true,
  "explicitWaitMaxDurationInSeconds": 50
}

Below are details of attributes used above -

  • type - This is for types of object like text or button, as of now it is only for informational purposes and not used in this code.

  • locateBy - It is used to indicate which locator technique to use and the same is used to find locater value in the json.

  • name - This attribute can be name, id, css, xpath with locator value.

  • explicitWait - This is an optional field. If the value of this attribute is true than, explicitWait will be used to locate the element with the condition presenceOfElementLocated.

  • explicitWaitMaxDurationInSeconds - This is an optional field with maximum duration to wait to locate an element with the explicit wait.

LoginPage.json

{
  "userName": {
    "type": "text",
    "locateBy": "name",
    "name": "username",
    "explicitWait": true,
    "explicitWaitMaxDurationInSeconds": 50
  },
  "password": {
    "type": "text",
    "locateBy": "name",
    "name": "password"
  },
  "login": {
    "type": "button",
    "locateBy": "xpath",
    "xpath": "//button[@type='submit' and text()=' Login ']"
  },
  "invalidCredential": {
    "type": "label",
    "locateBy": "xpath",
    "xpath": "//div[@class='orangehrm-login-error']//div[@class='oxd-alert-content oxd-alert-content--error']//p",
    "explicitWait": true,
    "explicitWaitMaxDurationInSeconds": 50
  },
  "userNameFieldRequired": {
    "type": "label",
    "locateBy": "xpath",
    "xpath": "//input[@name='username']/../..//span",
    "explicitWait": true,
    "explicitWaitMaxDurationInSeconds": 50
  },
  "passwordFieldRequired": {
    "type": "label",
    "locateBy": "xpath",
    "xpath": "//input[@name='password']/../..//span"
  }
}

DashboardPage.json

{
  "dashboardTitle": {
    "type": "label",
    "locateBy": "xpath",
    "xpath": "//span[@class='oxd-topbar-header-breadcrumb']/h6",
    "explicitWait": true,
    "explicitWaitMaxDurationInSeconds": 50
  }
}

Adding Login Page and Flow

Login Page - POM should have logic to interact with the respective page/component.

  • Methods to perform actions on the page like typing a value in a text field or clicking the button

  • Methods to return value for assertions in tests like getting a text from the label

LoginPage.java

public class LoginPage {
    private final WebElementHelper webElementHelper;
    private final Properties envProperties;
    private final WebDriver driver;
    public LoginPage(WebDriver driver, Properties envProperties){
        this.driver = driver;
        this.envProperties = envProperties;
        String pageObjectRepositoryName = "LoginPage.json";
        webElementHelper = new WebElementHelper(driver,envProperties, pageObjectRepositoryName);
    }
    public void typeUserName(String userName){
        webElementHelper.findElement("userName").sendKeys(userName);
    }
    public void typePassword(String password){
        webElementHelper.findElement("password").sendKeys(password);
    }
    public DashboardPage submitLogin(){
        webElementHelper.findElement("login").click();
        return new DashboardPage(driver,envProperties);
    }
    public LoginPage submitLoginWhenLoginFailed(){
        webElementHelper.findElement("login").click();
        return new LoginPage(driver,envProperties);
    }
    public String getInvalidCredentialsText(){
        return webElementHelper.findElement("invalidCredential").getText();
    }
    public String getUserNameRequiredText(){
        return webElementHelper.findElement("userNameFieldRequired").getText();
    }
    public String getPasswordRequiredText(){
        return webElementHelper.findElement("passwordFieldRequired").getText();
    }

}

Here userName, password and login is referred from attribute defined in LoginPage.json i.e. object repository for Login Page.

DashboardPage.java

public class DashboardPage {
    private final WebElementHelper webElementHelper;
    public DashboardPage(WebDriver driver, Properties envProperties){
        String pageObjectRepositoryName = "DashboardPage.json";
        webElementHelper = new WebElementHelper(driver,envProperties, pageObjectRepositoryName);
    }
    public String getDashboardHeader(){
        return webElementHelper.findElement("dashboardTitle").getText();
    }
}

Below is LoginFlow class

  • Create a WebDriver instance and launch URL

  • to interact with LoginPage and DashboardPage

LoginFlow.java

public class LoginFlow {
    private final BrowserActions browserActions;
    private LoginPage loginPage;
    private DashboardPage dashboardPage;
    public LoginFlow(Properties properties){
        WebDriver driver = DriverFactory.newDriver(properties.getProperty("browserName"));
        browserActions = new BrowserActions(driver);
        browserActions.launchUrl(properties.getProperty("appUrl"));
        loginPage = new LoginPage(driver, properties);
    }
    public BrowserActions getBrowserActions(){
        return browserActions;
    }
    public void performValidLogin(String userName, String password){
        loginPage.typeUserName(userName);
        loginPage.typePassword(password);
        dashboardPage = loginPage.submitLogin();
    }
    public void performInValidLogin(String userName, String password){
        loginPage.typeUserName(userName);
        loginPage.typePassword(password);
        this.loginPage = loginPage.submitLoginWhenLoginFailed();
    }
    public String getDashboardHeader(){
        return dashboardPage.getDashboardHeader();
    }
    public String getInvalidCredentialText(){
        return loginPage.getInvalidCredentialsText();
    }
    public String getUserNameFieldRequiredText(){
        return loginPage.getUserNameRequiredText();
    }
    public String getPasswordFieldRequiredText(){
        return loginPage.getPasswordRequiredText();
    }
}

Writing test for Login

I have defined a properties file which has environment details for the application

env_orange_hrm.properties

browserName=chrome
appUrl=https://opensource-demo.orangehrmlive.com/web/index.php
objectRepositoryBasePath=src/main/resources/OR/
appObjectRepositoryDirName=orangeHrm/

LoginTests.java

public class LoginTests {
    Properties properties;
    String envFilePath = "src/test/resources/env_orange_hrm.properties";
    LoginFlow loginFlow;
    @BeforeClass
    public void getEnv(){
        properties = ConfigReader.getProperties(envFilePath);
    }
    @BeforeMethod
    public void setup(){
        loginFlow = new LoginFlow(properties);
    }
    @AfterMethod
    public void tearDown(){
        loginFlow.getBrowserActions().quitBrowser();
    }
    @Test
    public void successfulLoginTest() {
        String expectedDashboardTitle = "Dashboard";
        loginFlow.performValidLogin("Admin","admin123");
        String actualDashboardTitle = loginFlow.getDashboardHeader();
        Assert.assertEquals(actualDashboardTitle,expectedDashboardTitle);
    }
    @Test
    public void failedLoginWithInvalidCredentialsTest(){
        String expectedInvalidCredentialText = "Invalid credentials";
        loginFlow.performInValidLogin("Admin","admin");
        String actualInvalidCredentialText = loginFlow.getInvalidCredentialText();
        Assert.assertEquals(actualInvalidCredentialText,expectedInvalidCredentialText);
    }
    @Test
    public void failedLoginWithBlankFieldsTest(){
        String expectedRequiredText = "Required";
        loginFlow.performInValidLogin("","");
        String actualUserNameFieldRequiredText = loginFlow.getUserNameFieldRequiredText();
        Assert.assertEquals(actualUserNameFieldRequiredText,expectedRequiredText);
        String actualPasswordFieldRequiredText = loginFlow.getPasswordFieldRequiredText();
        Assert.assertEquals(actualPasswordFieldRequiredText,expectedRequiredText);
    }
}

GitHub repo - https://github.com/sksingh329/SeleniumPractice

Did you find this article valuable?

Support SUBODH SINGH by becoming a sponsor. Any amount is appreciated!