Search code examples
c#selenium-webdrivernunitparallel-testingspinwait

SpinWait.SpinUntil taking MUCH longer than timeout to exit while waiting for Selnium element to exist


I have a relatively simple method to wait until an element exists and is displayed. The method handles the situation where more than a single element is returned for the given By (usually we only expect one of them to be displayed but in any case the method will return the first displayed element found).

The issue I'm having is that when there is no matching element on the page (at all), it is taking magnitudes of time more* than the TimeSpan specified, and I can't figure why.

*I just tested with a 30s timeout and it took a little over 5m

code:

    /// <summary>
    /// Returns the (first) element that is displayed when multiple elements are found on page for the same by
    /// </summary>
    public static IWebElement FindDisplayedElement(By by, int secondsToWait = 30)
    {
        WebDriver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(secondsToWait);
        // Wait for an element to exist and also displayed
        IWebElement element = null;
        bool success = SpinWait.SpinUntil(() =>
        {
            var collection = WebDriver.FindElements(by);
            if (collection.Count <= 0)
                return false;
            element = collection.ToList().FirstOrDefault(x => x.Displayed == true);
            return element != null;
        }
        , TimeSpan.FromSeconds(secondsToWait));

        if (success)
            return element;
        // if element still not found
        throw new NoSuchElementException("Could not find visible element with by: " + by.ToString());
    }

You would call it with something like this:

    [Test]
    public void FindDisplayedElement()
    {
       webDriver.Navigate().GoToUrl("https://stackoverflow.com/questions");
       var nonExistenetElementBy = By.CssSelector("#custom-header99");
       FindDisplayedElement(nonExistenetElementBy , 10);
    }

If you run the test (with 10s timeout) you will find it takes about 100 seconds to actually exit.

It looks like it might have something to do with the mix of the inherit wait built into WebDriver.FindElements() wrapped inside a SpinWait.WaitUntil().

Would like to hear what you guys think about this conundrum.

Cheers!


Solution

  • Doing some further testing I found out that reducing the WebDriver Implicit Wait Timeout to a low number (e.g. 100ms) fixes the issue. This corresponds to the explanation @Evk provided to why using SpinUntil doesn't work.

    I've changed the function to use WebDriverWait instead (as shown in this answer to a different question) and it now works correctly. This removed the need to use the implicit wait timeout at all.

        /// <summary>
        /// Returns the (first) element that is displayed when multiple elements are found on page for the same by
        /// </summary>
        /// <exception cref="NoSuchElementException">Thrown when either an element is not found or none of the found elements is displayed</exception>
        public static IWebElement FindDisplayedElement(By by, int secondsToWait = DEFAULT_WAIT)
        {
            var wait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(secondsToWait));
            try
            {
                return wait.Until(condition =>
                {
                    return WebDriver.FindElements(by).ToList().FirstOrDefault(x => x.Displayed == true);
                });
            }
            catch (WebDriverTimeoutException ex)
            {
                throw new NoSuchElementException("Could not find visible element with by: " + by.ToString(), ex);
            }
        }