Click here to Skip to main content
13,897,577 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

6.1K views
3 bookmarked
Posted 4 Jul 2016
Licenced Ms-PL

Create Hybrid Test Framework – Selenium Driver Controls

, 4 Jul 2016
Rate this:
Please Sign up or sign in to vote.
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

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

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 <span class="bold_text">IContentElement</span> 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

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

The only particular part of the interface is the <span class="bold_text">Url</span> property.

Anchor Selenium Driver Implementation

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 <span class="bold_text">Anchor</span> class inherits from the <span class="bold_text">ContentElement</span> base class instead from the <span class="bold_text">Element</span> class. We use the GetAttribute IWebElement's method to get the HREF of the item.

ICheckbox Interface

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

    bool IsChecked { get; }

    void Check();

    void Uncheck();
}

Checkbox Selenium Driver Implementation

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 <span class="bold_text">Selected</span> property of the IWebElement interface to find out if the checkbox is checked. To check or uncheck the item, we just use the <span class="bold_text">Click</span> method. It is possible to have only a single Check method with a boolean parameter carrying the information for the check status.

IComboBox Interface

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

    void SelectValue(string value);
}

Through the <span class="bold_text">SelectedValue</span> property, we get a string representation of the currently selected item. <span class="bold_text">SelectValue</span> 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

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 <span class="bold_text">SelectElement</span> wrapper, part of the <span class="bold_text">S<span class="bold_text">elenium</span></span>.Support NuGet. Through its <span class="bold_text">SelectedOption</span> 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

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

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 <span class="bold_text">GetAttribute</span> method but this time we get the value attribute. The Focus method's implementation is identical to the one of the <span class="bold_text">H<span class="bold_text">o<span class="bold_text">ver</span></span></span> method.

IDiv Interface

public interface IDiv : IContentElement
{
}

Div Selenium Driver Implementation

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

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

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

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 <span class="bold_text">IElementFinder</span> and <span class="bold_text">INavigationService</span> 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

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

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

[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)

Share

About the Author

Anton Angelov
CEO Automate The Planet
Bulgaria Bulgaria
Anton Angelov is an IT Consultant and Quality Assurance Architect at Innovative Lab. He is passionate about automation testing and designing test harness and tools, having the best industry development practices in mind. In addition, he is an active blogger and the founder of Automate The Planet. He strives to make the site one of the leading authorities in Automation Testing by presenting compelling articles, inspiring ardent discussions amongst the community. He is also one of the most-rated-answer authors of questions about Test Automation Frameworks (WebDriver) on Stack Overflow.

You may also be interested in...

Pro
Pro

Comments and Discussions

 
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web03 | 2.8.190306.1 | Last Updated 5 Jul 2016
Article Copyright 2016 by Anton Angelov
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid