Serenity core 1.0.42 is out, with a major overhaul to implicit and explicit timeouts, making the timeout behaviour more consistent and more flexible.
Modern AJAX-based web applications add a great deal of complexity to web testing. The basic problem is, when you access a web element on a page, it may not be available yet. So you need to wait a bit. Indeed, many tests contain hard-coded pauses scattered through the code to cater for this sort of thing.
But hard-coded waits are evil. They slow down your test suite, and cause them to fail randomly if they are not long enough. Rather, you need to wait for a particular state or event. Selenium provides great support for this, and Serenity builds on this support to make it easier to use.
Implicit Waits
The first way you can manage how WebDriver handles tardy fields is to use the webdriver.timeouts.implicitlywait property. This determines how long, in milliseconds, WebDriver will wait if an element it tries to access is not present on the page. To quote the WebDriver documentation:
“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 value in Serenity for this property is currently 2 seconds. This is different from standard WebDriver, where the default is zero.
Let’s look at an example. Suppose we have a PageObject with a field defined like this:
@FindBy(id="slow-loader")
public WebElementFacade slowLoadingField;
This field takes a little while to load, so won’t be ready immediately on the page.
Now suppose we set the webdriver.timeouts.implicitlywait value to 5000, and that our test uses the slowLoadingField:
boolean loadingFinished = slowLoadingField.isDisplayed()
When we access this field, two things can happen. If the field takes less than 5 seconds to load, all will be good. But if it takes more than 5 seconds, a NoSuchElementException (or something similar) will be thrown.
That this timeout also applies for lists. Suppose we have defined a field like this, which takes some time to dynamically load:
@FindBy(css="#elements option")
public List<WebElementFacade> elementItems;
Now suppose we count the values of the element like this:
int itemCount = elementItems.size()
The number of items returned will depend on the implicit wait value. If we set the webdriver.timeouts.implicitlywait value to a very small value, WebDriver may only load some of the values. But if we give the list enough time to load completely, we will get the full list.
The implicit wait value is set globally for each WebDriver instance, but you can override the value yourself. The simplest way to do this from within a Serenity PageObject is to use the setImplicitTimeout() method:
setImplicitTimeout(5, SECONDS)
But remember this is a global configuration, so will also affect other page objects. So once you are done, you should always reset the implicit timeout to its previous value. Serenity gives you a handy method to do this:
resetImplicitTimeout()
See http://docs.seleniumhq.org/docs/04_webdriver_advanced.jsp#implicit-waits for more details on how the WebDriver implicit waits work.
Explicit Timeouts
You can also wait until an element is in a particular state. For example, we could wait until a field becomes visible:
slowLoadingField.waitUntilVisible()
You can also wait for more arbitrary conditions, e.g.
waitFor(ExpectedConditions.alertIsPresent())
The default time that Serenity will wait is determined by the webdriver.wait.for.timeout property. The default value for this property is 5 seconds.
Sometimes you want to give WebDriver some more time for a specific operation. From within a PageObject, you can override or extend the implicit timeout by using the withTimeoutOf() method. For example, you could wait for the #elements list to load for up to 5 seconds like this:
withTimeoutOf(5, SECONDS).waitForPresenceOf(By.cssSelector("#elements option"))
You can also specify the timeout for a field. For example, if you wanted to wait for up to 5 seconds for a button to become clickable before clicking on it, you could do the following:
someButton.withTimeoutOf(5, SECONDS).waitUntilClickable().click()
You can also use this approach to retrieve elements:
elements = withTimeoutOf(5, SECONDS).findAll("#elements option")
Finally, if a specific element a PageObject needs to have a bit more time to load, you can use the timeoutInSeconds attribute in the Serenity @FindBy annotation, e.g.
import net.serenitybdd.core.annotations.findby.FindBy;
...
@FindBy(name = "country", timeoutInSeconds="10")
public WebElementFacade country;
You can also wait for an element to be in a particular state, and then perform an action on the element. Here we wait for an element to be clickable before clicking on the element:
addToCartButton.withTimeoutOf(5, SECONDS).waitUntilClickable().click()
Or, you can wait directly on a web element:
@FindBy(id="share1-fb-like")
WebElementFacade facebookIcon;
...
public WebElementState facebookIcon() {
return withTimeoutOf(5, TimeUnit.SECONDS).waitFor(facebookIcon);
}
Or even:
List<WebElementFacade> currencies = withTimeoutOf(5, TimeUnit.SECONDS)
.waitFor(currencyTab)
.thenFindAll(".currency-code");
This is just an overview of a few of the ways you can handle asynchronous fields in Serenity – there are many variations around these themes. More detailed documentation will be available soon in the Serenity BDD documentation.