Click here to Skip to main content
Click here to Skip to main content

Lightweight Wait.Until() Mechanism

, 3 Jul 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
This article presents a Wait.Until() mechanism that might be helpful in various scenarios.

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
{
    //Mininum time in mSec waited before execution
    public const int MinIntervalMills = 1;
    //Maximum time in mSec waited before next execution
    public const int MaxIntervalMills = 100;

    //Maxium time to be waited before timeout
    public const int TimeoutInMills = 10 * 1000;
    public static TimeSpan Timeout = TimeSpan.FromMilliseconds(TimeoutInMills);

    /// <summary>
    /// This method execute any commands wrapped as a Predicate periodically until it is timeout.
    /// If the command execution is success (when predicate returns true), then it would return immediately.
    /// Otherwise, all exepections except the last one due to the command execution would be discarded considering
    /// that they are identical; and the last exception would be throw when command execution is not success when
    /// timeout happens.
    /// </summary>
    /// <param name="predicate">Wrapper of the execution codes that might throw exceptions.</param>
    /// <param name="timeoutMills">Time waited before draw conclusion that the command cannnot succeed.</param>
    public static void Until(Func<bool> predicate, int timeoutMills = TimeoutInMills)
    {
        if (timeoutMills <= 0)
            throw new InvalidEnumArgumentException("The timeout must be a positive value");

        //Get the moment when the execution is considered to be timeout
        DateTime timeoutMoment = DateTime.Now + TimeSpan.FromMilliseconds(timeoutMills);

        int interval = MinIntervalMills;
        Exception lastException = null;

        do
        {
            try
            {
                //If something happen as expected, return immediately and ignore the previous exception
                if (predicate())
                    return;
            }
            catch (Exception ex)
            {
                // Intentionally record only the last Exception due to the fact that usually it is due to same reason
                lastException = ex;
            }

            //Waiting for a period before execution codes within predicate()
            Thread.Sleep(interval);

            //The waiting time is extended, but no more than that defined by MaxIntervalMills
            interval = Math.Min(interval * 2, MaxIntervalMills);

        } while (DateTime.Now < timeoutMoment);

        //Exected only when timeout before expected event happen

        //If there is some exception during the past executions, throw it for debugging purposes
        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();

            //Testing of Wait.Until()
            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");

            //Testing of Wait.Until() when timeout happens
            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");
            }

            //Testing of Wait.UntilString()
            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);

            //Testing of Wait.UntilContains()
            number = 1;
            startTick = Environment.TickCount;
            fromUntilString = Wait.UntilContains(getNumberString, "F"); //"Four" or "Five"
            Console.WriteLine("\r\nAfter Wait.UntilContains(getNumberString, \"F\"), number={0}, numberString={1}.\r\n"
                , number, fromUntilString);

            //Testing of GenericWait.Until()
            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);

            //Testing of GenericWait.Until() when timeout is sure to happen
            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);
            }

            //Set done to quit the thread of t
            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:

/// <summary>
/// This method keep executing any function that a string as result by using the mechnism of Until()
/// until timeout or the result is exactly as "expectedString".
/// </summary>
/// <param name="getStringFunc">Any function returning string.
///  For functions with parameters, for example: 
///     public string someFunc(int param), 
///  This method can be called with assitance of LINQ as below:
///     UntilString(()=>someFunc(param), expectedString)
/// </param>
/// <param name="expectedString">string expected that cannot be null.</param>
/// <param name="timeoutMills">Time waited before draw conclusion that the command cannnot succeed.</param>
/// <returns>The final result of calling getStringFunc().</returns>
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;
}

/// <summary>
/// This method keep executing any function that a string as result by using the mechnism of Until()
/// until timeout or the result contains the "expectedString".
/// </summary>
/// <param name="getStringFunc">Any function returning string.
///  For functions with parameters, for example: 
///     public string someFunc(int param), 
///  This method can be called with assitance of LINQ as below:
///     UntilString(()=>someFunc(param), expectedString)
/// </param>
/// <param name="expectedString">string expected to be contained by calling getStringFunc().</param>
/// <param name="timeoutMills">Time waited before draw conclusion that the command cannnot succeed.</param>
/// <returns>The final result of calling getStringFunc().</returns>
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()
{
    ...
    //Testing of Wait.UntilString()
    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);

    //Testing of Wait.UntilContains()
    number = 1;
    startTick = Environment.TickCount;
    fromUntilString = Wait.UntilContains(getNumberString, "F"); //"Four" or "Five"
    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>
{
    /// <summary>
    /// This method execute func() continuously by calling Wait.Until() until timeout or expected condition is met.
    /// </summary>
    /// <param name="func">
    /// Any function returning T as result.
    /// For functions whose signature has one or more parameters, for example: 
    ///     public T someFunc(int param), 
    ///  This method can be called with assitance of LINQ as below:
    ///     Until(()=>someFunc(param), isExpected)
    /// </param>
    /// <param name="isExpected">Predicate to judge if the result returned by func() is expected</param>
    /// <param name="timeoutMills">Time waited before draw conclusion that the command cannnot succeed.</param>
    /// <returns>The last result returned by func().</returns>
    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);

    //Testing of GenericWait.Until() when timeout is sure to happen
    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);

    //Set done to quit the thread of t
    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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

WilliamCruisoring
Software Developer
Australia Australia
No Biography provided

Comments and Discussions

 
QuestionHow about SpinWait()? PinmemberFatCatProgrammer19-Jun-14 4:49 
AnswerRe: How about SpinWait()? PinmemberWilliamCruisoring19-Jun-14 17:32 
GeneralRe: How about SpinWait()? PinmemberFatCatProgrammer20-Jun-14 7:02 
GeneralRe: How about SpinWait()? PinmemberWilliamCruisoring20-Jun-14 15:29 
QuestionNice William! PinprofessionalVolynsky Alex18-Jun-14 23:20 
AnswerRe: Nice William! PinmemberWilliamCruisoring19-Jun-14 1:59 
GeneralRe: Nice William! PinprofessionalVolynsky Alex19-Jun-14 3:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411023.1 | Last Updated 3 Jul 2014
Article Copyright 2014 by WilliamCruisoring
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid