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

Create Hybrid Test Framework – Advanced Element Find Extensions

4.67/5 (2 votes)
24 Jul 2016Ms-PL2 min read 5.5K  
Build a Hybrid Test Automation Framework. Learn how to create an extendable element find logic without changing the core test automation framework. The post Create Hybrid Test Framework – Advanced Element Find Extensions appeared first on Automate The Planet.

This is the newest article from the Design & Architecture Series. In the first articles from the series, I showed you how to create a common interface for finding elements based on abstract classes. However, we can extend the idea even further. Here, I am going to show you how to create extensions for the ElementFinder interface so that you can locate elements with less writing and with more complex locators.

Hybrid Test Framework- Create Advanced Element - Find Extensions

Basic Implementation of ElementFinder

Below, you can find the basic implementation of the IElementFinder interface. You can locate web elements using the generic Find method through configuring it via By locator. However, I think that it requires too much writing for locating a single element. I will show you how to create Find methods that do not require By configuration and even contains more advanced locators.

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

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

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

Basic By

The basic implementation of the By class includes just the most important locators such as find by ID, class, CSS, link text and tag name. Most of the time, in my tests, I use more complicated locators' strategies such as find by ID ending with, ID containing, XPath, XPath contains and so on. So I believe it is useful to have these at your disposal.

C#
public class By
{
    public By(SearchType type, string value) : this(type, value, null)
    {
    }

    public By(SearchType type, string value, IElement parent)
    {
        this.Type = type;
        this.Value = value;
        this.Parent = parent;
    }

    public SearchType Type { get; private set; }

    public string Value { get; private set; }

    public IElement Parent { get; private set; }

    public static By Id(string id)
    {
        return new By(SearchType.Id, id);
    }
        
    public static By Id(string id, IElement parentElement)
    {
        return new By(SearchType.Id, id, parentElement);
    }

    public static By LinkText(string linkText)
    {
        return new By(SearchType.LinkText, linkText);
    }

    public static By CssClass(string cssClass, IElement parentElement)
    {
        return new By(SearchType.CssClass, cssClass, parentElement);
    }

    public static By Tag(string tagName)
    {
        return new By(SearchType.Tag, tagName);
    }

    public static By Tag(string tagName, IElement parentElement)
    {
        return new By(SearchType.Tag, tagName, parentElement);
    }

    public static By CssSelector(string cssSelector)
    {
        return new By(SearchType.CssSelector, cssSelector);
    }

    public static By CssSelector(string cssSelector, IElement parentElement)
    {
        return new By(SearchType.CssSelector, cssSelector, parentElement);
    }

    public static By Name(string name)
    {
        return new By(SearchType.Name, name);
    }

    public static By Name(string name, IElement parentElement)
    {
        return new By(SearchType.Name, name, parentElement);
    }
}

Advanced By

Most of the people may not need to use the more advanced locators, so I put them in a dedicated child of the By class, named AdvancedBy. Of course, you need to add the new locators' strategies in the SearchType enum.

C#
public class AdvancedBy : By
{
    public AdvancedBy(SearchType type, string value, IElement parent) 
        : base(type, value, parent)
    {
    }

    public static By IdEndingWith(string id)
    {
        return new By(SearchType.IdEndingWith, id);
    }

    public static By ValueEndingWith(string valueEndingWith)
    {
        return new By(SearchType.ValueEndingWith, valueEndingWith);
    }

    public static By Xpath(string xpath)
    {
        return new By(SearchType.XPath, xpath);
    }

    public static By LinkTextContaining(string linkTextContaing)
    {
        return new By(SearchType.LinkTextContaining, linkTextContaing);
    }

    public static By CssClass(string cssClass)
    {
        return new By(SearchType.CssClass, cssClass);
    }

    public static By CssClassContaining(string cssClassContaining)
    {
        return new By(SearchType.CssClassContaining, cssClassContaining);
    }

    public static By InnerTextContains(string innerText)
    {
        return new By(SearchType.InnerTextContains, innerText);
    }

    public static By NameEndingWith(string name)
    {
        return new By(SearchType.NameEndingWith, name);
    }

    public static By XPathContaining(string xpath)
    {
        return new By(SearchType.XPathContaining, xpath);
    }

    public static By IdContaining(string id)
    {
        return new By(SearchType.IdContaining, id);
    }
}

ElementFinder Extensions- AdvancedElementFinder

I decided that the best way to enhance the basic ElementFinder is to create extension methods for it. You can extend the idea even further and place the class with extension methods in a dedicated project so that only if someone needs them to add a reference. The Selenium.WebDriver.Support NuGet works in the same manner. We create an additional method for each new advanced localization strategy.

C#
public static class AdvancedElementFinder
{
    public static TElement FindByIdEndingWith<TElement>(
        this IElementFinder finder, string idEnding) 
        where TElement : class, IElement
    {
        return finder.Find<TElement>(AdvancedBy.IdEndingWith(idEnding));
    }

    public static TElement FindByIdContaining<TElement>(
        this IElementFinder finder, string idContaining) 
        where TElement : class, IElement
    {
        return finder.Find<TElement>(AdvancedBy.IdContaining(idContaining));
    }

    public static TElement FindByValueEndingWith<TElement>(
        this IElementFinder finder, string valueEnding) 
        where TElement : class, IElement
    {
        return finder.Find<TElement>(AdvancedBy.ValueEndingWith(valueEnding));
    }

    public static TElement FindByXpath<TElement>(
        this IElementFinder finder, string xpath) 
        where TElement : class, IElement
    {
        return finder.Find<TElement>(AdvancedBy.Xpath(xpath));
    }

    public static TElement FindByLinkTextContaining<TElement>(
        this IElementFinder finder, string linkTextContaining) 
        where TElement : class, IElement
    {
        return finder.Find<TElement>(AdvancedBy.LinkTextContaining(linkTextContaining));
    }

    public static TElement FindByClass<TElement>(
        this IElementFinder finder, string cssClass) 
        where TElement : class, IElement
    {
        return finder.Find<TElement>(AdvancedBy.CssClass(cssClass));
    }

    public static TElement FindByClassContaining<TElement>(
        this IElementFinder finder, string cssClassContaining) 
        where TElement : class, IElement
    {
        return finder.Find<TElement>(AdvancedBy.CssClassContaining(cssClassContaining));
    }

    public static TElement FindByInnerTextContaining<TElement>(
        this IElementFinder finder, string innerText) 
        where TElement : class, IElement
    {
        return finder.Find<TElement>(AdvancedBy.InnerTextContains(innerText));
    }

    public static TElement FindByNameEndingWith<TElement>(
        this IElementFinder finder, string name) 
        where TElement : class, IElement
    {
        return finder.Find<TElement>(AdvancedBy.NameEndingWith(name));
    }
}

Advanced Element Find Extensions in Tests

Page Object Map

In order to be able to use the new advanced locators' methods, you only need to add a using statement to their namespace. After that, you will be able to choose between them through the Visual Studio's IntelliSence.

C#
public partial class BingMainPage
{
    public ITextBox SearchBox
    {
        get
        {
            ////return this.ElementFinder.Find<ITextBox>(By.Id("sb_form_q"));
            return this.ElementFinder.FindByIdEndingWith<ITextBox>("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"));
        }
    }
}

Test Example

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

There are no changes in the tests' bodies. All necessary changes need to be placed in the pages' element maps.

Design & Architecture

The post Create Hybrid Test Framework – Advanced Element Find Extensions 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)