Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Create Hybrid Test Framework – Improved Execution Engine

5.00/5 (2 votes)
9 Oct 2016Ms-PL7 min read 6.2K  
The next step in building a Hybrid Test Automation Framework is to abstract the unit test framework layer. Learn how to switch it without changes in tests.

Introduction

In the articles from the Design & Architecture Series, we are talking about the most important steps in the creation of entirely hybrid and abstract test automation framework. If you have read some my publications, you know that most of the ideas are implemented as common reusable CORE projects. In order for the framework to be completely abstract, all of the core projects should not depend on any specific technologies such as automation frameworks - WebDriver, Testing Framework or unit test frameworks- MSTest, NUnit. In one of my previous posts, I showed you how to configure the tests' execution engine (engine = test automation framework + particular browser) though the observer design pattern and attributes. However, there, we coupled the observers with both automation frameworks which is not a good practice. Here, I am going to show you how to implement it the right way.

Execution Engine v.0.1

The first version of the execution engine v.0.1 contains two important parts - the observer where most of the magic happens and the BaseTest class where we plug the observers in the execution flow. There are two main problems. First, the observer depends on all test automation frameworks that your hybrid engine support (such as WebDriver, Testing Framework and so on). Secondly, the BaseTest class depends on the concrete implementations of the observers.

ExecutionEngineBehaviorObserver v.0.1

C#
public class ExecutionEngineBehaviorObserver : BaseTestBehaviorObserver
{
    private readonly IUnityContainer unityContainer;
    private ExecutionEngineType executionEngineType;
    private Browsers executionBrowserType;
    private IDriver driver;

    public ExecutionEngineBehaviorObserver(IUnityContainer unityContainer)
    {
        this.unityContainer = unityContainer;
    }

    protected override void PostTestCleanup(object sender, TestExecutionEventArgs e)
    {
        this.driver.Quit();
    }

    protected override void PreTestInit(object sender, TestExecutionEventArgs e)
    {
        this.executionBrowserType = this.ConfigureTestExecutionBrowser(e.MemberInfo);
        this.executionEngineType = this.GetExecutionEngineType(e.MemberInfo);
        this.ResolveAllDriverDependencies();
    }

    private ExecutionEngineType GetExecutionEngineTypeByMethodInfo(MemberInfo memberInfo)
    {
        if (memberInfo == null)
        {
            throw new ArgumentNullException("The test method's info cannot be null.");
        }

        var executionEngineTypeMethodAttribute = 
                  memberInfo.GetCustomAttribute<ExecutionEngineAttribute>();
        if (executionEngineTypeMethodAttribute != null)
        {
            return executionEngineTypeMethodAttribute.ExecutionEngineType;
        }
        return ExecutionEngineType.NotSpecified;
    }

    private ExecutionEngineType GetExecutionEngineType(Type currentType)
    {
        if (currentType == null)
        {
            throw new ArgumentNullException("The test method's type cannot be null.");
        }

        var executionEngineClassAttribute = 
                currentType.GetCustomAttribute<ExecutionEngineAttribute>(true);
        if (executionEngineClassAttribute != null)
        {
            return executionEngineClassAttribute.ExecutionEngineType;
        }
        return ExecutionEngineType.NotSpecified;
    }

    private ExecutionEngineType GetExecutionEngineType(MemberInfo memberInfo)
    {
        var executionEngineType = ExecutionEngineType.TestStudio;

        ExecutionEngineType methodExecutionEngineType = 
                            this.GetExecutionEngineTypeByMethodInfo(memberInfo);
        ExecutionEngineType classExecutionEngineType = 
                            this.GetExecutionEngineType(memberInfo.DeclaringType);

        if (methodExecutionEngineType != ExecutionEngineType.NotSpecified)
        {
            executionEngineType = methodExecutionEngineType;
        }
        else if (classExecutionEngineType != ExecutionEngineType.NotSpecified)
        {
            executionEngineType = classExecutionEngineType;
        }

        return executionEngineType;
    }

    private void ResolveAllDriverDependencies()
    {
        var browserSettings = new BrowserSettings(this.executionBrowserType);
        if (this.executionEngineType.Equals(ExecutionEngineType.TestStudio))
        {
            #region Default Registration
                
            this.unityContainer.RegisterType<IDriver, TestingFrameworkDriver>(
                new InjectionFactory
                  (x => new TestingFrameworkDriver(this.unityContainer, browserSettings)));
                
            #endregion
                
            this.driver = this.unityContainer.Resolve<IDriver>();
                
            this.unityContainer.RegisterType<IButton, TestingFrameworkControls.Button>();
            this.unityContainer.RegisterType<ITextBox, TestingFrameworkControls.TextBox>();
            this.unityContainer.RegisterType<IDiv, TestingFrameworkControls.Div>();
            this.unityContainer.RegisterType<ISearch, TestingFrameworkControls.Search>();
            this.unityContainer.RegisterType<IInputSubmit, TestingFrameworkControls.InputSubmit>();
        }
        else if (this.executionEngineType.Equals(ExecutionEngineType.WebDriver))
        {
            this.unityContainer.RegisterType<IDriver, SeleniumDriver>(
                new InjectionFactory(x => new SeleniumDriver(this.unityContainer, browserSettings)));
            this.driver = this.unityContainer.Resolve<IDriver>();
                
            this.unityContainer.RegisterType<IButton, SeleniumControls.Button>();
            this.unityContainer.RegisterType<ITextBox, SeleniumControls.TextBox>();
            this.unityContainer.RegisterType<IDiv, SeleniumControls.Div>();
            this.unityContainer.RegisterType<ISearch, SeleniumControls.Search>();
            this.unityContainer.RegisterType<IInputSubmit, SeleniumControls.InputSubmit>();
        }
            
        this.unityContainer.RegisterInstance<IDriver>(this.driver);
        this.unityContainer.RegisterInstance<IBrowser>(this.driver);
        this.unityContainer.RegisterInstance<ICookieService>(this.driver);
        this.unityContainer.RegisterInstance<IDialogService>(this.driver);
        this.unityContainer.RegisterInstance<IJavaScriptInvoker>(this.driver);
        this.unityContainer.RegisterInstance<INavigationService>(this.driver);
        this.unityContainer.RegisterInstance<IElementFinder>(this.driver);
            
        # region 11. Failed Tests ?nalysis - Decorator Design Pattern
            
        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>())));
        
        #endregion
    }

    private Browsers ConfigureTestExecutionBrowser(MemberInfo memberInfo)
    {
        var currentExecutionBrowserType = Browsers.Firefox;
        Browsers methodExecutionBrowser = this.GetExecutionBrowser(memberInfo);
        Browsers classExecutionBrowser = this.GetExecutionBrowser(memberInfo.DeclaringType);
            
        if (methodExecutionBrowser != Browsers.NotSet)
        {
            currentExecutionBrowserType = methodExecutionBrowser;
        }
        else
        {
            if (classExecutionBrowser != Browsers.NotSet)
            {
                currentExecutionBrowserType = classExecutionBrowser;
            }
            else
            {
                currentExecutionBrowserType = Browsers.InternetExplorer;
            }
        }
        
        return currentExecutionBrowserType;
    }
        
    private Browsers GetExecutionBrowser(MemberInfo memberInfo)
    {
        if (memberInfo == null)
        {
            throw new ArgumentNullException("The test method's info cannot be null.");
        }
            
        var executionBrowserAttribute = memberInfo.GetCustomAttribute<ExecutionEngineAttribute>(true);
        if (executionBrowserAttribute != null)
        {
            return executionBrowserAttribute.Browser;
        }
        return Browsers.NotSet;
    }
        
    private Browsers GetExecutionBrowser(Type currentType)
    {
        if (currentType == null)
        {
            throw new ArgumentNullException("The test method's type cannot be null.");
        }
            
        var executionBrowserAttribute = currentType.GetCustomAttribute<ExecutionEngineAttribute>(true);
        if (executionBrowserAttribute != null)
        {
            return executionBrowserAttribute.Browser;
        }
        return Browsers.NotSet;
    }
}

