Getting Started with Selenium: Chapter 5 - Writing Resilient Test Code

Posted by Bill McGee in Guest Blog PostsSelenium Resources

selenium-logo-160x144

This post is the fifth in a series of “Getting Started with Selenium Testing” posts from Dave Haeffner, a noted expert on Selenium and automated testing, and a frequent contributor to the Sauce blog and Selenium community. This series is for those who are brand new to test automation with Selenium and a new chapter will be posted every Tuesday (eight chapters in all).

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.

Dynamically Loaded Page Elements

Example 1: Element on page that is hidden


At a glance it's simple enough to tell that there are unique idattributes 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 usingwait_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.

Dynamically Loaded Page Elements

Example 2: Element rendered after the fact


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.

Hello World!

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.

Dave is the author of Elemental Selenium (a free, once weekly Selenium tip newsletter that is read by hundreds of testing professionals) as well as a new book, The Selenium Guidebook. He is also the creator and maintainer of ChemistryKit (an open-source Selenium framework). He has helped numerous companies successfully implement automated acceptance testing; including The Motley Fool, ManTech International, Sittercity, and Animoto. He is a founder and co-organizer of the Selenium Hangout and has spoken at numerous conferences and meetups about acceptance testing.

Discuss: Getting Started with Selenium: Chapter 5 - Writing Resilient Test Code
0 Comments

Free Trial

Get access to a free 14-day trial version, or contact Sales for more information.