Using DRb to Preserve a SeleniumDriver

[Update] A Much Simpler Way

Philippe showed me a much simpler way of keeping the SeleniumDriver open. Start selenium like this:
selenium -browserSessionReuse -proxyInjectionMode

I'm leaving the original post here as an example of using DRb.

Original Post

Selenium is a great tool for web acceptance testing, but working on tests can be laborious. In addition to the general slowness of actually testing through the browser, starting a SeleniumDriver takes about 6 seconds on my MacBook.

SeleniumDriver Benchmark

require "rubygems"
gem "Selenium"
require "selenium"

benchmark = Benchmark.measure do
  driver = Selenium::SeleniumDriver.new(
    "localhost", 4444,
    "*firefox", "http://localhost:3000"
  )
  driver.start
  driver.stop
end
benchmark.to_s
#=> "  0.000000   0.000000   0.000000 (  6.294741)"

If the selenium driver could stay open between test runs, running a test could be much faster. Fortunately, DRb makes this easy. Here are the rough steps.

SeleniumDriver DRb Server

First, start a SeleniumDriver and place it in a DRb service.

require "rubygems"
gem "Selenium"
require "selenium"
require "drb"

SERVER = "druby://:51785"

driver = Selenium::SeleniumDriver.new(
 "localhost", 4444,
 "*firefox", "http://localhost:3000"
)
driver.start
DRb.start_service SERVER, driver
puts "Ready"
DRb.thread.join  

The DRb.thread.join call at the end of this script means the script won't exit. Run it from a terminal, use Ctrl+C to stop it.

SeleniumDriver DRb Client

The next step is to use this instance when running tests. DRb provides a class that functions as a proxy to the object running in the server. In this case, that's the selenium driver.

DRb.start_service
driver = DRbObject.new(nil, SERVER)
class << driver
  # make sure type hits method_missing to delegate to driver
  undef_method :type
end

Using the DRbObject as a proxy to the SeleniumDriver requires hacking the type method to make sure it delegates properly.

SeleniumDriver with DRb Benchmark

Here's the benchmark for this version:

benchmark = Benchmark.measure do
  DRb.start_service
  driver = DRbObject.new(nil, SERVER)
  class << driver
    # make sure type hits method_missing to delegate to driver
    undef_method :type
  end
end
benchmark.to_s
#=> 0.000000   0.000000   0.000000 (  0.070634)

The time to start the driver is now negligible.

I've been using this for a few days, and it is working well. If you actually want to try this, here are a couple tweaks to the code that you'll want to make.

Stopping the Server

To stop the SeleniumDriver when stopping the process, wrap DRb.thread.join like this:

begin
  DRb.thread.join
rescue Interrupt
  begin
    puts "Stopping..."
    driver.stop
  rescue Exception
    puts "failed stopping selenium driver"
  end
end

Checking the Client Connection

DRbObject does not check the connection when it's first initialized. You may want to force it to check the connection by doing something like this:

begin
  DRb.start_service
  driver = DRbObject.new(nil, SERVER)
  driver.respond_to?(:anything?)
rescue DRb::DRbConnError
  # do something
  # fail over to non-DRb driver?
end

A call to respond_to? will check to see if the DRb server is available. You may want print out an informative message or fail over to using a non-DRb driver if it cannot connect.