Failed Tests Аnalysis – Decorator Design Pattern

Failed Tests Аnalysis – Decorator Design Pattern

Here I will present to you the third version of the Failed Tests Analysis engine part of the Design Patterns in Automated Testing Series. We are going to utilise the power of the Decorator Design Pattern to create the most improved version of the engine.

Definition

Definition

The Decorator Design Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

UML Class Diagram

classDiagram
    Component <|-- ConcreteComponent
    Component <|-- Decorator
    Decorator o-- Component
    Decorator <|-- ConcreteDecorator
    class Component {
        +Operation()
    }
    class ConcreteComponent {
        +Operation()
    }
    class Decorator {
        -Component component
        +Operation()
    }
    class ConcreteDecorator {
        +Operation()
        +AddedBehavior()
    }

Participants

  • Component

    Defines the interface for objects that can have responsibilities added to them dynamically.

  • Decorator

    The decorators implement the same interface(abstract class) as the component they are going to decorate. The decorator has a HAS-A relationship with the object that is extending, which means that the former has an instance variable that holds a reference to the later.

  • ConcreteComponent

    Is the object that is going to be enhanced dynamically. It inherits the Component.

  • ConcreteDecorator

    Decorators can enhance the state of the component. They can add new methods. The new behavior is typically added before or after an existing method in the component.

What Are the Problems That We Try to Solve?

The previous two solutions of the problem were fine. However, I think that they have a problem with the integration in the hybrid test framework’s engine. I didn’t want to modify the existing ElementFinderService and couple it with the exception analysis engine. Also, the registration of named instances in Unity IoC container and their resolution through a service locator didn’t seem to be the best available solution.

Decorator Design Pattern for Failed Tests Analysis

IExceptionAnalysationHandler

public interface IExceptionAnalysationHandler
{
    bool IsApplicable(Exception ex = null, params object[] context);
    string DetailedIssueExplanation { get; }
}

The IExceptionAnalysationHandler is the primary interface for all handlers. This is an additional abstraction above the Handler base class.

IExceptionAnalyser

public interface IExceptionAnalyser
{
    void Analyse(Exception ex = null, params object[] context);
    void AddExceptionAnalysationHandler<TExceptionAnalysationHandler>(
    IExceptionAnalysationHandler exceptionAnalysationHandler)
    where TExceptionAnalysationHandler : IExceptionAnalysationHandler;
    void AddExceptionAnalysationHandler<TExceptionAnalysationHandler>()
    where TExceptionAnalysationHandler : IExceptionAnalysationHandler, new();
    void RemoveFirstExceptionAnalysationHandler();
}

In the first two solutions, there wasn’t an interface abstraction for the ExceptionAnalyser. However, we need it for the observers’ version.

ExceptionAnalyser

public class ExceptionAnalyser : IExceptionAnalyser
{
    private readonly List<IExceptionAnalysationHandler> exceptionAnalysationHandlers;
    public ExceptionAnalyser(IEnumerable<IExceptionAnalysationHandler> handlers)
    {
        this.exceptionAnalysationHandlers = new List<IExceptionAnalysationHandler>();
        this.exceptionAnalysationHandlers.AddRange(handlers);
    }
    public void RemoveFirstExceptionAnalysationHandler()
    {
        if (exceptionAnalysationHandlers.Count > 0)
        {
            exceptionAnalysationHandlers.RemoveAt(0);
        }
    }
    public void Analyse(Exception ex = null, params object[] context)
    {
        foreach (var exceptionHandler in exceptionAnalysationHandlers)
        {
            if (exceptionHandler.IsApplicable(ex, context))
            {
                throw new AnalyzedTestException(exceptionHandler.DetailedIssueExplanation, ex);
            }
        }
    }
    public void AddExceptionAnalysationHandler<TExceptionAnalysationHandler>(
    IExceptionAnalysationHandler exceptionAnalysationHandler)
    where TExceptionAnalysationHandler : IExceptionAnalysationHandler
    {
        exceptionAnalysationHandlers.Insert(0, exceptionAnalysationHandler);
    }
    public void AddExceptionAnalysationHandler<TExceptionAnalysationHandler>()
    where TExceptionAnalysationHandler : IExceptionAnalysationHandler, new()
    {
        exceptionAnalysationHandlers.Insert(0, new TExceptionAnalysationHandler());
    }
}

As you can see, the new improved version of the ExceptionAnalyser doesn’t use any design patterns. When it starts to analyse the exception it just iterates through the list of all handlers. Also, another significant change is that it doesn’t use Unity IoC container to create the handlers. The main application chain of handlers is passed as a collection through the constructor. I am going to show you how the resolve magic happens through Unity a little bit later.

IUiExceptionAnalyser

public interface IUiExceptionAnalyser : IExceptionAnalyser
{
    void AddExceptionAnalysationHandler(
    string textToSearchInSource,
    string detailedIssueExplanation);
}

For UI tests, I created the new IUiExceptionAnalyser interface that defines a method for registering new custom test-case-specific handlers for searching for a particular text in the HTML source code.

UiExceptionAnalyser

public class UiExceptionAnalyser : ExceptionAnalyser, IUiExceptionAnalyser
{
    public UiExceptionAnalyser(IEnumerable<IExceptionAnalysationHandler> handlers) :
    base(handlers)
    {
    }
    public void AddExceptionAnalysationHandler(
    string textToSearchInSource,
    string detailedIssueExplanation)
    {
        this.AddExceptionAnalysationHandler<CustomHtmlExceptionHandler>(
        new CustomHtmlExceptionHandler(textToSearchInSource, detailedIssueExplanation));
    }
}

The concrete implementation of the UiExceptionAnalyser is a successor of the ExceptionAnalyser and only adds the method for addition of new CustomHtmlExceptionHandlers to the chain.

HtmlSourceExceptionHandler

public abstract class HtmlSourceExceptionHandler : IExceptionAnalysationHandler
{
    public HtmlSourceExceptionHandler()
    {
    }
    public abstract string DetailedIssueExplanation { get; }
    public abstract string TextToSearchInSource { get; }
    public bool IsApplicable(Exception ex = null, params object[] context)
    {
        IBrowser browser = (IBrowser)context.FirstOrDefault();
        if (browser == null)
        {
            throw new ArgumentNullException("The browser cannot be null!");
        }
        bool result = browser.SourceString.Contains(this.TextToSearchInSource);
        return result;
    }
}

The implementation of the HtmlSourceExceptionHandler is identical to the ones of the previously proposed solutions with the only difference that it implements the IExceptionAnalysationHandler. Now all handlers implement this interface so that the primary chain can be resolved as an IEnumerable collection through the Unity container.

ExceptionAnalysedPage

public abstract class ExceptionAnalysedPage : BasePage
{
    public ExceptionAnalysedPage(
    IElementFinder elementFinder,
    INavigationService navigationService)
    {
        var exceptionAnalyzedElementFinder =
        new ExceptionAnalyzedElementFinder(
        elementFinder as ExceptionAnalyzedElementFinder);
        var exceptionAnalyzedNavigationService =
        new ExceptionAnalyzedNavigationService(
        navigationService as ExceptionAnalyzedNavigationService,
        exceptionAnalyzedElementFinder.UiExceptionAnalyser);
        this.ExceptionAnalyser = exceptionAnalyzedElementFinder.UiExceptionAnalyser;
        this.ElementFinder = exceptionAnalyzedElementFinder;
        this.NavigationService = exceptionAnalyzedNavigationService;
    }
    public IUiExceptionAnalyser ExceptionAnalyser { get; private set; }
}

In order a page to be able to use the exception analysis engine it needs to inherit the new ExceptionAnalysedPage base class. It adds a new property to all pages- the UiExceptionAnalyser so that they can add custom handlers where needed. Also, here the new decorator classes are created so that when an element is not found the exception analysis engine to be called. The same is valid for the navigation engine. If the waiting for a particular URL fails, the engine will be called. Most importantly, the pages and the decorators should share a common instance of the exception analysis engine because of that we assign the decorators’ instance of the engine to the base page’s property. Otherwise, the pages’ added handlers won’t be executed.

ExceptionAnalyzedElementFinder

Below you can find the code of the decorator of the ElementFinder. It contains a property of the original ElementFinder and more or less calls its methods. It implements the IElementFinder interface which means that it has the same methods as the original ElementFinder class. It adds the calls to the Analyse methods if the localisation of some element fails.

public class ExceptionAnalyzedElementFinder : IElementFinder
{
    public ExceptionAnalyzedElementFinder(
    IElementFinder elementFinder,
    IUiExceptionAnalyser exceptionAnalyser)
    {
        this.ElementFinder = elementFinder;
        this.UiExceptionAnalyser = exceptionAnalyser;
    }
    public ExceptionAnalyzedElementFinder(
    ExceptionAnalyzedElementFinder elementFinderDecorator)
    {
        this.UiExceptionAnalyser = elementFinderDecorator.UiExceptionAnalyser;
        this.ElementFinder = elementFinderDecorator.ElementFinder;
    }
    public IUiExceptionAnalyser UiExceptionAnalyser { get; private set; }
    public IElementFinder ElementFinder { get; private set; }
    public TElement Find<TElement>(By by) where TElement : class, IElement
    {
        TElement result = default(TElement);
        try
        {
            result = this.ElementFinder.Find<TElement>(by);
        }
        catch (Exception ex)
        {
            this.UiExceptionAnalyser.Analyse(ex, this.ElementFinder);
            throw;
        }
        return result;
    }
    public IEnumerable<TElement> FindAll<TElement>(By by)
    where TElement : class, IElement
    {
        IEnumerable<TElement> result = default(IEnumerable<TElement>);
        try
        {
            result = this.ElementFinder.FindAll<TElement>(by);
        }
        catch (Exception ex)
        {
            this.UiExceptionAnalyser.Analyse(ex, this.ElementFinder);
            throw;
        }
        return result;
    }
    public bool IsElementPresent(By by)
    {
        bool result = default(bool);
        try
        {
            result = this.ElementFinder.IsElementPresent(by);
        }
        catch (Exception ex)
        {
            this.UiExceptionAnalyser.Analyse(ex, this.ElementFinder);
            throw;
        }
        return result;
    }
}

ExceptionAnalyzedNavigationService

The structure of the NavigationService decorator is the same as the one of the ElementFinderService. We call the analysis engine if the waiting for a particular URL timeouts.

public class ExceptionAnalyzedNavigationService : INavigationService
{
    public ExceptionAnalyzedNavigationService(
    INavigationService navigationService,
    IUiExceptionAnalyser exceptionAnalyser)
    {
        this.NavigationService = navigationService;
        this.UiExceptionAnalyser = exceptionAnalyser;
    }
    public ExceptionAnalyzedNavigationService(
    ExceptionAnalyzedNavigationService exceptionAnalyzedNavigationService)
    {
        this.UiExceptionAnalyser = exceptionAnalyzedNavigationService.UiExceptionAnalyser;
        this.NavigationService = exceptionAnalyzedNavigationService.NavigationService;
    }
    public ExceptionAnalyzedNavigationService(
    ExceptionAnalyzedNavigationService exceptionAnalyzedNavigationService,
    IUiExceptionAnalyser exceptionAnalyser)
    {
        this.UiExceptionAnalyser = exceptionAnalyser;
        this.NavigationService = exceptionAnalyzedNavigationService.NavigationService;
    }
    public IUiExceptionAnalyser UiExceptionAnalyser { get; private set; }
    public INavigationService NavigationService { get; private set; }
    public event EventHandler<PageEventArgs> Navigated;
    public string Url
    {
        get
        {
            return this.NavigationService.Url;
        }
    }
    public string Title
    {
        get
        {
            return this.NavigationService.Title;
        }
    }
    public void Navigate(string relativeUrl, string currentLocation, bool sslEnabled = false)
    {
        this.NavigationService.Navigate(relativeUrl, currentLocation, sslEnabled);
    }
    public void NavigateByAbsoluteUrl(string absoluteUrl, bool useDecodedUrl = true)
    {
        this.NavigationService.NavigateByAbsoluteUrl(absoluteUrl, useDecodedUrl);
    }
    public void Navigate(string currentLocation, bool sslEnabled = false)
    {
        this.NavigationService.Navigate(currentLocation, sslEnabled);
    }
    public void WaitForUrl(string url)
    {
        try
        {
            this.NavigationService.WaitForUrl(url);
        }
        catch (Exception ex)
        {
            this.UiExceptionAnalyser.Analyse(ex, this.NavigationService);
            throw;
        }
    }
    public void WaitForPartialUrl(string url)
    {
        try
        {
            this.NavigationService.WaitForPartialUrl(url);
        }
        catch (Exception ex)
        {
            this.UiExceptionAnalyser.Analyse(ex, this.NavigationService);
            throw;
        }
    }
}

While ago when we were working on the first version of the BELLATRIX test automation framework, I did this research while I was working on a similar feature for our solution.

Decorator Design Pattern in Tests

ExecutionEngineBehaviorObserver

this.unityContainer.RegisterType<IEnumerable<IExceptionAnalysationHandler>, IExceptionAnalysationHandler[]>();
this.unityContainer.RegisterType<IUiExceptionAnalyser, UiExceptionAnalyser>();
this.unityContainer.RegisterType<IElementFinder, ExceptionAnalyzedElementFinder>(
new InjectionFactory(x => new ExceptionAnalyzedElementFinder(this.driver, this.unityContainer.Resolve<IUiExceptionAnalyser>())));
this.unityContainer.RegisterType<INavigationService, ExceptionAnalyzedNavigationService>(
new InjectionFactory(x => new ExceptionAnalyzedNavigationService(this.driver, this.unityContainer.Resolve<IUiExceptionAnalyser>())));

Through the first line of code, we tell Unity that when an IEnumerable collection of IExceptionAnalysationHandler is required, to create it from all types registered for this particular interface. Because the decorators don’t have empty constructors, we need to instruct Unity how to deal with their initialization. For the job, we use InjectionFactory objects.

LoginPage

public partial class LoginPage : ExceptionAnalysedPage
{
    public LoginPage(
    IElementFinder elementFinder,
    INavigationService navigationService) : base(elementFinder, navigationService)
    {
    }
    public void Navigate()
    {
        this.NavigationService.NavigateByAbsoluteUrl(@"productSiteUrllogin/");
    }
    public void Login()
    {
        this.LoginButton.Click();
    }
    public void Logout()
    {
        this.ExceptionAnalyser.
        AddExceptionAnalysationHandler<EmptyEmailValidationExceptionHandler>();
        this.LogoutButton.Click();
        this.ExceptionAnalyser.RemoveFirstExceptionAnalysationHandler();
    }
}

The usage of the new pages’ property is straightforward. First, the page needs to inherit from the ExceptionAnalysedPage base class. Then you first add a custom handler and after that removes it from the chain once it is not more required.

LoginProductSiteTests

[TestClass,
ExecutionEngineAttribute(ExecutionEngineType.TestStudio, Browsers.Firefox),
VideoRecordingAttribute(VideoRecordingMode.DoNotRecord)]
public class LoginProductSiteTests : BaseTest
{
    public override void TestInit()
    {
        base.TestInit();
        UnityContainerFactory.GetContainer().
        RegisterType<IExceptionAnalysationHandler, ServiceUnavailableExceptionHandler>(
        Guid.NewGuid().ToString());
        UnityContainerFactory.GetContainer().
        RegisterType<IExceptionAnalysationHandler, FileNotFoundExceptionHandler>(
        Guid.NewGuid().ToString());
    }
    
    public void TryToLoginProductSite_Decorator()
    {
        var loginPage = this.Container.Resolve<LoginPage>();
        loginPage.Navigate();
        loginPage.Login();
        loginPage.Logout();
    }
}

There is nothing special here except the TestInit method. There, as you can see we register two global Handlers that will be executed for the whole application. Because we register them both with the IExceptionAnalysationHandler interface, they will be passed as an IEnumerable collection to the exception analysis engine.

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

Page Objects- App 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- App Design Pattern- WebDriver C#

Design Architecture

Benchmarking for Assessing Automated Test Components Performance

The evaluation of core quality attributes is not enough to finally decide which implementation is better or not. The test execution time should be a key compone

Benchmarking for Assessing Automated Test Components Performance

Design Architecture

Unit Testing Guidelines What to Test And What Not

During the years of consulting, many people asked me to help them get started to write unit tests. During the process always pop up one question- "What should I

Unit Testing Guidelines What to Test And What Not

Design Architecture

Defining High-Quality Test Attributes for Automated Tests

To be able to write high-quality automated tests, more knowledge is needed than just knowing how to program in a certain language or use a specific framework. T

Defining High-Quality Test Attributes for Automated Tests

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 with Partial Classes and Properties- WebDriver C#

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

Page Objects with Partial Classes and Properties- 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.