I've fiddled with my blog template because I decided I wanted more horizontal viewing space, given that it was using less than a third of my 1920 horizontal pixels. If it feels too spread out for you, I added a drag-and-drop handle over to the left to let you resize the main content column. The javascript is pretty primitive. If it breaks, drop me a comment.

Wednesday, October 15, 2008

How To: Run Selenium Tests with Hudson on a Headless Linux Server, Part Three--Configuring Hudson

So you've got Xvfb running, and you've tested it by taking a screenshot or three of xclock. Now to get it working with Selenium tests in your Hudson builds. First, let me say that my work was done with Selenium RC, in which you run a standalone Selenium server which is resposible for launching browsers and receives commands from your test scripts to run in the browsers. I'm not highly familiar with the other varieties of Selenium, so I can't say how similar the setup would be for them.
First, Selenium has to know which browser to start and/or how to start it and which display to use. If you're already using Selenium RC, you'll know that you have to pass a browser command to Selenium to indicate what browser to use. However, if you normally work in Windows, and Selenium can't find Firefox or IE on your Linux box, you might need to do a little more configuration here. A typical browser command to launch Firefox is "*firefox". Selenium has a list of "default locations" where it looks for the Firefox launcher. If it can't find it, you can specify it manually, like *firefox /usr/bin/firefox-bin. This tells Selenium that it's starting a Firefox instance and to use the given path. You must provide the path to firefox-bin and not just to the firefox script. Selenium checks to see if it's been given a script or an executable binary, and it will throw an exception if it finds a script. There's also an option to just pass a path and arguments to Selenium, leaving off the "*firefox" designator, but as the docs say, "If you specify your own custom browser, it's up to you to configure it correctly. At a minimum, you'll need to configure your browser to use the Selenium Server as a proxy, and disable all browser-specific prompting."
It's simple to tell Selenium to use the virtual display. Setting an environment variable named "DISPLAY" in Linux tells any graphical app that starts to start on the specified display, so it's just a matter of getting that variable set properly for the Selenium server process. Remember that it's the server that's responsible for starting the browser, so that's where the DISPLAY variable has to be available. If you're launching the server from a shell, you can just do export DISPLAY=:5.0 before launching the server. Naturally, you'll need to make sure the numbers match up with the display and screen that you configured in Xvfb. (See the first post in this series for details on that.) If you're launching the server with Ant, just add a nested element to the <jar> target that looks like this: <env key="DISPLAY" value=":5.0" />. However, keep in mind that if you add this to your build script, then you're tying all Linux users to that display. That's probably not good. (Been there, done that.)
Finally, make sure that Hudson is orchestrating everything correctly. This might actually be the smallest part of the whole thing since Hudson can invoke shell scripts, Ant targets, Maven goals, and a ton of other things. Make sure that Xvfb is running or that Hudson starts it. Make sure your application server/web container is running or that Hudson starts it. Make sure that any other stuff your application depends on is available, like a database. Make sure Hudson builds your web application and deploys it as appropriate. Make sure the Selenium server is running or that Hudson starts it. Then just have Hudson invoke the target, goal, or whatever that starts your Selenium test suite. Since Selenium tests are written as normal unit tests with JUnit or TestNG or whatever, there's really nothing to that. The only tricky part here is that you need to make sure the tests don't start until your application has fully started up. It's possible that you could start running tests before the URL for your application is even available on your web server.
That should be it! When the Selenium client starts up, it will communicate the start command to the server, which will start a browser in the virtual display. Then the tests will run just like they always do, sending commands to the Selenium server, and in turn to the browser, which makes HTTP requests to your web server, which is running the application that Hudson just built. You can use xwd and xwud, explained in the second post, to take and see screenshots of the browser as your tests are running. A cool idea that I've implemented in our environment is to set up a listener in your test framework (I used an ITestListener in TestNG) that will take a screenshot any time a test fails. This gives you extremely useful feedback to use when checking out the failures.


Ezra Wolfe said...

Thank you for this. It's a great series -- Keep this stuff coming!

Ryan said...

I'm glad you found it helpful. I hope to write some more posts soon. I've been busy with other, related pursuits for the past couple of months.

BAD said...

Great Post, Ryan!
Need some advice here. I want to run Selenium Test cases written in PHP which I usually run via PHPUnit locally from my machine which has Selenium RC installed.

How can i hook these tests to my Hudson builds and what sort of configuration should I have to enable this?

Ryan said...

BAD, I don't do PHP, so I can't help you with specifics, but it looks like Hudson has a plugin to support the output of many different xUnit test frameworks by doing XSL transformations on them:

If that plugin works as advertised, it should only take a few steps to get your tests working in Hudson. First, install the plugin. Then set up a job with a build step that will execute your tests. Hudson support several different kinds of build steps, and plugins can add more, so you should be able to find something that will execute your tests. The final step would be enabling and configuring the xUnit plugin in the job. You'll at least need to give it the path to the output file (should be relative to the workspace) and probably the format (PHPUnit).

I hope that points you in the right direction.

Anonymous said...

Thanks a lot! It helped me a lot. This solution is great.

I created an init.d script and set a different display depending on an environment variable, so as to be able to run multiple tests concurrently.