C# Expected Conditions are Deprecated. So what?

Alex Siminiuc
Feb 16 · 8 min read
Photo by Jon Tyson on Unsplash

My recent Selenium automation projects are built in C#.

It was a while since I did any coding in this language but, since C# was created from Java, I did not expect more than a few days of adjusting to the Selenium C# binding.

One of the surprises I had was the fact that the C# ExpectedConditions class is deprecated.

How was I supposed to do any synchronization between the site and the tests without it?

It turned out that synchronization was still achievable in just a few lines of code.


I am going to show you a quick Selenium solution without any synchronization so it is easy to understand where the synchronization comes into play.

The test class has 2 tests only, both about searching for a keyword:

using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using System;
using System.Collections.ObjectModel;
using OpenQA.Selenium.Support.UI;
namespace UnitTestPackage {

public class TestClass {
private ChromeDriver driver; private readonly String homeUrl = “http://www.vpl.ca";
private readonly String resultsUrl = “vpl.bibliocommons.com”;
private readonly String keyword = “Java”; private readonly By searchBoxId = By.Id(“edit-search”); private readonly By searchButtonId = By.Id(“edit-submit”); private readonly By searchTitleXpath =
By.XPath(“//h1[@data-test-id = ‘searchTitle’]”);
private readonly By resultCountXpath =
By.XPath(“(//span[@data-key = ‘pagination-text’])[1]”);
private readonly By resultLinkXpath =
By.XPath(“//a[@data-key = ‘bib-title’]”);

[SetUp]
public void SetUp() {
driver = new ChromeDriver();
driver.Manage().Window.Maximize();
}
[TearDown]
public void TearDown() {
driver.Quit();
}
[Test]
public void Search_For_Java_Works() {
OpenHomePage();

Assert.IsTrue(Url().Contains(“vpl.ca”),
“home page homeUrl does not contain vpl.ca!”);


SearchFor(keyword);

Assert.IsTrue(Url().Contains(resultsUrl),
“results page homeUrl is wrong!”);
Assert.IsTrue(SearchTitle().Contains(keyword),
“Java is not included in the search page title”);
}

[Test]
public void Search_For_Keyword_Returns_Results() {
OpenHomePage();

Assert.IsTrue(Url().Contains(“vpl.ca”),
“home page homeUrl does not contain vpl.ca!”);

SearchFor(keyword);

Assert.IsTrue(Url().Contains(resultsUrl),
“results page homeUrl is wrong!”);
Assert.IsTrue(ResultCount() > 0,
“result count is not positive!”);

Assert.AreEqual(10, ResultCountPerPage(),
“the title count is not equal to 10!”);
}

private void OpenHomePage() {
driver.Navigate().GoToUrl(homeUrl);
}

private void SearchFor(string keyword) {

IWebElement searchBox = driver.FindElement(searchBoxId);
searchBox.SendKeys(keyword);
IWebElement searchButton = driver.FindElement(searchButtonId);
searchButton.Click();
}
private string SearchTitle(){

IWebElement searchTitle = driver.FindElement(searchTitleXpath);
return searchTitle.Text;
}
private int ResultCount() {

IWebElement countLabel = driver.FindElement(resultCountXpath);
string countText = countLabel.Text; int i = countText.IndexOf(“of “) + 3;
int j = countText.IndexOf(“ results”);
countText = countText.Substring(i, j — i); int count = Int32.Parse(countText); return count; }
private int ResultCountPerPage() {
ReadOnlyCollection<IWebElement> titles =
driver.FindElements(resultLinkXpath);
return titles.Count; } private string Url() {
return driver.Url;
}
}}

The code should be easy to understand.

The first test has the following steps:

  • open the home page of the site
  • verify that the home page url is correct
  • search for a keyword
  • verify that the results page url is correct
  • verify that the search keyword is included in the results page header

The second test has similar steps:

  • open the home page of the site
  • verify that the home page url is correct
  • search for a keyword
  • verify that the results page url is correct
  • verify that the total result count is greater than 0
  • verify that the results count per page is equal to 10

The code has no synchronization implemented with the pages of the site.

If the site loads fast, both tests will work.

But if the site loads slowly, the tests will start breaking.

To prevent breaking, we need to add synchronization in a few places:

  • before finding an element (such as a texbox or button), the code should wait until the element is displayed and enabled
  • before finding an element such as a label, the code should wait until the element is displayed
  • before finding a list of elements, the code should wait until there is at least an element displayed
  • before interacting with a page, the code should wait until the url is correct

This is the code updated with the needed synchonisation. It does not repeat the locators, test fixtures and the test methods as they have no changes:

private void OpenHomePage() {
driver.Navigate().GoToUrl(homeUrl);
}
private void SearchFor(string keyword) {

//create a 30 seconds timeout
TimeSpan timeout = TimeSpan.FromSeconds(30);
//create the wait object using the driver and the timeout
WebDriverWait wait = new WebDriverWait(driver, timeout);
//create the condition for search box to be displayed and enabled
Func<IWebDriver, bool> isSearchBoxEnabled =
d => {
IWebElement e = d.FindElement(searchBoxId);
return e.Displayed && e.Enabled;
};
//wait until the condition is true
wait.Until(isSearchBoxEnabled);
//find search box
IWebElement searchBox = driver.FindElement(searchBoxId);
searchBox.SendKeys(keyword);
//create the condition about search button being enabled
Func<IWebDriver, bool> isSearchButtonEnabled =
d =>
{
IWebElement e = d.FindElement(searchButtonId);
return e.Displayed && e.Enabled;
};
//wait until the search button is displayed and enabled
wait.Until(isSearchButtonEnabled);
//find search button
IWebElement searchButton = driver.FindElement(searchButtonId);
searchButton.Click();
}
private string SearchTitle() { //timeout can be created when passed as parameter to wait
WebDriverWait wait = new WebDriverWait(driver,
TimeSpan.FromSeconds(30));
//wait until search title is displayed
//wait and condition can be used together in the same sentence
wait.Until(d =>
{
IWebElement e = d.FindElement(searchTitleXpath);
return e.Displayed;
});
//find search title
IWebElement searchTitle = driver.FindElement(searchTitleXpath);
return searchTitle.Text;
}
private int ResultCount() { WebDriverWait wait = new WebDriverWait(driver,
TimeSpan.FromSeconds(30));
//wait for the results count to be displayed
wait.Until(d =>
{
IWebElement e = d.FindElement(resultCountXpath);
return e.Displayed;
});
//find results count
IWebElement countLabel = driver.FindElement(resultCountXpath);
string countText = countLabel.Text; int i = countText.IndexOf(“of “) + 3;
int j = countText.IndexOf(“ results”);
countText = countText.Substring(i, j — i); int count = Int32.Parse(countText); return count;
}

private int ResultCountPerPage() {
WebDriverWait wait = new WebDriverWait(driver,
TimeSpan.FromSeconds(30));
// create the condition for at least one result being found
Func<IWebDriver, bool> areResultsFound =
d => {
ReadOnlyCollection<IWebElement> titles =
driver.FindElements(resultLinkXpath);
return titles.Count > 0;
};
//wait until the condition is true
wait.Until(areResultsFound);
ReadOnlyCollection<IWebElement> resultTitles =
driver.FindElements(resultLinkXpath);
return resultTitles.Count;
}
private string Url() {
return driver.Url;
}

Let’s go into the details of how this waiting for a condition works.

We can take as an example the waiting until the search box is displayed and enabled.

The waiting is implemented in 4 steps:

1. create a 30 seconds timeout object:

TimeSpan timeout = TimeSpan.FromSeconds(30);

2. create a WebDriverWait object using the driver and the timeout as parameters:

 WebDriverWait wait = new WebDriverWait(driver, timeout);

3. create a condition for an element:

For example, the following condition checks if the search box is displayed and enabled:

Func<IWebDriver, bool> isSearchBoxEnabled = 
d => {
IWebElement e = d.FindElement(searchBoxId);
return e.Displayed && e.Enabled;
};

The condition finds the search box element using its id and returns

  • true if the element is displayed and enabled
  • false if the element is not displayed or not enabled

The condition needs to be executed so we will use the wait object for this.

4. The wait object executes the condition until the condition passes or until the condition fails and the timeout expires:

 wait.Until(isSearchBoxEnabled);

When the condition is executed by the wait object, the d variable used inside of the condition is substituted by the driver of the wait.

So,

d => {
IWebElement e = d.FindElement(searchBoxId);
return e.Displayed && e.Enabled;
};

becomes

driver => {
IWebElement e = driver.FindElement(searchBoxId);
return e.Displayed && e.Enabled;
};

This is how the waiting works:

1. the wait executes the condition.

2. if the condition returns true, the wait ends successfully.

3. if the condition returns false, the wait is paused for 500ms;
the wait then tries the condition again until either

  • the condition is true or
  • the timeout of the wait is reached

4. if the condition fails and the timeout is reached, the code generates a timeout exception.


The code synchronizes now with the site.

Before finding any element, it waits until the element is available.

It also waits until at least a title is available before finding all titles.

One thing that can be improved is to re-use the wait object instead of creating this object in each method.

We can create the wait as a class member and instantiate it in the SetUp() method:

//create the wait as class member so all methods can share it
private WebDriverWait wait;
[SetUp]
public void SetUp()
{
driver = new ChromeDriver();
driver.Manage().Window.Maximize();
//instantiate the wait
TimeSpan timeout = TimeSpan.FromSeconds(30);
this.wait = new WebDriverWait(driver, timeout);
wait.Message = “wait timed out after 30 seconds”;
wait.PollingInterval = TimeSpan.FromMilliseconds(250);
wait.IgnoreExceptionTypes(typeof(NotFoundException));
}

Since the wait is a class member, all class methods can use it:

private string SearchTitle()
{
wait.Until(d =>
{
IWebElement e = d.FindElement(searchTitleXpath);
return e.Displayed;
});
IWebElement searchTitle = driver.FindElement(searchTitleXpath);
return searchTitle.Text;
}

In the SetUp() method, we customized the wait object by

  • changing the polling interval; the default retry interval is 500 ms so we reduced it to 250 ms
  • if a NotfoundException is generated by the condition, this exception will be ignored
  • the message displayed by the wait if it times out is modified as well

The class’s methods share now the wait object.

But their code is so verbose.

We can reduce the verbosity by creating methods that handle the waiting for elements:

//wait until the element matched by locator is displayed and enabled
private void WaitUntilElementIsEnabled(By locator)
{
Func<IWebDriver, bool> condition =
d =>
{
IWebElement e = d.FindElement(locator);
return e.Displayed && e.Enabled;
};
wait.Until(condition);}
//wait until the element matched by locator is displayed
//the wait and condition are used together
private void WaitUntilElementIsDisplayed(By locator)
{
wait.Until(d =>
{
IWebElement e = d.FindElement(locator);
return e.Displayed;
});
}
//wait until the current url includes a keyword
private bool UrlContains(string keyword)
{
wait.Until(d =>
{
return d.Url.ToLower().Contains(keyword);
});
return true;
}

With the new “wait” methods, the class’s methods become much simpler. For example,

private void SearchFor(string keyword)
{
WaitUntilElementIsEnabled(searchBoxId); IWebElement searchBox = driver.FindElement(searchBoxId);
searchBox.SendKeys(keyword);

WaitUntilElementIsEnabled(searchButtonId);
IWebElement searchButton = driver.FindElement(searchButtonId);
searchButton.Click();
}

Notice that a method was added also for waiting until the current url includes a keyword.

Because of it, the tests can be improved as well:

[Test]
public void Search_For_Keyword_Returns_Results()
{
OpenHomePage();
Assert.IsTrue(UrlContains(“vpl.ca”),
“home page homeUrl does not contain vpl.ca!”);
SearchFor(keyword);
Assert.IsTrue(UrlContains(resultsUrl),
“results page homeUrl is wrong!”);
Assert.IsTrue(ResultCount() > 0, “result count is not positive!”);
Assert.AreEqual(10, ResultCountPerPage(),
“the title count is not equal to 10!”);
}

Finally, we could make another improvement to the code.

We can create new methods that wait for an element before finding it:

private IWebElement FindEnabledElement(By locator)
{
WaitUntilElementIsEnabled(locator);
IWebElement element = driver.FindElement(locator); return element;
}

private IWebElement FindElement(By locator)
{
WaitUntilElementIsDisplayed(locator);
IWebElement element = driver.FindElement(locator); return element;
}

Using these new methods simplifies the page methods further:

private void SearchFor(string keyword)
{
IWebElement searchBox = FindEnabledElement(searchBoxId);
searchBox.SendKeys(keyword);
IWebElement searchButton = FindEnabledElement(searchButtonId);
searchButton.Click();
}

You can create similarly your own expected conditions for any cases that may be important in your project:

  • wait until an attribute of a web element is created
  • wait until an element is hidden
  • wait until an attribute of a web element has a particular value
  • wait until the page title is equal to a value

I hope that you found this article useful and interesting.

If it was so, please clap for it. Thanks.

Alex Siminiuc

Written by

Blogs about Selenium and Java at https://seleniumjava.com.

More From Medium

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade