Luke Melia

personal

September 12, 2007

Expressing Yourself With Selenium and Rails

Selenium, the web testing framework, is powerful tool for your testing toolbox. I use it extensively on Tracks to nail down pesky AJAX bugs. Yesterday, I hit a wall that took a few hours to overcome. Once I did, though, I had a new level of appreciation for how expressive I can be with Selenium and Selenium on Rails.

This was the situation: in Tracks, you can add a new “to-do” specifying a “context” that it is associated with. The to-do is added to the page within the corresponding context container via AJAX. If a brand new context is specified, the app should create a new context container to hold the new to-do. That was the behavior I needed to test.

After much trial and error, here is what I came up with:

setup :fixtures => :all
login :as => 'admin'
open "/"
store_eval "this.browserbot.getCurrentWindow().$$('.context').length", 'initial_context_count'
type "todo_description", "a new action"
type "todo_context_name", "Brand new context"
click "css=#todo-form-new-action .submit_box button"
store_eval "${initial_context_count} + 1", 'expected_context_count'
wait_for_eval "this.browserbot.getCurrentWindow().$$('.context').length", "${expected_context_count}"

We’re looking at the contents of a .rsel file, called RSelenese, which is translated to a Selenium test by the Selenium on Rails plugin. The lines I had trouble with were 4, 8 and 9. I had never used this feature of Selenium before, and had trouble figuring out that to execute javascript in the context of the page under test, I had to use the this.browserbot.getCurrentWindow() reference.

In a nutshell, the test simulates a login, loads the homepage, gets the current number of context containers on the page and stores it in a javascript variable, stores that number plus 1 in another variable, adds a new to-do to a non-existent context, and then waits to make sure that the new number of contexts is equal to one more than the original number.

I was satisfied that I got this working, but disappointed with how ugly the syntax was. There had to be a better way…

It turned out that Selenium had an extensions model that could help. Selenium looks for a file called user-extensions.js and loads it automatically. I created one like this:

// All get* methods on the Selenium prototype result in
// store, assert, assertNot, verify, verifyNot, waitFor, & waitForNot commands.
// Will result in support for storeContextCount, assertContextCount, etc.
Selenium.prototype.getContextCount = function() {
    return this.browserbot.getCurrentWindow().$$('.context').length;
};

To get at it elegantly from Selenium on Rails, I followed the pattern the plugin uses in defining methods to call the built-in commands. To avoid modifying the plugin code directly, I reopened the module SeleniumOnRails::TestBuilderAccessors in my own selenium_helper.rb in my app’s lib directory. (I “require” this file in my config/environments/test.rb).

module SeleniumOnRails::TestBuilderAccessors

   # How many elements with the class "context" are present on the page? 
   #
   # Related Assertions, automatically generated:
   # * +assert_context_count+
   # * +assert_not_context_count+
   # * +verify_context_count+
   # * +verify_not_context_count+
   # * +wait_for_context_count+
   # * +wait_for_not_context_count+
   def store_context_count variable_name
     command 'storeContextCount', variable_name
   end
  
   each_assertion 'store_context_count' do |assertion_method, command_name|
     define_method assertion_method do |expected_count|
        command command_name, expected_count
     end
   end
 end

This let me modify my test case to the more pleasing:

setup :fixtures => :all
login :as => 'admin'
open "/"
store_context_count 'initial_context_count'
type "todo_description", "a new action"
type "todo_context_name", "Brand new context"
click "css=#todo-form-new-action .submit_box button"
store_eval "${initial_context_count} + 1", 'expected_context_count'
wait_for_context_count "${expected_context_count}"

I was a lot happier with this, but I thought of the way that Marcel Molina’s assert_difference reads so nicely, and added this:

module SeleniumOnRails::TestBuilderAccessors
 
  def assert_context_count_incremented(&block)
    store_context_count 'initial_context_count'
    store_eval "${initial_context_count} + 1", 'expected_context_count'
    yield
    wait_for_context_count "${expected_context_count}"
  end

end

Sure enough, this let me improve my test case yet again to:


setup :fixtures => :all
login :as => 'admin'
open "/"
assert_context_count_incremented do
  type "todo_description", "a new action"
  type "todo_context_name", "Brand new context"
  click "css=#todo-form-new-action .submit_box button"
end

The test case now gives me hope that my Selenium test case can be not only pragmatic, but also expressive!

P.S. If you want to dig further, all this code can be found and studied in the Tracks repository.

Leave a Reply

LukeMelia.com created 1999. ··· Luke Melia created 1976. ··· Live With Passion!