27
votes

I am new to Selenium WebDriver and am trying to understand the correct way to 'wait' for elements to be present.

I am testing a page with a bunch of questions that have radio button answers. As you select answers, Javascript may enable/disable some of the questions on the page.

The problem seems to be that Selenium is 'clicking too fast' and not waiting for the Javascript to finish. I have tried solving this problem in two ways - explicit waits solved the problem. Specifically, this works, and solves my issue:

private static WebElement findElement(final WebDriver driver, final By locator, final int timeoutSeconds) {
    FluentWait<WebDriver> wait = new FluentWait<WebDriver>(driver)
            .withTimeout(timeoutSeconds, TimeUnit.SECONDS)
            .pollingEvery(500, TimeUnit.MILLISECONDS)
            .ignoring(NoSuchElementException.class);

    return wait.until(new Function<WebDriver, WebElement>() {
        public WebElement apply(WebDriver webDriver) {
            return driver.findElement(locator);
        }
    });
}

However, I would prefer to use an implicit wait instead of this. I have my web driver configured like this:

driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);

This does not solve the problem and I get a NoSuchElementException. Additionally, I do not notice a 10 second pause - it just errors out immediately. I have verified this line in the code is being hit with a debugger. What am I doing wrong? Why does implicitlyWait not wait for the element to appear, but FluentWait does?

Note: As I mentioned I already have a work around, I really just want to know why Implicit wait isn't solving my issue. Thanks.

9

9 Answers

27
votes

Remember that there is a difference between several scenarios:

  • An element not being present at all in the DOM.
  • An element being present in the DOM but not visible.
  • An element being present in the DOM but not enabled. (i.e. clickable)

My guess is that if some of the page is being displayed with javascript, the elements are already present in the browser DOM, but are not visible. The implicit wait only waits for an element to appear in the DOM, so it returns immediately, but when you try to interact with the element you get a NoSuchElementException. You could test this hypothesis by writing a helper method that explicits waits for an element to be be visible or clickable.

Some examples (in Java):

public WebElement getWhenVisible(By locator, int timeout) {
    WebElement element = null;
    WebDriverWait wait = new WebDriverWait(driver, timeout);
    element = wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
    return element;
}

public void clickWhenReady(By locator, int timeout) {
    WebDriverWait wait = new WebDriverWait(driver, timeout);
    WebElement element = wait.until(ExpectedConditions.elementToBeClickable(locator));
    element.click();
}
3
votes

basic idea is in the following:

Explicit wait

WebDriverWait.until(condition-that-finds-the-element);

Implicit wait

driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);

In other words, explicit is associated with some condition to be held, whereas implicit with some time to wait for something. see this link

To make work fluentWait properly try this:

public WebElement fluentWait(final By locator){
    Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
        .withTimeout(Duration.ofSeconds(30))
        .pollingEvery(Duration.ofMillis(100))
        .ignoring(NoSuchElementException.class);

    WebElement foo = wait.until(
        new Function<WebDriver, WebElement>() {
            public WebElement apply(WebDriver driver) {
                return driver.findElement(locator);
            }
        }
    );
    return foo;
};

Hope this helps)

2
votes

Word of warning for a common mistake:

Once you set implicit waiting, you cannot use explicit- or fluent wait until you reset the implicit waiting again. This means that ExpectedConditions, which contain driver.findElement calls will not work as expected with implicit wait! You'll often encounter cases where you want to check for an element or its non-existence instantly - but you can't do that either.

After ~2 years of experience and problems with this I strongly recommend against using implicit wait.

1
votes

A kotlin version of the https://stackoverflow.com/users/503060/hedley answer:

clickWhenReady("#suggest",10,driver)

via

fun clickWhenReady(selector: String,timeout: Long, webdriver: WebDriver?) {
    val wait = WebDriverWait(webdriver, timeout);
    val element = wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector(selector)));
    element.click();
}
0
votes

I wrote a small method in C# using the WebDriverWait class. Works great for me.

public static void WaitForAjaxElement(IWebDriver driver, By byElement, double timeoutSeconds)
{
  WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds));
  wait.Until(x => x.FindElement(byElement));
}

Using:

WaitForAjaxElement(driver, By.ClassName("ui-menu-item"), 10);

Hope it helps.

0
votes

From Seleniumhq.com:

An implicit wait is to tell WebDriver to poll the DOM for a certain amount of time when trying to find an element or elements if they are not immediately available. The default setting is 0. Once set, the implicit wait is set for the life of the WebDriver object instance.

If you post your test code what you actually want to do I can provide more information.

0
votes

I have another solution to solve this issue (only for IE, i never try other browser):

1) after create Selenium driver instance, you can get its ie COM instance

Add-Type -Path .\SePSX.NET35\WebDriver.dll
$ieDriver = New-Object "OpenQA.Selenium.IE.InternetExplorerDriver"

$ieShell = $null

$shell_apps = (New-Object -ComObject Shell.Application).Windows()
foreach($app in $shell_apps)
{
    if ($app.LocationURL -eq $ieDriver.URL)
    {
        $ieShell = $app
        break
    }
}

if ($ieShell -eq $null)
{
    throw "Can't get WebDriver IE Instance"
}

2) after each call GotoURL or click action, check $ieShell.Busy status, it will wait for until page is loaded.

$ieDriver.Navigate().GotoUrl("www.google.com")
while ($ieShell.Busy -eq $true) {sleep 1}   

then call Selenium driver to get element id and do the further action

$ieDriver.FindElementById ...

use this way, you don't need to set page load and findElement timeout for Selenium

0
votes
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;


FluentWait<WebDriver> wait = new FluentWait<WebDriver>(driver);
            wait.pollingEvery(250,  TimeUnit.MILLISECONDS);
            wait.withTimeout(20, TimeUnit.SECONDS);
            wait.ignoring(NoSuchElementException.class);

            Predicate<WebDriver> predicate  = new Predicate <WebDriver>()
                    {
                        public boolean apply(WebDriver arg0) {
                            WebElement element = arg0.findElement(By.id("colorVar"));
                            String color = element.getAttribute("color");
                            System.out.println("The color if the button is " + color);
                            if(color.equals("blue"))
                            {
                                return true;
                            }
                            return false;
                        }
                    };

            wait.until(predicate);
-1
votes

Below is the code equivalet code for fluient wait in c#.Net using DefaultWait.

         IWait<IWebDriver> wait = new DefaultWait<IWebDriver>(driver);
         wait.Timeout = TimeSpan.FromSeconds(10);
         wait.PollingInterval = TimeSpan.FromMilliseconds(100);

        IWebElement elementt = wait.Until<IWebElement>(ExpectedConditions.ElementIsVisible(By.Id("selectedfirstlast1")));
        SelectElement se = new SelectElement(driver.FindElement(By.Id("selectedfirstlast1")));
        element = se.SelectedOption;              
            if (element.Text.Contains("Mumbai") && element.Selected)
                driver.FindElement(By.XPath("//table/tbody/tr[2]/td[7]/a")).Click();