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

Create Hybrid Test Framework – Testing Framework Driver Implementation

5.00/5 (1 vote)
10 Jul 2016Ms-PL4 min read 8.1K  
Build a Hybrid Test Automation Framework. Learn how to create an abstract Testing Framework Implementation of it following SOLID principles. The post Create Hybrid Test Framework – Testing Framework Driver Implementation appeared first on Automate The Planet.

Introduction

The fourth publication from the Design & Architecture Series is about the creation of a concrete implementation of the hybrid test framework through Testing Framework. Also, you will find out how to find and resolve web elements though Unity IoC Container.

Create Testing Framework Driver Implementation

Create a new project - HybridTestFramework.UITests.TestingFramework. Add a reference to the core project HybridTestFramework.UITests.Core (the project that contains all main interfaces). You need to install the Unity NuGet package. Add references to all Testing Framework DLLs. Please refer to my article Getting Started with Telerik Testing Framework C# in 10 Minutes. Similar to the Selenium Driver implementation, you have a partial class for every core driver interface, placed under the Engine folder.

Image 1

Implement IDriver Interface

Below, you can find the main partial class of the Testing Framework Driver. It requires an instance of the Unity IoC Container and BrowserSettings where different execution settings are placed. I am not going to explain in detail the Testing Framework related classes. You can read more about them in my Testing Framework Series. Based on the BrowserSettings, a new Testing Framework Manager is created, and a new browser instance is started.

C#
public partial class TestingFrameworkDriver : IDriver
{
    private readonly Browser originalBrowser;
    private readonly ElementFinderService elementFinderService;
    private Manager driver;
    private IUnityContainer container;
    private BrowserSettings browserSettings;
    private Browser currentActiveBrowser;

    public TestingFrameworkDriver(
        IUnityContainer container,
        BrowserSettings browserSettings)
    {
        this.container = container;
        this.browserSettings = browserSettings;
        this.InitializeManager(browserSettings);
        this.LaunchNewBrowser();
        this.originalBrowser = this.driver.ActiveBrowser;
        this.currentActiveBrowser = this.driver.ActiveBrowser;
        this.elementFinderService = new ElementFinderService(container);
    }

    private void InitializeManager(BrowserSettings browserSettings)
    {
        if (Manager.Current == null)
        {
            Settings localSettings = new Settings();
            localSettings.Web.KillBrowserProcessOnClose = true;
            localSettings.Web.RecycleBrowser = true;
            localSettings.DisableDialogMonitoring = true;
            localSettings.UnexpectedDialogAction =
                UnexpectedDialogAction.DoNotHandle;
            localSettings.ElementWaitTimeout =
                browserSettings.ElementsWaitTimeout;
            this.ResolveBrowser(
                localSettings,
                browserSettings.Type);
            if (localSettings.Web.DefaultBrowser != BrowserType.NotSet)
            {
                this.driver = new Manager(localSettings);
                this.driver.Start();
            }
        }
    }

    private void ResolveBrowser(
        Settings localSettings,
        Browsers executionBrowser)
    {
        switch (executionBrowser)
        {
            case Browsers.NotSet:
            case Browsers.InternetExplorer:
                localSettings.Web.ExecutingBrowsers.Add(
                    BrowserExecutionType.InternetExplorer);
                localSettings.Web.Browser =
                    BrowserExecutionType.InternetExplorer;
                localSettings.Web.DefaultBrowser =
                    BrowserType.InternetExplorer;
                break;
            case Browsers.Safari:
                localSettings.Web.ExecutingBrowsers.Add(
                    BrowserExecutionType.Safari);
                localSettings.Web.Browser =
                    BrowserExecutionType.Safari;
                localSettings.Web.DefaultBrowser =
                    BrowserType.Safari;
                break;
            case Browsers.Chrome:
                localSettings.Web.ExecutingBrowsers.Add(
                    BrowserExecutionType.Chrome);
                localSettings.Web.Browser =
                    BrowserExecutionType.Chrome;
                localSettings.Web.DefaultBrowser =
                    BrowserType.Chrome;
                break;
            case Browsers.Firefox:
                localSettings.Web.ExecutingBrowsers.Add(
                    BrowserExecutionType.FireFox);
                localSettings.Web.Browser =
                    BrowserExecutionType.FireFox;
                localSettings.Web.DefaultBrowser =
                    BrowserType.FireFox;
                break;
            case Browsers.NoBrowser:
                localSettings.Web.ExecutingBrowsers.Clear();
                localSettings.Web.Browser =
                    BrowserExecutionType.NotSet;
                localSettings.Web.DefaultBrowser =
                    BrowserType.NotSet;
                break;
            default:
                localSettings.Web.ExecutingBrowsers.Add(
                    BrowserExecutionType.InternetExplorer);
                localSettings.Web.Browser =
                    BrowserExecutionType.InternetExplorer;
                localSettings.Web.DefaultBrowser =
                    BrowserType.InternetExplorer;
                break;
        }
    }
}

ElementFinderService

ElementFinderService is probably one of the most important classes in the whole Testing Framework concrete implementation of the hybrid test framework. First, we convert the abstract By expression to Testing Framework's find expression through the help of the extension methods placed in the ByExtensions class. Then, we wait for an element with the specified expression to show up. If such an element does not exist, we throw a new ElementTimeoutException. Through the ThrowTimeoutExceptionIfElementIsNull method, we create a meaningful expression's message where you can find the name of the not-found element and the current URL. The Find context parameter can be Find's instance of the currently active browser or the Find of a specific web element. We create a new instance of the particular Testing Framework element's implementation through the ResolveElement generic method.

C#
public class ElementFinderService
{
    private readonly IUnityContainer container;

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

    public TElement Find<TElement>(IDriver driver, Find findContext, Core.By by)
        where TElement : class, Core.Controls.IElement
    {
        string testingFrameworkExpression = by.ToTestingFrameworkExpression();
        this.WaitForExists(driver, testingFrameworkExpression);
        var element = findContext.ByExpression(by.ToTestingFrameworkExpression());
        TElement result = this.ResolveElement<TElement>(driver, element);

        return result;
    }

    public IEnumerable<TElement> FindAll<TElement>(IDriver driver, Find findContext, Core.By by)
        where TElement : class, Core.Controls.IElement
    {
        string testingFrameworkExpression = by.ToTestingFrameworkExpression();
        this.WaitForExists(driver, testingFrameworkExpression);
        var elements = findContext.AllByExpression(testingFrameworkExpression);
        List<TElement> resolvedElements = new List<TElement>();
        foreach (var currentElement in elements)
        {
            TElement result = this.ResolveElement<TElement>(driver, currentElement);
            resolvedElements.Add(result);
        }

        return resolvedElements;
    }

    public bool IsElementPresent(Find findContext, Core.By by)
    {
        try
        {
            string controlFindExpression = by.ToTestingFrameworkExpression();
            Manager.Current.ActiveBrowser.RefreshDomTree();
            HtmlFindExpression hfe = new HtmlFindExpression(controlFindExpression);
            Manager.Current.ActiveBrowser.WaitForElement(hfe, 5000, false);
        }
        catch (TimeoutException)
        {
            return false;
        }
        catch (FindElementException)
        {
            return false;
        }

        return true;
    }

    private void WaitForExists(IDriver driver, string findExpression)
    {
        try
        {
            driver.WaitUntilReady();
            HtmlFindExpression hfe = new HtmlFindExpression(findExpression);
            Manager.Current.ActiveBrowser.WaitForElement(hfe, 5000, false);
        }
        catch (Exception)
        {
            this.ThrowTimeoutExceptionIfElementIsNull(driver, findExpression);
        }
    }

    private TElement ResolveElement<TElement>(
        IDriver driver,
        ArtOfTest.WebAii.ObjectModel.Element element)
        where TElement : class, Core.Controls.IElement
    {
        TElement result = this.container.Resolve<TElement>(
            new ResolverOverride[]
            {
                new ParameterOverride("driver", driver),
                new ParameterOverride("element", element),
                new ParameterOverride("container", this.container)
            });
        return result;
    }

    private void ThrowTimeoutExceptionIfElementIsNull(IDriver driver, params string[] customExpression)
    {
        StackTrace stackTrace = new StackTrace();
        StackFrame[] stackFrames = stackTrace.GetFrames();
        StackFrame callingFrame = stackFrames[3];
        MethodBase method = callingFrame.GetMethod();
        string currentUrl = driver.Url;
        throw new ElementTimeoutException(
            string.Format(
            "TIMED OUT- for element with Find Expression:\n {0}\n 
            Element Name: {1}.{2}\n URL: {3}\nElement Timeout: {4}",
            string.Join(",", customExpression.Select(p => p.ToString()).ToArray()),
            method.ReflectedType.FullName, method.Name, 
            currentUrl, Manager.Current.Settings.ElementWaitTimeout));
    }
}

You can find more about the Testing Framework's find expression in my article Getting Started with Telerik Testing Framework C# in 10 Minutes.

C#
public static class ByExtensions
{
    public static string ToTestingFrameworkExpression(this Core.By by)
    {
        string controlFindExpression = string.Empty;
        switch (by.Type)
        {
            case SearchType.Id:
                controlFindExpression = by.Value.GenerateIdExpression();
                break;
            case SearchType.IdEndingWith:
                controlFindExpression = by.Value.GenerateIdEndingWithExpression();
                break;
            case SearchType.IdContaining:
                controlFindExpression = by.Value.GenerateIdContainingExpression();
                break;
            case SearchType.CssClass:
                controlFindExpression = by.Value.GenerateClassContainingExpression();
                break;
            case SearchType.XPath:
                controlFindExpression = by.Value.GenerateXpathExpression();
                break;
            case SearchType.CssSelector:
                controlFindExpression = by.Value.GenerateClassContainingExpression();
                break;
            case SearchType.Name:
                controlFindExpression = by.Value.GenerateNameContainingExpression();
                break;
            case SearchType.ValueEndingWith:
                controlFindExpression = by.Value.GenerateValueEndingWithExpression();
                break;
            case SearchType.LinkTextContaining:
                controlFindExpression = by.Value.GenerateLinkHrefContainingExpression();
                break;
            case SearchType.LinkText:
                controlFindExpression = by.Value.GenerateLinkHrefExpression();
                break;
            case SearchType.CssClassContaining:
                controlFindExpression = by.Value.GenerateClassContainingExpression();
                break;
            case SearchType.InnerTextContains:
                controlFindExpression = by.Value.GenerateInnerTextContainingExpression();
                break;
            case SearchType.NameEndingWith:
                controlFindExpression = by.Value.GenerateNameEndingWithExpression();
                break;
            default:
                throw new Exception("The specified locator type was not find.");
        }

        return controlFindExpression;
    }
}

Implement IElementFinder Interface

In the partial class for the IElementFinder interface, we just call the already created instance of the ElementFinderService. We pass the currently active browser's Find context to the ElementFinderService's methods.

C#
public partial class TestingFrameworkDriver : IElementFinder
{
    public TElement Find<TElement>(Core.By by)
        where TElement : class, Core.Controls.IElement
    {
        return this.elementFinderService.Find<TElement>(
            this,
            this.currentActiveBrowser.Find,
            by);
    }

    public IEnumerable<TElement> FindAll<TElement>(Core.By by)
        where TElement : class, Core.Controls.IElement
    {
        return this.elementFinderService.FindAll<TElement>(
            this,
            this.currentActiveBrowser.Find,
            by);
    }

    public bool IsElementPresent(Core.By by)
    {
        return this.elementFinderService.IsElementPresent(
            this.currentActiveBrowser.Find,
            by);
    }
}

Implement IElement Interface

The parent of all Testing Framework's web elements is the HtmlControl class. So our base Element class wraps it. The find methods return Element objects. You can convert them to the specific HtmlControl classes via the As method. As you know, you can search inside web elements so the IElement interface derives from the IElementFinder interface. Again, we wrap the calls to the ElementFinderService but this time, we pass the Find context of the currently located HTML control.

C#
public class Element : IElement
{
    protected readonly HtmlControl htmlControl;
    protected readonly ElementFinderService elementFinderService;
    protected readonly IDriver driver;

    public Element(
        IDriver driver,
        ArtOfTest.WebAii.ObjectModel.Element element,
        IUnityContainer container)
    {
        this.driver = driver;
        this.htmlControl = element.As<HtmlControl>();
        this.elementFinderService = new ElementFinderService(container);
    }

    public string GetAttribute(string name)
    {
        var attribute = 
            this.htmlControl.Attributes.FirstOrDefault(x => x.Name == name);
        return attribute == null ? null : attribute.Value;
    }

    public void WaitForExists()
    {
        this.htmlControl.Wait.ForExists();
    }

    public void WaitForNotExists()
    {
        this.htmlControl.Wait.ForExistsNot();
    }

    public void Click()
    {
        this.htmlControl.Click();
    }

    public void MouseClick()
    {
        this.htmlControl.ScrollToVisible(
            ScrollToVisibleType.ElementTopAtWindowTop);
        this.htmlControl.MouseClick();
    }

    public bool IsVisible
    {
        get
        {
            return this.htmlControl.IsVisible();
        }
    }

    public int Width
    {
        get
        {
            return int.Parse(this.GetAttribute("width"));
        }
    }

    public string CssClass
    {
        get
        {
            return this.htmlControl.CssClass;
        }
    }

    public string Content
    {
        get
        {
            return this.htmlControl.BaseElement.InnerText;
        }
    }

    public TElement Find<TElement>(Core.By by)
        where TElement : class, Core.Controls.IElement
    {
        return this.elementFinderService.Find<TElement>(
            this.driver,
            this.htmlControl.Find,
            by);
    }

    public IEnumerable<TElement> FindAll<TElement>(Core.By by)
        where TElement : class, Core.Controls.IElement
    {
        return this.elementFinderService.FindAll<TElement>(
            this.driver,
            this.htmlControl.Find, by);
    }

    public bool IsElementPresent(Core.By by)
    {
        return this.elementFinderService.IsElementPresent(
            this.htmlControl.Find,
            by);
    }
}

Implement IBrowser Interface

C#
public partial class TestingFrameworkDriver : IBrowser
{
    public BrowserSettings BrowserSettings
    {
        get
        {
            return this.browserSettings;
        }
    }

    public string SourceString
    {
        get
        {
            return this.driver.ActiveBrowser.ViewSourceString;
        }
    }

    public void SwitchToFrame(IFrame newContainer)
    {
        this.RefreshDomTree();
        this.currentActiveBrowser =
            this.driver.ActiveBrowser.Frames[newContainer.Name];
    }

    public IFrame GetFrameByName(string frameName)
    {
        return new TestStudioFrame(frameName);
    }

    public void SwitchToDefault()
    {
        this.RefreshDomTree();
        this.currentActiveBrowser = this.originalBrowser;
    }

    public void Quit()
    {
        if (Manager.Current != null)
        {
            Manager.Current.Dispose();
        }
        if (this.driver != null)
        {
            this.driver.Dispose();
            this.driver = null;
        }
    }

    public void WaitForAjax()
    {
        this.driver.ActiveBrowser.WaitForAjax(browserSettings.ScriptTimeout);
    }

    public void WaitUntilReady()
    {
        this.driver.ActiveBrowser.WaitUntilReady();
    }

    public void FullWaitUntilReady()
    {
        this.WaitForAjax();
        this.WaitUntilReady();
    }

    public void RefreshDomTree()
    {
        this.driver.ActiveBrowser.RefreshDomTree();
    }

    public void ClickBackButton()
    {
        this.driver.ActiveBrowser.GoBack();
    }

    public void ClickForwardButton()
    {
        this.driver.ActiveBrowser.GoForward();
    }

    public void LaunchNewBrowser()
    {
        this.driver.LaunchNewBrowser();
    }

    public void MaximizeBrowserWindow()
    {
        if (!this.currentActiveBrowser.Window.IsMaximized)
        {
            this.currentActiveBrowser.Window.Maximize();
        }
    }

    public void ClickRefresh()
    {
        this.driver.ActiveBrowser.Refresh();
    }
}

The different methods in this class only wrap the Testing Framework's native methods.

Implement ICookieService Interface

You can use the ICookieService in your tests or pages to work with cookies. The ClearAllCookies method comes in handy to assure the clean state of your browser before tests' execution.

C#
public partial class TestingFrameworkDriver : ICookieService
{
    public string GetCookie(string host, string cookieName)
    {
        string currentCookieValue = null;
        var cookies =
            this.driver.ActiveBrowser.Cookies.GetCookies(host);
        foreach (Cookie currentCookie in cookies)
        {
            if (currentCookie.Name.Equals(cookieName))
            {
                currentCookieValue = currentCookie.Value;
            }
        }

        return currentCookieValue;
    }

    public void AddCookie(string cookieName, string cookieValue, string host)
    {
        Cookie cookie = new Cookie()
        {
            Name = cookieName,
            Value = cookieValue,
            Domain = host,
            Path = "/"
        };
        this.driver.ActiveBrowser.Cookies.SetCookie(cookie);
    }

    public void DeleteCookie(string cookieName)
    {
        this.driver.ActiveBrowser.Cookies.DeleteCookie(cookieName);
    }

    public void CleanAllCookies()
    {
        this.driver.ActiveBrowser.ClearCache(
            BrowserCacheType.Cookies);
        this.driver.ActiveBrowser.ClearCache(
            BrowserCacheType.TempFilesCache);
    }
}

Implement INavigationService Interface

C#
public partial class TestingFrameworkDriver : INavigationService
{
    public event EventHandler<PageEventArgs> Navigated;

    public string Url
    {
        get
        {
            return this.driver.ActiveBrowser.Url;                
        }
    }

    public string Title
    {
        get
        {
            return this.driver.ActiveBrowser.PageTitle;
        }
    }

    public void Navigate(
        string relativeUrl,
        string currentLocation,
        bool sslEnabled = false)
    {
        throw new NotImplementedException();
    }

    public void NavigateByAbsoluteUrl(
        string absoluteUrl,
        bool useDecodedUrl = true)
    {
        this.currentActiveBrowser.NavigateTo(absoluteUrl, true);
    }

    public void Navigate(
        string currentLocation,
        bool sslEnabled = false)
    {
        throw new NotImplementedException();
    }

    public void WaitForUrl(string url)
    {
        int timeout = this.BrowserSettings.PageLoadTimeout;
        this.driver.ActiveBrowser.WaitForUrl(url, false, timeout);
    }

    public void WaitForPartialUrl(string url)
    {
        int timeout = this.BrowserSettings.PageLoadTimeout;
        this.driver.ActiveBrowser.WaitForUrl(url, true, timeout);
    }

    private void RaiseNavigated(string url)
    {
        if (this.Navigated != null)
        {
            this.Navigated(this, new PageEventArgs(url));
        }
    }
}

Compared to the Selenium Driver implementation of the hybrid test framework, I believe that Testing Framework provides more advanced navigation capabilities. As you can see, their usage is straightforward.

Implement IJavaScriptInvoker Interface

C#
public partial class TestingFrameworkDriver : IJavaScriptInvoker
{
    public string InvokeScript(string script)
    {
        return this.driver.ActiveBrowser.Actions.InvokeScript(script);
    }
}

You can use the IJavaScriptInvoker interface in your pages or tests to invoke JavaScript code. Also, it comes in handy when you create the implementation for some more sophisticated web controls such as grids, data pickers and so on.

Testing Framework Driver Implementation in Tests

Register Types and Instances- Unity IoC Container

C#
private IDriver driver;
private IUnityContainer container;

[TestInitialize]
public void SetupTest()
{
    this.container = new UnityContainer();
    this.container.RegisterType<IDriver, Engine.TestingFrameworkDriver>();
    this.container.RegisterType<INavigationService, Engine.TestingFrameworkDriver>();
    this.container.RegisterType<IBrowser, Engine.TestingFrameworkDriver>();
    this.container.RegisterType<ICookieService, Engine.TestingFrameworkDriver>();
    this.container.RegisterType<IDialogService, Engine.TestingFrameworkDriver>();
    this.container.RegisterType<IElementFinder, Engine.TestingFrameworkDriver>();
    this.container.RegisterType<IJavaScriptInvoker, Engine.TestingFrameworkDriver>();
    this.container.RegisterType<IElement, Element>();
    this.container.RegisterType<IButton, Button>();
    this.container.RegisterInstance<IUnityContainer>(this.container);
    this.container.RegisterInstance<BrowserSettings>(BrowserSettings.DefaultInternetExplorerSettings);
    this.driver = this.container.Resolve<IDriver>();
}

The code is copy-paste from the Selenium Driver tests examples. The only difference is that instead of SeleniumDriver, we register in Unity the new TestingFrameworkDriver implementation of the hybrid test framework's engine and controls.

Test Example

C#
[TestMethod]
public void NavigateToAutomateThePlanet()
{
    this.driver.NavigateByAbsoluteUrl(@"http://automatetheplanet.com/");
    var blogButton = this.driver.Find<IButton>(
        By.Xpath("//*[@id='tve_editor']/div[2]/div[4]/div/div/div/div/div/a"));
    blogButton.Hover();
    Console.WriteLine(blogButton.Content);
    this.driver.NavigateByAbsoluteUrl(
                @"http://automatetheplanet.com/download-source-code/");
    this.driver.ClickBackButton();
    Console.WriteLine(this.driver.Title);
}

Absolutely nothing changes in the body of the tests. This is the whole point of creating a hybrid test framework, isn't it? If you want to execute the same test with Selenium WebDriver, you only need to change the registrations in Unity.

Design & Architecture

The post Create Hybrid Test Framework – Testing Framework Driver Implementation appeared first on Automate The Planet.

All images are purchased from DepositPhotos.com and cannot be downloaded and used for free.
License Agreement

License

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