Reputation: 1220
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
Upvotes: 6
Views: 13298
Reputation: 553
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;
};
Upvotes: 0
Reputation: 143
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));
}
}
Upvotes: 0
Reputation: 606
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);
Upvotes: 0
Reputation: 31
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);
}
Upvotes: 3
Reputation: 588
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!
Upvotes: 2
Reputation: 4989
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 ) );
}
} );
}
Upvotes: 2