Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / DevOps / testing

Page Objects that Make Code More Maintainable

5.00/5 (2 votes)
27 Jan 2018Ms-PL7 min read 18.3K  
Through the help of interfaces, extension methods and partial classes, 3 page objects variations are presented that lead to more maintainable code.

Introduction

If you have read some of my previous posts, most probably you have checked some of my articles about Design Patterns in Automation Testing. One of the most prominent patterns that I have blogged about is the Page Object Pattern. It is so fundamental to the creation of a fast, robust and readable UI automation that I even dedicated to it a second article. In my team, we have applied it for a couple of years in different projects. During the process, we came up with a few different variations of the page objects. I believe that some of them lead to more readable and more maintainable code compared to the previously presented ones.

Image 1

Page Objects v.2.0 – Interfaces

I like this pattern so much that I even created a fluent API for page objects. However, I think that its usage is a little bit troublesome, at least for me. It turned out that I am not a big fan of the Fluent Interface. If you have read my article on the subject, you may have found out that I suggested using the pages as singletons. Nonetheless, I think that there are more “cleaner” ways for doing this. The base singleton’s class and all other base generic pages make the code more unreadable and not so easy to grasp. We resolved this particular problem through the application of an IoC Container. Unfortunately, the page objects’ inheritance tree was still relatively complicated because of the two generic parameters.

IoC Container – Previous Version

C#
public class BasePage<M>
    where M : BasePageElementMap, new()
{
    protected readonly string url;

    public BasePage(string url)
    {
        this.url = url;
    }

    public BasePage()
    {
        this.url = null;
    }

    protected M Map
    {
        get
        {
            return new M();
        }
    }

    public virtual void Navigate(string part = "")
    {
        Driver.Browser.Navigate().GoToUrl(string.Concat(url, part));
    }
}

public class BasePage<M, V> : BasePage<M>
    where M : BasePageElementMap, new()
    where V : BasePageValidator<M>, new()
{
    public BasePage(string url) : base(url)
    {
    }

    public BasePage()
    {
    }

    public V Validate()
    {
        return new V();
    }
}

After a couple of discussions about the assertion methods, should they be part of the page objects, I started thinking about how can I improve them. On the one hand, I wanted the assertions to be reused, on the other hand, they somehow shouldn’t have been directly part of the page objects. I thought that because they were the reason for the existence of the second base class with the additional generic parameter. Also, there was a new requirement- the pages to be interchangeable. In summary, if you have a Page A, later it is deprecated and its substitute is Page A1, it should be easy to exchange both implementations without breaking all the surrounding code.

Interfaces- Improved Version

C#
public class BingMainPage : BasePage<BingMainPageMap>, IBingMainPage
{
    public BingMainPage(IWebDriver driver)
        : base(driver, new BingMainPageMap(driver))
    {
    }

    public override string Url
    {
        get
        {
            return @"http://www.bing.com/";
        }
    }

    public void Search(string textToType)
    {
        this.Map.SearchBox.Clear();
        this.Map.SearchBox.SendKeys(textToType);
        this.Map.GoButton.Click();
    }
}

To solve the lastly mentioned problem, we introduced a new participant of the Page Object Pattern- page’s interface. It defines what actions the page should be able to do.

C#
public interface IBingMainPage : IPage
{
    void Search(string textToType);
}

Now all the code that depends on the page can use the page as an interface. That means if you have to replace it, the new version only needs to implement the same interface.

The second improvement that we made was related to the previously called Validators classes. In general, they were holding assertion methods, so the first thing that we decided to do was to rename them to end with suffix Asserter. We did that because in the production code the validators are meant for a different job, like validating user’s input, not asserting things.

After that, another big refactoring that we applied was that the Asserter’s methods were used as extension methods of the page’s interface. As a result, they can be used as ordinary methods provided by the concrete page.

C#
public static class BingMainPageAsserter
{
    public static void AssertResultsCountIsAsExpected(this IBingMainPage page, int expectedCount)
    {
        Assert.AreEqual(page.GetResultsCount(), expectedCount, "The results count is not as expected.");
    }
}

The only drawback of the presented implementation is that you always need to create a wrapper method of the asserted element because you don’t have a direct access to the page’s element map through its interface. However, these ideas resulted in eliminating the second base page and the additional generic parameter.

C#
public abstract class BasePage<TMap>
    where TMap : BaseElementMap
{
    private readonly TMap map;
    protected IWebDriver driver;

    public BasePage( IWebDriver driver, TMap map)
    {
        this.driver = driver;
        this.map = map;
    }

    internal TMap Map 
    {
        get
        {
            return this.map;
        }
    }

    public abstract string Url { get; }

    public virtual void Open(string part = "")
    {
        this.driver.Navigate().GoToUrl(string.Concat(this.Url, part));
    }
}

As you can see from the example, the inheritance model is simplified compared to the previous versions.

The usage of the provided solution is straightforward.

C#
[TestClass]
public class BingTests 
{
    private IBingMainPage bingMainPage;
    private IWebDriver driver;

    [TestInitialize]
    public void SetupTest()
    {
        driver = new FirefoxDriver();
        bingMainPage = new BingMainPage(driver);
    }

    [TestCleanup]
    public void TeardownTest()
    {
        driver.Quit();
    }

    [TestMethod]
    public void SearchForAutomateThePlanet()
    {
        this.bingMainPage.Open();
        this.bingMainPage.Search("Automate The Planet");
        this.bingMainPage.AssertResultsCountIsAsExpected(264);
    }
}

The tests use the pages as in the previously presented versions. The only subtle detail here is that if you want to be able to use the extension assertion methods, you need to add the using statement for the namespace, their class is in.

Image 2

Page Objects v.2.1 – Public Map, Skip Interfaces

As pointed in the subtitle, the next “generation” of page objects expose their element map to the code, that uses the page. Also, we decided that it is an overhead to create an interface for every particular page. Moreover, we found out that most of the time the interfaces of the substitute pages should be different from the old ones because different changes are applied to the pages. So the first subtle adjustment that we made was in the base page class, marking the Map property as public.

C#
public abstract class BasePage<TMap>
    where TMap : BaseElementMap
{
    private readonly TMap map;
    protected IWebDriver driver;

    public BasePage(IWebDriver driver, TMap map)
    {
        this.driver = driver;
        this.map = map;
    }

    public TMap Map 
    {
        get
        {
            return this.map;
        }
    }

    public abstract string Url { get; }

    public virtual void Open(string part = "")
    {
        this.driver.Navigate().GoToUrl(string.Concat(this.Url, part));
    }
}

The second alternation was connected with the Asserter classes. Now they don’t extend the pages’ interfaces rather the pages themselves.

C#
public static class BingMainPageAsserter
{
    public static void AssertResultsCountIsAsExpected(this BingMainPage page, int expectedCount)
    {
        Assert.AreEqual(page.Map.ResultsCountDiv.Text, expectedCount, 
                        "The results count is not as expected.");
    }
}

The only difference of the usage in tests is that now you can access the Map’s elements directly in tests.

C#
[TestMethod]
public void SearchForAutomateThePlanet()
{
    this.bingMainPage.Open();
    this.bingMainPage.Map.FeelingLuckyButton.Click();
    this.driver.Navigate().Back();
    this.bingMainPage.Search("Automate The Planet");            
    this.bingMainPage.AssertResultsCountIsAsExpected(264);           
}

In my opinion, this is a good idea only if the nitty-gritty framework’s logic is hidden behind the page objects. I mean actions like clearing the text input before real typing, executing JavaScript calls and so forth.

Page Objects v.2.3 – Partial Pages

The thing that I love most in the page objects, that we designed, is that the elements, page’s logic and assertions are placed inside different files. I believe that this makes the page objects a lot more understandable and readable. Additionally, it decreases the associated maintainability costs.

To simplify the inheritance tree even further, we decided to use partial classes instead of generic base classes. With the small adjustment of making the Map property public, we thought that it will be an excellent idea of putting the maps’ elements directly in the page. However, if they were combined in the same file, the design would have had the same drawbacks as the WebDriver’s one. It would have led to larger files where elements would have been mixed with the page’s service methods. We didn’t want that. The solution was to leave elements in a separate file, but now it is a partial class of the main page’s one.

C#
public partial class BingMainPage : BasePage
{
    public IWebElement SearchBox
    {
        get
        {
            return this.driver.FindElement(By.Id("sb_form_q"));
        }
    }

    public IWebElement GoButton
    {
        get
        {
            return this.driver.FindElement(By.Id("sb_form_go"));
        }
    }

    public IWebElement ResultsCountDiv
    {
        get
        {
            return this.driver.FindElement(By.Id("b_tween"));
        }
    }
}

This is how the new page map looks like. It uses the driver instance defined in the main page class.

C#
public partial class BingMainPage : BasePage
{
    public BingMainPage(IWebDriver driver) : base(driver)
    {
    }

    public override string Url
    {
        get
        {
            return @"http://www.bing.com/";
        }
    }

    public void Search(string textToType)
    {
        this.SearchBox.Clear();
        this.SearchBox.SendKeys(textToType);
        this.GoButton.Click();
    }
}

There are some small changes in the main page class too. Now it inherits a simpler version of the BasePage which doesn’t require a generic element map parameter.

The usage of the new page objects is identical to the previously presented with the only difference that the page’s elements can be accessed directly from the page’s instance.

C#
[TestMethod]
public void SearchForAutomateThePlanet_Second()
{
    this.bingMainPage.Open();
    this.bingMainPage.SearchBox.Clear();
    this.bingMainPage.SearchBox.SendKeys("Automate The Planet");
    this.bingMainPage.GoButton.Click();
    this.bingMainPage.AssertResultsCountIsAsExpected(264);
}

Image 3

Pros

One of the biggest advantages of the proposed ideas is the single responsibility of the page’s classes.

In object-oriented programming, the single responsibility principle states that every class should have responsibility over a single part of the functionality, and that responsibility should be entirely encapsulated by the class.

In the presented implementation, the map class is responsible only for locating elements, the asserter class for asserting things and the page itself for providing service methods.

So Far in the "Design Patterns in Automated Testing" Series

  1. Page Object Pattern
  2. Advanced Page Object Pattern
  3. Facade Design Pattern
  4. Singleton Design Pattern
  5. Fluent Page Object Pattern
  6. IoC Container and Page Objects
  7. Strategy Design Pattern
  8. Advanced Strategy Design Pattern
  9. Observer Design Pattern
  10. Observer Design Pattern via Events and Delegates
  11. Observer Design Pattern via IObservable and IObserver
  12. Decorator Design Pattern- Mixing Strategies
  13. Page Objects That Make Code More Maintainable
  14. Improved Facade Design Pattern in Automation Testing v.2.0
  15. Rules Design Pattern
  16. Specification Design Pattern
  17. Advanced Specification Design Pattern

If you enjoy my publications, feel free to SUBSCRIBE.
Also, hit these share buttons. Thank you!

Source Code

References

All images are purchased from DepositPhotos.com and cannot be downloaded and used for free.
License Agreement

The post Page Objects That Make Code More Maintainable appeared first on Automate The Planet.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)