Template Method Design Pattern in Automated Testing

Template Method Design Pattern in Automated Testing

In my series of articles “Design Patterns in Automated Testing“, I am presenting you the most useful techniques for structuring the code of the automation tests. In previous articles, we discussed how we could use the Facade design pattern to reuse common test workflows like creating purchases or submitting support tickets. Also, in a subsequent article, we looked into how you can modify a bit the pattern so that you can use the facades to test the same workflow but for different versions of the website. In the modification the facades used the page objects as interfaces. In this publication, I will show you how you can achieve similar results combining the facades with another design pattern called Template Method.

Test’s Test Case

Ebay Item Page

Click Continue as a guest

5. Fill Shipping Info

Fill Shipping Info

Checkout Page Ebay Validate Total Price

The primary goal of the design of the test classes is going to be to enable us to reuse the code for all different test cases. For example, purchasing different items with а different combination of shipping data, thus different total and subtotal prices.

To make things even more interesting, imagine that the company is working on a newer improved version of the same workflow and website. We will need to support both for a while. Since the test workflow will be the same we will need to have similar tests for the new website, but the page objects will be slightly different, different elements and same actions but how the users do them may be a bit changed.

Solution 1- Facade Design Pattern with Page Objects Interfaces

As I mentioned at the beginning of the article, I already showed you a solution to the defined problem in the publication- Improved Facade Design Pattern in Automated Testing v.2.0. Here I will give you a short overview so that we can see the differences between the approaches. In the facade, we define the test workflow that we want to reuse, in this case, the purchase item process. In the public method of the facade, we use the methods of the dependent page objects. We initialize them in the constructor of the facade, and we use them through composition, not inheritance. To be able to test the different versions of the website the pages are passed as interfaces instead as concrete implementations. Let’s look into the facade’s code.

public class ShoppingCart
{
    private readonly IItemPage _itemPage;
    private readonly ISignInPage _signInPage;
    private readonly ICheckoutPage _checkoutPage;
    private readonly IShippingAddressPage _shippingAddressPage;
    public ShoppingCart(IItemPage itemPage,
    ISignInPage signInPage,
    ICheckoutPage checkoutPage,
    IShippingAddressPage shippingAddressPage)
    {
        _itemPage = itemPage;
        _signInPage = signInPage;
        _checkoutPage = checkoutPage;
        _shippingAddressPage = shippingAddressPage;
    }
    public void PurchaseItem(string item, double itemPrice, ClientInfo clientInfo)
    {
        _itemPage.Open(item);
        _itemPage.AssertPrice(itemPrice);
        _itemPage.ClickBuyNowButton();
        _signInPage.ClickContinueAsGuestButton();
        _shippingAddressPage.FillShippingInfo(clientInfo);
        _shippingAddressPage.AssertSubtotalAmount(itemPrice);
        _shippingAddressPage.ClickContinueButton();
        _checkoutPage.AssertSubtotal(itemPrice);
    }
}

After that, we intialized the pages and the facade through an abstract factory.

public class ShoppingCartFactory : IFactory<ShoppingCart>
{
    private readonly IWebDriver _driver;
    public ShoppingCartFactory(IWebDriver driver)
    {
        _driver = driver;
    }
    public ShoppingCart Create()
    {
        var itemPage = new ItemPage(_driver);
        var signInPage = new SignInPage(_driver);
        var checkoutPage = new CheckoutPage(_driver);
        var shippingAddressPage = new ShippingAddressPage(_driver);
        var purchaseFacade = new ShoppingCart(itemPage, signInPage, checkoutPage, shippingAddressPage);
        return purchaseFacade;
    }
}

This is how we use both in tests.


public class ShoppingCartTests
{
    private IFactory<ShoppingCart> _shoppingCartFactory;
    private ShoppingCart _shoppingCart;
    private IWebDriver _driver;
    
    public void SetupTest()
    {
        _driver = new FirefoxDriver();
        _shoppingCartFactory = new ShoppingCartFactory(_driver);
    }
    
    public void TeardownTest()
    {
        _driver.Quit();
    }
    
    public void Purchase_Book_Discounts()
    {
        _shoppingCart = _shoppingCartFactory.Create();
        _shoppingCart.PurchaseItem("The Hitchhiker's Guide to the Galaxy", 22.2, new ClientInfo());
    }
}

First, we create the factory and then we use it to get the instance of the facade. The main part of the tests is the call of the public facade method.

Solution 2- Facade with Template Method Design Pattern

Template Method Design Pattern

Definition

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

UML Class Diagram

classDiagram
    ShoppingCart <|-- OldShoppingCart
    class ShoppingCart {
        +PurchaseItem(string item, double itemPrice, ClientInfo clientInfo)
        #OpenItem(string item)*
        #AssertPrice(double itemPrice)*
        #ClickBuyNowButton()*
        #ClickContinueAsGuestButton()*
        #FillShippingInfo(ClientInfo clientInfo)*
        #AssertSubtotalAmount(double itemPrice)*
        #ClickContinueButton()*
        #AssertSubtotal(double itemPrice)*
    }
    class OldShoppingCart {
        -ItemPage _itemPage
        -SignInPage _signInPage
        -CheckoutPage _checkoutPage
        -ShippingAddressPage _shippingAddressPage
        #OpenItem(string item)
        #AssertPrice(double itemPrice)
        #ClickBuyNowButton()
        #FillShippingInfo(ClientInfo clientInfo)
        #AssertSubtotal(double itemPrice)
    }

