Advanced Page Object Pattern in Automated Testing

Advanced Page Object Pattern in Automated Testing

While ago we were working on the first version of the BELLATRIX test automation framework, I did this research so that we can find the most convenient way for creating page objects.

Base WebDriver Class

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

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 to set 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

public class SearchEngineMainPageElementMap
{
    private readonly IWebDriver browser;

    public SearchEngineMainPageElementMap(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.

public class BasePageElementMap
{
    protected IWebDriver browser;

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

Improved Version

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

First Version

public class SearchEngineMainPageValidator
{
    private readonly IWebDriver browser;

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

    protected SearchEngineMainPageElementMap Map
    {
        get
        {
            return new SearchEngineMainPageElementMap(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

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.

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

First Version

public class SearchEngineMainPage
{
    private readonly IWebDriver browser;
    private readonly string url = @"searchEngineUrl";

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

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

    public SearchEngineMainPageValidator Validate()
    {
        return new SearchEngineMainPageValidator(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. In order to fix this problem, we can create the following two base classes.

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

public class SearchEngineMainPage :
    BasePage<SearchEngineMainPageElementMap, SearchEngineMainPageValidator>
{
    public SearchEngineMainPage()
        : base(@"searchEngineUrl")
    {
    }

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

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

For more detailed overview and usage of many more design patterns and best practices in automated testing, check my book “Design Patterns for High-Quality Automated Tests, C# Edition, High-Quality Tests Attributes, and Best Practices”.  You can read part of three of the chapters:

Defining High-Quality Test Attributes for Automated Tests

Benchmarking for Assessing Automated Test Components Performance

Generic Repository Design Pattern- Test Data Preparation

Advanced Page Object Pattern- Usage in Tests


public class AdvancedSearchEngineTests
{

    
    public void SetupTest()
    {
        Driver.StartBrowser();
    }

    
    public void TeardownTest()
    {
        Driver.StopBrowser();
    }

    
    public void SearchTextInSearchEngine_Advanced_PageObjectPattern()
    {
        SearchEngineMainPage searchEngineMainPage = new SearchEngineMainPage();
        searchEngineMainPage.Navigate();
        searchEngineMainPage.Search("Automate The Planet");
        searchEngineMainPage.Validate().ResultsCount(",000 RESULTS");
    }
}

Advanced Page Object Pattern No Access to Element Map

Related Articles

Design Patterns

Simple Factory Design Pattern- WebDriver Anonymous Browsing with Reverse Proxy

In the series “Design Patterns in Automated Testing“, you can read about the most useful techniques for structuring the automation tests' code. The article was

Simple Factory Design Pattern- WebDriver Anonymous Browsing with Reverse Proxy

Design Patterns

Fluent Page Object Pattern in Automated Testing

In my previous articles from the series "Design Patterns in Automated Testing", I explained in details how to improve your test automation framework through the

Fluent Page Object Pattern in Automated Testing

Design Patterns, Web Automation Java

Mastering Parameterized Tests in JUnit with Selenium WebDriver

In the evolving landscape of software testing, efficiency and coverage are paramount. JUnit 5 introduces enhanced parameterized testing capabilities, allowing d

Mastering Parameterized Tests in JUnit with Selenium WebDriver

Design Patterns

Page Objects- Partial Classes Singleton Design Pattern- WebDriver C#

Editorial Note: I originally wrote this post for the Test Huddle Blog. You can check out the original here, at their site.

Page Objects- Partial Classes Singleton Design Pattern- WebDriver C#

Design Patterns

Enhanced Selenium WebDriver Page Objects through Partial Classes

Editorial Note: I originally wrote this post for the Test Huddle Blog. You can check out the original text at their site.

Enhanced Selenium WebDriver Page Objects through Partial Classes

Design Patterns

Advanced Behaviours Design Pattern in Automated Testing Part 2

My last two articles were dedicated to the Behaviours Design Pattern. It is a pattern that eases the creation of tests through a build process similar to LEGO.

Advanced Behaviours Design Pattern in Automated Testing Part 2
Anton Angelov

About the author

Anton Angelov is Managing Director, Co-Founder, and Chief Test Automation Architect at Automate The Planet — a boutique consulting firm specializing in AI-augmented test automation strategy, implementation, and enablement. He is the creator of BELLATRIX, a cross-platform framework for web, mobile, desktop, and API testing, and the author of 8 bestselling books on test automation. A speaker at 60+ international conferences and researcher in AI-driven testing and LLM-based automation, he has been recognized as QA of the Decade and Webit Changemaker 2025.