Running your Selenium tests in parallel: Clojure

December 28th, 2009 by The Sauce Labs Team

Clojure is a dialect of Lisp that runs on the JVM. It has very elegant syntax and a novel approach to concurrency.

In this installment, we’ll build a Clojure framework for running Selenium tests in parallel. We’ll use Clojure’s Java interop to interact with Selenium, and agents to distribute the work. Once you have the tests executing in parallel, you can run them against your internal selenium farm or use our Sauce OnDemand cloud hosted service to get running with no further configuration.

First, the framework, in about 50 lines of code:

(import '[com.thoughtworks.selenium DefaultSelenium])

(defn- run-with-client
  "Run a test function with new client"
  [test-fn opts]
  (let [client (new DefaultSelenium (:host opts) (:port opts) (:command opts)
                    (:url opts))]
    (.start client)
    (try
      (test-fn client)
      (catch Exception e false)
      (finally
        (.stop client)))))

(defn- run-single-test
  "Run a single test, append test result to results"
  [test results opts]
  (let [test-fn (test :test)
        value (run-with-client test-fn opts)
        res {:test (test :name) :value value}]
    (dosync (alter results (fn [old] (conj old res))))))

(defn- gen-agents
  [num-agents]
  (take num-agents (map agent (repeat nil)))) 

(defn- parse-options
  "Get options as hash map, provide defaults"
  [args]
  (let [opts (apply hash-map args)] ; Convert [:port 4444] to {:port 4444}
    (-> opts
      (assoc :port (:port opts 4444))
      (assoc :host (:host opts "localhost"))
      (assoc :command (:command opts "*firefox"))
      (assoc :url (:url opts "http://localhost/"))
      (assoc :num-agents (:num-agents opts 4)))))

(defn run-tests
  "Run tests in parallel, return list of test results.
  After tests are done, passes the list of results to reporter.

  A test has the format
  {
    :name NAME
    :test (fn [client] ...)
  }
  "
  [tests reporter & args]
  (let [opts (parse-options args)
        agents (gen-agents (:num-agents opts))
        results (ref [])]
    (doseq [[test agent] (map vector tests (cycle agents))]
      (send agent (fn [_] (run-single-test test results opts))))
    (doseq [agent agents] (await agent)) ; Wait for tests to finish
    (shutdown-agents)
    (reporter @results)
    @results))

(defn tests-failed?
  "Check if all tests passed"
  [results]
  (not (empty? (filter false? (map :value results)))))

Now let’s define some tests and run them:

(require 'selenium)

(def test-google
  {
   :name "google"
   :test (fn [client]
           (doto client
             (.open "http://www.google.com")
             (.type "q" "Sauce Labs")
             (.click "btnG")
             (.waitForPageToLoad "5000"))
           (.isTextPresent client "Selenium"))
   })

(def test-yahoo
  {
   :name "yahoo"
   :test (fn [client]
           (doto client
             (.open "http://yahoo.com")
             (.type "p" "Sauce Labs")
             (.click "search-submit")
             (.waitForPageToLoad "5000"))
           (.isTextPresent client "Selenium"))
   })

(def test-bing
  {
   :name "bing"
   :test (fn [client]
           (doto client
             (.open "http://www.bing.com")
             (.type "q" "Sauce Labs")
             (.click "go")
             (.waitForPageToLoad "5000"))
           (.isTextPresent client "Selenium"))
   })

(def tests [test-google test-yahoo test-bing])

; Our fancy reporter :)
(defn reporter
  [results]
  (doseq [r results]
    (println r)))

(let [results (run-tests tests reporter)]
  (if (tests-failed? results)
    (System/exit 1)))

To run the system locally, you’ll need the Selenium server and client jars and the Clojure jar (we’re using 1.1.0). You can use Sauce RC to get a Selenium server up and running using a GUI-based installer. Then, just issue the following command:

java -jar selenium-server.jar&
java -cp selenium-java-client-driver.jar:clojure.jar \
    clojure.main test-sauce.clj

(See the run-tests script, you can control the number of concurrent tests by providing :num-agents N to run-tests)

To run it against our cloud hosted service without having to configure any servers, simply follow the instructions at Sauce OnDemand documentation and supply the right options to run-tests. It will probably look something like:

(run-tests tests reporter
           :host "saucelabs.com"
           :command "{
              \"username\" : \"SAUCE-USER-NAME\",
              \"access-key\" : \"SAUCE-API-KEY\",
              \"os\" : \"Windows 2003\",
              \"browser\" : \"firefox\",
              \"browser-version\" : \"3.5.\"}"
           :url "http://saucelabs.com")

You won’t need to run any servers locally, just execute the clojure test:

java -cp selenium-java-client-driver.jar:clojure.jar \
    clojure.main test-sauce.clj

Future enhancements to this framework might include better reporting, and integration with the clojure.test testing framework.

All of the code from this installment, including the needed jar files can be found at http://github.com/saucelabs/parallel-test-examples/tree/master/clojure.

Share

Continuous Try Server Integration

December 24th, 2009 by John Dunham

Collin Jackson of Betable talks about their successful use of “Continuous Try Server Integration” using Selenium together with Sauce OnDemand cloud-hosted Selenium service.

Share

Creating Selenium Tests for Mature Ruby on Rails Project

December 24th, 2009 by John Dunham

Consultant Sarah Mei talks about effectively using outsourcing to ‘catch up’ implementing Selenium tests on an existing Ruby on Rails project.

Share

Top 10 Reasons to Use Selenium

December 21st, 2009 by John Dunham

Last week we hosted the December edition of the San Francisco Selenium Meetup. Chosen topic of the evening was “Selenium-related lightning talks” with some delightful results. Amit Kumar of Betable presents “Top 10 Reasons to Use Selenium.” Enjoy!

Share

Selenium users talk about their experience with Selenium

December 21st, 2009 by John Dunham

Users talk about their favorite Selenium success stories and advice to new users at the 12/15/09 SF Selenium Meetup.

Share

Selenium Tips: Capturing Screenshots vs. Scrollbars!

December 11th, 2009 by Santiago Suarez Ordoñez

Capturing screenshots of your tests is one of the most important features you can give to an automation engineer. It’s the easiest way to actually understand from a report, why a test failed and how to reproduce it.

While our OnDemand will help you record videos of your entire test, sometimes you just want a screenshot. Selenium supports capturing screenshots on remote machines, as Sean discussed in a previous Tip of the Week (Taking ScreenShots on the Server).

Selenium has two methods dedicated to screenshot taking:

  • CaptureScreenshot
  • CaptureEntirePageScreenshot

An example of using the later function shows that it’s pretty amazing, giving you a full rendering of the page:

Entire Page Screenshot

While this function is perfect, it only works with Firefox, which greatly limits it’s usefulness. Unfortunately due to limited extension support in other browsers it’s going to be a while before we see this functionality ported.

If you’re stuck with the cross-browser version you’ll get limited success. We can see up to the fold after opening a new page, but we have no idea what’s going on in the depths of our page.

Regular Screenshot

This just isn’t going to work if we want to test something below the fold. There is a partial workaround, focus(). Focus will force all browsers to scroll the page so the selected element is visible. Here’s an example of a test we run to view the bottom of our homepage:

def test_case(self):
        browser = self.browser
        browser.open("/")
        browser.focus("css=a.indexTwitter")
        png = browser.capture_screenshot_to_string()
        f = open('screenshot.png', 'wb')
        f.write(png.decode('base64'))
        f.close()

Regular Plus Focus Screenshot

While this requires more code than Firefox + selenium, it will let you debug those hard to find rendering issues without having to boot up a virtual machine.

I hope you find this useful and start taking screenshots as soon your tests get harder to debug. If you need more visual debugging tools, our service Sauce OnDemand will capture full screen videos which will record every single selection event without any extra code!

Thank for tuning in, and check back after the new year for more Selenium Tip of The Week from Sauce Labs!

PS: You should follow us on twitter

Santiago

Share

Sauce RC Released for Mac. Marks first-ever fully functional Selenium RC on Snow Leopard.

December 9th, 2009 by John Dunham

Today Sauce Labs released Sauce RC for Mac. You can download it here.  Sauce RC is Sauce Labs’ certified, enhanced and commercially-supported version of Selenium RC.

This is big news for several reasons:

  • For the first time ever, Mac OS X Snow Leopard users can use Selenium RC, the underlying technology in Sauce RC, on the full range of supported browsers
  • Google Chrome Beta was released for Mac OS X yesterday;  Sauce Labs is shipping Sauce RC support for Google Chrome Beta today.
  • Sauce RC brings the same easy-to-install, easy-to-operate benefits to the Mac that Windows Sauce RC users already enjoy.

And of course Sauce RC is available for Windows.  Both Sauce RC versions can be used with Sauce Labs’ Sauce OnDemand cloud-hosted Selenium service.  The combination makes it unprecedentedly easy to use Selenium RC and get immediate access to cross-browser testing on ten (10) popular browsers, including Google Chrome.

Here’s a brief screencast showing how incredibly easy it is to install and use Sauce RC:

Sauce Labs will open the code for Sauce RC following the beta period.  Users interested in the Sauce RC open source release should follow this blog or follow @saucelabs on Twitter.

Download Sauce RC for Mac.

Share