All of the evil comes from the enormous ResolveAllDriverDependencies method. As you can see, there we have a condition where the chosen framework is WebDriver or T??esting Fr??amework. The first problem here is that if you want to introduce a third implementation of the hybrid framework (for example Coded UI), you need to add one more branch. This doesn't follow the Open Closed Principle which states that the software entities should be open for extension but closed for modification. Additional evil to the previously stated is that your behaviours project should reference the assemblies of all dependent test automation frameworks. As I mentioned at the beginning, the CORE projects should not depend on any concrete frameworks. As depicted in the image the v.0.1 of the HybridTestFramework.UITests.Core.Behaviours depends on both WebDriver and Testing Framework.

Behaviors Core Project Dependecies Concrete Frameworks

BaseTest v.0.1

C#
[TestClass]
public class BaseTest
{
    private readonly TestExecutionProvider currentTestExecutionProvider;
    private IDriver driver;
    private readonly IUnityContainer container;
    private TestContext testContextInstance;

    public BaseTest()
    {
        this.container = UnityContainerFactory.GetContainer();
        this.container.RegisterInstance<IUnityContainer>(this.container);
        this.currentTestExecutionProvider = new TestExecutionProvider();
        this.InitializeTestExecutionBehaviorObservers(
            this.currentTestExecutionProvider, this.container);
    }

    public IDriver Driver
    {
        get
        {
            return driver;
        }
    }

    public IUnityContainer Container
    {
        get
        {
            return container;
        }
    }

    public TestContext TestContext
    {
        get
        {
            return testContextInstance;
        }
        set
        {
            testContextInstance = value;
        }
    }

    public string TestName
    {
        get
        {
            return this.TestContext.TestName;
        }
    }

    [TestInitialize]
    public void CoreTestInit()
    {
        var memberInfo = GetCurrentExecutionMethodInfo();
        this.currentTestExecutionProvider.PreTestInit(
            (TestOutcome)this.TestContext.CurrentTestOutcome, 
            this.TestContext.TestName, 
            memberInfo);
        this.driver = this.container.Resolve<IDriver>();
        this.TestInit();
        this.currentTestExecutionProvider.PostTestInit(
            (TestOutcome)this.TestContext.CurrentTestOutcome, 
            this.TestContext.TestName, 
            memberInfo);
    }

    [TestCleanup]
    public void CoreTestCleanup()
    {
        var memberInfo = GetCurrentExecutionMethodInfo();
        this.currentTestExecutionProvider.PreTestCleanup(
            (TestOutcome)this.TestContext.CurrentTestOutcome, 
            this.TestContext.TestName, 
            memberInfo);
        this.TestCleanup();
        this.currentTestExecutionProvider.PostTestCleanup(
            (TestOutcome)this.TestContext.CurrentTestOutcome, 
            this.TestContext.TestName, 
            memberInfo);
    }

    public virtual void TestInit()
    {
    }

    public virtual void TestCleanup()
    {
    }

    public virtual void InitializeTestExecutionBehaviorObservers(
        TestExecutionProvider testExecutionProvider,
        IUnityContainer container)
    {
        var executionEngine = new ExecutionEngineBehaviorObserver(container);
        var videoRecording = 
            new VideoBehaviorObserver(new MsExpressionEncoderVideoRecorder());
        executionEngine.Subscribe(testExecutionProvider);
        videoRecording.Subscribe(testExecutionProvider);
    }

    private MethodInfo GetCurrentExecutionMethodInfo()
    {
        var memberInfo = this.GetType().GetMethod(this.TestContext.TestName);
        return memberInfo;
    }
}

The BaseTest class also doesn't follow the Open Closed Principle, mainly because of the InitializeTestExecutionEngineBehaviorObservers. In v.0.1, if you want to add a new observer, you need to modify the whole class which can lead to lots of regression issues in the tests that are using it.

Improved Execution Engine v.0.2

ExecutionEngineBehaviorObserver v.0.2

To solve the problems introduced in the previous points, the v.0.2 of the ExecutionEngineBehaviourObserver works entirely with abstractions. It contains a list of test execution engines that are resolved through the Unity IoC Container. If a particular execution engine is selected by the client, then we call its RegisterDependencies method. The determination and registration logic is part of the concrete implementation which is not present in this project anymore. This way, we can remove the assembly references from our behaviour core project.

C#
public class ExecutionEngineBehaviorObserver : BaseTestBehaviorObserver
   {
       private readonly IUnityContainer unityContainer;
       private Browsers executionBrowserType;
       private readonly List<ITestExecutionEngine> testExecutionEngines;

       public ExecutionEngineBehaviorObserver(IUnityContainer unityContainer)
       {
           this.unityContainer = unityContainer;
           this.testExecutionEngines =
               this.unityContainer.ResolveAll<ITestExecutionEngine>().ToList();
       }

       protected override void PostTestCleanup(object sender, TestExecutionEventArgs e)
       {
           foreach (var testExecutionEngine in this.testExecutionEngines)
           {
               if (testExecutionEngine != null)
               {
                   testExecutionEngine.Dispose();
               }
           }
       }

       protected override void PreTestInit(object sender, TestExecutionEventArgs e)
       {
           this.executionBrowserType = this.ConfigureTestExecutionBrowser(e.MemberInfo);

           foreach (var testExecutionEngine in this.testExecutionEngines)
           {
               if (testExecutionEngine.IsSelected(e.MemberInfo))
               {
                   testExecutionEngine.RegisterDependencies(executionBrowserType);
                   break;
               }
           }
       }

       private Browsers ConfigureTestExecutionBrowser(MemberInfo memberInfo)
       {
           var currentExecutionBrowserType = Browsers.Firefox;
           Browsers methodExecutionBrowser =
           this.GetExecutionBrowser(memberInfo);
           Browsers classExecutionBrowser =
           this.GetExecutionBrowser(memberInfo.DeclaringType);

           if (methodExecutionBrowser != Browsers.NotSet)
           {
               currentExecutionBrowserType = methodExecutionBrowser;
           }
           else
           {
               if (classExecutionBrowser != Browsers.NotSet)
               {
                   currentExecutionBrowserType = classExecutionBrowser;
               }
               else
               {
                   currentExecutionBrowserType = Browsers.InternetExplorer;
               }
           }

           return currentExecutionBrowserType;
       }

       private Browsers GetExecutionBrowser(MemberInfo memberInfo)
       {
           if (memberInfo == null)
           {
               throw new ArgumentNullException("The test method's info cannot be null.");
           }

           var executionBrowserAttribute =
           memberInfo.GetCustomAttribute<ExecutionEngineAttribute>(true);
           if (executionBrowserAttribute != null)
           {
               return executionBrowserAttribute.Browser;
           }
           return Browsers.NotSet;
       }

       private Browsers GetExecutionBrowser(Type currentType)
       {
           if (currentType == null)
           {
               throw new ArgumentNullException("The test method's type cannot be null.");
           }

           var executionBrowserAttribute =
           currentType.GetCustomAttribute<ExecutionEngineAttribute>(true);
           if (executionBrowserAttribute != null)
           {
               return executionBrowserAttribute.Browser;
           }
           return Browsers.NotSet;
       }
   }

BaseTest v.0.2

I did almost the same thing for the BaseTest v.0.2. Now it works with the ITestBehaviorObserver interface instead of using real observers. This way, the observers are truly plug & play because now it is only a matter of configuration to include/exclude them. There is no need to modify the BaseTest class each time.

C#
[TestClass]
public class BaseTest
{
    private readonly TestExecutionProvider currentTestExecutionProvider;
    private IDriver driver;
    private readonly IUnityContainer container;
    private TestContext testContextInstance;

    public BaseTest()
    {
        this.container = UnityContainerFactory.GetContainer();
        this.container.RegisterInstance<IUnityContainer>(this.container);
        this.currentTestExecutionProvider = new TestExecutionProvider();
        this.InitializeTestExecutionBehaviorObservers(this.currentTestExecutionProvider);
    }

    public IDriver Driver
    {
        get
        {
            return driver;
        }
    }

    public IUnityContainer Container
    {
        get
        {
            return container;
        }
    }

    public TestContext TestContext
    {
        get
        {
            return testContextInstance;
        }
        set
        {
            testContextInstance = value;
        }
    }

    public string TestName
    {
        get
        {
            return this.TestContext.TestName;
        }
    }

    [TestInitialize]
    public void CoreTestInit()
    {          
        var memberInfo = GetCurrentExecutionMethodInfo();
        this.currentTestExecutionProvider.PreTestInit(
            (TestOutcome)this.TestContext.CurrentTestOutcome,
            this.TestContext.TestName, 
            memberInfo);
        this.driver = this.container.Resolve<IDriver>();
        this.TestInit();
        this.currentTestExecutionProvider.PostTestInit(
            (TestOutcome)this.TestContext.CurrentTestOutcome,
            this.TestContext.TestName, 
            memberInfo);
    }

    [TestCleanup]
    public void CoreTestCleanup()
    {
        var memberInfo = GetCurrentExecutionMethodInfo();
        this.currentTestExecutionProvider.PreTestCleanup(
            (TestOutcome)this.TestContext.CurrentTestOutcome, 
            this.TestContext.TestName, 
            memberInfo);
        this.TestCleanup();
        this.currentTestExecutionProvider.PostTestCleanup(
            (TestOutcome)this.TestContext.CurrentTestOutcome, 
            this.TestContext.TestName, 
            memberInfo);
    }

    public virtual void TestInit()
    {
    }

    public virtual void TestCleanup()
    {
    }

    private void InitializeTestExecutionBehaviorObservers(
        TestExecutionProvider testExecutionProvider)
    {
        var observers = 
            UnityContainerFactory.GetContainer().ResolveAll<ITestBehaviorObserver>();
        foreach (var observer in observers)
        {
            observer.Subscribe(testExecutionProvider);
        }
    }

    private MethodInfo GetCurrentExecutionMethodInfo()
    {
        var memberInfo = this.GetType().GetMethod(this.TestContext.TestName);
        return memberInfo;
    }
}

Shared Behaviors Contracts Project

All common interfaces and shared logic of the v.0.2 abstract behavior solution is placed under the new HybridTestFramework.Core.Behaviours.Contracts project.

ITestExecutionEngine

Maybe the most important member of the new project is the ITestExecutionEngine interface. It is used in the HybridTestFramework.Core.Behaviours project instead of using the concrete implementation of the execution engine. And of course in the actual code of the drivers.

C#
public interface ITestExecutionEngine : IDisposable
{
    bool IsSelected(MemberInfo memberInfo);

    void RegisterDependencies(Browsers executionBrowserType);
}

BaseTestExecutionEngine

As you probably know, I am a fan of code reuse. So I decided to introduce a new base class for all test execution engines where the selection logic is held. The actual implementations are responsible only for adding the Unity registration logic and specifying the type of their execution engine attribute. Also, here we implement the shared Dispose method where the utilized driver is cleaned.

