Facade Design Pattern in Automated Testing

Facade Design Pattern in Automated Testing

Definition

An object that provides a simplified interface to a larger body of code, such as class library. Make a software library easier to use, understand and more readable. Reduce dependencies of outside code. Keeps the Principle of least knowledge. Wrap a poorly designed APIs in a better one.

UML Class Diagram

Facade Design Pattern Diagram

Participants

The classes and objects participating in this pattern are:

  • Facade

    Holds methods that combine actions executed on multiple pages.

  • Page Objects (ItemPage)

    olds the actions that can be performed on the page like Search and Navigate. Exposes an easy access to the Page Validator though the Validate() method. The best implementations of the pattern hide the usage of the Element Map, wrapping it through all action methods.

  • UI Tests (EbayPurchaseTests)

    This class contains a group of tests related to the above facade; it can hold only a single instance of the facade.

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.

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

Facade Design Pattern C# Code

Solution Explorer Classes Structure

The following class structure is going to be used.

There is nothing unusual in the most of the page objects. Probably, the most interesting logic is located in the ShippingAddressPage.

public class ShippingAddressPage : BasePage<ShippingAddressPageMap, ShippingAddressPageValidator>
{
    public void ClickContinueButton()
    {
        this.Map.ContinueButton.Click();
    }

    public void FillShippingInfo(ClientInfo clientInfo)
    {
        this.Map.SwitchToShippingFrame();
        this.Map.CountryDropDown.SelectByText(clientInfo.Country);
        this.Map.FirstName.SendKeys(clientInfo.FirstName);
        this.Map.LastName.SendKeys(clientInfo.LastName);
        this.Map.Address1.SendKeys(clientInfo.Address1);
        this.Map.City.SendKeys(clientInfo.City);
        this.Map.Zip.SendKeys(clientInfo.Zip);
        this.Map.Phone.SendKeys(clientInfo.Phone);
        this.Map.Email.SendKeys(clientInfo.Email);
        this.Map.SwitchToDefault();
    }
}
public class ClientInfo
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Country { get; set; }
    public string Address1 { get; set; }
    public string City { get; set; }
    public string Phone { get; set; }
    public string Zip { get; set; }
    public string Email { get; set; }
}

Before the engine can start typing the information, the shipping frame should be switched to first. Otherwise, our test is going to fail. This logic is located in the ShippingAddressMap class.

public class ShippingAddressPageMap : BasePageElementMap
{
    public SelectElement CountryDropDown
    {
        get
        {
            this.browserWait.Until<IWebElement>((d) => { return d.FindElement(By.Name("country")); });
            return new SelectElement(this.browser.FindElement(By.Name("country")));
        }
    }

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

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

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

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

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

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

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

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

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

    public void SwitchToShippingFrame()
    {
        this.WaitForLogo();
        this.browser.SwitchTo().Frame("shpFrame");
    }

    private void WaitForLogo()
    {
        this.browserWait.Until<IWebElement>((d) => { return d.FindElement(By.Id("gh-logo")); });
    }
}

In order the switch frame command to be successful, a wait operation is performed. The logo of the page is located outside of the frame, so its waiting guarantees that the page is completely loaded.

Tests without Facade Design Pattern

If we desire to perform two different tests- buy two separate products with varying information. We can perform them only with the above classes without a Facade class.


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

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

    
    public void Purchase_Casio_GShock()
    {
        string itemUrl = "watchItemUrl";
        string itemPrice = "AU $168.00";
        ClientInfo currentClientInfo = new ClientInfo()
        {
            FirstName = "Anton",
            LastName = "Angelov",
            Country = "Bulgaria",
            Address1 = "33 Alexander Malinov Blvd.",
            City = "Sofia",
            Zip = "1729",
            Phone = "0035964644885",
            Email = "aangelov@yahoo.com"
        };
        ItemPage itemPage = new ItemPage();
        CheckoutPage checkoutPage = new CheckoutPage();
        ShippingAddressPage shippingAddressPage = new ShippingAddressPage();
        SignInPage signInPage = new SignInPage();

        itemPage.Navigate(itemUrl);
        itemPage.Validate().Price(itemPrice);
        itemPage.ClickBuyNowButton();
        signInPage.ClickContinueAsGuestButton();
        shippingAddressPage.FillShippingInfo(currentClientInfo);
        shippingAddressPage.Validate().Subtotal(itemPrice);
        shippingAddressPage.ClickContinueButton();
        checkoutPage.Validate().Subtotal(itemPrice);
    }

    
    public void Purchase_WhiteOpticalKeyboard()
    {
        string itemUrl = "watchItemUrl";
        string itemPrice = "C $20.86";
        ClientInfo currentClientInfo = new ClientInfo()
        {
            FirstName = "Anton",
            LastName = "Angelov",
            Country = "Bulgaria",
            Address1 = "33 Alexander Malinov Blvd.",
            City = "Stara Zagora",
            Zip = "6000",
            Phone = "0035964644885",
            Email = "aangelov@yahoo.com"
        };
        ItemPage itemPage = new ItemPage();
        CheckoutPage checkoutPage = new CheckoutPage();
        ShippingAddressPage shippingAddressPage = new ShippingAddressPage();
        SignInPage signInPage = new SignInPage();

        itemPage.Navigate(itemUrl);
        itemPage.Validate().Price(itemPrice);
        itemPage.ClickBuyNowButton();
        signInPage.ClickContinueAsGuestButton();
        shippingAddressPage.FillShippingInfo(currentClientInfo);
        shippingAddressPage.Validate().Subtotal(itemPrice);
        shippingAddressPage.ClickContinueButton();
        checkoutPage.Validate().Subtotal(itemPrice);
    }
}

As you can see, the main problem in the examples is that we need to create instances of our pages for every test. Moreover, the whole workflow of the tests as method calls is copied. Practices mentioned above make our tests much harder to maintain. Also, it brakes one of the most important programming principles- DRY (Don’t-Repeat-Yourself).

Tests Using Facade Design Pattern

The solution of the above problems is to encapsulate our test’s logic/workflow in a Facade class.

public class PurchaseFacade
{
    private ItemPage itemPage;
    private CheckoutPage checkoutPage;
    private ShippingAddressPage shippingAddressPage;
    private SignInPage signInPage;

    public ItemPage ItemPage
    {
        get
        {
            if (itemPage == null)
            {
                itemPage = new ItemPage();
            }
            return itemPage;
        }
    }

    public SignInPage SignInPage
    {
        get
        {
            if (signInPage == null)
            {
                signInPage = new SignInPage();
            }
            return signInPage;
        }
    }

    public CheckoutPage CheckoutPage
    {
        get
        {
            if (checkoutPage == null)
            {
                checkoutPage = new CheckoutPage();
            }
            return checkoutPage;
        }
    }

    public ShippingAddressPage ShippingAddressPage
    {
        get
        {
            if (shippingAddressPage == null)
            {
                shippingAddressPage = new ShippingAddressPage();
            }
            return shippingAddressPage;
        }
    }

    public void PurchaseItem(string item, string itemPrice, ClientInfo clientInfo)
    {
        this.ItemPage.Navigate(item);
        this.ItemPage.Validate().Price(itemPrice);
        this.ItemPage.ClickBuyNowButton();
        this.SignInPage.ClickContinueAsGuestButton();
        this.ShippingAddressPage.FillShippingInfo(clientInfo);
        this.ShippingAddressPage.Validate().Subtotal(itemPrice);
        this.ShippingAddressPage.ClickContinueButton();
        this.CheckoutPage.Validate().Subtotal(itemPrice);
    }
}

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

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

    
    public void Purchase_Casio_GShock()
    {
        string itemUrl = "watchItemUrl";
        string itemPrice = "AU $168.00";
        ClientInfo currentClientInfo = new ClientInfo()
        {
            FirstName = "Anton",
            LastName = "Angelov",
            Country = "Bulgaria",
            Address1 = "33 Alexander Malinov Blvd.",
            City = "Sofia",
            Zip = "1729",
            Phone = "0035964644885",
            Email = "aangelov@yahoo.com"
        };
        new PurchaseFacade().PurchaseItem(itemUrl, itemPrice, currentClientInfo);
    }

    
    public void Purchase_WhiteOpticalKeyboard()
    {
        string itemUrl = "watchItemUrl";
        string itemPrice = "C $20.86";
        ClientInfo currentClientInfo = new ClientInfo()
        {
            FirstName = "Anton",
            LastName = "Angelov",
            Country = "Bulgaria",
            Address1 = "33 Alexander Malinov Blvd.",
            City = "Stara Zagora",
            Zip = "6000",
            Phone = "0035964644885",
            Email = "aangelov@yahoo.com"
        };
        new PurchaseFacade().PurchaseItem(itemUrl, itemPrice, currentClientInfo);
    }
}

Related Articles

Design Patterns

Decorator Design Pattern in Automated Testing

In my articles "Strategy Design Pattern" and "Advanced Strategy Design Pattern", I explained the benefits of the application of Strategy Design Pattern in your

Decorator Design Pattern in Automated Testing

Design Patterns

Page Objects- Partial Classes Base Pages- 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 Base Pages- WebDriver C#

Design Patterns

Proxy Design Pattern in Automated Testing

Achieving high-quality test automation that brings value- you need to understand core programming concepts such as SOLID and the usage of design patterns. In th

Proxy Design Pattern in Automated Testing

Design Patterns

Advanced Specification Design Pattern in Automated Testing

In my last publication from the Design Patterns in Automated Testing Series, I explained to you how you can benefit from the usage of the Specification Design P

Advanced Specification Design Pattern in Automated Testing

Design Patterns

Generic Repository Design Pattern- Test Data Preparation

Often we can run the tests against an empty DB. However, we still need initial data. We can generate it ourselves. To do so, we need to add a code for accessing

Generic Repository Design Pattern- Test Data Preparation

Design Patterns

Advanced Observer Design Pattern via Events and Delegates in Automated Testing

In my articles from the series "Design Patterns in Automated Testing", I am sharing with you ideas how to integrate the most useful code design patterns in the

Advanced Observer Design Pattern via Events and Delegates in Automated Testing
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.