Page Object Model- Make It Simple, Use Abstraction

After covering the fundamentals for a creating a test automation infrastructure, in this post I’ll elaborate more about the idea behind page abstraction while using page object model in test automation.

First, let me explain the expression”Abstraction”, what does it mean? When do we use it?

Abstraction is a technique used in software design that aims to simplify logical entities code representation and allows the software developer to “hide” complex implementations. The goal is to create an abstract (simple) layers which will only expose the most relevant properties and capabilities for the specific entity.

The Abstraction technique comes handy also when writing a test automation project; We can use it to hide complexity and establish desired actions on top of the page we are working on. In a matter of fact, we can define the project as a multi layer structure, this structure will help to implement the logic of functional testing with minimum effort and exposure to the complexity of the infrastructure.

Page Object Model Multi-Layer Structure:

1. First layer– consists of the driver layer which could be either Appium or Selenium and the implementation of methods and logic we don’t change often (such as cross browser support, reporting module, data driven implementation).

2. Second layer-  can be a library code that is built on top of the driver layer (Appium or Selenium), good examples for such are: Robot framework and Selenide. These layers are used to hide any complex actions as waits, page elements validations and more.

3. Third layer- the third layer could be used by the test automation developer for any complexes action that can come on top of the first or second layer. For example, after utilizing the wait function and validation in the second layer, we’ll be able to add several other actions such as calls to externals libraries or any other specific logic.

4. Fourth layer- we’ll be able to easily call actions we established in the third layer creating functional test cases and will eventually become our testing layer.

Next, I would like to demonstrate a basic example of page abstraction using page object model in C#:

Page abstraction using page object model in C#

First, we’ll create a class named Page and connect a builder to it which will receive the driver and the time frame which will be the upper limit for all ‘waits’ in our test implementation.

public class Page
{
    private IWebDriver driver;
    private TimeSpan defaultTimeSpan;
 
    public Page(IWebDriver driver, TimeSpan defaultTimeSpan)
    {
        this.driver = driver;
        this.defaultTimeSpan = defaultTimeSpan;
    }
}

Now, we will implement the locators which are the methods for identification, and the same object that Selenium uses to identify elements. Overall, we’ll have 8 different types of locators (read more about determining element locators).

By.Id
By.Name
By.ClassName
By.TagName
By.LinkText
By.PartialLinkText
By.CssSelector
By.XPath

‘By’ is the object also called the locator and the implementation will look as follows:

private By ByLocator(string element, Locator locator)
{
     switch (locator)
     {
          case Locator.Id:
              return By.Id(element);
           case Locator.Name:
               return By.Name(element);
           case Locator.TagName:
               return By.TagName(element);
           case Locator.ClassName:
                return By.ClassName(element);
            case Locator.LinkText:
                return By.LinkText(element);
            case Locator.PartialLinkText:
                return By.TagName(element);
            case Locator.XPath:
                return By.XPath(element);
            case Locator.CssSelector:
                return By.CssSelector(element);
            default:
                throw new Exception("No Locator Found");
       }

Next we’ll implement the ‘Find’ method which will be the base for several locations later on (using the ByLocator method we created earlier):

public IWebElement Find(string element, Locator locator)
{
     return driver.FindElement(ByLocator(element, locator));
}

We will continue implementing the rest of the methods (while utilizing the Find method)

public Page Fill(string element, string value, Locator locator)
{
      Find(element, locator).Clear();
      Find(element, locator).SendKeys(value);
      return this;
}
public Page WaitUntil(Func<IWebDriver, TResult> condiction)
{
      new WebDriverWait(driver, this.defaultTimeSpan).Until(condiction);
      return this;
}
public Page NavigateTo(string url, string proofElement, Locator locator)
{
driver.Navigate().GoToUrl(url);
new WebDriverWait(driver, this.defaultTimeSpan)
.Until(d => d.FindElement(ByLocator(proofElement, locator)));
 
return this;
}

In the top layer, we will initialize the methods same as ‘Fill’, while sending the required arguments (these arguments come from the same tester as a part of the automated test). Nonetheless, the test automation engineer which creates the test case scenario, is unfamiliar with the complexity of the Fill method which itself uses other methods.

Please note: the example presented above, does not represent a classic case of an Abstract Class in which we are declaring methods and making sure to implement in inherited classes, but another method to use abstraction over pages.