Skip to main content

_ruby

Example

Let's step through some examples that deal with dynamically loaded content available on the-internet. There are two examples, each is constructed slightly differently, but they have the same behavior (e.g., when you click the button on the page a loading bar appears for 5 seconds then disappears and displays some text).

First let's pull in our dependent libraries (e.g., selenium-webdriver to drive the browser, and rspec/expectations and RSpec::Matchers for our assertions) and wire up some simple methods (e.g., setup, teardown, and run) to handle our test configuration.

# filename: waiting.rb

require 'selenium-webdriver'
require 'rspec/expectations'
include RSpec::Matchers

def setup
@driver = Selenium::WebDriver.for :firefox
@driver.manage.timeouts.implicit_wait = 3
end

def teardown
@driver.quit
end

def run
setup
yield
teardown
end

Notice that in setup we are specifying an implicit wait of 3 seconds. This tells Selenium to retry each find_element action for up to 3 seconds. If it can complete the action in that amount of time, it will proceed onto the next command. Otherwise, it will raise a timeout exception.

Now let's add our first test.

run do
@driver.get 'http://the-internet.herokuapp.com/dynamic_loading/1'
@driver.find_element(css: '#start button').click
@driver.find_element(id: 'finish').displayed?
expect(@driver.find_element(id: 'finish').text).to eql('Hello World!')
end

In this example the element we're interested in is already on the page, just hidden. When we execute this (e.g., ruby waiting.rb from the command-line) the .displayed? step runs, but it doesn't trigger the implicit wait. Instead, the test proceeds directly to the assertion looking for text that's not there and failing.

Let's run the same test against the other dynamic loading example.

run do
@driver.get 'http://the-internet.herokuapp.com/dynamic_loading/2'
@driver.find_element(css: '#start button').click
@driver.find_element(id: 'finish').displayed?
expect(@driver.find_element(id: 'finish').text).to eql('Hello World!')
end

In this example the element we're interested in gets rendered after the loading bar. When we run this (e.g., ruby waiting.rb from the command-line) the .displayed? step will wait as we intend, but the assertion will still fail. That's because the implicit wait is not long enough (because the loading bar takes 5 seconds to complete, but the implicit wait is set at 3 seconds).

Now we're at a cross-roads. Do we increase the implicit wait to account for this? That would be a simple enough fix for this example. But that's a bad option since it would impact all tests that use this setup. Instead, we can use an explicit wait.

def wait_for(seconds)
Selenium::WebDriver::Wait.new(timeout: seconds).until { yield }
end

run do
@driver.get 'http://the-internet.herokuapp.com/dynamic_loading/2'
@driver.find_element(css: '#start button').click
wait_for(10) { @driver.find_element(id: 'finish').displayed? }
expect(@driver.find_element(id: 'finish').text).to eql('Hello World!')
end

If we wrap our .displayed? action in an explicit wait we are able to override the implicit wait and wait for up 10 seconds. Now when we run our test, our test will pass. And if we revisit our first example and do the same, then it will pass too.

run do
@driver.get 'http://the-internet.herokuapp.com/dynamic_loading/1'
@driver.find_element(css: '#start button').click
wait_for(10) { @driver.find_element(id: 'finish').displayed? }
expect(@driver.find_element(id: 'finish').text).to eql('Hello World!')
end

On Not Mixing Explicit and Implicit WaitsIf your test suite uses both explicit and implicit waits, then you're in for some pain (e.g., transient failures as you scale your test suite). For more details about this, check out this StackOverflow answer from Jim Evans (a member of the Selenium core team).The best thing is to only use explicit waits. We already have them in place, so we can go ahead and simply remove the implicit wait from our setup method.```rubydef setup @driver = Selenium::WebDriver.for :firefoxend

If we save the file and run it (e.g., ruby waiting.rb from the command-line) here is what will happen:

  • Open the browser
  • Visit the page
  • Click the Start button
  • Wait for the progress bar to disappear and finish text to appear
  • Assert that the finish text appears on the page
  • Close the browser

Summary

While an implicit wait can be useful, providing you an initial blanket of cover, it's not ideal for every circumstance. Instead, explicit waits are a better tool for the job since they provide more resilient and predictable results (even if they make your test code more verbose).

Regardless of the approach you choose, be sure never to mix implicit and explicit waits together.

Happy Testing!