A powerful, elegant testing library for Clojure that combines Serenity BDD reporting with Playwright browser automation and REST Assured API testing. Write beautiful, readable tests in pure Clojure with automatic screenshots, step-by-step reporting, and rich HTML documentation.
clojure.test - no Java interop requiredStart an nREPL with a headed browser for interactive exploration:
clj -M:nrepl
Then in your editor (or any nREPL client connected to port 7888):
(require '[testing.repl :refer [page restart stop]])
(.navigate @page "https://example.com")
(.textContent (.locator @page "h1")) ;; => "Example Domain"
(.screenshot @page)
;; Use the same Playwright API as in tests
(.fill (.locator @page "#email") "test@example.com")
(.click (.locator @page "button"))
(restart) ;; restart browser
(stop) ;; close browser
Add to your deps.edn:
{:deps {com.alpha-prosoft/serenity-clojure {:mvn/version "1.4"}}}
Or with Leiningen (project.clj):
:dependencies [[com.alpha-prosoft/serenity-clojure "1.4"]]
Install Playwright browsers:
npx playwright install chromium
(ns my-app.tests
(:require [clojure.test :refer [deftest is]]
[testing.junit :refer [with-serenity ui-step]]))
(deftest simple-navigation-test
(with-serenity [page]
(ui-step page "Navigate to example.com"
#(.navigate page "https://example.com"))
(ui-step page "Verify page title"
#(is (clojure.string/includes? (.title page) "Example")))))
Generate reports:
clojure -M -e "(require '[testing.junit :as junit])(junit/generate-reports)"
View results:
Open target/site/serenity/index.html in your browser to see beautiful test reports with embedded screenshots.
with-serenity [page]Main test wrapper that provides:
(deftest my-test
(with-serenity [page]
;; `page` is a Playwright Page instance
;; Browser runs in headless Chromium by default
;; All events are automatically reported to Serenity
(.navigate page "https://example.com")))
step [description f]Execute a test step with automatic reporting:
(step "Navigate to homepage"
#(.navigate page "https://example.com"))
ui-step [page description f]Execute a UI step with automatic before/after screenshots:
(ui-step page "Login to application"
#(do
(.fill page "#username" "testuser")
(.fill page "#password" "password")
(.click page "#login-button")))
This creates a step hierarchy:
Login to application
├── Login to application - Before (screenshot)
└── Login to application - After (screenshot)
Perfect for:
api-step [description f]Execute an API call with automatic REST logging:
step with SerenityRest integrationImportant: Use SerenityRest/given (not RestAssured/given) to ensure API calls are logged in Serenity reports. Capture the response directly from the HTTP method call (.get, .post, etc.) rather than using .extract().response().
(api-step "Get user details"
#(let [response (-> (SerenityRest/given)
(.baseUri "https://jsonplaceholder.typicode.com")
(.when)
(.get "/users/1" (into-array Object [])))]
(is (= 200 (.statusCode response)))
(is (= "Leanne Graham" (-> response .jsonPath (.getString "name"))))))
take-screenshot [page description]Capture and attach screenshot to current step:
(take-screenshot page "after-login")
;; => "after_login-1767163337862-1.png"
Screenshots appear in reports as:
generate-reports []Generate HTML reports from JSON test results:
(require '[testing.junit :as junit])
(junit/generate-reports)
Outputs:
========================================
Generating Serenity Reports
========================================
Source: target/site/serenity
Project: Serenity Clojure
✓ HTML reports generated
Open: target/site/serenity/index.html
========================================
serenity.properties# Project identification
serenity.project.name=My Test Project
# Output directory
serenity.outputDirectory=target/site/serenity
# Screenshot settings (manual via take-screenshot)
serenity.take.screenshots=FOR_EACH_ACTION
# Report configuration
serenity.report.json=true
serenity.report.show.step.details=true
serenity.console.colors=true
clojure -M:test -e "(require 'clojure.test)(clojure.test/run-all-tests #\"samples.*\")"
clojure -M:test -e "(require '[samples.demo-test])(clojure.test/run-tests 'samples.demo-test)"
clojure -M:it
clojure -M:aggregate-reports
Or manually:
clojure -M -e "(require '[testing.junit :as junit])(junit/generate-reports)"
# Open in browser (macOS)
open target/site/serenity/index.html
# Linux
xdg-open target/site/serenity/index.html
# Or use Python web server
python3 -m http.server 8000 --directory target/site/serenity
# Then visit http://localhost:8000
The ui-step macro automatically captures screenshots before and after each UI action, creating a visual timeline of your test:
(ns my-app.ui-test
(:require [clojure.test :refer [deftest is]]
[testing.junit :refer [with-serenity ui-step api-step]])
(:import [com.microsoft.playwright.options LoadState]))
(deftest user-login-journey
(with-serenity [page]
;; Each ui-step creates Before and After screenshots automatically
(ui-step page "Navigate to login page"
#(do
(.navigate page "https://myapp.com/login")
(.waitForLoadState page LoadState/NETWORKIDLE)))
(ui-step page "Fill in login credentials"
#(do
(.fill page "#username" "testuser")
(.fill page "#password" "password123")))
(ui-step page "Submit login form"
#(.click page "button[type='submit']"))
(ui-step page "Verify successful login"
#(do
(.waitForSelector page ".dashboard")
(is (.isVisible (.locator page ".user-profile")))))))
Report Output:
Navigate to login page
├── Navigate to login page - Before (screenshot)
└── Navigate to login page - After (screenshot)
Fill in login credentials
├── Fill in login credentials - Before (screenshot)
└── Fill in login credentials - After (screenshot)
Submit login form
├── Submit login form - Before (screenshot)
└── Submit login form - After (screenshot)
Verify successful login
├── Verify successful login - Before (screenshot)
└── Verify successful login - After (screenshot)
This creates 8 screenshots (4 steps x 2 screenshots each) documenting every state change in your UI test.
Test that combines browser automation with API calls in a single scenario, using automatic before/after screenshots for UI steps:
(ns my-app.integration-test
(:require [clojure.test :refer [deftest is]]
[clojure.string :as str]
[testing.junit :refer [with-serenity ui-step api-step]])
(:import [net.serenitybdd.rest SerenityRest]
[io.restassured.http ContentType]
[com.microsoft.playwright.options LoadState]))
(deftest combined-ui-api-test
(with-serenity [page]
;; UI Testing with automatic before/after screenshots
(ui-step page "Navigate to application homepage"
#(do
(.navigate page "https://example.com")
(.waitForLoadState page LoadState/NETWORKIDLE)))
(ui-step page "Verify page loaded correctly"
#(is (str/includes? (.title page) "Example")))
;; API Testing - Call REST endpoints with full logging
(api-step "Fetch user data from API"
#(let [response (-> (SerenityRest/given)
(.baseUri "https://jsonplaceholder.typicode.com")
(.when)
(.get "/users/1" (into-array Object [])))]
(is (= 200 (.statusCode response)))
(is (= "Leanne Graham" (-> response .jsonPath (.getString "name"))))))
;; More API testing with POST request
(api-step "Create new resource via API"
#(let [response (-> (SerenityRest/given)
(.baseUri "https://jsonplaceholder.typicode.com")
(.contentType ContentType/JSON)
(.body {"name" "Test" "value" 42})
(.when)
(.post "/posts" (into-array Object [])))]
(is (= 201 (.statusCode response)))))
;; Continue UI interaction with automatic screenshots
(ui-step page "Verify page content after API calls"
#(let [heading (.locator page "h1")]
(is (> (.count heading) 0))))))
This test will generate a Serenity report that includes:
Focus on REST API testing with automatic request/response capture:
(ns my-app.api-test
(:require [clojure.test :refer [deftest is]]
[testing.junit :refer [with-serenity api-step]])
(:import [net.serenitybdd.rest SerenityRest]
[io.restassured.http ContentType]))
(deftest comprehensive-api-test
(with-serenity [page]
(api-step "GET request with query parameters"
#(let [response (-> (SerenityRest/given)
(.baseUri "https://jsonplaceholder.typicode.com")
(.queryParam "userId" (into-array Object ["1"]))
(.when)
(.get "/posts" (into-array Object [])))]
(is (= 200 (.statusCode response)))))
(api-step "POST request with JSON body"
#(let [data {"title" "New Post"
"body" "Post content"
"userId" 1}
response (-> (SerenityRest/given)
(.baseUri "https://jsonplaceholder.typicode.com")
(.contentType ContentType/JSON)
(.body data)
(.when)
(.post "/posts" (into-array Object [])))]
(is (= 201 (.statusCode response)))
(is (= "New Post" (-> response .jsonPath (.getString "title"))))))
(api-step "PUT request to update resource"
#(let [response (-> (SerenityRest/given)
(.baseUri "https://jsonplaceholder.typicode.com")
(.contentType ContentType/JSON)
(.body {"title" "Updated Post"})
(.when)
(.put "/posts/1" (into-array Object [])))]
(is (= 200 (.statusCode response)))))
(api-step "DELETE request"
#(let [response (-> (SerenityRest/given)
(.baseUri "https://jsonplaceholder.typicode.com")
(.when)
(.delete "/posts/1" (into-array Object [])))]
(is (= 200 (.statusCode response)))))))
API Logging includes:
Validate that Serenity reports are properly generated with all artifacts. Note that screenshots and API call data are stored in nested children within test steps, not at the top level:
(ns my-app.validation-test
(:require [clojure.test :refer [deftest is testing]]
[clojure.java.io :as io]
[clojure.data.json :as json]))
(defn collect-from-steps
"Recursively collect items from test steps and their children."
[steps pred]
(when (seq steps)
(concat
(filter pred steps)
(mapcat #(collect-from-steps (:children %) pred) steps))))
(defn validate-json-reports []
(let [output-dir "target/site/serenity"
json-files (filter #(.endsWith (.getName %) ".json")
(file-seq (io/file output-dir)))]
(doseq [json-file json-files]
(when (.isFile json-file)
(let [content (json/read-str (slurp json-file) :key-fn keyword)]
(is (contains? content :testSteps) "Report should have test steps")
;; Screenshots are nested in children of test steps
(let [screenshots (collect-from-steps
(:testSteps content)
#(seq (:screenshots %)))]
(println (str "Screenshots in report: " (count screenshots))))
;; API calls (restQuery) are also nested in children
(let [api-calls (collect-from-steps
(:testSteps content)
#(contains? % :restQuery))]
(println (str "API calls in report: " (count api-calls)))))))))
(deftest report-validation-test
(testing "Validate report artifacts"
(validate-json-reports)))
Here's a complete workflow from writing tests to viewing reports:
# 1. Clean previous test outputs
clojure -M:clean
# 2. Run your tests
clojure -M:it
# 3. Generate HTML reports
clojure -M:aggregate-reports
# 4. View reports
open target/site/serenity/index.html
Serenity reports automatically include:
step, ui-step, and api-step call with timing and statusui-step, automatic before/after screenshots as nested child stepstake-screenshot with thumbnails and galleriesStep Hierarchy Example:
Login to application (parent step)
├── Login to application - Before (child step with screenshot)
└── Login to application - After (child step with screenshot)
API: Get user data (api step with full request/response in child)
└── GET https://api.example.com/users/1 (child with restQuery data)
Report Structure:
target/site/serenity/
├── index.html # Main dashboard
├── *.json # Test result data
├── *.png # Screenshots (before/after pairs)
├── serenity.css # Styling
└── serenity.js # Interactive features
| Library | Version | Purpose |
|---|---|---|
| Clojure | 1.12.4 | Language runtime |
| Serenity BDD | 5.2.2 | Test reporting and BDD framework |
| Playwright | 1.58.0 | Browser automation |
| REST Assured | 6.0.0 | REST API testing (via serenity-rest-assured) |
| JUnit Platform | 6.0.3 | Test platform integration |
| Kaocha | 1.91.1392 | Clojure test runner |
| SLF4J | 2.0.17 | Logging facade |
| Logback | 1.5.32 | Logging implementation |
Serenity Clojure follows these principles:
See LICENSE file for details.
Built with:
Made with care for the Clojure testing community
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |