Click here to Skip to main content
15,880,972 members
Articles / Programming Languages / C#

Create Hybrid Test Framework – Selenium Driver Controls

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
4 Jul 2016Ms-PL4 min read 9.1K   3  
Build a Hybrid Test Automation Framework. Your next step will be to create a Selenium WebDriver implementation of most used web controls.The post Create Hybrid Test Framework – Selenium Driver Controls appeared first on Automate The Planet.

Introduction

The third publication from the Design & Architecture Series is going to be dedicated to the creation of Hybrid Test Framework Controls and their Selenium WebDriver implementation. First, I am going to show you how to create several controls' interfaces that will be part of the Hybrid Test Framework Core project. After that, I am going to guide you through the process of their concrete implementation via Selenium WebDriver. Finally, you will see a fully working test example.

Hybrid Test Framework Controls

IContentElement Interface

C#
public interface IContentElement : IElement
{
    new string Content { get; }

    void Hover();
}

This is the interface for all elements that can have an inner text. You can get their text through the Content property and Hover on them.

ContentElement Selenium Driver Implementation

C#
public class ContentElement : Element, IContentElement
{
    public ContentElement(
        IWebDriver driver,
        IWebElement webElement,
        IUnityContainer container) : base(driver, webElement, container)
    {
    }

    public new string Content
    {
        get
        {
            return this.webElement.Text;
        }
    }

    public void Hover()
    {
        Actions action = new Actions(driver);
        action.MoveToElement(this.webElement).Perform();
    }
}

The concrete Selenium Driver implementation inherits from the Element base class. You can find its code in my previous article Create Hybrid Test Automation Framework - Selenium Driver Implementation. Also, to be able to use the control in the hybrid mode, we need to implement the IContentElement interface.To get the text of the WebDriver element, we use the Text property of the IWebElement interface. To simulate hover over the item, we use the Selenium Actions class.

IAnchor Interface

C#
public interface IAnchor : IContentElement
{
    string Url { get; }
}

The only particular part of the interface is the Url property.

Anchor Selenium Driver Implementation

C#
public class Anchor : ContentElement, IAnchor
{
    public Anchor(
        IWebDriver driver,
        IWebElement webElement,
        IUnityContainer container) : base(driver, webElement, container)
    {
    }

    public string Url
    {
        get
        {
            return this.webElement.GetAttribute("href");
        }
    }
}

The Anchor class inherits from the ContentElement base class instead from the Element class. We use the GetAttribute IWebElement's method to get the HREF of the item.

ICheckbox Interface

C#
public interface ICheckbox : IContentElement
{
    bool IsEnabled { get; }

    bool IsChecked { get; }

    void Check();

    void Uncheck();
}

Checkbox Selenium Driver Implementation

C#
public class Checkbox : ContentElement, ICheckbox
{
    public Checkbox(
        IWebDriver driver,
        IWebElement webElement,
        IUnityContainer container) : base(driver, webElement, container)
    {
    }

    public bool IsChecked
    {
        get
        {
            return this.webElement.Selected;
        }
    }

    public void Check()
    {
        if (!this.webElement.Selected)
        {
            this.webElement.Click();
        }
    }

    public void Uncheck()
    {
        if (this.webElement.Selected)
        {
            this.webElement.Click();
        }
    }
}

We can use the Selected property of the IWebElement interface to find out if the checkbox is checked. To check or uncheck the item, we just use the Click method. It is possible to have only a single Check method with a boolean parameter carrying the information for the check status.

IComboBox Interface

C#
public interface IComboBox : IContentElement
{
    string SelectedValue { get; }

    void SelectValue(string value);
}

Through the SelectedValue property, we get a string representation of the currently selected item. SelectValue method selects an option by its text value. If we need, we can add more variations of this method (select by index).

ComboBox Selenium Driver Implementation

C#
public class ComboBox : ContentElement, IComboBox
{
    public ComboBox(
        IWebDriver driver, 
        IWebElement webElement, 
        IUnityContainer container) : base(driver, webElement, container)
    {
    }
        
    public string SelectedValue
    {
        get
        {
            var select = new SelectElement(this.webElement);
            return select.SelectedOption.Text;
        }
    }

    public void SelectValue(string value)
    {
        var select = new SelectElement(this.webElement);
        select.SelectByValue(value);
    }
}

The pure WebDriver doesn't provide an easy way to work with comboBoxes. However, we use the SelectElement wrapper, part of the Selenium.Support NuGet. Through its SelectedOption property, we can get the currently selected item. Also, it provides a couple of different methods to select options. You can read more about it in my article - Getting Started with WebDriver C# in 10 Minutes.

ITextBox Interface

C#
public interface ITextBox : IContentElement
{
    string Text { get; set; }

    bool IsEnabled { get; }

    void Focus();

    void SimulateRealTyping(string valueToBeTyped);
}

To simplify the API, we can get or set the text box's text though the Text property. Also, you can check if the item is disabled or enabled via the IsEnabled property. The Focus method is identical to Hover.

TextBox Selenium Driver Implementation

C#
public class TextBox : ContentElement, ITextBox
{
    public TextBox(
        IWebDriver driver,
        IWebElement webElement,
        IUnityContainer container) : base(driver, webElement, container)
    {
    }

    public string Text
    {
        get
        {
            return this.webElement.GetAttribute("value");
        }
        set
        {
            this.webElement.Clear();
            this.webElement.SendKeys(value);
        }
    }

    public void Focus()
    {
        Actions action = new Actions(driver);
        action.MoveToElement(this.webElement).Perform();
    }
}

To get the text of the text box, we can use again the GetAttribute method but this time we get the value attribute. The Focus method's implementation is identical to the one of the Ho<span class="bold_text">ver</span> method.

IDiv Interface

C#
public interface IDiv : IContentElement
{
}

Div Selenium Driver Implementation

C#
public class Div : ContentElement, IDiv
{
    public Div(
        IWebDriver driver,
        IWebElement webElement,
        IUnityContainer container) : base(driver, webElement, 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.

IKendoGrid Interface

C#
public interface IKendoGrid : IElement
{
    void RemoveFilters();

    int TotalNumberRows();

    void Reload();

    int GetPageSize();

    int GetCurrentPageNumber();

    void ChangePageSize(int newSize);

    void NavigateToPage(int pageNumber);

    void Sort(string columnName, SortType sortType);

    void Filter(
        string columnName, 
        FilterOperator filterOperator, 
        string filterValue);

    void Filter(params GridFilter[] gridFilters);

    List<T> GetItems<T>() where T : class;
}

Sometimes, there are more complex controls such as the Kendo Grid. The interface contains the most common usages of the control wrapped as C# methods.

KendoGrid Selenium Driver Implementation

C#
public class KendoGrid : ContentElement, IKendoGrid
{
    private readonly string gridId;
    private readonly IJavaScriptExecutor driver;

    public KendoGrid(
        IWebDriver driver, 
        IWebElement webElement, 
        IUnityContainer container) : base(driver, webElement, container)
    {
        this.gridId = webElement.GetAttribute("id");
        this.driver = (IJavaScriptExecutor)driver;
    }

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

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

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

    public int GetPageSize()
    {
        string jsToBeExecuted = this.GetGridReference();
        jsToBeExecuted = string.Concat(jsToBeExecuted, "return grid.dataSource.pageSize();");
        var currentResponse = this.driver.ExecuteScript(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.ExecuteScript(jsToBeExecuted);
    }

    public void NavigateToPage(int pageNumber)
    {
        string jsToBeExecuted = this.GetGridReference();
        jsToBeExecuted = string.Concat
        (jsToBeExecuted, "grid.dataSource.page(", pageNumber, ");");
        this.driver.ExecuteScript(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.ExecuteScript(jsToBeExecuted);
    }

    public List<T> GetItems<T>() where T : class
    {
        string jsToBeExecuted = this.GetGridReference();
        jsToBeExecuted = 
            string.Concat(
            jsToBeExecuted, 
            "return JSON.stringify(grid.dataSource.data());");
        var jsResults = this.driver.ExecuteScript(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.ExecuteScript(jsToBeExecuted);
    }

    public int GetCurrentPageNumber()
    {
        string jsToBeExecuted = this.GetGridReference();
        jsToBeExecuted = string.Concat(jsToBeExecuted, "return grid.dataSource.page();");
        var result = this.driver.ExecuteScript(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;
    }
}

You can read a lot more in my dedicated article - Automate Telerik Kendo Grid with WebDriver and JavaScript.

Hybrid Test Framework Controls in Test

Page Object

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;
    }
}

Here, there is nothing different compared to the examples from the previous articles. We just pass the IElementFinder and INavigationService interfaces to the constructor of the base page class. You can read more about this type of page objects in my dedicated article - Page Objects That Make Code More Maintainable.

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"));
        }
    }
}

The most interesting part is situated here. All properties do not return the WebDriver's IWebElement interface. To support different driver's implementations, the page object map now uses the different hybrid test framework's interfaces. Their concrete implementation will be resolved at runtime through Unity IoC container.

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, SeleniumDriver>();
        this.container.RegisterType<INavigationService, SeleniumDriver>();
        this.container.RegisterType<IBrowser, SeleniumDriver>();
        this.container.RegisterType<ICookieService, SeleniumDriver>();
        this.container.RegisterType<IDialogService, SeleniumDriver>();
        this.container.RegisterType<IElementFinder, SeleniumDriver>();
        this.container.RegisterType<IJavaScriptInvoker, SeleniumDriver>();
        this.container.RegisterType<IElement, Element>();
        this.container.RegisterType<IButton, Button>();
        this.container.RegisterType<ITextBox, TextBox>();
        this.container.RegisterType<IDiv, Div>();
        this.container.RegisterType<IContentElement, ContentElement>();
        this.container.RegisterInstance<IUnityContainer>(this.container);
        this.container.RegisterInstance<BrowserSettings>(
        BrowserSettings.DefaultFirefoxSettings);
        this.driver = this.container.Resolve<IDriver>();
    }

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

As in the previous article's test code, we first register all driver instances. They will be resolved as their Selenium Driver implementation. The same is valid for all web controls. So when you resolve the BingMainPage, the SeleniumDriver is passed as parameter to the page object's constructor. Also, when the ElementFinderService tries to determine the different controls' interfaces, it will get their WebDriver's implementation.

Design & Architecture

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


Written By
CEO Automate The Planet
Bulgaria Bulgaria
CTO and Co-founder of Automate The Planet Ltd, inventor of BELLATRIX Test Automation Framework, author of "Design Patterns for High-Quality Automated Tests: High-Quality Test Attributes and Best Practices" in C# and Java. Nowadays, he leads a team of passionate engineers helping companies succeed with their test automation. Additionally, he consults companies and leads automated testing trainings, writes books, and gives conference talks. You can find him on LinkedIn every day.

Comments and Discussions

 
-- There are no messages in this forum --