Participants

The classes and objects participating in this pattern are:

  • Abstract Class

    Defines abstract operations that concrete subclasses define to implement steps of an algorithm. Implements a template method defining the skeleton of an algorithm. The template method calls primitive operations as well as operations defined in AbstractClass or those of other objects.

  • Concrete Class

    Implements the primitive operations ot carry out subclass-specific steps of the algorithm.

Template Method Design Pattern Implementation

Now we will use the presented design pattern to solve the given problem. Instead of using page objects interfaces we will define the test workflow in the facade as abstract template methods. Later, we will have sub-facades with the concrete implementations. This way we won’t need page objects interfaces.

public abstract class ShoppingCart
{
    public void PurchaseItem(string item, double itemPrice, ClientInfo clientInfo)
    {
        OpenItem(item);
        AssertPrice(itemPrice);
        ClickBuyNowButton();
        ClickContinueAsGuestButton();
        FillShippingInfo(clientInfo);
        AssertSubtotalAmount(itemPrice);
        ClickContinueButton();
        AssertSubtotal(itemPrice);
    }
    protected abstract void OpenItem(string item);
    protected abstract void AssertPrice(double itemPrice);
    protected abstract void ClickBuyNowButton();
    protected abstract void ClickContinueAsGuestButton();
    protected abstract void FillShippingInfo(ClientInfo clientInfo);
    protected abstract void AssertSubtotalAmount(double itemPrice);
    protected abstract void ClickContinueButton();
    protected abstract void AssertSubtotal(double itemPrice);
}

In our base facade, we define the workflow as a series of protected abstract methods which needs to be implemented in the concrete facades. Another important note is that the base class don’t know anything about any page objects.

We can have one concrete implementation of the base class for the new version of our website and another for the old one.

public class OldShoppingCart : ShoppingCart
{
    private readonly ItemPage _itemPage;
    private readonly SignInPage _signInPage;
    private readonly CheckoutPage _checkoutPage;
    private readonly ShippingAddressPage _shippingAddressPage;
    public OldShoppingCart(ItemPage itemPage, SignInPage signInPage, CheckoutPage checkoutPage, ShippingAddressPage shippingAddressPage)
    {
        _itemPage = itemPage;
        _signInPage = signInPage;
        _checkoutPage = checkoutPage;
        _shippingAddressPage = shippingAddressPage;
    }
    protected override void AssertPrice(double itemPrice) => _itemPage.AssertPrice(itemPrice);
    protected override void AssertSubtotal(double itemPrice) => _checkoutPage.AssertSubtotal(itemPrice);
    protected override void AssertSubtotalAmount(double itemPrice) => _shippingAddressPage.AssertSubtotalAmount(itemPrice);
    protected override void ClickBuyNowButton() => _itemPage.ClickBuyNowButton();
    protected override void ClickContinueAsGuestButton() => _signInPage.ClickContinueAsGuestButton();
    protected override void ClickContinueButton() => _shippingAddressPage.ClickContinueButton();
    protected override void FillShippingInfo(ClientInfo clientInfo) => _shippingAddressPage.FillShippingInfo(clientInfo);
    protected override void OpenItem(string item) => _itemPage.Open(item);
}

Two important things to notice. First, now we don’t use the pages as interfaces. Instead, the concrete implementations are used. Second, there isn’t a public method since it comes from the base class. Instead, we implement the protected abstract methods where we call the page objects.

The facade initialization stays almost the same. Now we have two separate methods for the different concrete facades.

public class ShoppingCartFactory
{
    private readonly IWebDriver _driver;
    public ShoppingCartFactory(IWebDriver driver) => _driver = driver;
    public ShoppingCart CreateOldShoppingCart()
    {
        var itemPage = new ItemPage(_driver);
        var signInPage = new SignInPage(_driver);
        var checkoutPage = new CheckoutPage(_driver);
        var shippingAddressPage = new ShippingAddressPage(_driver);
        var oldShoppingCart = new OldShoppingCart(itemPage, signInPage, checkoutPage, shippingAddressPage);
        return oldShoppingCart;
    }
    public ShoppingCart CreateNewShoppingCart()
    {
        var itemPage = new ItemPage(_driver);
        var signInPage = new SignInPage(_driver);
        var checkoutPage = new CheckoutPage(_driver);
        var shippingAddressPage = new ShippingAddressPage(_driver);
        var oldShoppingCart = new NewShoppingCart(itemPage, signInPage, checkoutPage, shippingAddressPage);
        return oldShoppingCart;
    }
}

The usage in tests stays the same.

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

Related Articles

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

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

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

Custom Test Automation Framework: Modularity Planning and Design

In the new Build Custom Automation Framework Series, we will look into detailed explanations on creating custom test automation frameworks. Many people starting

Custom Test Automation Framework: Modularity Planning and Design

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- Elements Access Styles- 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- Elements Access Styles- WebDriver C#
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.