Waiting for updates whether they are synchronous or asynchronous is vital for tests to function properly. This part of documentation will tell you everything you need to know about waiting in ZWL tests. We'll try to cover most of the problems you may face and their solution.
You've already learned how finding elements waits automatically and work in both sync or async updates. We've also showed you how to wait manually for something to happen when you're not finding elements.
We provide extensive set of functions for various manual wait use cases. These functions wait for a certain time until a predefined expectation is met. If the expectation is not met before timeout reaches, a timeout error is thrown leading to test failure. Following sample shows an until-expectation function that expects an element to be visible:
# Imagine some menu is set to 'hidden'hiddenMenu = findElement('billingMenu', by.testId)# Checking a box will make an ajax call to check some permissions before# showing the menuclick(findElement('show-billing', by.name))# Wait until the menu becomes visibleuntilVisible(hiddenMenu)# Menu is visible, we can now interact with it
The maximum time for which until-expectation functions wait depends on the type of function used. For instance,
untilVisible(elemId) waits for Element access timeout.
See Api Reference for all available until-expectation functions.
You can customize the timeout value for each call of until-expectation functions. This allows you to wait for longer or shorter time period depending on the asynchronous or synchronous work you're doing.
Every until-expectation function has ability to customize timeout value that is specific to a function call. They all have a parameter called
functionLevelTimeout accepted as the last argument. It's value is retrieved using function
untilFlt(timeout, poll). The parameters of this function are:
timeout: The desired timeout in milliseconds. To set just the poll interval, set it to 0.
poll: Optional poll interval to repeatedly check for the expectation. Default is 500 milliseconds. If set to 0, default poll interval is used. Be careful before changing poll interval as a longer poll time can lead to longer waits if expectations can be met earlier (for instance, a poll is set to 2 seconds whereas one of the expectation is met in less than a second). Similarly a very short poll can check expectations too many times wasting CPU resources and may lead to problems in build. The default poll suits most needs and may not need to be changed unless you've a specific use case.
When timeout is set to 0 and poll is either omitted or set to 0, default timeout and poll interval is used. Following snippet shows how to use it in a function to customize the timeout.
hiddenMenu = findElement('billingMenu', by.testId)click(findElement('show-billing', by.name))untilVisible(hiddenMenu, untilFlt(120 * 1000))# Will increase the maximum time to wait for 120 seconds just for this function call # We can also keep the returned value of `untilFlt` in a variable# and use in multiple places like so,customTimeout = untilFlt(200 * 1000, 1000)untilVisible(hiddenMenu, customTimeout)# Will increase the maximum time to wait to 200 seconds and poll interval to 1 second
Imagine you've found an element and kept it's unique
id in a variable. Due to some action, the current page is reloaded, replaced by new page or replaced by new partial content. If any of these happens, the found element looses it's reference on page and further access of it throws a
StaleElementAccess error leading to test failure. If the page was reloaded or the same element is present on new page/content, we can gain access to the element by finding it again using the same selector.
Element staleness is special because it is not tied to the selector of the element. For instance, a found element goes stale if the page is reloaded. The same element is still present on the page after reload and can be accessed using the same selector but it's old reference or unique
id is no more valid. This property of an element helps greatly in some use cases including waiting for new content.
refresh() causes navigation that is handled internally by webdriver. Webdriver then waits on it's own until navigation is completed before processing the next command. No explicit code is needed here to wait for the navigation to complete.
Most of the time a page load is triggered through another ways, such as submitting a page, clicking on links, clicking on page elements that triggers asynchronous page rendering without reload etc. In these situations we may or may not have to apply a manual wait for the new page to render. See below:
If you're accessing an element from the new page, the wait will be automatic and no explicit wait is required. ZWL will wait until the new element is rendered and that will happen once the new page is rendered. You just need to make sure the selector you're using for the element doesn't select an element from the current page (from where you triggered the action) otherwise an element will be returned immediately that is not from the new page but old page and you wouldn't yet have the new page rendered.
If the element finding approach isn't an option for any reason, there are numerous ways to wait for the new page:
Find any element on page and keep it's
idin a variable before triggering the action. Use function
untilStale(elemId)which will wait until the element is removed from the DOM tree. This means that the current page is being replaced by new page. Once the wait completes, further accesses will use content from new page. We strongly suggest using this method over others as it is not dependent on any hardcoded values and is very reliable.
If you're ok with matching urls and all pages have unique urls, you can use
untilUrlLike(regex)functions and provide new page's url. This will wait until the url matches the given expression which means new page has come up.
Similar to page url, page title can also be used using functions
During an asynchronous operation, new partial content can render on a page replacing some old content. Similar to new page render scenario, if we're finding a new element from new content whose selector doesn't match any element in old content, the wait will be automatic. Otherwise we can use element's staleness property for waiting similar to what we've just advised in new page render scenario.
Make sure the element whose
id we've kept is part of the content that is going to be replaced by new content. Invoking
untilStale(elemId) will then wait until the element is removed from page which means new content is rendered.
Sometimes you trigger an action that removes an element from page with a delay such as deleting a completed note from a note taking app after receiving a successful deletion response from api. To wait for the removal to complete, use function
untilRemoved(using, by). This will wait until element is removed from DOM tree.
untilStale and not
untilRemoved to wait for new content#
untilRemoved depends on element's selector rather than element identity. Imagine you provided a selector, used
untilRemoved and waited till it's removed from the page before accessing elements from new content. What if the new content has an element that matches your selector intended for the old content? In that case,
untilRemoved will never return and timeout occurs because the selector always found an element on page.
In contrast to
untilStale isn't dependent on selector but identity. Even if there is an element with same selector in new content, the element from old content will stale as soon as new content replaced the old.