Saturday, January 3, 2009

Selenium on Rails DSL

When upgrading Rails to version 2.2.2 on one of the applications I work on, it was clear that the SeleniumOnRails plugin was not compatible. This was easily fixed by upgrading the plugin with the non-official version available at here.

The test cases written in RSelanese use a DSL (Domain Specific Language) module to help keep the test code clean and easy to read.

At the time I set this up I was fairly new to Selenium and SeleniumOnRails, and followed some advice in this article that suggested to hack the the RSelenese class to include a custom module for your DSLs. As a Selenium newbie I was probably focusing on the tests, and forgot to fix the weird hack suggested. Needless to say this came back an bit me when I upgraded SeleniumOnRails and reminded me to fix it. This is how I did it.

Why a DSL?


Let me start with a short intro to what I wanna do.

The application I'm working on uses AJAX to login. To do this in my Selenium test cases I would run the following .rsel snippet:

[sourcecode lang="ruby" gist="42878"]open '/'
type "login", name
type "password", password
click "loginSubmit"
wait_for_text "flashNotice", "Logged in successfully"[/sourcecode]

As several test cases will need to log in before getting down to business, pasting that snippet into the test cases would produce enormous amount of duplication. One way to get rid of the duplicated code would be to put the login steps in a .rsel partial and execute the login that way. The tests would in that case include the partial using the following syntax:

[sourcecode lang="ruby" gist="42881"]include_partial "login", :name => "quentin", :password => "test"[/sourcecode]

I guess you would agree this is not a very tasteful syntax. I'd rather see a method call like login("quentin", "test"). If we define a login method in a module, we could include that in the RSelenese class and make it available to our test cases.

[sourcecode lang="ruby" gist="42884"]# lib/selenium/dsl.rb

module Selenium
module DSL

def login(name, password)
open '/'
type "login", name
type "password", password
click "loginSubmit"
wait_for_text "flashNotice", "Logged in successfully"
end

end
end[/sourcecode]

Including the DSL module in RSelenese


With the DSL module in place it still needs to be included in the class SeleniumOnRails::RSelenese. As this setup is only needed in the test environment, the class_eval is done in the test environment config. It is put in an after_initialize block to allow the SeleniumOnRails plugin to be loaded before the inclusion.

[sourcecode lang="ruby" gist="42890"]# added to the end of environments/test.rb

config.after_initialize do
if SeleniumOnRailsConfig.new.get(:environments).include? RAILS_ENV
class SeleniumOnRails::RSelenese
class_eval do
include Selenium::DSL
end
end
end
end[/sourcecode]

That's it!
Just remember that you need to restart your environment when you make any updates to your DSL module.

1 comment:

  1. Thanks for the suggestion - unfortunately this didn't work for me (Rails 2.2.2, Ruby 1.8.7, S-o-R stable as at 16/04/2009, Ubuntu 8.10 on Mac Pro) without one tweak. I ended up having to explicitly extend the Evaluator class:

    class SeleniumOnRails::RSelenese::Evaluator
    class_eval do
    include Selenium::DSL
    end
    end

    ReplyDelete