Skip to content

Instantly share code, notes, and snippets.

@tourdedave
Created March 3, 2014 02:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tourdedave/9317509 to your computer and use it in GitHub Desktop.
Save tourdedave/9317509 to your computer and use it in GitHub Desktop.

Writing Resilient Test Code

Ideally, you should be able to write your tests once and run them across all supported browsers. While this is a rosy proposition, there is some work to make this a reliable success. And sometimes there may be a hack or two involved. But the lengths you must go really depends on the browsers you care about and the functionality you're dealing with.

By using high quality locators you will be well ahead of the pack, but there are still some persnickity issues to deal with. Most notably -- timing. This is especially true when working with dynamic, JavaScript heavy pages (which is more the rule than the exception in a majority of applications you'll deal with).

But there is a simple approach that makes up the bedrock of reliable and resilient Selenium tests -- and that's how you wait and interact with elements. The best way to accomplish this is through the use of explicit waits.

An Example

Let's step through an example that demonstrates this against a dynamic page on the-internet. The functionality is pretty simple -- there is a button. When you click it a loading bar appears for 5 seconds, then disappears, and gets replaced with the text 'Hello World!'.

Let's start by looking at the markup on the page.

<div class="example">
  <h3>Dynamically Loaded Page Elements</h3>
  <h4>Example 1: Element on page that is hidden</h4>

  <br>

  <div id="start">
    <button>Start</button>
  </div>

  <div id="finish" style="display:none">
    <h4>Hello World!</h4>
  </div>

</div>

At a glance it's simple enough to tell that there are unique id attributes that we can use to reference the start button and finish text.

Let's add a page object for Dynamic Loading.

Part 1: Create A Page Object

# filename: dynamic_loading.rb

class DynamicLoading

  START_BUTTON  = { css: '#start button' }
  FINISH_TEXT   = { id: 'finish' }

  def initialize(driver)
    @driver = driver
    @driver.get "http://the-internet.herokuapp.com/dynamic_loading/1"
  end

  def start
    @driver.find_element(START_BUTTON).click
  end

  def finish_text_present?
    wait_for { is_displayed? FINISH_TEXT }
  end

  def is_displayed?(locator)
    @driver.find_element(locator).displayed?
  end

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

end

This approach should look familiar to you if you checked out the last write-up. The thing which is new is the wait_for method. In it we are using a built-in Selenium wait action. This is the mechanism with which we will perform explicit waits.

More On Explicit Waits

It's important to set a reasonably sized default timeout for the explicit wait. But you want to be careful not to make it too high. Otherwise you run into a lot of the same timing issues you get from implicit waits. But set it too low and your tests will be brittle, forcing you to run down trivial and transient issues.

In our page object when we're using wait_for { is_displayed? FINISH_TEXT } we are telling Selenium to to see if the finish text is displayed on the page. It will keep trying until it either returns true or reaches fifteen seconds -- whichever comes first.

If the behavior on the page takes longer than we expect (e.g., due to slow inherently slow load times), we can simply adjust this one wait time to fix the test (e.g., wait_for(30) { is_displayed? FINISH_TEXT }) -- rather than increase a blanket wait time (which impacts every test). And since it's dynamic, it won't always take the full amount of time to complete.

Part 2: Write A Test To Use The New Page Object

Now that we have our page object we can wire this up in a new test file.

# filename: dynamic_loading_spec.rb

require 'selenium-webdriver'
require_relative 'dynamic_loading'

describe 'Dynamic Loading' do

  before(:each) do
    @driver = Selenium::WebDriver.for :firefox
    @dynamic_loading = DynamicLoading.new(@driver)
  end

  after(:each) do
    @driver.quit
  end

  it 'Waited for Hidden Element' do
    @dynamic_loading.start
    @dynamic_loading.finish_text_present?.should be_true
  end

end

When we run it (rspec dynamic_loading_page.rb from the command-line) it should pass, rather than throwing an exception (like in the last write-up when the element wasn't present).

As an aside -- an alternative approach would be to rescue the exception like this:

  def is_displayed?(locator)
    begin
      @driver.find_element(locator).displayed?
    rescue Selenium::WebDriver::Error::NoSuchElementError
      false
    end
  end

This would enable you to check the negative condition for whether or not an element is displayed. And it can be used with an explicit wait as well (it won't change it's behavior).

Part 3: Add A Second Test

Let's step through one more dynamic page example to see if our explicit wait approach holds up.

Our second example is laid out similarly to the last one, the main difference is that it will render the final result after the progress bar completes. Here's the markup for it.

<div class="example">
  <h3>Dynamically Loaded Page Elements</h3>
  <h4>Example 2: Element rendered after the fact</h4>

  <br>

  <div id="start">
    <button>Start</button>
  </div>

  <br>
</div>

In order to find the selector for the finish text element we need to inspect the page after the loading bar sequence finishes. Here's what it looks like.

<div id="finish" style=""><h4>Hello World!</h4></div>

Before we add our test, we need to modify our page object to accommodate visiting the different example URLs.

# filename: dynamic_loading.rb

class DynamicLoading

  START_BUTTON  = { css: '#start button' }
  FINISH_TEXT   = { id: 'finish' }

  def initialize(driver)
    @driver = driver
  end

  def visit_example(example_number)
    @driver.get "http://the-internet.herokuapp.com/dynamic_loading/#{example_number}"
  end

  ...

Now that we have that sorted, let's add a new test to reference the markup shown above (and update our existing test to use the new .visit_example method).

# filename: dynamic_loading_spec.rb

require_relative 'dynamic_loading'

describe 'Dynamic Loading' do

  ...

  it 'Waited for Hidden Element' do
    @dynamic_loading.visit_example 1
    @dynamic_loading.start
    @dynamic_loading.finish_text_present?.should be_true
  end

  it 'Waited for Element To Render' do
    @dynamic_loading.visit_example 2
    @dynamic_loading.start
    @dynamic_loading.finish_text_present?.should be_true
  end

end

If we run these tests (rspec dynamic_loading_spec.rb from the command-line) then the same approach will work for both cases.

Explicit waits are one of the most important concepts in testing with Selenium. Use them often.

For a more in-depth look at explicit waits and how to address cross-browser timing issues -- grab your copy of The Selenium Guidebook.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment