Monday, October 13, 2008

Running Selenium From CruiseControl with DRB


Doesn't DRb sound like a hip-hop handle from the 80's? Yo! Yo! My favorite MC is DRb! Uh. Actually it is way cooler than that, because it stands for distributed ruby. But I'm getting ahead of myself. DRb is the solution. Let's look at the problem. I'm working on a Java project that has a Selenium suite. The problem solved below was how to best kick off Selenium remotely from Cruise Control. Many options were discussed including a separate cc.rb instance and using Net::SSH. In the end we decided on DRB because we thought it would require the least effort, be cross platformy, and reliable.

Enough talk. Here is what we did: First thing was to write the ruby code that would call from a Linux server over to a Windows server. (I mention the OSes only to highlight the fact that there is nothing special that we have to do to make this work cross platform.)

The following code runs on the linux server that runs Cruise Control. The most interesting line is the one that creates the DRbObject. Basically that line goes out over the network and retrieves whatever object the drb process at druby://seleniumblx:9999 is handing out. Then it calls the run method on that object.


#!/usr/bin/env ruby
require 'drb'

class InvokeFromCruise
def run_rake
remote_runner = DRbObject.new(nil, "druby://seleniumbox:9999")
remote_runner.run
end
end

invoker = InvokeFromCruise.new
invoker.run_rake


Over here on our Windows box running Selenium, the DRb.start_service call starts a ruby process that listens on port 9999. When some other DRb process connects to that port it will simply hand over an instance of RakeRunner. RakeRunner is a dumb class that basically makes a system call to run rake with our smoke task, thus invoking the smoke suite.


require 'drb'

class StartSuite
def start
@rake_runner = RakeRunner.new
DRb.start_service("druby://seleniumbox:9999", @rake_runner)
end
def daemon
DRb.thread.join
end
end

class RakeRunner
def run
`ruby.exe C:/ruby/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake.rb smoke`
end
end

starter = StartSuite.new
starter.start
starter.daemon


Disclaimer: The above code was slightly modified without subsequent tests.

Now to tie this together with our cruise build... First, get the listener up on the selenium box. To do that you invoke the StartSuite class from the directory that contains the selenium suite's rakefile. It will listen on port 9999 until you kill the process. Next, we tell cruise control to invoke the InvokeFromCruise class. Here is a snippet from cruise's config.xml. Notice it is simply calling a invokeSelenium task when the build is successful.


<onsuccess>
<antpublisher buildfile="/path/to/build.xml" target="invokeSelenium" anthome="/path/to/apache-ant">
</onsuccess>


Here is the invokeSelenium ant task that calls the InvokeFromCruise code. The spawn attribute of the exec tag simply tells ant not to wait around while the ruby executable runs.

<target name="invokeSelenium">
<exec executable="ruby" spawn="true">
<arg line="${parameterized-path-to-executable}/build/invoke_from_cruise.rb">
</exec>
</target>


That is pretty much it. The StartSuite class waits around on our windows box and hands out little remote control objects that can kick off a rake task. CruiseControl executes a ruby script that triggers a remote control object after every successful build. The results of the build are simply logged to a file that is in apache's web root so you can check there anytime you need to.

I was happy with how easy it was to do this with DRb. It is lightweight, cross-platform, easy to customize, and more fun than a couple of 80's rappers holding a doll house.

2 comments:

Shlomo said...

great. but is it free? If I wanted a proprietary solution I would use the one my company, ThoughtWorks, sells. Cruise is even more sophisticated because of the support for build pipelines.

If you want to go really cutting edge with selenium and care to pay... you can look at the product that sauce labs is creating. Massively parallelize your selenium suite with amazon EC2.

Jeffrey Fredrick said...

Neat trick! I'll share it with the CC user mailing list...