How to WebDriverWait

As WebDriver moves more towards being more of an API and less of a testing tool, functions that contained the logic to wait for pages such as wait_for_page_to_load() are being removed. The reason for this is that it is difficult to maintain consistent behaviour across all of the browsers that WebDriver supports on modern, dynamic webpages.

That leaves the onus on the people writing the framework and tests (that’s you and me!) to write the logic. This is both good and bad. The bad side is that it adds a lot of extra work for us to do and a lot of extra things for us to think about. Your tests might be frail if you don’t get your head around how to wait properly. But the good side is that we can control WebDriver and make our tests more stable so let’s get on with learning about it!

The first issue to understand is that detecting when a click is just a click and when a click loads a page is difficult for WebDriver. There are just too many things going on on modern webpages with Ajax, Javascript, CSS animations and so forth. So let’s forget all about that and just think about what we need on the page to be ready before the test can proceed.

What we are looking for is a good signal. The signal can be an element appearing, disappearing, being created, being deleted or something else altogether! However what is important is that it’s relevant to the action you are performing. For example if you are scrolling through pages of search results and waiting for the page of results to change then you should instruct WebDriver to wait for something in the new set of search results. Waiting for something outside of that area can be an unreliable signal.

At this point it’s a good start to step through the test manually or if you’re debugging, watch the test run on your computer. Watch for elements appearing, javascript, ajax, css animations. Narrow your target on the page down to the area that is changing dynamically or even better the specific element that you want to interact with in the next step of the test. Firebug and Firediff are very useful for this task.

WebDriver’s aim is to replicate the user’s action and as such if an element is not displayed then you can’t click it. This is where a lot of tests come unstuck. By stepping through manually or watching the test run we are looking from the user’s perspective. WebDriver can’t see elements changing so we need to see them with our own eye before we can tell WebDriver to check on them.

Waiting for element visibility
In WebDriver an element can be present but not visible – be wary of this! If an element is not visible we can’t click, type or interact with it so the test is not ready to proceed. It’s hard to judge whether you will be checking for element’s presence or visibility; every case might be different. But generally when dealing with CSS animation or ajax transitions we will check visibility. In this example we’ve just clicked on a button that changes the loginbox to be displayed:
WebDriverWait(self.selenium, 10).until(lambda s: s.find_element(By.ID, loginbox).is_displayed())

Waiting for elements to be deleted
When dealing with elements being deleted from the page we check that there are 0 on the page (WebDriverWait will suppress the ElementNotFoundException). This example is checking that all items in a list have been deleted:
WebDriverWait(self.selenium, 10).until(lambda s: len(s.find_elements(By.CSS_SELECTOR, ‘list-item’)) == 0)

You may have noticed in the example of waiting for elements to have been deleted that I used find_elements instead of find_element. This is because WebDriverWait’s until is written to wait for elements to appear and as such suppresses the ElementNotFoundException.
If you try and use this code WebDriverWait will timeout and finish your test even if the element is not present:
WebDriverWait(self.selenium, 10).until(lambda s: not s.find_element(By.ID, ‘delete-me’))

Waiting for attributes: avoid this!
Waiting for attributes (class, text, etc) of an element can be unreliable as it relies on the element being stable inside WebDriver’s element cache. In you-and-me terms that means that waiting for a new node to be present is safer than waiting for an existing one to have changed.
Unreliable:
WebDriverWait(self.selenium, 10).until(lambda s: s.find_element(By.ID, ‘label’).text == “Finished”)

When performing an action that requires a wait you can always log a value before (for example page number of the search results), perform the action and wait for that value to have changed:
page = page_object.page_number
self.selenium.find_element(By.ID, ‘next-page’).click()
WebDriverWait(self.selenium, 10).until(lambda s: page_object.page_number == page+1)

Reporting failures upon timeout
Reporting to the user a clear reason for a timeout failure is very valuable. In cases where the user has no knowledge of the steps of the test or the workflow of the AUT it saves time in having to re-run and debug the test investigating a failure.
As much as we try to make locators and variable names readable, sometimes a complex explicit wait is not clear. Treat it like an inline code comment where you want to communicate to the user, but keep the message brief.
To add a failure message simply add the message to the ‘until’ method:
WebDriverWait(self.selenium, 10).until(lambda s: s.find_elements(By.CSS_SELECTOR, ‘list-item’) == 0, “The list items were not deleted before the timeout”)

Tracking DOM attributes
Occasionally if a javascript package like jQuery is used to manipulate the contents of the page. Can you look at the DOM attributes to see when ajax actions are occurring? Use firebug’s DOM panel to inspect values or set a breakpoint and then replicate the action and watch the value change. This is a very stable option because it bypasses WebDriver’s element cache. jQuery has an attribute called ‘active’ that is easily watchable using this code:
WebDriverWait(self.selenium, 10).until(lambda s: s.execute_script("return jQuery.active == 0"))

Dealing with loading spinners and animations
Catching spinners or loading animations that come and go can be tricky! If you detect the spinner not being present then this might resolve to true before the spinner exists! Occasionally it’s more reliable to ignore the spinner altogether and just focus on waiting for an element on the page that the user will be waiting for. If you’re really struggling you can use a combination of the spinner and the dynamic element. Here is an example of both catching the spinner being deleted and a new element arriving:
WebDriverWait(self.selenium, 10).until(lambda s: s.find_element(By.ID, ‘new-element’) and s.find_elements(By.ID, ‘spinner’) == 0)

The order of WebDriverWait’s polling
While dealing with Ajax and WebDriverWait it is helpful to know a bit about exactly how the internals of WebDriverWait work. In simplified terms it will check the until equation, sleep, then check the equation again until the timeout is reached. The default setting for polling frequency (that means how much sleep between each the until equation) is 0.5 seconds.
The tricky part, however, is that WebDriverWait will check the until equation before it performs the first sleep. Thus if your Ajax has a slight delay, the very first poll of WebDriverWait might resolve true before the ajax has started. In effect, the the wait will not really have occurred at all because the first sleep was never reached.
There is no workaround for this and the only way to avoid it is to change the way or which element you are waiting for.

The StaleElementReferenceException during Waits
A StaleElementReferenceException may occur if javascript or Ajax is reloading the page during your explicit wait. The exception is thrown because, while WebDriver can find the locator before and after the page reload, it can also see that the element is different and it deems it untrustworthy (or stale). This relates to the previous section about WebDriverWait’s polling order.
If the developers are changing the classes of an element before and after then one effective way to wait is to use two locators to locate a single element in each of its states. This is slightly more verbose but the trade-off is a reliable test.

Before login: (By.CSS_SELECTOR, ‘div#user.not_authenticated’)
After login: (By.CSS_SELECTOR, ‘div#user.authenticated’)
WebDriverWait(self.selenium, 10).until(lambda s: s.find_element(By.ID, ‘div#user.authenticated’).is_displayed())

In this case even though the HTML

is the same, WebDriver will consider the elements to be different and hence one will only be found after the page refresh and the authenticated class is set.

20 Responses to How to WebDriverWait

  1. I want to wait on a page for 10 minutes without having any condition, Please suggest any method to do it properly.

    Thanks
    Rishi Sharma

    • You should first ask yourself why you need to do that. It sounds like a hatchet job you’re executing there.

      However if you must the code is not complicated. Just substitute the 10 in my examples for 600 (seconds).

  2. Pingback: Improve the performance of your automated tests | Laurent Bristiel

  3. In my situation webdriver times out at times but not at others. I want to globally increase the wait time for these elements. When the exception occurs, it proclaims the wait time is set very low (i.e. 567 milliseconds).

    org.openqa.selenium.NoSuchElementException: Unable to locate element: {“method”:”tag name”,”selector”:”html”}
    Command duration or timeout: 567 milliseconds

  4. it is really nice explanation about WebDriverWait. but my qus is

    i am try to control pop-up windows in”http://www.quikr.com” site. when i am testing this site a popup windows come up and ask for choose your current location. how can i avoid this and direct access in to home page.

    please give me a correct suggestion

  5. Hello!
    Tell me, please, what to do if the page is not loaded? If it not fully loaded – I can’t do anything.
    For example:
    I have some pages, which loading a very long time. I can use driver.set_page_load_timeout(), but it works with driver.get or driver.refresh. But If I click on the page, then it doesn’t work, and page is hanging some times, so I can’t even to stop it or refresh. So the program hanging forever.

  6. In the section titled Waiting for elements to be deleted, it says “WebDriverWait will suppress the ElementNotFoundException”, but the example code uses find_elements(By.CSS_SELECTOR..). Does find_elements ever throw ElementNotFoundException? I thought only find_element methods do this -whereas the plural version simply returns an empty list if nothing is found? Thanks for any thoughts

  7. Pingback: QA | Pearltrees

  8. Pingback: How to click an component manifest after hovering with selenium? - Aberle

  9. When I log in to the website, I want to be sure that some element is displayed, what type of wait I should use?

  10. Is it possible to call a function outside of the WebDriver in the .until? No matter what I try, I get the exception: Exception: ‘WebDriver’ object has no attribute ‘verifyObj_tag’. I have a class called ‘ad_selenium’ and all calls to selenium are encapsulated within the library. The explicitWait function I wrote is trying to use another class method in the .until:
    element = WebDriverWait(self.__WD, seconds).until( lambda self: self.verifyObj_tag(tag_name,search_for,element=element,compare=compare,debug=debug) )

  11. WebDriverWait(driver, 60)).until(ExpectedConditions.presenceOfElementLocated(By.id))
    is not timing out even after ten seconds.
    The call to this method results in the test case to hang.
    Any pointers where to look at the issue.

    PS: When the condition is met the method execution is fine else the method call hangs

  12. WebDriverWait(driver, 60)).until(ExpectedConditions.presenceOfElementLocated(By.id))
    is not timing out even after ten seconds.
    The call to this method results in the test case to hang.
    Any pointers where to look at the issue.

    PS: When the condition is met the method execution is fine else the method call hangs.

    The firefox version is 27.0.1 and selenium web driver is 2.4.0

  13. this post is still so good!

  14. Excellent info ! Thanks..

  15. Super useful information, thank you!

  16. when ever i click on any element i encounter “element not clickable”.. but that element is not a dropdown menu. but it is linked to dropdown menu..
    what should i do in this case?????

  17. Hi,

    I want to wait till the status of a element changes. So, while waiting i need to refresh the page as well so that the changed status should be reflected.
    I am using python-selenium.
    Please suggest some good options.

Leave a Reply

Your email address will not be published. Required fields are marked *