The wait.until returns stale element, how to wait until DOM is stable

Viewed 58

I have a blazor application running fine and want to have some behavior test with selenium. The test does now currently the following:

  • goes to a page (directly using an URL, no page loaded before)
  • tries to click on a button

The first point does work, but the second has an issue. If I use the wait.until the button is available, then I receive back an early version of the button, which then redrawn and updated in the DOM later. This will give me the "stale element reference: element is not attached to the page document" error.

Here is the code:

var xPath = By.XPath($".//tr//td[normalize-space()=\"{name}\"]/ancestor-or-self::tr//button");
var button = _wait.Until(ExpectedConditions.ElementToBeClickable(xPath));
Thread.Sleep(1000);
button = _chromeDriver.FindElement(xPath);
button.Click();

the _wait.until will return an item that will be stale, while the next FindElement will return a valid, but only after ~1 sec sleep. If I don't have sleep there, it will return the same as the other line.

The final question: How can I ensure in the _wait.until line, that my returned element is the final one to avoid using Thread.Sleep?

5 Answers

There is no gentle solution for such issues.
The only solution I know for this is to make a loop:
Wait for element clickability and click it.
In case exception is thrown wait for the clickability again.
If click succed - break the loop and continue to outer code / return True.
Something like this:

for(i=0; i<6; i++){
    try: {
        wait.Until(ExpectedConditions.ElementToBeClickable(xPath)).Click(); 
        break;
    }
    catch(Exception e){
        Thread.Sleep(100);
    }
}

Selenium Wait.Until conditions are not 100% reliable. I was in shock when I figure×’ this myself, but yes, after the condition has finished you sometimes have to wait or try clicking or hovering in a loop.

I wish to suggest 2 options here:

  1. Wait for the element to be both Clickable and Enabled:

    getWait().until((ExpectedCondition) driver -> element.isEnabled());

  2. Use a much better UI tool for testing like Playwright. It has a much sophisticated waiting system conditions and is also faster and very reliable.

Prophet's answer is a good approach, so have a look at that. Sometimes you just need to try something more than one time. This is an alternative that is easier to reuse and more flexible.

All of the methods on the WebDriverWait class expect a lambda express to return a value. Sometimes I need to call a method with a void return type, so I write an extension method on WebDriverWait to support this:

public static class WebDriverWaitExtensions
{
    public static void Until(this WebDriverWait wait, Action<IWebDriver> action)
    {
        wait.Until(driver =>
        {
            action(driver);

            return true;
        });
    }
}

This is much more flexible and easier to call with your code. This will attempt to click the element until it succeeds. A word of caution, however. The StaleElementException will cancel a wait operation immediately. Be sure to ignore this kind of exception.

var xPath = By.XPath($".//tr//td[normalize-space()=\"{name}\"]/ancestor-or-self::tr//button");

_wait.IgnoredExceptionTypes(typeof(StaleElementException));

_wait.Until(driver => driver.FindElement(xpath).Click());

This should work with any web driver method that has a void return type.

It seems another solution is this:

_wait.Until(driver => (bool)((IJavaScriptExecutor)driver).ExecuteScript("return typeof Blazor.reconnect != 'undefined'"));

as that variable only loaded when the page is fully loaded. And after that it's possible to do a simple find without an issue:

var button = _chromeDriver.FindElement(xPath);
Related