Testing Clojure Web Applications with Selenium

Selenium is a commonly used is a commonly used set of tools for automating browsers. This tutorial guides you though setting up Selenium for a Clojure project.

Brought to you by

Semaphore

Selenium is a commonly used set of tools for automating browsers. It allows you to drive a browser interaction with a web page by writing code. It's most often used to write browser-based tests for web applications. Tests can be executed in a development environment or even on a real application as part of a smoke test suite.

Selenium provides support for most popular languages and browsers. This tutorial explains how to use Selenium for testing Clojure web applications. Our setup will be based on clj-webdriver and Firefox, and we will use Compojure to write a very simple web application.

Setting Up the Project

Prerequisites

For developing a Clojure application using this tutorial you will need:

  • Java JDK version 6 or later.
  • Leiningen 2.
  • Firefox 39 or lower.

At the time of this writing, if you have a newer version of Firefox, the example might not work for you. If it doesn't, you will need to downgrade Firefox. You can see what's the last version of Firefox that Selenium officially supports on the Selenium changelog page. If you plan to use Selenium-based tests regularly, you might want to hold Firefox updates until Selenium starts supporting them.

Create a Hello World Compojure Application

Compojure is a routing library for Ring, and a popular choice for writing web applications in Clojure. Leiningen provides a Compojure template that allows us to get started with Compojure quickly.

Create a Compojure-based Clojure project:

lein new compojure clj-webdriver-tutorial

The second parameter compojure is the name of the template that's going to be used for creating the application. The last parameter, clj-webdriver-tutorial, is the name of your project.

Navigate to the project directory:

cd clj-webdriver-tutorial

Start the server:

lein ring server-headless

After the server starts, visit http://localhost:3000 in a browser and you should see the Hello World greeting from the application:

Hello World

Compojure Application Structure

The structure of your application should look like this:

├── project.clj
├── README.md
├── resources
│   └── public
├── src
│   └── clj_webdriver_tutorial
│       └── handler.clj
├── target
│   ├── ...
└── test
    └── clj_webdriver_tutorial
        └── handler_test.clj

The file that we're interested in is src/clj_webdriver_tutorial/handler.clj. If you open it, it should contain the following code:

(ns clj-webdriver-tutorial.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]))

(defroutes app-routes
  (GET "/" [] "Hello World")
  (route/not-found "Not Found"))

(def app
  (wrap-defaults app-routes site-defaults))

It defines the access point to the application (/ - the root path), and we can see that this is where that "Hello World" is coming from.

We can also notice that Leiningen created the handler_test.clj file that's using clojure.test to test the handler. Since we're concentrating on clj-webdriver instead, let's remove the test:

rm test/clj_webdriver_tutorial/handler_test.clj

Install clj-webdriver

Install clj-webdriver by adding the project development dependencies to project.clj:

(defproject clj-webdriver-tutorial "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :min-lein-version "2.0.0"
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [compojure "1.3.1"]
                 [ring/ring-defaults "0.1.2"]]
  :plugins [[lein-ring "0.8.13"]]
  :ring {:handler clj-webdriver-tutorial.handler/app}
  :profiles
  {:dev {:dependencies [[clj-webdriver "0.7.1"]
                        [org.seleniumhq.selenium/selenium-server "2.47.0"]
                        [javax.servlet/servlet-api "2.5"]
                        [ring-mock "0.1.5"]
                        [ring/ring-jetty-adapter "1.4.0"]]}})

There are several new things in project.clj:

  • We added clj-webdriver "0.7.1".
  • Next, we explicitly added the selenium-server that supports at least Firefox
    1. If you have a newer version of Firefox, you can try upgrading selenium-server to the latest available Selenium version.
  • We also added ring-jetty-adapter to run the application before executing tests.

First clj-webdriver Test

Create the features directory where you will put clj-webdriver tests:

mkdir test/clj_webdriver_tutorial/features

Open test/clj_webdriver_tutorial/features/config.clj and add some common configurations that we will use in tests:

(ns clj-webdriver-tutorial.features.config)

(def test-port 5744)
(def test-host "localhost")
(def test-base-url (str "http://" test-host ":" test-port "/"))

The default configuration states that tests will be executed against the application running on http://localhost:5744. 5744 is the default port for Selenium.

Our first test will check if the home page really displays the "Hello World" message. Since we're testing by opening a real browser, the test needs some setup and teardown. Here are the steps that need to be executed:

  1. Start the server for the application.
  2. Open the root path in the browser.
  3. Check if the "Hello World" message is present on the page.
  4. Close the browser.
  5. Shut down the server.

Let's write a skeleton of that code in test/clj_webdriver_tutorial/features/homepage.clj:

(ns clj-webdriver-tutorial.features.homepage
  (:require [clojure.test :refer :all]
            [ring.adapter.jetty :refer [run-jetty]]
            [clj-webdriver.taxi :refer :all]
            [clj-webdriver-tutorial.features.config :refer :all]
            [clj-webdriver-tutorial.handler :refer [app-routes]]))

(deftest homepage-greeting
  (let [server (start-server)]
    (start-browser)
    (to test-base-url)
    (is (= (text "body") "Hello World"))
    (stop-browser)
    (stop-server server)))

The most important parts are the to and text functions that are used for navigating to a page and extracting text from a node, respectively. They are part of the clj-webdriver Taxi API.

Before running the test, we need to implement the start-server, start-browser, stop-browser and stop-server functions.

The start-server function is the most complex one, as it starts the jetty server on the test port and waits for the server to be started:

(defn start-server []
  (loop [server (run-jetty app-routes {:port test-port, :join? false})]
    (if (.isStarted server)
      server
      (recur server))))

The other functions are much simpler:

(defn stop-server [server]
  (.stop server))

(defn start-browser []
  (set-driver! {:browser :firefox}))

(defn stop-browser []
  (quit))

As they are actually wrappers against respective functions in the clj-webdriver, they can be used directly in a real application test.

Putting in all together and our first code in test/clj_webdriver_tutorial/features/homepage.clj looks like this:

(ns clj-webdriver-tutorial.features.homepage
  (:require [clojure.test :refer :all]
            [ring.adapter.jetty :refer [run-jetty]]
            [clj-webdriver.taxi :refer :all]
            [clj-webdriver-tutorial.features.config :refer :all]
            [clj-webdriver-tutorial.handler :refer [app-routes]]))

(defn start-server []
  (loop [server (run-jetty app-routes {:port test-port, :join? false})]
    (if (.isStarted server)
      server
      (recur server))))

(defn stop-server [server]
  (.stop server))

(defn start-browser []
  (set-driver! {:browser :firefox}))

(defn stop-browser []
  (quit))

(deftest homepage-greeting
  (let [server (start-server)]
    (start-browser)
    (to test-base-url)
    (is (= (text "body") "Hello World"))
    (stop-browser)
    (stop-server server)))

Run the tests suite:

lein test

And you will see that the test passed:

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.

You now have a basic setup for testing Clojure web applications with Selenium.

Cleaning Up

This setup works well, but we have to remember to start the server and the browser before each test, and to shut them down after the tests are done. To make things easier, we can implement test fixtures that will do this automatically before and after every test.

The fixture for handling the server can be implemented as follows:

(defn with-server [t]
  (let [server (start-server)]
    (t)
    (stop-server server)))

The t parameter stands for test case function. It starts the server before the test case, executes the test function, and stops the server.

The fixture for handling the browser is similar:

(defn with-browser [t]
  (start-browser)
  (t)
  (stop-browser))

Using fixtures, we can write a much cleaner test:

(ns clj-webdriver-tutorial.features.homepage
  (:require [clojure.test :refer :all]
            [ring.adapter.jetty :refer [run-jetty]]
            [clj-webdriver.taxi :refer :all]
            [clj-webdriver-tutorial.features.config :refer :all]
            [clj-webdriver-tutorial.handler :refer [app-routes]]))

;; Fixtures

(defn start-server []
  (loop [server (run-jetty app-routes {:port test-port, :join? false})]
    (if (.isStarted server)
      server
      (recur server))))

(defn stop-server [server]
  (.stop server))

(defn with-server [t]
  (let [server (start-server)]
    (t)
    (stop-server server)))

(defn start-browser []
  (set-driver! {:browser :firefox}))

(defn stop-browser []
  (quit))

(defn with-browser [t]
  (start-browser)
  (t)
  (stop-browser))

(use-fixtures :once with-server with-browser)

;; Tests

(deftest homepage-greeting
  (to test-base-url)
  (is (= (text "body") "Hello World")))

Note that we passed the :once parameter to the use-fixtures function. This means that the browser will be started once before all tests, and stopped after all tests are finished. The same goes for the server. This should significantly speed up the tests in the file.

In a real application, you can move fixture functions to a separate namespace that is shared by all tests.

Conclusion

Selenium is a valuable tool for testing web applications, and it is indispensable if an application is heavily using JavaScript. Setting up Selenium with Clojure requires several steps covered in the tutorial. Using fixtures, a test setup can be reused, which results in cleaner and faster tests.

You can find more information about common functions for interacting with web pages and inspecting page content in the clj-webdriver Taxi API documentation.

81dc6254e4ae1e5578bfba393a844bd4
Nebojša Stričević

An independent Ruby on Rails and JavaScript consultant. Visit my website for more info.

on this tutorial so far.
User deleted author {{comment.createdAt}}

Edited on {{comment.updatedAt}}

Cancel

Sign In You must be logged in to comment.