A thin Clojure wrapper around the Playwright Java SDK. Every test automatically produces a Playwright Trace Viewer recording — no JUnit Platform, no extra reporting setup.
Raw Playwright Java interop works, but repeating browser/context/tracing lifecycle in every test is noise. This library provides three macros that handle that lifecycle so test authors can focus on assertions:
with-ui— start browser, enable tracing, run body, save trace, close browserwith-api— provide anAPIRequestContext; attach to an existing trace or produce a standalone onestep— annotate a logical block in the trace viewer with a name and timing
That is the entire abstraction. Everything else is direct Playwright interop.
Install Playwright browsers (once):
npx playwright install chromiumRun the sample tests:
clojure -M:testExpected: 6 tests, 1 intentional failure (failing-step-demo).
(ns my-app.tests
(:require [clojure.test :refer [deftest is]]
[clojure.string :as str]
[testing.pw :refer [with-ui with-api step pw-screenshot *current-page*]]))
(deftest homepage-test
(with-ui [page]
(step "Navigate to example.com"
(fn []
(.navigate page "https://example.com")))
(step "Verify heading"
(fn []
(is (= "Example Domain"
(.textContent (.locator *current-page* "h1"))))))))The with-ui binding accepts an optional options map:
(with-ui [page {:headless false :output-dir "target/my-traces"}]
...)| Option | Default | Description |
|---|---|---|
:headless |
true |
Show a browser window when false |
:output-dir |
"target/pw-traces" |
Where trace zips are written |
:slow-mo |
0 (headed: 80) |
Milliseconds between actions |
To run in headed mode without changing test code, pass the JVM property on the command line:
clojure -M:test -J-Dplaywright.browser.headless=falsewith-api provides a Playwright APIRequestContext bound to client.
Attached — nested inside with-ui, HTTP calls appear in the same trace:
(with-ui [page]
(step "Navigate"
#(.navigate page "https://example.com"))
(step "REST call in same trace"
#(with-api [client]
(let [resp (.get client "https://api.example.com/status" nil)]
(is (= 200 (.status resp)))))))Standalone — no browser required, produces its own trace.zip:
(deftest api-test
(with-api [client]
(let [resp (.get client "https://jsonplaceholder.typicode.com/posts/1" nil)]
(is (= 200 (.status resp))))))Starts Chromium, enables full tracing (screenshots + DOM snapshots + sources),
runs the body, saves <test-name>-<timestamp>.zip, closes the browser.
Binds the Page to the name you choose and also to the dynamic var
*current-page* so helpers called from inside the body do not need page
threaded through as an argument.
(with-pw is a deprecated alias — delegates to with-ui.)
Provides a Playwright APIRequestContext bound to client.
- Inside
with-ui:BrowserContext.request()returns the context'sAPIRequestContextdirectly (it is not a factory — do not call.newContexton it). - Standalone: uses
Playwright.request().newContext(opts)to create the client, starts/stops a tracing context around the body.
Accepts :ignore-https-errors true as an option.
Wraps a zero-arg function in a named Playwright trace group. Reads
*current-page* automatically.
(step "Fill login form"
(fn []
(.fill *current-page* "#email" "user@example.com")
(.click *current-page* "button[type=submit]")))Captures a labelled screenshot inside the current trace context. Reads
*current-page*. Use this for explicit named snapshots; the trace's
automatic film-strip is usually sufficient.
(pw-screenshot "after-submit")Low-level helpers used internally. start-browser returns
{:playwright :browser :context :page :trace-path}.
| Alias | Command | What it does |
|---|---|---|
:test |
clojure -M:test |
Run tests via Kaocha |
:aggregate |
clojure -M:aggregate |
Copy trace viewer assets + generate index.html from existing zips |
:traces |
clojure -M:traces |
Serve the report over HTTP at http://localhost:8080 |
:nrepl |
clojure -M:nrepl |
Start headed browser + nREPL (port 7888) |
:clean |
clojure -M:clean |
Delete target/ |
clojure -M:nrepl
# nREPL on port 7888; headed Chromium opens immediately(require '[testing.repl :refer [page restart stop]])
(.navigate @page "https://example.com")
(.textContent (.locator @page "h1")) ;; => "Example Domain"
(restart) ;; fresh browser
(stop) ;; close everythingEach test writes target/pw-traces/<name>-<timestamp>.zip.
Via the built-in HTTP server (serves the full Trace Viewer UI):
clojure -M:tracesThen open http://localhost:8080/index.html in your browser. The port and
serve directory can be overridden:
clojure -M:traces -J-Dpw.server.port=9090 -J-Dpw.server.dir=target/pw-tracesVia the Playwright CLI (requires Node/npx):
npx playwright show-trace target/pw-traces/my-test-1234567890.zipOr drag and drop the zip to https://trace.playwright.dev
| Library | Version | Purpose |
|---|---|---|
| Clojure | 1.12.4 | Language runtime |
| Playwright | 1.58.0 | Browser automation + tracing + HTTP client |
| Kaocha | 1.91.1392 | Clojure test runner |
See LICENSE file for details.