August 9, 2020

Automated QA: Handling Selenium .NET Elements

Fetching an element using Selenium .NET, waiting for it to be visible, asserting on it or performing any other action on it is not a challenge if you are familiar with the framework. However, not having an adequate code architecture for your integration tests can betray you; especially if a feature changed and now you urgently need to modify the integration test. But phew, you are lucky your resume says, "Can work under pressure".

An example of how to fetch an element is .FindElement(By.CssSelector("#id")) which will return an IWebElement. However, the By class has different methods for other locator techniques, so the same element can be fetched in any of the following ways:

  • .FindElement(By.ClassName("class"))
  • .FindElement(By.Id("id"))
  • .FindElement(By.Name("name"))
  • .FindElement(By.TagName("tag"))
  • .FindElement(By.LinkText("full link text"))
  • .FindElement(By.PartialLinkText("link text"))
  • .FindElement(By.XPath("//*[@id=\"id\"]"))

In the case you are attempting to fetch many elements, the FindElements method can be used with the same variations. You probably knew that already but being aware of this will help you find the value of this article.

You may be thinking that choosing a locator technique to use should not be hard (debatable!) and saving the IWebElement to a variable will make it easy for you to reuse the element. Which totally makes sense and will work perfectly.

Have you ever heard of ExpectedConditions? Probably yes. The traitor lies in many of the methods of this class, waiting for you to use them. If you take a look at the signature of these methods, you will see that most of them take an instance of the By class as a parameter. Your inadequate code architecture is starting to betray you, since you can still make it work but remember that working under pressure is no fun. You will now have to remember (or copy paste) the By instance that you used to instantiate your IWebElement.

A correct Page Object design pattern implementation can save you from having to constantly remember the locator technique you were using to fetch an element. So instead of saving an IWebElement to a variable, you can instead save a By instance to a variable and avoid code duplication.

You may have noticed that it is not challenging to fetch an element using Selenium .NET, but a correct implementation does make the difference. What about waiting for ExpectedConditions? You might already know that the WebDriverWait classs can be used for that. Using it to wait for ExpectedConditions is straight forward. First, you must create a WebDriverWait object and then use its .Until method. Waiting for an element to be visible would look like this:

WebDriverWait wait = new WebDriverWait(driver, timespan);
wait.Until(ExpectedConditions.ElementIsVisible(ByLocator));

The syntax is not difficult to remember, and you can even partly avoid doing so. You can have a global wait variable for the entire test and either use the same timespan every time you must wait or just change its value before using it. That does not sound bad but can be slightly improved. Not convinced? Take a look at the second line and imagine if you only had to type element.WaitForVisible(10) instead, where 10 is expressed in seconds. But if that is an option, why was it not mentioned in the first place? Well, it is not really an option, yet...

In general, that design pattern can be applied to perform any action to an element. So now it is time to create an ElementHandlers hierarchy to provide methods such as element.WaitForVisible(int timeoutSeconds) which will improve the quality of life of every Automated QA Engineer on the team by making the code for the tests cleaner.


The tests will be developed against WebDriverUniversity.com, which has many examples of parts of a website and is perfect for learning purposes. The specific page that will be used is the To Do List.

Without an ElementHandlers hierarchy, a test to verify that activity is added to the list would look like this:

[Test]
public void TestAddItemToDoList()
{
	ToDoListPage toDoListPage = new ToDoListPage();

	Browser.NavigateTo(toDoListPage);

	WebDriverWait wait = new WebDriverWait(Browser.WebDriver, TimeSpan.FromSeconds(10));
    
	wait.Until(ExpectedConditions.VisibilityOfAllElementsLocatedBy(toDoListPage.ActivitiesLabelsByLocator));
	
    string activityToAdd = "Read Web Calendar Handler article";

	IWebElement newActivityTextbox = Browser.WebDriver.FindElement(toDoListPage.NewActivityTextboxByLocator);

	newActivityTextbox.SendKeys(activityToAdd + Keys.Enter);

	ReadOnlyCollection<IWebElement> activitiesLabels = Browser.WebDriver.FindElements(toDoListPage.ActivitiesLabelsByLocator);

	CollectionAssert.Contains(activitiesLabels.Select(activityLabel => activityLabel.Text), activityToAdd);
}

The scenario covered by this integration test is simple, however the test does not seem as simple. The wait.Until is long and not quite readable, the .Select in the CollectionAssert does not help in recognizing what is the assertion being made, etc.

Let the ElementHandlers hierarchy help you out.

First, you will need to create your base class ElementHandler. This base class is abstract and has two fields and two abstract methods:

  • Webdriver which allows all the interactions with the browser.
  • ByLocator which allows elements to be fetched.
  • WaitForVisible(int timeoutSeconds) which allows to wait up to a specific amount of time (timeoutSeconds) until the element is visible.
  • WaitForInvisible(int timeoutSeconds) which allows to wait up to a specific amount of time (timeoutSeconds) until the element is invisible.
public abstract class ElementHandler
{
	protected readonly IWebDriver WebDriver;
    protected readonly By ByLocator;

	public ElementHandler(IWebDriver webDriver, By byLocator)
    {
    	WebDriver = webDriver;
        ByLocator = byLocator;
	}

	public abstract void WaitForVisible(int timeoutSeconds);

	public abstract void WaitForInvisible(int timeoutSeconds);
}

The hierarchy provides a distinction between dealing with one single element or a collection of elements. So, you will create two classes that derive from ElementHandler; SingleElementHandler and CollectionElementsHandler.

Next step will be to create the SingleElementHandler class, which overrides methods WaitForVisible(int timeoutSeconds) and WaitForInvisible(int timeoutSeconds) which were mentioned previously. It also has a getter and one method:

  • Element which fetches the element from the browser using the WebDriver.
  • Hover() which allows to position the mouse over the element.
public class SingleElementHandler : ElementHandler
{
	public IWebElement Element => WebDriver.FindElement(ByLocator);

	public SingleElementHandler(IWebDriver webDriver, By byLocator) : base(webDriver, byLocator) { }

	public override void WaitForVisible(int timeoutSeconds)
    {
    	WebDriverWait wait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(timeoutSeconds));
        wait.Until(ExpectedConditions.ElementIsVisible(ByLocator));
    }

	public override void WaitForInvisible(int timeoutSeconds)
    {
    	WebDriverWait wait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(timeoutSeconds));
		wait.Until(ExpectedConditions.InvisibilityOfElementLocated(ByLocator));
    }

	public void Hover()
    {
    	Actions action = new Actions(WebDriver);
        action.MoveToElement(Element).Perform();
    }
}

You are now ready to create the CollectionElementsHandler, which also overrides methods WaitForVisible(int timeoutSeconds) and WaitForInvisible(int timeoutSeconds). Additionally, it has two getters, one setter, and six methods:

  • Count which allows to keep track of the size of the collection and enables the ability to wait for a change in the size.
  • Elements which fetches the collection of elements from the browser using the WebDriver.
  • SaveCount() which allows for the user of this handler to save the count at any given moment of the test.
  • WaitForVisibleElement(int timeoutSeconds, int index) which allows to wait up to a specific amount of time (timeoutSeconds) until the element at a specific position of the collection (index) is visible.
  • WaitForSizeChange(int timeoutSeconds) which allows to wait up to a specific amount of time (timeoutSeconds) until the size of the collection of elements changes.
  • Hover(int index) which allows to position the mouse over an element at a specific position of the collection (index).
  • VerifyContainsText(string expectedText) which allows to make an assertion to verify an expectedText is present within the collection of elements.
  • VerifyDoesNotContainText(string expectedText) which allows to make an assertion to verify an expectedText is not present within the collection of elements.
public class CollectionElementsHandler : ElementHandler
{
	private int Count { get; set; }

	public ReadOnlyCollection<IWebElement> Elements => WebDriver.FindElements(ByLocator);

	public CollectionElementsHandler(IWebDriver webDriver, By byLocator) : base(webDriver, byLocator) { }

	public void SaveCount()
    {
    	Count = Elements.Count;
    }

	public override void WaitForVisible(int timeoutSeconds)
    {
    	WebDriverWait wait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(timeoutSeconds));
    	wait.Until(ExpectedConditions.VisibilityOfAllElementsLocatedBy(ByLocator));
    }

	public void WaitForVisibleElement(int timeoutSeconds, int index)
    {
    	WebDriverWait wait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(timeoutSeconds));
        wait.Until(ExpectedConditions.PresenceOfAllElementsLocatedBy(ByLocator));
        wait.Until(driver => driver.FindElements(ByLocator)[index].Displayed);
    }

	public override void WaitForInvisible(int timeoutSeconds)
    {
    	WebDriverWait wait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(timeoutSeconds));
        wait.Until(driver => (ExpectedConditions.VisibilityOfAllElementsLocatedBy(ByLocator))(driver) == null);
    }

	public void WaitForSizeChange(int timeoutSeconds)
    {
    	WebDriverWait wait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(timeoutSeconds));
        wait.Until(driver => driver.FindElements(ByLocator).Count != Count);
    }

	public void Hover(int index)
    {
    	Actions action = new Actions(WebDriver);
        action.MoveToElement(Elements[index]).Perform();
    }

	public void VerifyContainsText(string expectedText)
    {
    	CollectionAssert.Contains(Elements.Select(element => element.Text), expectedText);
    }

	public void VerifyDoesNotContainText(string expectedText)
    {
    	CollectionAssert.DoesNotContain(Elements.Select(element => element.Text), expectedText);
    }
}

Now that you are done with the ElementsHandler hierarchy, you can finally rewrite the test case for verifying an activity is added to the To Do List:

[Test]
public void TestAddItemToDoList()
{
	ToDoListPage toDoListPage = new ToDoListPage();

	Browser.NavigateTo(toDoListPage);

	CollectionElementsHandler activitiesLabelsHandler = new CollectionElementsHandler(Browser.WebDriver, toDoListPage.ActivitiesLabelsByLocator);

	activitiesLabelsHandler.WaitForVisible(10);

	SingleElementHandler newActivityTextboxHandler = new SingleElementHandler(Browser.WebDriver, toDoListPage.NewActivityTextboxByLocator);

	string activityToAdd = "Read Web Calendar Handler article";

	newActivityTextboxHandler.Element.SendKeys(activityToAdd + Keys.Enter);

	activitiesLabelsHandler.VerifyContainsText(activityToAdd);
}

Now you can stress a little less when creating a test or making changes when a feature is modified.

The complete code is in GitHub.

This is a creative way to handle elements using Selenium .NET, try it yourself and reduce the time it takes to write a test case.

Thank you very much for reading, I hope you enjoyed it.