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

Advanced Page Object Design Pattern

4.88/5 (10 votes)
9 Oct 2015Ms-PL4 min read 20.5K  
Explains in details how to create an OOP design of base classes in C# which can improve page object pattern oriented web automation testing framework.

Introduction

In my last post “Page Object Pattern“, from the series “Design Patterns in Automation Testing“, I gave you examples of how to use one of the most popular patterns in the Web Automation the Page Object Pattern. In this article, I am going to further improve the central idea of the pattern.

If you read the code from my last post, you will most probably notice that it brakes one of the most important programming principles- DRY (Don’t-Repeat-Yourself). The primary goal of the improvements of the pattern is going to be to create an OOP design of classes to limit the repetition of code blocks.

Image 1

Base WebDriver Class

The primary goal of the class below is to provide a direct way to initialize and access the web driver instance.

C#
public static class Driver
{
    private static WebDriverWait browserWait;

    private static IWebDriver browser;

    public static IWebDriver Browser
    {
        get
        {
            if (browser == null)
            {
                throw new NullReferenceException("The WebDriver browser instance was not initialized. 
                        You should first call the method Start.");
            }
            return browser;
        }
        private set
        {
            browser = value;
        }
    }

    public static WebDriverWait BrowserWait
    {
        get
        {
            if (browserWait == null || browser == null)
            {
                throw new NullReferenceException("The WebDriver browser wait instance 
                     was not initialized. You should first call the method Start.");
            }
            return browserWait;
        }
        private set
        {
            browserWait = value;
        }
    }

    public static void StartBrowser(BrowserTypes browserType = BrowserTypes.Firefox, 
                                    int defaultTimeOut = 30)
    {
        switch (browserType)
        {
            case BrowserTypes.Firefox:
                Driver.Browser = new FirefoxDriver();
                    
                break;
            case BrowserTypes.InternetExplorer:
                break;
            case BrowserTypes.Chrome:
                break;
            default:
                break;
        }
        BrowserWait = new WebDriverWait(Driver.Browser, TimeSpan.FromSeconds(defaultTimeOut));
    }

    public static void StopBrowser()
    {
        Browser.Quit();
        Browser = null;
        BrowserWait = null;
    }
}

The driver is initialized through the StartBrowser method where the client is capable of setting a particular browser type and timeout. The stop of the browser that is usually performed on TestCleanup is also an easy task via the static StopBrowser method. If the client tries to access the instance before the initialization, an exception is thrown.

Advanced Page Object Pattern OOP Design

The first class that needs improvement is the element map.

First Version

C#
public class BingMainPageElementMap
{
    private readonly IWebDriver browser;

    public BingMainPageElementMap(IWebDriver browser)
    {
        this.browser = browser;
    }

    public IWebElement SearchBox 
    {
        get
        {
            return this.browser.FindElement(By.Id("sb_form_q"));
        }
    }
}

The main problem here is that every client of the class should pass to its constructor the current Web Driver instance. We can make it better with the help of the previously created static class Driver. We can create a base element map that all other element maps are going to derive.

C#
public class BasePageElementMap
{
    protected IWebDriver browser;

    public BasePageElementMap()
    {
        this.browser = Driver.Browser;
    }
}

Improved Version

C#
public class BingMainPageElementMap : BasePageElementMap
{
    public IWebElement SearchBox 
    {
        get
        {
            return this.browser.FindElement(By.Id("sb_form_q"));
        }
    }
}

As you can see in the new version, the class doesn’t have a constructor. So this code block is not going to be repeated in all other map classes.

The next step in the improving process is to create a base class for the validator classes.

First Version

C#
public class BingMainPageValidator
{
    private readonly IWebDriver browser;

    public BingMainPageValidator(IWebDriver browser)
    {
        this.browser = browser;
    }

    protected BingMainPageElementMap Map
    {
        get
        {
            return new BingMainPageElementMap(this.browser);
        }
    }

    public void ResultsCount(string expectedCount)
    {
        Assert.IsTrue(this.Map.ResultsCountDiv.Text.Contains(expectedCount), 
                      "The results DIV doesn't contains the specified text.");
    }
}

In the first version of the class, the DRY design principle is again not followed. The Map property and the constructor need to be placed in every validator class.

Improved Version

C#
public class BasePageValidator<M>
    where M : BasePageElementMap, new()
{
    protected M Map
    {
        get
        {
            return new M();
        }
    }
}

When derived, this generic class is going to provide direct access to the element map.

C#
public class BingMainPageValidator : BasePageValidator<BingMainPageElementMap>
{
    public void ResultsCount(string expectedCount)
    {
        Assert.IsTrue(this.Map.ResultsCountDiv.Text.Contains(expectedCount), 
                      "The results DIV doesn't contains the specified text.");
    }
}

After the refactoring, we end up with more readable and cleaner solution.

By the way, during my research for the "Design Patterns in Automation Testing" series, I always first read about the presented pattern in several books. One of them that you might want to check is "Head First Design Patterns" by Eric Freeman. The author uses a very unique methodology for presenting the material that I haven't found anywhere else. Probably, most of you will like it. For the more hardcore fans that might find the book too easy, I recommend the bible of the design patterns- "Design Patterns- Elements of Reusable Object-Oriented Software". It will change your way of thinking about object-oriented design.

The final step in the process of making a better page object pattern OOP design is to create a base class for all page classes.

First Version

C#
public class BingMainPage
{
    private readonly IWebDriver browser;
    private readonly string url = @"http://www.bing.com/";

    public BingMainPage(IWebDriver browser)
    {
        this.browser = browser;
    }

    protected BingMainPageElementMap Map
    {
        get
        {
            return new BingMainPageElementMap(this.browser);
        }
    }

    public BingMainPageValidator Validate()
    {
        return new BingMainPageValidator(this.browser);
    }

    public void Navigate()
    {
        this.browser.Navigate().GoToUrl(this.url);
    }

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

There are a couple of items in the above class that is going to be repeated for every page class- the constructor, the Navigate method, the Validate method, and the Map property. As you can imagine, this is a lot of boilerplate code. To fix this problem, we can create the following two base classes:

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

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

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

    public void Navigate()
    {
        Driver.Browser.Navigate().GoToUrl(this.url);
    }
}

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

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

The first one can be derived if you want to have a page without validator. The second one extends the first and adds to it the Validate method. Via the generic parameters and their constraints, we can use these classes for all of our pages.

Improved Version

C#
public class BingMainPage
{
    private readonly IWebDriver browser;
    private readonly string url = @"http://www.bing.com/";

    public BingMainPage(IWebDriver browser)
    {
        this.browser = browser;
    }

    protected BingMainPageElementMap Map
    {
        get
        {
            return new BingMainPageElementMap(this.browser);
        }
    }

    public BingMainPageValidator Validate()
    {
        return new BingMainPageValidator(this.browser);
    }

    public void Navigate()
    {
        this.browser.Navigate().GoToUrl(this.url);
    }

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

Now the BingMainPage class consists only of a single constructor and the Search method, all of the boilerplate code is moved to the base class.

Advanced Page Object Pattern- Usage in Tests

C#
[TestClass]
public class AdvancedBingTests
{       

    [TestInitialize]
    public void SetupTest()
    {
        Driver.StartBrowser();
    }

    [TestCleanup]
    public void TeardownTest()
    {
        Driver.StopBrowser();
    }

    [TestMethod]
    public void SearchTextInBing_Advanced_PageObjectPattern()
    {
        BingMainPage bingMainPage = new BingMainPage();
        bingMainPage.Navigate();
        bingMainPage.Search("Automate The Planet");
        bingMainPage.Validate().ResultsCount(",000 RESULTS");
    }
}

If you compare this sample usage with the one presented in the previous solution, you will notice that they are completely identical.

There is also another benefit that comes from the usage of the BasePage object- you cannot use the Map property directly in your tests. In my opinion, the direct usage of element maps in the tests is not a good practice because it breaks the DRY principle.

License

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