简体   繁体   中英

Selenium 2 - How to check if element not present while implicitly waiting?

If you check absent elements with the following code:

// ...
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
try {
    driver.findElement(By.cssSelector("td.name"));
} catch (NoSuchElementException e) {

    // here you go, element not found

}

You get right result, but running time is always 30 seconds, due to findElement method blocking on the implicit wait.

Is there a way to avoid this behavior, while keeping the implicit wait in place?

<EDIT> tests are going to be generated through Selenium IDE by non-developers, so I need a solution that keeps their job as simple as possible (that's keeping waits implicit!). </EDIT>

Thanks,

Marco

The methods above wait for the provided amount of time even if the element is not present anymore. I wrote my own methods for waiting until element is visible and not present. They work for me. Here they are:

public void waitUntilElementExists(By by, int waitSeconds,
        int noOfRetries) {
    getDriver().manage().timeouts().implicitlyWait(waitSeconds, TimeUnit.SECONDS);
    boolean foundElement = false;
    for (int i = 0; i < noOfRetries; i++)
        try {
            getDriver().findElement(by);
            foundElement = true;
            break;
        } catch (Exception e) {
        }
    assertTrue("The searched element was not found after " + noOfRetries * waitSeconds + " seconds!", foundElement);
}

public void waitUntilElementDoesntExist(By by, int waitSeconds,
        int noOfRetries) {
    getDriver().manage().timeouts().implicitlyWait(waitSeconds, TimeUnit.SECONDS);
    boolean elementDisappeared = false;
    for (int i = 0; i < noOfRetries; i++)
        try {
            getDriver().findElement(by);
            waitABit(1000 * waitSeconds);
        } catch (Exception e) {
            elementDisappeared = true;
            break;
        }
    assertTrue("The searched element did not disappear after " + noOfRetries * waitSeconds + " seconds!", elementDisappeared);
}

You might be able to do it with xpath selectors. Find the element just before it that you know should be there, then use "following-sibling" to get the next element. Something like:

//td.previous/following-sibling::td

Then check to see that it hasn't returned the "name" one. Of course that would only work if there is another "td" element.

Personally I'd be tempted to drop the implicit waits and just use waits when they are required.

private WebElement cssWait( final String css )
{
    return new WebDriverWait( driver, 30 ).until( new ExpectedCondition< WebElement >()
    {
        @Override
        public WebElement apply( WebDriver d )
        {
            return d.findElement( By.cssSelector( css ) );
        }
    } );
}

Instead of setting up timeouts I use fluentWait which were introduced in 2.25.

public void waitForElement(WebDriver driver, final String xpath)
{
 //Set up fluentWait to wait for 35 seconds polling every 1
 Wait<WebDriver> fluentWait = new FluentWait<WebDriver>(driver)
     .withTimeout(35, TimeUnit.SECONDS)
     .pollingEvery(1, TimeUnit.SECONDS)
     .ignoring(NoSuchElementException.class);

 WebElement element;

 //Look for element, if not found start fluentWait
 try
 {
     element = driver.findElement(By.xpath(xpath));
 }
 catch (WebDriverException e)
 {
     logger.info("[getElementByXpath] Element not initially found. Starting fluentWait ["+xpath+"]");

     try
     {
         element = fluentWait.until(new Function<WebDriver, WebElement>() {
             public WebElement apply(WebDriver d) {

                 return d.findElement(By.xpath(xpath));
             }
         });
     }
     catch (WebDriverException f)
     {
         logger.info("[getElementByXpath] FluentWait findElement threw exception:\n\n" + f +"\n\n");

         throw new WebDriverException("Unable to find element ["+xpath+"]");
     }
 }

 //Once we've found the element wait for element to become visible
 fluentWait.until(ExpectedConditions.visibilityOf(element));
}

If you were to convert your methods to something like this, you would be able to remove your driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS); allowing you to 'Not' find an element instantly.

Hope this helps!

You need a function like this, that uses findElements , not findElement :

public static ExpectedCondition<Boolean> elementCountIs(final By sel, final int count) {
    return new ExpectedCondition<Boolean>() {
        public Boolean apply(WebDriver driver) {
            return driver.findElements(sel).size() == count;
        }
    };
}

Then you can set up a FluentWait object as described by Falkenfighter and:

fluentWait.until(elementCountIs(By.cssSelector("td.name"), 0);

You'll have to update the ImplicitWait temporarily, and reset it after you're done.

This is the way we've handled this situation - save the current default, update the ImplicitWait temporarily, then change back to the default afterwards.
This is based off Mozilla's suggestion, which is how they handle this situation where you are expecting something to not be present: https://blog.mozilla.org/webqa/2012/07/12/webdrivers-implicit-wait-and-deleting-elements/

public bool ElementExists(By by, int waitMilliseconds)
{
        var defaultWebDriverTimeout = 30000;// Get default timeout that you're using
        WebDriver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromMilliseconds(waitMilliseconds));

        try
        {
            WebDriver.FindElement(by); //Note could be FindElements instead, but this is quicker
            return true;
        }
        catch (NoSuchElementException)
        {
            return false;
        }
        finally
        {
            WebDriver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromMilliseconds(defaultWebDriverTimeout));
        }
}

No, you cannot. implicit wait time will have precedence over explicit waits. If your implicit time is 30s, any finds you run will at least 30s in case the element is not present. What you could do is manipulate the implicit wait time on your framework, but not sure how that goes with the IDE, I've never worked with it.

I created a custom method that returns a boolean with the result. The input is any By locator supported by WebDriver (CSS, xpath, etc) . Or, you can modify it as you like.

It helped to make my code cleaner and faster. I hope it helps other people too.

The default pooling is 500 Millis, but it can be changed on the wait object.

public boolean isElementNotPresent(final By locator) {
        boolean result = false;
        // use your custom timeout here
        long timeout = ConfigurationProvider.getWebDriverWaitTimeout();

        // log4j used
        msg = "isElementNotPresent: " + locator;
        LOG.info(msg);

        Wait<WebDriver> wait = new FluentWait<WebDriver>(
                getDriver()).withTimeout(timeout, TimeUnit.SECONDS);

        try {
            result = wait.until(new Function<WebDriver, Boolean>() {
                @Override
                public Boolean apply(WebDriver driver) {
                    return driver.findElements(locator).size() == 0;
                }
            });
        } catch (TimeoutException e) {
            msg = String.format("Element remained visible after %.2f seconds",
            ((float) timeout / 1000));
            LOG.debug(msg);
        } catch (Exception e) {
            msg = "Exception at isElementNotPresent()\n" + e.getMessage();
            // I use jUnit to fail my test
            Assert.fail(msg);
        }

        return result;
    };

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM