Wait States

jnyman edited this page Nov 11, 2014 · 1 revision

The asynchronous nature of browsers makes it difficult to write up a script that only does certain actions when a page is "loaded." This is particularly true in single page applications, where the page is always loaded. To avoid brittle scripts, you should explicitly wait for elements to appear or for DOM changes to occur before trying to interact with the page. Both of these techniques are highly preferable to adding sleep statements that need constant adjustment.

Symbiont provides a wrapper around watir-webdriver and selenium-webdriver. That means a blocking API is automatically provided.

Explicitly Handling Wait States

You can handle wait states by using explicit helper methods. The default timeout for all these methods is 30 seconds, but you can pass an argument to any of these to increase or decrease that timeout, if you feel it is warranted.

If you are using Symbiont's page object pattern, let's say you have an element declaration like this:

div :loading, id: 'loading'

With that in place, you could provide a wait state for that element like this:

@page.loading.wait_until_present
@page.loading.wait_while_present

The wait_until_present would wait for the div element called "loading" to appear before doing anything else. The wait_while_present would wait for the div element to disappear before doing anything else.

Do note that that this works even if you are not using the page object pattern. You can call this method on the @browser object itself, as such:

@browser.div(id: 'loading').wait_until_present
@browser.div(id: 'loading').wait_while_present

You can also take action on a given element while first waiting for the element to be available. Let's say you have a button that you want to click declared as an element declaration like this:

button :submit, id: 'submit'

However let's say the button only appears after a certain period of time. So you have to wait until the button is present before clicking it. You can do that as such:

@page.submit.when_present.click
@page.submit.when_present(2).click

The first line waits for the button to be present using the default timeout of 30 seconds. The second line would only wait two seconds for the button to appear. As with the previous examples, you can make these calls directly on the @browser object itself.

@browser.button(id: 'submit).when_present.click
@browser.button(id: 'submit').when_present(2).click

You can also use a block with the when_present statement if you want to do more actions. For example, let's says you have a div that you've called "container". You can do this:

@page.container.when_present { |div| div.button.click }

Here you are passing a block. The parameter |div| will contain the object reference of "container". You can then perform any actions you want in the context of that object reference.

Explicit Waits – With Watir

Sometimes you my find it useful to simply call out to Watir directly. There are two ways you can do that:

Watir::Wait.until { ...arbitrary predicate... }
Watir::Wait.while { ...arbitrary predicate... }

Here are two examples of how that might be used:

Watir::Wait.until { @page.text.include? 'Submitted' }
Watir::Wait.while { @page.loading_spinner.exists? }

Implicitly Handling Wait States

As an alternative, you can use the WebDriver's implicit waits to specify a maximum time (in seconds) that your script will try to find an element before timing out. This is done by setting the property of the underlying driver:

@browser.driver.manage.timeouts.implicit_wait = 10

Note that using implicit waits can make your tests slower and more difficult to understand when they fail.