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
Participants
The classes and objects participating in this pattern are:
- 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;
}
}
}
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(@"https://www.telerik.com/login/");
}
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.
LoginTelerikTests
[TestClass,
ExecutionEngineAttribute(ExecutionEngineType.TestStudio, Browsers.Firefox),
VideoRecordingAttribute(VideoRecordingMode.DoNotRecord)]
public class LoginTelerikTests : BaseTest
{
public override void TestInit()
{
base.TestInit();
UnityContainerFactory.GetContainer().
RegisterType<IExceptionAnalysationHandler, ServiceUnavailableExceptionHandler>(
Guid.NewGuid().ToString());
UnityContainerFactory.GetContainer().
RegisterType<IExceptionAnalysationHandler, FileNotFoundExceptionHandler>(
Guid.NewGuid().ToString());
}
[TestMethod]
public void TryToLoginTelerik_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.
Design Patterns in Automated Testing
Reference
Decorator Design Pattern in Automated Testing
.NET Factory Decorator
Decorator Design Pattern – C#
The post Failed Tests Аnalysis – Decorator Design Pattern appeared first on Automate The Planet.
All images are purchased from DepositPhotos.com and cannot be downloaded and used for free.
License Agreement