C#
public abstract class BaseTestExecutionEngine<TExecutionEngineAttribute> 
    : ITestExecutionEngine
    where TExecutionEngineAttribute : ExecutionEngineAttribute
{
    protected IDriver driver;      
    protected readonly IUnityContainer container;
    private bool isDisposed;

    public BaseTestExecutionEngine(IUnityContainer container)
    {
        this.container = container;
    }

    public void Dispose()
    {
        if (!this.isDisposed && this.driver != null)
        {
            this.driver.Quit();
            this.isDisposed = true;
        }
    }

    public bool IsSelected(MemberInfo memberInfo)
    {
        bool isSelectedOnMethodLevel = this.GetExecutionEngineTypeByMethodInfo(memberInfo);
        bool isSelectedOnClassLevel = this.GetExecutionEngineType(memberInfo.DeclaringType);

        return isSelectedOnMethodLevel || isSelectedOnClassLevel;
    }

    public abstract void RegisterDependencies(Browsers executionBrowserType);

    private bool GetExecutionEngineTypeByMethodInfo(MemberInfo memberInfo)
    {
        if (memberInfo == null)
        {
            throw new ArgumentNullException("The test method's info cannot be null.");
        }
        var executionEngineTypeMethodAttribute =
            memberInfo.GetCustomAttribute<ExecutionEngineAttribute>();
        if (executionEngineTypeMethodAttribute != null)
        {
            return true;
        }
        return false;
    }

    private bool GetExecutionEngineType(Type currentType)
    {
        if (currentType == null)
        {
            throw new ArgumentNullException("The test method's type cannot be null.");
        }

        var executionEngineClassAttribute = 
            currentType.GetCustomAttribute<ExecutionEngineAttribute>(true);
        if (executionEngineClassAttribute != null)
        {
            return true;
        }
        return false;
    }
}

The implementation of the IsSelected methods depends on the type of the specified attribute which should derive the below base ExecutionEngineAttribute that holds the type of the specified browser. It can be placed on class or method level. The latter overrides the former.

ExecutionEngineAttribute

C#
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
    Inherited = true, AllowMultiple = false)]
public class ExecutionEngineAttribute : Attribute
{
    public ExecutionEngineAttribute(Browsers browser)
    {
        this.Browser = browser;
    }

    public Browsers Browser { get; set; }
}

The different actual implementation of the hybrid test framework should have their own versions of this attribute like WebDriverExecutionEngineAttribute.

Concrete Execution Engine Implementations

Testing Framework TestExecutionEngine

There are two important things about this class. The first one is that it derives from the previously introduced BaseTestExecutionEngine and specifies the concrete implementation of the ExecutionEngineAttribute for the Testing Framework. Next, the content of the RegisterDependencies is basically the same code that was present in the v.0.1 RegisterAllDriverDependencies method under the T????est????ing Framework branch.

C#
public class TestExecutionEngine : 
    BaseTestExecutionEngine<TestingFrameworkExecutionEngineAttribute>
{
    public TestExecutionEngine(IUnityContainer container) : base(container)
    {
    }

    public override void RegisterDependencies(Browsers executionBrowserType)
    {
        var browserSettings = new BrowserSettings(executionBrowserType);
        this.driver = new TestingFrameworkDriver(this.container, browserSettings);

        this.container.RegisterType<IButton, Button>();
        this.container.RegisterType<ITextBox, TextBox>();
        this.container.RegisterType<IDiv, Div>();
        this.container.RegisterType<ISearch, Search>();
        this.container.RegisterType<IInputSubmit, InputSubmit>();

        this.container.RegisterInstance<IDriver>(this.driver);
        this.container.RegisterInstance<IBrowser>(this.driver);
        this.container.RegisterInstance<ICookieService>(this.driver);
        this.container.RegisterInstance<IDialogService>(this.driver);
        this.container.RegisterInstance<IJavaScriptInvoker>(this.driver);
        this.container.RegisterInstance<INavigationService>(this.driver);
        this.container.RegisterInstance<IElementFinder>(this.driver);

        # region 11. Failed Tests ?nalysis - Decorator Design Pattern
            
        this.container.RegisterType<
            IEnumerable<IExceptionAnalysationHandler>, IExceptionAnalysationHandler[]>();
        this.container.RegisterType<
            IUiExceptionAnalyser, UiExceptionAnalyser>();
        this.container.RegisterType<
            IElementFinder, ExceptionAnalyzedElementFinder>(
            new InjectionFactory(x => new ExceptionAnalyzedElementFinder(
                this.driver, this.container.Resolve<IUiExceptionAnalyser>())));
        this.container.RegisterType<INavigationService, ExceptionAnalyzedNavigationService>(
            new InjectionFactory(x => new ExceptionAnalyzedNavigationService(
                this.driver, this.container.Resolve<IUiExceptionAnalyser>())));
        
        #endregion
    }
}

TestingFrameworkExecutionEngineAttribute

C#
public class TestingFrameworkExecutionEngineAttribute : ExecutionEngineAttribute
{
    public TestingFrameworkExecutionEngineAttribute(
        Browsers browser = Browsers.Firefox) : base(browser)
    {
        this.Browser = browser;
    }
}

Nothing special about this attribute. Deriving from the base ExecutionEngineAttribute is important. As a bonus, you can specify which is the default browser.

WebDriver TestExecutionEngine

As in the Testing Framework implementation, here we derive from the BaseTestExecutionEngine class specifying the WebDriverExecutionEngineAttribute as a primary attribute for this version. The code under the RegisterDependencies method is identical to the one present under the WebDriver branch of the v.0.1 RegisterAllDriverDependencies method.

C#
public class TestExecutionEngine : 
    BaseTestExecutionEngine<WebDriverExecutionEngineAttribute>
{
    public TestExecutionEngine(IUnityContainer container) : base(container)
    {
    }

    public override void RegisterDependencies(Browsers executionBrowserType)
    {
        var browserSettings = new BrowserSettings(executionBrowserType);
        this.driver = new SeleniumDriver(this.container, browserSettings);

        this.container.RegisterType<IButton, Button>();
        this.container.RegisterType<ITextBox, TextBox>();
        this.container.RegisterType<IDiv, Div>();
        this.container.RegisterType<ISearch, Search>();
        this.container.RegisterType<IInputSubmit, InputSubmit>();

        this.container.RegisterInstance<IDriver>(this.driver);
        this.container.RegisterInstance<IBrowser>(this.driver);
        this.container.RegisterInstance<ICookieService>(this.driver);
        this.container.RegisterInstance<IDialogService>(this.driver);
        this.container.RegisterInstance<IJavaScriptInvoker>(this.driver);
        this.container.RegisterInstance<INavigationService>(this.driver);
        this.container.RegisterInstance<IElementFinder>(this.driver);

        # region 11. Failed Tests ?nalysis - Decorator Design Pattern
            
        this.container.RegisterType<IEnumerable<
            IExceptionAnalysationHandler>, IExceptionAnalysationHandler[]>();
        this.container.RegisterType<IUiExceptionAnalyser, UiExceptionAnalyser>();
        this.container.RegisterType<IElementFinder, ExceptionAnalyzedElementFinder>(
            new InjectionFactory(x => new ExceptionAnalyzedElementFinder(
                this.driver, this.container.Resolve<IUiExceptionAnalyser>())));
        this.container.RegisterType<INavigationService, ExceptionAnalyzedNavigationService>(
            new InjectionFactory(x => new ExceptionAnalyzedNavigationService(
                this.driver, this.container.Resolve<IUiExceptionAnalyser>())));
            
        #endregion
    }
}

WebDriverExecutionEngineAttribute

C#
public class WebDriverExecutionEngineAttribute : ExecutionEngineAttribute
{
    public WebDriverExecutionEngineAttribute(
        Browsers browser = Browsers.Firefox) : base(browser)
    {
        this.Browser = browser;
    }
}

Improved Execution Engine v.0.2 in Tests

v.0.1 Execution Engine Example

