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:
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 39. If you have a newer version of Firefox, you can try upgradingselenium-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:
- Start the server for the application.
- Open the root path in the browser.
- Check if the “Hello World” message is present on the page.
- Close the browser.
- 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.