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.
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.
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
.
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.
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.
public partial class BingMainPage
{
public ITextBox SearchBox
{
get
{
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
[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