C#
[TestClass,
ExecutionEngineAttribute(ExecutionEngineType.TestStudio, Browsers.Firefox)]
public class BingTests : BaseTest
{
    [TestMethod]
    public void SearchForAutomateThePlanet()
    {
        var bingMainPage = this.Container.Resolve<BingMainPage>();
        bingMainPage.Navigate();
        bingMainPage.Search("Automate The Planet");
        bingMainPage.AssertResultsCountIsAsExpected(264);
    }
}

In v.0.1 of the tests, we specify the execution engine through the ExecutionEngineType enum.

Tests Using Separate Browsers Example

C#
[TestClass,
TestingFrameworkExecutionEngine(Browser = Browsers.Chrome)]
public class BingTests : BaseTest
{
    [AssemblyInitialize]
    public static void AssemblyInit(TestContext context)
    {
        var container = UnityContainerFactory.GetContainer();
        UnityContainerFactory.GetContainer().RegisterType<
            ITestBehaviorObserver, ExecutionEngineBehaviorObserver>(Guid.NewGuid().ToString());
        UnityContainerFactory.GetContainer().RegisterType<
            ITestExecutionEngine, TestExecutionEngine>(Guid.NewGuid().ToString());
        UnityContainerFactory.GetContainer().RegisterInstance<IUnityContainer>(container);
    }

    [TestMethod]
    public void SearchForAutomateThePlanet()
    {
        var bingMainPage = this.Container.Resolve<BingMainPage>();
        bingMainPage.Navigate();
        bingMainPage.Search("Automate The Planet");
        bingMainPage.AssertResultsCountIsAsExpected("422,000 results");
    }
}

The v.0.2 of the tests specifies the test framework with the particular test execution engine attribute, in this case - TestingFrameworkExecutionEngineAttribute. All the magic is happening in the static AssemblyInit method which will be executed only once per tests' execution. There, we specify which observers to be plugged in the tests' execution workflow. Next, we point which are the available concrete implementations of the ITestExecutionEngine interface. If we decide to create another version of the framework, we need to plug it only here to be able to use it in our tests.

Tests Projects Reference Only One Concrete Implementation of Hybrid Test Framework

In the solution, there are two concrete implementations of the hybrid framework. However, as we reference only one of them and include only it in the Unity container, we are no more obligated to reference the assemblies of the other frameworks, in this case, WebDriver.

Tests Reusing Single Browser's Instance Example

C#
[TestClass,
TestingFrameworkExecutionEngine(Browser = Browsers.Chrome)]
public class BingTests : BaseTest
{
    private static TestExecutionEngine testingFrameworkTestExecutionEngine;

    [AssemblyInitialize]
    public static void AssemblyInit(TestContext context)
    {
        var container = UnityContainerFactory.GetContainer();
        UnityContainerFactory.GetContainer().RegisterType<
        ITestExecutionEngine, TestExecutionEngine>(Guid.NewGuid().ToString());
        UnityContainerFactory.GetContainer().RegisterInstance<IUnityContainer>(container);
            
        testingFrameworkTestExecutionEngine = new TestExecutionEngine(container);
        testingFrameworkTestExecutionEngine.RegisterDependencies(Browsers.Chrome);
    }

    [AssemblyCleanup]
    public static void AssemblyCleanup()
    {
        testingFrameworkTestExecutionEngine.Dispose();
    }

    [TestMethod]
    public void SearchForAutomateThePlanet()
    {
        var bingMainPage = this.Container.Resolve<BingMainPage>();
        bingMainPage.Navigate();
        bingMainPage.Search("Automate The Planet");
        bingMainPage.AssertResultsCountIsAsExpected("422,000 results");
    }
}

Through the help of the v.0.2 refactoring, we can utilize the execution engine in another way. We can reuse a single instance of the browser for all tests. The first thing you need to do is not registering the ExecutionEngineBehaviorObserver in the Unity container. Next, you need to initialize the TestExecutionEngine yourself and call its RegisterDependencies method, and that is.

Design & Architecture

 

The post Create Hybrid Test Framework – Improved Execution Engine appeared first on Automate The Planet.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)