Wednesday, February 14, 2007

Selenium and Ruby: They actually work together!

So we found out this week. Just wanted to do a brain-dump so I could remember how to configure the Selenium Remote Control to work with Ruby on Rails, and hopefully impart knowledge to those who wish to play with Selenium.

For a refresher, Selenium is a testing framework that allows you to test the clicking-around aspect of your web application. It allows you to record clicks and all sorts of actions that you may routinely perform to test whether you have broken something in your web application after changing some code. I recently started using this week after having some major changes in a application that I am working on with a friend.

A major portion of code was changed from being mostly server-side code (with a ton of SQL calls) to client side (using JavaScript). This threw a wrench into our testing of the application because we could no longer test the actions that one might perform on the screen because there were very few calls being made to the server through Rails (it was all being done with JavaScript in the browser). So to test this we decided to go with the most excellent program Selenium. The following is a somewhat detailed overview of how to get Selenium tests working with Ruby and Rails, and by that I mean being able to write your Selenium tests with Ruby code:

  • Get Selenium IDE (the Firefox extension).

  • Download the Selenium Remote Control and stick the unzipped folder into the lib directory of your application; you may wish to delete all the extraneous garbage, since all you will need is the ruby folder and the server folder. Also, in your lib directory make a file named selenium.rb, and the only thing needed in the file is require "selenium/ruby/selenium-ruby". You will see why later.

  • Start a test server (script/server -e test --port 3001) for selenium to work with so it doesn't try to use your development db. Choosing the port number is optional, but you will need to know which port this server is running on to use in your test file.

  • Start a java server (make sure you have JRE 1.5 or higher) by navigating to the selenium/server folder in your lib directory on a command line and type java -jar selenium-server.jar. This will start a Jetty server on port 4444.

  • Make a folder in your test directory named selenium. This is where you will put your selenium tests when you make them.

  • Save some recorded tests from the Selenium IDE in your test/selenium folder. You will need to perform the test in the IDE and then export the tests as "Ruby - Selenium RC". This will package the normal table tests into some handy Ruby code. You will need to rename the test class to be a proper name (something like MyNewSeleniumTest) and also whack the require 'test/unit' and replace it with require File.dirname(__FILE__) + '/../test_helper'.

Your selenium test should look something like the following, and should follow the normal Rails test-naming conventions "my_new_test.rb":

require File.dirname(__FILE__) + '/../test_helper'
require "selenium"

class MyNewSeleniumTest < Test::Unit::TestCase
def setup
@verification_errors = []
if $selenium
@selenium = $selenium
@selenium ="localhost",
4444, "*firefox", "http://localhost:3001", 10000);
@selenium.set_context("test_new", "info")

def teardown
@selenium.stop unless $selenium
assert_equal [], @verification_errors

def test_new "/"
@selenium.type "b-query", "hey yo" "b-searchbtn"
@selenium.wait_for_page_to_load "30000" "link=Privacy Policy"
@selenium.wait_for_page_to_load "30000"

The line require "selenium" will call the selenium.rb file in your lib folder, which calls the selenium-ruby.rb file. This nice trick avoids having to load and use the selenium gem. You can, however, download the gem if you wish (gem install selenium) and kill the selenium.rb in your lib folder, and everything will still work properly.

Pay close attention to this line in the setup method: @selenium ="localhost", 4444, "*firefox", "http://localhost:3001", 10000). The "http://localhost:3001" should be the port that your test server is running on. We banged our heads against a wall for quite a while until we figured that out. You can also choose Firefox or (::dread::) IE as the browser that Seleium will open. Mac users may want Safari, but we haven't checked to see about any surprises with Safari and Selenium.

That should be all you need (not that much, right?). Now you can run your tests from the command line like you would any other individual test (in Win-doze: ruby test\selenium\new_selenium_test). You can set up a rake task (to go into your lib/tasks folder) to run all of your tests for you pretty easily, like the following one we set up:

namespace :test do => "db:test:prepare") do |t|
t.libs << "test"
t.pattern = 'test/selenium/**/*_test.rb'
t.verbose = true
Rake::Task['test:selenium'].comment =
"Run the selenium tests in test/selenium"

Now you can simply call rake test:selenium from a command line (with your test server and Jetty server running) and your tests will pop up in a new browser window and spit out the results at the end. If you do run any of the tests individually without using the rake task you will be faced with the problem of your test db not clearing out (for some unknown reason Rails does not seem to want to treat the Selenium tests as normal tests, and thus does not prepare the test database before running each test). We struggled with this for quite a while and had to finally dig into the rake tasks to find out how to tell MYSQL what we wanted it to do. The result was a small smorgasbord of ActiveRecord::Base commands to make a schema of the current test db, clear the db completely, and then rebuild the db using the just made schema. The following code was put into a test helper (which was required by the test file by using the test_helper :selenium call) and called inside of the setup of the test:

def prepare_test_db
abcs = ActiveRecord::Base.configurations
ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])"#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql", "w+"
) { |f| f << ActiveRecord::Base.connection.structure_dump }
ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
).join.split("\n\n").each do |table|

Now you're test db will be cleared out no matter how you invoke the test.

Now, go and use this information for the greater good. Share it and love it.