Introduction
During my previous Automation Testing project based on a wrapper of Selenium WebDriver, it is not uncommon when some operation failed to get the expected result or even throw exceptions when the browser has not finished rendering.
Although there are some measure to make sure the process waiting long enough before the browser is ready, the involved codes is too complex to follow and keeps throwing same Excetions repeatedly, so I wrote some simple functions to enable Wait.Until() mechanism that is also thread-blocking to some extend but still suitable for Automation Testing scenarios or used as a WatchDog.
Now, when I am converting my framework to be based on WebDriver directly, I believe the same mechanism can be used to replace the official WebDriverWait class provided by WebDriver.Support.UI, and because it is a such simple mechnism, the Generic version might be helpful in other scenarios.
Background
A good introduction of the waiting can be explained with this sample:
IWebDriver driver = new FirefoxDriver();
driver.Url = "http://somedomain/url_that_delays_loading";
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
IWebElement myDynamicElement = wait.Until<IWebElement>((d) =>
{
return d.FindElement(By.Id("someDynamicElement"));
});
In this case, what we really want to do in ideal situation is:
IWebElement myDynamicElement = d.FindElement(By.Id("someDynamicElement"));
However, the browser cannot work ideally without any delay to load web pages and always show them before executing the above codes, that is why a Generic wait.Until is used to encapsulate the codes as a Lamda expression and keep running it until timeout or we get the expected result. The same concept is enforced but I means to make it more extendable thanks to LINQ/Lamda and Function Delegates.
Basic Wait.Until()
The base codes is listed below:
public static class Wait
{
public const int MinIntervalMills = 1;
public const int MaxIntervalMills = 100;
public const int TimeoutInMills = 10 * 1000;
public static TimeSpan Timeout = TimeSpan.FromMilliseconds(TimeoutInMills);
public static void Until(Func<bool> predicate, int timeoutMills = TimeoutInMills)
{
if (timeoutMills <= 0)
throw new InvalidEnumArgumentException("The timeout must be a positive value");
DateTime timeoutMoment = DateTime.Now + TimeSpan.FromMilliseconds(timeoutMills);
int interval = MinIntervalMills;
Exception lastException = null;
do
{
try
{
if (predicate())
return;
}
catch (Exception ex)
{
lastException = ex;
}
Thread.Sleep(interval);
interval = Math.Min(interval * 2, MaxIntervalMills);
} while (DateTime.Now < timeoutMoment);
if (lastException != null)
throw lastException;
else
throw new TimeoutException();
}
}
For Wait.Until(), actually only a predicate whose signature is Func<bool> is needed to judge if the operation is success or not. The codes encapsulated within the predicate is executed with following intervals in principle:
After 0ms: if get expected result, return immdiately.
After another 1ms: if get expected result, return immdiately.
After another 2ms: if get expected result, return immdiately.
...
After another 64ms: if get expected result, return immdiately.
After another 100ms: if get expected result, return immdiately.
After another 100ms: if get expected result, return immdiately.
...
Timeout: if there is any Exception caught during the above execution, throw it.
The changing of execution intervals doesn't really matter, the key point is that the concerned codes within the predicate is executed but to the Wait.Until(), only if is returns true makes difference, and this can be verified with the following test codes where a thread running changeNumberPeriodically() would update "number" randomly:
private const int intervalInMills = 20;
static string[] numbers = {"One", "Two", "Three", "Four", "Five"};
public static int number = 0;
static void Main()
{
var t = new Thread(changeNumberPeriodically);
t.Start();
Func<bool> predicate1 = () =>
{
logTickAndNumber();
return number >= 4;
};
startTick = Environment.TickCount;
Wait.Until(predicate1);
Console.WriteLine("\r\nAfter Wait.Until(predicate1), number={0}, {1} larger than 4\r\n",
number, number >= 4 ? "" : "not");
Func<bool> predicate2 = () =>
{
logTickAndNumber();
return number >= 10;
};
startTick = Environment.TickCount;
try
{
Wait.Until(predicate2, 5000);
}
catch (TimeoutException timeout)
{
Console.WriteLine("\r\nAfter Wait.Until(predicate2, 5000), number={0}, {1} larger than 10.\r\n"
, number, number >= 10 ? "" : "not");
}
Func<string> getNumberString = () =>
{
string result = numbers[number - 1];
logTickAndNumber();
return result;
};
startTick = Environment.TickCount;
string fromUntilString = Wait.UntilString(getNumberString, "Five");
Console.WriteLine("\r\nAfter Wait.UntilString(getNumberString, \"Five\"), number={0}, numberString={1}.\r\n"
, number, fromUntilString);
number = 1;
startTick = Environment.TickCount;
fromUntilString = Wait.UntilContains(getNumberString, "F"); Console.WriteLine("\r\nAfter Wait.UntilContains(getNumberString, \"F\"), number={0}, numberString={1}.\r\n"
, number, fromUntilString);
Func<int> getNumber = () =>
{
logTickAndNumber();
return number;
};
number = 1;
startTick = Environment.TickCount;
GenericWait<int>.Until(getNumber, i => i >= 3);
Console.WriteLine("\r\nAfter GenericWait<int>.Until(getNumber, i => i >= 3), number={0}.\r\n"
, number);
number = 1;
startTick = Environment.TickCount;
try
{
GenericWait<int>.Until(getNumber, i => i < 0);
}
catch (TimeoutException timeout)
{
Console.WriteLine("\r\nAfter GenericWait<int>.Until(getNumber, i => i < 0), number={0}.\r\n"
, number);
}
done = true;
Console.ReadKey();
}
private static int startTick = Environment.TickCount;
static void logTickAndNumber()
{
Console.WriteLine("After {0}ms: number={1}", Environment.TickCount - startTick, number);
}
public static bool done = false;
static void changeNumberPeriodically()
{
Random rdm = new Random();
do
{
Thread.Sleep(intervalInMills);
number = rdm.Next(1, 6);
} while (!done);
}
The first predicate returns "true" when the number is equal or greater than "4", and the result is:
After 0ms: number=0
After 0ms: number=0
After 0ms: number=0
After 16ms: number=0
After 16ms: number=0
After 32ms: number=3
After 78ms: number=2
After 141ms: number=5
After Wait.Until(predicate1), number=5, larger than 4
The second predicate shall always return "false" so Wait.Until() should timeout after 5 seconds.
After 0ms: number=5
After 0ms: number=5
After 0ms: number=5
After 0ms: number=4
After 16ms: number=4
After 31ms: number=2
After 62ms: number=3
After 125ms: number=3
After 234ms: number=4
After 328ms: number=4
...
After 4875ms: number=1
After 4984ms: number=2
After Wait.Until(predicate2, 5000), number=1, not larger than 10.
Wait.Until() to return a String
To design an Automation Test case, it is so offen to try to call a function to get a string as result, thus I expand the base Wait.Until() as below:
public static string UntilString(Func<string> getStringFunc, string expectedString, int timeoutMills = TimeoutInMills)
{
if (expectedString == null)
throw new ArgumentNullException();
string result = null;
Func<bool> predicate = () =>
{
result = getStringFunc();
return result == expectedString;
};
Until(predicate, timeoutMills);
return result;
}
public static string UntilContains(Func<string> getStringFunc, string expectedString, int timeoutMills = TimeoutInMills)
{
if (expectedString == null)
throw new ArgumentNullException();
string result = null;
Func<bool> predicate = () =>
{
result = getStringFunc();
return result.Contains(expectedString);
};
Until(predicate, timeoutMills);
return result;
}
The UntilString(Func<string>, string, int) expects a function without any parameters and merges it with the execptedString to compose the Func<bool> predicate parameter of Wait.Until(Func<bool> predicate, int timeoutMills).
However, in most cases, we might want to execute methods whose signature looks like string functionWithParameters(int, string, ...), then the tricky things need to be done is demonstrated as this example:
var wrapper = () => functionWithParameters(intParam, stringParam, otherParam);
string result = Wait.UntilString(wrapper, expectedString);
LINQ of .NET is really gorgeous, isn't it?
Its functionality is validated by following tests:
static void Main()
{
...
Func<string> getNumberString = () =>
{
string result = numbers[number - 1];
logTickAndNumber();
return result;
};
startTick = Environment.TickCount;
string fromUntilString = Wait.UntilString(getNumberString, "Five");
Console.WriteLine("\r\nAfter Wait.UntilString(getNumberString, \"Five\"), number={0}, numberString={1}.\r\n"
, number, fromUntilString);
number = 1;
startTick = Environment.TickCount;
fromUntilString = Wait.UntilContains(getNumberString, "F"); Console.WriteLine("\r\nAfter Wait.UntilContains(getNumberString, \"F\"), number={0}, numberString={1}.\r\n"
, number, fromUntilString);
...
}
The getNumberString shows how to compose a function with LINQ to perform any operations needed, and the result happens as expected:
After 0ms: number=1
After 0ms: number=1
After 0ms: number=1
After 15ms: number=1
After 15ms: number=1
After 31ms: number=2
After 62ms: number=5
After Wait.UntilString(getNumberString, "Five"), number=5, numberString=Five.
After 0ms: number=1
After 16ms: number=5
After Wait.UntilContains(getNumberString, "F"), number=5, numberString=Five.
Generic Wait.Unti()
More likely in real word, we need to call a function that might return any type of result, thus a generic version is defined as below:
public static class GenericWait<T>
{
public static T Until(Func<T> func, Func<T, bool> isExpected, int timeoutMills=Wait.TimeoutInMills)
{
if (func == null || isExpected == null)
throw new ArgumentNullException();
T result = default(T);
Func<bool> predicate = () =>
{
result = func();
return isExpected(result);
};
Wait.Until(predicate, timeoutMills);
return result;
}
}
The functions returning string discussed in last section can actually be replaced in this way:
Func<string> someFuncReturnsString = ()=>getString();
Func<string, bool> someDelegateOfString = s => s.Contains("abc");
string finalStringResult = GenericWait<string>.Until(someFuncReturnsString, someDelegateOfString);
And the functionality of GenericWait<T>.Until() is validated as below:
static void Main()
{
number = 1;
startTick = Environment.TickCount;
GenericWait<int>.Until(getNumber, i => i >= 3);
Console.WriteLine("\r\nAfter GenericWait<int>.Until(getNumber, i => i >= 3), number={0}.\r\n"
, number);
number = 1;
startTick = Environment.TickCount;
GenericWait<int>.Until(getNumber, i => i < 0);
Console.WriteLine("\r\nAfter GenericWait<int>.Until(getNumber, i => i < 0), number={0}.\r\n"
, number);
done = true;
Console.ReadKey();
}
And the result is:
After 16ms: number=1
After 16ms: number=1
After 16ms: number=1
After 16ms: number=3
After GenericWait<int>.Until(getNumber, i => i >= 3), number=3.
After 0ms: number=1
After 0ms: number=1
After 0ms: number=1
After 15ms: number=1
After 15ms: number=1
...
After 9843ms: number=2
After 9937ms: number=1
After GenericWait<int>.Until(getNumber, i => i < 0), number=5.
Notice that the second one quit after 10s that is the default TimeSpan Timeout defined in Wait class.
Summary
This article presents a lightweight Wait.Until() mechanism, and also because of its simplicity and the convenience coupled with LINQ and Function Delegate of .NET, it can be used universally to remove a lot of redundant codes.
History
19 May 2014: First version.
3 July 2014: Wait.Until() throw TimeoutException when timeout happens.