Composite Design Pattern in Automated Testing

Composite 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 the previous article, we investigated the Adapter design pattern and how it can help us to eliminate the usage of hard-coded pauses in our automated tests and instead automatically wait for elements to appear. Moreover, it helped us to improve the API Usability of our tests.

In this publication, we will investigate how we can extend the Adapter design pattern through the Composite design pattern so that our adapters to include the same style assertions. The style assertions are verification methods for checking the styles of the web elements against our style sheet requirements.

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

Overview Video

The Problem

We will start by checking the requirements of what we need to automate. Imagine that you work for a startup called “EU Space Rockets”. Our company makes the world a better place by allowing people to buy rockets through our website. How cool is that! Your job is to create a series of automated tests and make sure that everything is working as expected.

Our website uses modern web technologies and all actions are loading asynchronous instead of reloading the whole page.

Add To Cart Faclcon9

We received the requirement that all buttons on the website should follow the same styles. For example- their font size to be 14px, and the font-weight to be equal to 600. We need to create tests for all other visual elements on the website, such as images, links, text fields, and so on. The solution should work for a single element and as well as for a collection of elements.

Quick Recall- Adapter Design Pattern Implementation

How about quickly recall what our decorators looked like?

public class DriverAdapter : IDriver
{
    private readonly IWebDriver _driver;
    public DriverAdapter(IWebDriver driver)
    {
        _driver = driver;
    }
    public void GoToUrl(string url)
    {
        _driver.Navigate().GoToUrl(url);
    }
    public Uri Url
    {
        get => new Uri(_driver.Url);
        set => _driver.Url = value.ToString();
    }
    public IElement Create(By locator)
    {
        return new ElementAdapter(_driver, locator);
    }
    public IElementsList CreateElements(By locator)
    {
        return new ElementsList(_driver, locator);
    }
    public void WaitForAjax()
    {
        var timeout = TimeSpan.FromSeconds(30);
        var sleepInterval = TimeSpan.FromSeconds(2);
        var webDriverWait = new WebDriverWait(new SystemClock(), _driver, timeout, sleepInterval);
        var js = (IJavaScriptExecutor)_driver;
        webDriverWait.Until(wd => js.ExecuteScript("return jQuery.active").ToString() == "0");
    }
    public void Close()
    {
        _driver.Quit();
        _driver.Dispose();
    }
}

The DriverAdapter implements a simpler and enhanced IDriver interface. It wraps the usage of the Adaptee (IWebDriver) through the Composition Principle. It creates element adapters which are found later on when needed. We used the Lazy Loading design pattern to achieve this behavior. Also, it gives us the new WaitForAjax method.

Here is the implementation of the ElementDriver:

public class ElementAdapter : IElement
{
    private readonly IWebDriver _driver;
    private readonly ElementFinderService _elementFinder;

    public ElementAdapter(IWebDriver driver, By by)
    {
        _driver = driver;
        By = by;
        _elementFinder = new ElementFinderService(driver);
    }

    public IWebElement NativeWebElement
    {
        get => _elementFinder.Find(By);
    }

    public By By
    {
        get;
    }

    public string Text => NativeWebElement?.Text;
    public bool? Enabled => NativeWebElement?.Enabled;
    public bool? Displayed => NativeWebElement?.Displayed;

    public void Click()
    {
        WaitToBeClickable(By);
        NativeWebElement?.Click();
    }

    public IElement CreateElement(By locator)
    {
        return new ElementAdapter(_driver, locator);
    }

    public IElementsList CreateElements(By locator)
    {
        return new ElementsList(_driver, locator);
    }

    public void TypeText(string text)
    {
        var webElement = NativeWebElement;
        webElement?.Clear();
        webElement?.SendKeys(text);
    }

    private void WaitToBeClickable(By by)
    {
        var webDriverWait = new WebDriverWait(_driver, TimeSpan.FromSeconds(30));
        webDriverWait.Until(SeleniumExtras.WaitHelpers.ExpectedConditions.ElementToBeClickable(by));
    }
}

Also, to achieve the lazy loading, we created a collection for the new adapter elements called ElementList.

public class ElementsList : IElementsList
{
    private readonly By _by;
    private readonly ElementFinderService _elementFinder;
    private readonly IWebDriver _driver;

    public ElementsList(IWebDriver driver, By by)
    {
        _by = by;
        _elementFinder = new ElementFinderService(driver);
        _driver = driver;
    }

    public IElement this => GetAndWaitWebDriverElements().ElementAt(i);
    public IEnumerator<IElement> GetEnumerator() => GetAndWaitWebDriverElements().GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    public int Count()
    {
        return _elementFinder.FindAll(_by).Count();
    }

    public void ForEach(Action<IElement> action)
    {
        foreach (var element in this)
        {
            action(element);
        }
    }
    private IEnumerable<IElement> GetAndWaitWebDriverElements()
    {
        var nativeElements = _elementFinder.FindAll(_by);
        foreach (var nativeElement in nativeElements)
        {
            IElement element = new ElementAdapter(_driver, _by);
            yield
            return element;
        }
    }
}

Style Assertions

As mentioned, the style assertions are verification methods for checking the styles of the web elements against our style sheet requirements. We can add the following methods to the ElementDecorator class.

public void AssertBackgroundColor(string expectedBackgroundColor)
{
    Assert.AreEqual(expectedBackgroundColor, NativeWebElement.GetCssValue("background-color"));
}
public void AssertBorderColor(string expectedBorderColor)
{
    Assert.AreEqual(expectedBorderColor, NativeWebElement.GetCssValue("border-color"));
}
public void AssertColor(string expectedColor)
{
    Assert.AreEqual(expectedColor, NativeWebElement.GetCssValue("color"));
}
public void AssertFontFamily(string expectedFontFamily)
{
    Assert.AreEqual(expectedFontFamily, NativeWebElement.GetCssValue("font-family"));
}
public void AssertFontWeight(string expectedFontWeight)
{
    Assert.AreEqual(expectedFontWeight, NativeWebElement.GetCssValue("font-weight"));
}
public void AssertFontSize(string expectedFontSize)
{
    Assert.AreEqual(expectedFontSize, NativeWebElement.GetCssValue("font-size"));
}
public void AssertTextAlign(string expectedTextAlign)
{
    Assert.AreEqual(expectedTextAlign, NativeWebElement.GetCssValue("text-align"));
}
public void AssertVerticalAlign(string expectedVerticalAlign)
{
    Assert.AreEqual(expectedVerticalAlign, NativeWebElement.GetCssValue("vertical-align"));
}

This is how we can use them for a single web element.


public void VerifyStylesOfAddToCartButton()
{
    _driver.GoToUrl("http://demos.bellatrix.solutions/");
    var falcon0AddToCartButton = _driver.Create(By.CssSelector("[data-product_id*='28']"));
    falcon0AddToCartButton.AssertFontSize("14px");
    falcon0AddToCartButton.AssertFontWeight("600");
}

Let’s review two ways how you can execute the same style assertions for a collection of elements. Here is the first way:


public void VerifyStylesOfAddToCartButtons()
{
    _driver.GoToUrl("http://demos.bellatrix.solutions/");
    var addToCartButtons = _driver.CreateElements(By.XPath("//a[contains(text(),'Add to cart')]"));
    foreach (var addToCartButton in addToCartButtons)
    {
        addToCartButton.AssertFontSize("14px");
        addToCartButton.AssertFontWeight("600");
    }
}

Another way of doing the same thing but in a bit shorter manner is to use the ElementList ForEach method.


public void VerifyStylesOfAddToCartButtons()
{
    _driver.GoToUrl("http://demos.bellatrix.solutions/");
    var addToCartButtons = _driver.CreateElements(By.XPath("//a[contains(text(),'Add to cart')]"));
    addToCartButtons.ForEach(e => e.AssertFontSize("14px"));
    addToCartButtons.ForEach(e => e.AssertFontWeight("600"));
}

Anyhow, I believe the shorter version to be again a bit verbose because of the lambda expression. Won’t it be cool to be able to use the same syntax for a single element for the collection too? Something like this:


public void VerifyStylesOfAddToCartButtons()
{
    _driver.GoToUrl("http://demos.bellatrix.solutions/");
    var addToCartButtons = _driver.CreateElements(By.XPath("//a[contains(text(),'Add to cart')]"));
    addToCartButtons.AssertFontSize("14px");
    addToCartButtons.AssertFontWeight("600");
}

We can achieve this syntax if we implement the Composite design pattern. Let’s review how to do it.

Composite Design Pattern

Definition

Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

If I need to rephrase it- we can execute the same actions against a single item and the whole collection. If we are in the context of the collection, the action will be performed against all elements.

UML Class Diagram

classDiagram
    IStyleAssertedElement <|.. IElement
    IStyleAssertedElement <|.. IElementList
    IElement <|.. ElementAdapter
    IElementList <|.. ElementList
    ElementList o-- IElement
    Tests --> IStyleAssertedElement
    class IStyleAssertedElement {
        <<interface>>
        +AssertBackgroundColor(string color)
        +AssertBorderColor(string color)
        +AssertColor(string color)
        +AssertFontFamily(string fontFamily)
        +AssertFontWeight(string fontWeight)
        +AssertFontSize(string fontSize)
        +AssertTextAlign(string textAlign)
        +AssertVerticalAlign(string verticalAlign)
    }
    class IElement {
        <<interface>>
    }
    class IElementList {
        <<interface>>
    }
    class ElementAdapter {
    }
    class ElementList {
    }
    class Tests {
    }

Participants

  • Component (IStyleAssertedElement)

    The interface for objects in the composition. Defines methods for accessing and managing its child components.

  • Composite (IElementList, ElementList)

    Defines behavior for components having children. Stores child components. Implements child-related operations in the Component interface.

  • Leaf (IElement, ElementAdapter)

    Represents the lead object in the composition. A leaf has no children. Defines behavior for primitive objects in the composition.

  • Client (Tests)

    Collaborates with objects conforming to the Component interface.

Composite Design Pattern Implementation

Let’s first create the shared Component interface holding the style assertions. The IElement and IElementList interfaces will inherit it.

public interface IStyleAssertedElement
{
    void AssertBackgroundColor(string expectedBackgroundColor);
    void AssertBorderColor(string expectedBorderColor);
    void AssertColor(string expectedColor);
    void AssertFontFamily(string expectedFontFamily);
    void AssertFontWeight(string expectedFontWeight);
    void AssertFontSize(string expectedFontSize);
    void AssertTextAlign(string expectedTextAlign);
    void AssertVerticalAlign(string expectedVerticalAlign);
}

We don’t need to change anything in the ElementDecorator. The definitions of the style assertions will stay the same. The only difference we need to make  is that we need to implement these methods in the ElementListComposite” class.

public class ElementsList : IElementsList
{
    private readonly By _by;
    private readonly ElementFinderService _elementFinder;
    private readonly IWebDriver _driver;
    public ElementsList(IWebDriver driver, By by)
    {
        _by = by;
        _elementFinder = new ElementFinderService(driver);
        _driver = driver;
    }
    public IElement this => GetAndWaitWebDriverElements().ElementAt(i);
    public IEnumerator<IElement> GetEnumerator() => GetAndWaitWebDriverElements().GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    public int Count()
    {
        return _elementFinder.FindAll(_by).Count();
    }
    public void ForEach(Action<IElement> action)
    {
        foreach (var element in this)
        {
            action(element);
        }
    }
    public void AssertBackgroundColor(string expectedBackgroundColor)
    {
        ForEach(e => e.AssertBackgroundColor(expectedBackgroundColor));
    }
    public void AssertBorderColor(string expectedBorderColor)
    {
        ForEach(e => e.AssertBorderColor(expectedBorderColor));
    }
    public void AssertColor(string expectedColor)
    {
        ForEach(e => e.AssertColor(expectedColor));
    }
    public void AssertFontFamily(string expectedFontFamily)
    {
        ForEach(e => e.AssertFontFamily(expectedFontFamily));
    }
    public void AssertFontWeight(string expectedFontWeight)
    {
        ForEach(e => e.AssertFontWeight(expectedFontWeight));
    }
    public void AssertFontSize(string expectedFontSize)
    {
        ForEach(e => e.AssertFontSize(expectedFontSize));
    }
    public void AssertTextAlign(string expectedTextAlign)
    {
        ForEach(e => e.AssertTextAlign(expectedTextAlign));
    }
    public void AssertVerticalAlign(string expectedVerticalAlign)
    {
        ForEach(e => e.AssertVerticalAlign(expectedVerticalAlign));
    }
    private IEnumerable<IElement> GetAndWaitWebDriverElements()
    {
        var nativeElements = _elementFinder.FindAll(_by);
        foreach (var nativeElement in nativeElements)
        {
            IElement element = new ElementAdapter(_driver, _by);
            yield return element;
        }
    }
}

Now we can use the style assertions in the same way for a single element and an element collection!

Related Articles

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

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

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

Design Patterns

Facade Design Pattern in Automated Testing

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 reada

Facade Design Pattern in Automated Testing

Design Patterns

Strategy Design Pattern in Automated Testing

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

Strategy Design Pattern in Automated Testing

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
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.