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 Controls

5.00/5 (4 votes)
17 Jul 2016Ms-PL3 min read 5.3K  
Build a Hybrid Test Automation Framework. Your next step will be to create a Testing Framework Driver implementation of most used web controls.

Introduction

The fifth article from the Design & Architecture Series is going to be dedicated to the creation of Hybrid Test Framework Controls and their Testing Framework's implementation. First, I am going to guide you through the process of writing a concrete implementation of the abstract controls' interfaces. After that, you will see a fully working test example.

Hybrid Test Framework Controls- Testing Framework Implementation

Updated Element

C#
public class Element<TElementType> : IElement
    where TElementType : HtmlControl, new()
{
    protected readonly TElementType 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<TElementType>();
        this.elementFinderService = new ElementFinderService(container);
    }
    // ... the rest of the code ...
}

I made a slight change in the class- it is now generic. When you derive it, you should specify the exact type of the Testing Framework's control that it will wrap so that it can be converted successfully.

Updated ContentElement

C#
public class ContentElement<TElementType> : 
    Element<TElementType>, 
    IContentElement
    where TElementType : HtmlControl, new()
{
    private readonly HtmlControl inputControl;

    public ContentElement(IDriver driver,
        ArtOfTest.WebAii.ObjectModel.Element element,
        IUnityContainer container) : base(driver, element, container)
    {
        this.inputControl = element.As<TElementType>();
    }

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

    public new bool IsEnabled
    {
        get
        {
            return this.htmlControl.IsEnabled;
        }
    }

    public void Hover()
    {
        this.inputControl.MouseHover();
    }

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

I made the same change for the ContentElement class. Additionally, I moved the IsEnabled property and the Focus method to this base class because they should be available for all content elements. I change the IContentElement interface as well.

Anchor

C#
public class Anchor : ContentElement<HtmlAnchor>, IAnchor
{
    public Anchor(IDriver driver,
        ArtOfTest.WebAii.ObjectModel.Element element,
        IUnityContainer container) : base(driver, element, container)
    {
    }

    public string Url
    {
        get
        {
            return HttpUtility.HtmlDecode(
                HttpUtility.UrlDecode(this.htmlControl.HRef));
        }
    }
}

The Anchor class inherits from the ContentElement base class. As pointed above, we need to specify the actual type of the wrapped Testing Framework element, in this case, HtmlAnchor. Another difference compared to the WebDriver's implementation of the controls is that the Href of the element should be decoded because it is not by default.

Checkbox

C#
public class Checkbox : ContentElement<HtmlInputCheckBox>, ICheckbox
{
    public Checkbox(IDriver driver,
        ArtOfTest.WebAii.ObjectModel.Element element,
        IUnityContainer container) : base(driver, element, container)
    {
    }

    public bool IsChecked
    {
        get
        {
            return this.htmlControl.Checked;
        }
    }

    public void Check()
    {
        this.htmlControl.Check(true, true, true);
        this.driver.WaitForAjax();
        this.driver.WaitUntilReady();
    }

    public void Uncheck()
    {
        this.htmlControl.Check(false, true, true);
        this.driver.WaitForAjax();
        this.driver.WaitUntilReady();
    }
}

We can use the Checked property of the HtmlInputCheckbox class to find out if the checkbox is checked. To check or uncheck the item, we just use the Testing Framework's native Check method.

ComboBox

C#
public class ComboBox : ContentElement<HtmlInputCheckBox>, IComboBox
{
    private readonly string jqueryExpression = 
        "$(\"select[id$='{0}'] option\").each(function() 
          {{if (this.text == '{1}') {{ $(this).parent().val(this.value).change() }} }});";
        
    public ComboBox(IDriver driver,
        ArtOfTest.WebAii.ObjectModel.Element element,
        IUnityContainer container) : base(driver, element, container)
    {
    }

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

    public void SelectValue(string value)
    {
        this.JQuerySelectByText(this.htmlControl.ID, value);
    }

    private void JQuerySelectByText(string expression, string text)
    {
        string javaScriptToBeExecuted = string.Format(this.jqueryExpression, expression, text);
        this.driver.InvokeScript(javaScriptToBeExecuted);
        this.driver.WaitForAjax();
    }
}

Through the SelectedValue property, you can get the currently selected value of the combo box. As you can see from the code above, I used a JavaScript code to select a particular element. You can use the native Testing Framework's method, but it is a little bit slower.

TextBox

C#
public class TextBox : ContentElement<HtmlInputText>, ITextBox
{
    public TextBox(IDriver driver,
        ArtOfTest.WebAii.ObjectModel.Element element,
        IUnityContainer container) : base(driver, element, container)
    {
    }

    public string Text
    {
        get
        {
            return this.htmlControl.Text;
        }
        set
        {
            this.htmlControl.Text = value;
        }
    }

    public void SimulateRealTyping(string valueToBeTyped)
    {
        this.htmlControl.Focus();
        this.htmlControl.MouseClick();
        Manager.Current.Desktop.KeyBoard.TypeText(valueToBeTyped, 50, 100, true);
        this.driver.WaitForAjax();
        this.driver.WaitUntilReady();
    }
}

It is straightforward to get the text of a text box using Testing Framework. You can use the HtmlInputText control's Text property. The same property can be used to type a text. To simulate real typing, the TypeText method of the KeyBoard class is called.

Div

C#
public class Div : ContentElement<HtmlDiv>, IDiv
{
    public Div(IDriver driver,
        ArtOfTest.WebAii.ObjectModel.Element element,
        IUnityContainer container) : base(driver, element, container)
    {
    }
}

The Div element is nothing more than a content element. However, to be able to use it in a hybrid manner, we need to have a separate class.

KendoGrid

C#
public class KendoGrid : Element<HtmlControl>, IKendoGrid
{
    private readonly string gridId;

    public KendoGrid(IDriver driver,
        ArtOfTest.WebAii.ObjectModel.Element element,
        IUnityContainer container) : base(driver, element, container)
    {
        this.gridId = this.htmlControl.ID;
    }

    public void RemoveFilters()
    {
        string jsToBeExecuted = this.GetGridReference();
        jsToBeExecuted = string.Concat(jsToBeExecuted, "grid.dataSource.filter([]);");
        this.driver.InvokeScript(jsToBeExecuted);
    }

    public int TotalNumberRows()
    {
        string jsToBeExecuted = this.GetGridReference();
        jsToBeExecuted = string.Concat(jsToBeExecuted, "grid.dataSource.total();");
        var jsResult = this.driver.InvokeScript(jsToBeExecuted);
        return int.Parse(jsResult.ToString());
    }

    public void Reload()
    {
        string jsToBeExecuted = this.GetGridReference();
        jsToBeExecuted = string.Concat(jsToBeExecuted, "grid.dataSource.read();");
        this.driver.InvokeScript(jsToBeExecuted);
    }

    public int GetPageSize()
    {
        string jsToBeExecuted = this.GetGridReference();
        jsToBeExecuted = string.Concat(jsToBeExecuted, "return grid.dataSource.pageSize();");
        var currentResponse = this.driver.InvokeScript(jsToBeExecuted);
        int pageSize = int.Parse(currentResponse.ToString());
        return pageSize;
    }

    public void ChangePageSize(int newSize)
    {
        string jsToBeExecuted = this.GetGridReference();
        jsToBeExecuted = string.Concat(jsToBeExecuted, "grid.dataSource.pageSize(", newSize, ");");
        this.driver.InvokeScript(jsToBeExecuted);
    }

    public void NavigateToPage(int pageNumber)
    {
        string jsToBeExecuted = this.GetGridReference();
        jsToBeExecuted = string.Concat(jsToBeExecuted, "grid.dataSource.page(", pageNumber, ");");
        this.driver.InvokeScript(jsToBeExecuted);
    }

    public void Sort(string columnName, SortType sortType)
    {
        string jsToBeExecuted = this.GetGridReference();
        jsToBeExecuted =
            string.Concat(jsToBeExecuted,
                "grid.dataSource.sort({field: '",
                columnName, "', dir: '",
                sortType.ToString().ToLower(), "'});");
        this.driver.InvokeScript(jsToBeExecuted);
    }

    public List<T> GetItems<T>() where T : class
    {
        string jsToBeExecuted = this.GetGridReference();
        jsToBeExecuted =
            string.Concat(
                jsToBeExecuted,
                "grid.dataSource.data();");
        var jsResults = this.driver.InvokeScript(jsToBeExecuted);
        var items = JsonConvert.DeserializeObject<List<T>>(jsResults.ToString());
        return items;
    }

    public void Filter(
        string columnName,
        FilterOperator filterOperator,
        string filterValue)
    {
        this.Filter(new GridFilter(columnName, filterOperator, filterValue));
    }

    public void Filter(params GridFilter[] gridFilters)
    {
        string jsToBeExecuted = this.GetGridReference();
        StringBuilder sb = new StringBuilder();
        sb.Append(jsToBeExecuted);
        sb.Append("grid.dataSource.filter({ logic: \"and\", filters: [");
        foreach (var currentFilter in gridFilters)
        {
            DateTime filterDateTime;
            bool isFilterDateTime = DateTime.TryParse(currentFilter.FilterValue, out filterDateTime);
            string filterValueToBeApplied =
                                            isFilterDateTime ? string.Format("new Date({0}, {1}, {2})",
                                                filterDateTime.Year,
                                                filterDateTime.Month - 1,
                                                filterDateTime.Day) :
                                            string.Format("\"{0}\"", currentFilter.FilterValue);
            string kendoFilterOperator =
                this.ConvertFilterOperatorToKendoOperator(currentFilter.FilterOperator);
            sb.Append(string.Concat("{ field: \"",
                currentFilter.ColumnName,
                "\", operator: \"",
                kendoFilterOperator,
                "\", value: ",
                filterValueToBeApplied,
                " },"));
        }
        sb.Append("] });");
        jsToBeExecuted = sb.ToString().Replace(",]", "]");
        this.driver.InvokeScript(jsToBeExecuted);
    }

    public int GetCurrentPageNumber()
    {
        string jsToBeExecuted = this.GetGridReference();
        jsToBeExecuted = string.Concat(jsToBeExecuted, "return grid.dataSource.page();");
        var result = this.driver.InvokeScript(jsToBeExecuted);
        int pageNumber = int.Parse(result.ToString());
        return pageNumber;
    }

    private string GetGridReference()
    {
        string initializeKendoGrid =
            string.Format("var grid = $('#{0}').data('kendoGrid');", this.gridId);

        return initializeKendoGrid;
    }

    private string ConvertFilterOperatorToKendoOperator(FilterOperator filterOperator)
    {
        string kendoFilterOperator = string.Empty;
        switch (filterOperator)
        {
            case FilterOperator.EqualTo:
                kendoFilterOperator = "eq";
                break;
            case FilterOperator.NotEqualTo:
                kendoFilterOperator = "neq";
                break;
            case FilterOperator.LessThan:
                kendoFilterOperator = "lt";
                break;
            case FilterOperator.LessThanOrEqualTo:
                kendoFilterOperator = "lte";
                break;
            case FilterOperator.GreaterThan:
                kendoFilterOperator = "gt";
                break;
            case FilterOperator.GreaterThanOrEqualTo:
                kendoFilterOperator = "gte";
                break;
            case FilterOperator.StartsWith:
                kendoFilterOperator = "startswith";
                break;
            case FilterOperator.EndsWith:
                kendoFilterOperator = "endswith";
                break;
            case FilterOperator.Contains:
                kendoFilterOperator = "contains";
                break;
            case FilterOperator.NotContains:
                kendoFilterOperator = "doesnotcontain";
                break;
            case FilterOperator.IsAfter:
                kendoFilterOperator = "gt";
                break;
            case FilterOperator.IsAfterOrEqualTo:
                kendoFilterOperator = "gte";
                break;
            case FilterOperator.IsBefore:
                kendoFilterOperator = "lt";
                break;
            case FilterOperator.IsBeforeOrEqualTo:
                kendoFilterOperator = "lte";
                break;
            default:
                throw new ArgumentException("The specified filter operator is not supported.");
        }

        return kendoFilterOperator;
    }
}

Sometimes, there are more complex controls such as the ?K?endo Grid. You can read a lot more in my dedicated article- Automate Telerik Kendo Grid with WebDriver and JavaScript.

Test Framework Driver Controls in Test

PageObject

C#
public partial class BingMainPage : BasePage
{
    public BingMainPage(
        IElementFinder elementFinder,
        INavigationService navigationService) :
        base(elementFinder, navigationService)
    {
    }

    public void Navigate()
    {
        this.NavigationService.NavigateByAbsoluteUrl(@"http://www.bing.com/");
    }

    public void Search(string textToType)
    {
        this.SearchBox.Text = textToType;
        this.GoButton.Click();
    }
    
    public int GetResultsCount()
    {
        int resultsCount = default(int);
        resultsCount = int.Parse(this.ResultsCountDiv.Content);
        return resultsCount;
    }
}

This is actually the same page that I used in the test examples for the Selenium Driver controls' implementation. We are going to use the same pages and pages' maps. The only thing that we are going to change is the implementation of the IElementFinder and INavigationService interfaces.

Page Object Map

C#
public partial class BingMainPage
{
    public ITextBox SearchBox
    {
        get
        {
            return this.ElementFinder.Find<ITextBox>(By.Id("sb_form_q"));
        }
    }

    public IButton GoButton
    {
        get
        {
            return this.ElementFinder.Find<IButton>(By.Id("sb_form_go"));
        }
    }

    public IDiv ResultsCountDiv
    {
        get
        {
            return this.ElementFinder.Find<IDiv>(By.Id("b_tween"));
        }
    }
}

To run the tests with Testing Framework, you don't need to create a separate page or page map. The beauty of the hybrid test framework is that we can reuse their code.

Page Object Asserter

C#
public static class BingMainPageAsserter
{
    public static void AssertResultsCountIsAsExpected(
        this BingMainPage page, 
        int expectedCount)
    {
        Assert.AreEqual(
            page.ResultsCountDiv.Content, 
            expectedCount,
            "The results count is not as expected.");
    }
}

Test Example

C#
[TestClass]
public class BingTests
{
    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<IButton, Button>();
        this.container.RegisterType<ITextBox, TextBox>();
        this.container.RegisterType<IDiv, Div>();

        this.container.RegisterType<BingMainPage>();

        this.container.RegisterInstance<IUnityContainer>(this.container);
        this.container.RegisterInstance<BrowserSettings>(
            BrowserSettings.DefaultInternetExplorerSettings);

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

    [TestMethod]
    public void SearchForAutomateThePlanet()
    {
        var bingMainPage = this.container.Resolve<BingMainPage>();
        bingMainPage.Navigate();
        bingMainPage.Search("Automate The Planet");
        bingMainPage.AssertResultsCountIsAsExpected(264);
    }
}

As usual, we first register all driver types. It is important to call the RegisterInstance method for the created Testing Framework's driver. We also register the types of all web controls. So when you resolve the BingMainPage, the Testing Framework driver instance will be passed as a parameter to the page object's constructor. Also, when the ElementFinderService tries to determine the different controls' interfaces, it will get their Testing Framework's implementation.

Design & Architecture

The post Create Hybrid Test Framework – Testing Framework Driver Controls 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)