Playwright-native testing library for Clojure.
Provides:
with-ui — main test macro; manages browser + tracing lifecycle.
Produces a trace.zip per test, viewable in Playwright
Trace Viewer (npx playwright show-trace / trace.playwright.dev).
Binds *current-page* dynamic var for the duration of the test.
(with-pw is a deprecated alias for with-ui.)with-api — macro that provides an APIRequestContext bound to client.
If *current-page* is already bound (i.e. inside with-pw),
derives the client from the existing traced BrowserContext so
HTTP calls appear in the trace. Otherwise starts a lightweight
headless browser+context with tracing and tears it down after body.step — annotate a logical step in the trace (group of actions).
Reads page from *current-page*; no explicit context needed.pw-screenshot — manually attach a labelled screenshot to the trace.
Reads page from *current-page*; no explicit page arg needed.testing.pw-report namespace can aggregate all zips into
a static HTML dashboard.(ns my.tests (:require [clojure.test :refer [deftest is]] [testing.pw :refer [pw-screenshot step with-ui with-api]]))
(deftest homepage-test (with-ui [page] (step "Navigate to example.com" (fn [] (.navigate page "https://example.com") (is (clojure.string/includes? (.title page) "Example"))))
(step "Verify heading"
(fn []
(let [h1 (.locator page "h1")]
(is (= "Example Domain" (.textContent h1))))))))
Playwright-native testing library for Clojure.
Provides:
- `with-ui` — main test macro; manages browser + tracing lifecycle.
Produces a trace.zip per test, viewable in Playwright
Trace Viewer (npx playwright show-trace / trace.playwright.dev).
Binds `*current-page*` dynamic var for the duration of the test.
(`with-pw` is a deprecated alias for `with-ui`.)
- `with-api` — macro that provides an APIRequestContext bound to `client`.
If `*current-page*` is already bound (i.e. inside `with-pw`),
derives the client from the existing traced BrowserContext so
HTTP calls appear in the trace. Otherwise starts a lightweight
headless browser+context with tracing and tears it down after body.
- `step` — annotate a logical step in the trace (group of actions).
Reads page from `*current-page*`; no explicit context needed.
- `pw-screenshot` — manually attach a labelled screenshot to the trace.
Reads page from `*current-page*`; no explicit page arg needed.
Design goals
============
* Zero JUnit dependency — pure Playwright Java API on the JVM.
* Every test automatically produces target/pw-traces/<test-name>-<ts>.zip
* Traces are enriched with custom step annotations via
BrowserContext.tracing().group() / groupEnd(), which appear as named
groups in the Trace Viewer action list.
* A separate `testing.pw-report` namespace can aggregate all zips into
a static HTML dashboard.
Usage
=====
(ns my.tests
(:require [clojure.test :refer [deftest is]]
[testing.pw :refer [pw-screenshot step with-ui with-api]]))
(deftest homepage-test
(with-ui [page]
(step "Navigate to example.com"
(fn []
(.navigate page "https://example.com")
(is (clojure.string/includes? (.title page) "Example"))))
(step "Verify heading"
(fn []
(let [h1 (.locator page "h1")]
(is (= "Example Domain" (.textContent h1))))))))
Dynamically bound to the Playwright APIRequestContext inside with-api.
Allows nested helpers to access the REST client without explicit threading.
Dynamically bound to the Playwright APIRequestContext inside `with-api`. Allows nested helpers to access the REST client without explicit threading.
Dynamically bound to the Playwright Page inside with-pw.
step and pw-screenshot read this automatically so callers never need to
pass page/context explicitly.
Dynamically bound to the Playwright Page inside `with-pw`. `step` and `pw-screenshot` read this automatically so callers never need to pass page/context explicitly.
When true, step prints each step name, timing, and pass/fail to stdout.
Automatically set to true when running headed (:headless false or
-Dplaywright.browser.headless=false). Can also be bound manually.
When true, `step` prints each step name, timing, and pass/fail to stdout. Automatically set to true when running headed (`:headless false` or `-Dplaywright.browser.headless=false`). Can also be bound manually.
(begin-step context description)Open a named group in the Playwright trace.
Internal — use the step macro instead.
Open a named group in the Playwright trace. Internal — use the `step` macro instead.
(end-step context)Close the most recently opened trace group.
Internal — use the step macro instead.
Close the most recently opened trace group. Internal — use the `step` macro instead.
(pw-screenshot label)Take a screenshot and add it as an attachment annotation inside the current trace context. Returns the screenshot bytes.
Reads the current page from *current-page* (bound by with-pw).
Because Playwright's tracing automatically captures screenshots on every action, this is only needed for explicit labelled snapshots outside of normal user interactions.
Take a screenshot and add it as an attachment annotation inside the current trace context. Returns the screenshot bytes. Reads the current page from `*current-page*` (bound by `with-pw`). Because Playwright's tracing automatically captures screenshots on every action, this is only needed for *explicit* labelled snapshots outside of normal user interactions.
(start-browser)(start-browser opts)Start a Playwright browser context with tracing enabled.
Options: :headless — boolean, default true. Overridden by JVM property -Dplaywright.browser.headless=false :browser — :chromium (default) | :webkit | :firefox. Defaults from JVM property -Dplaywright.browser or env PW_BROWSER, so a whole suite can be pointed at another engine (e.g. WebKit ≈ Safari) without editing tests. :test-name — string used to derive the trace filename. :output-dir — directory for trace zips, default 'target/pw-traces'. :slow-mo — milliseconds to slow down actions (useful headed), default 0.
Returns a map with: :playwright :browser :context :page :trace-path
Start a Playwright browser context with tracing enabled.
Options:
:headless — boolean, default true.
Overridden by JVM property -Dplaywright.browser.headless=false
:browser — :chromium (default) | :webkit | :firefox.
Defaults from JVM property -Dplaywright.browser or env
PW_BROWSER, so a whole suite can be pointed at another
engine (e.g. WebKit ≈ Safari) without editing tests.
:test-name — string used to derive the trace filename.
:output-dir — directory for trace zips, default 'target/pw-traces'.
:slow-mo — milliseconds to slow down actions (useful headed), default 0.
Returns a map with:
:playwright :browser :context :page :trace-path(step description f)Execute f inside a named Playwright trace group.
(step "Log in" (fn [] (.fill page "#user" "admin") (.click page "#submit")))
Reads the current page from *current-page* (bound by with-pw).
The step name appears as a collapsible group in the Trace Viewer action list.
When *verbose-steps* is true (automatically set in headed mode), step
names, timings and pass/fail status are printed to stdout.
Execute f inside a named Playwright trace group.
(step "Log in"
(fn []
(.fill page "#user" "admin")
(.click page "#submit")))
Reads the current page from `*current-page*` (bound by `with-pw`).
The step name appears as a collapsible group in the Trace Viewer action list.
When `*verbose-steps*` is true (automatically set in headed mode), step
names, timings and pass/fail status are printed to stdout.(stop-browser bm)(stop-browser {:keys [playwright browser context page trace-path] :as _bm}
status)Stop tracing, save trace.zip, rename it with status suffix, then close everything.
status is :pass or :fail (default :pass).
Returns the java.nio.file.Path of the saved (and renamed) trace, or nil on error.
Stop tracing, save trace.zip, rename it with status suffix, then close everything. `status` is :pass or :fail (default :pass). Returns the java.nio.file.Path of the saved (and renamed) trace, or nil on error.
(with-api [client-binding & [opts]] & body)Provide a Playwright APIRequestContext bound to client-binding.
Behaviour depends on whether a browser session is already active:
Inside with-pw (*current-page* is bound):
BrowserContext.request() returns the context's own APIRequestContext
directly — no factory step needed. HTTP calls appear in the trace.
The browser lifecycle is untouched.
Standalone (no *current-page*):
Starts a fresh headless Chromium + BrowserContext with tracing enabled,
produces a trace.zip just like with-pw, then closes everything.
Uses Playwright.request().newContext(opts) to create the REST client.
Options map (optional second element of binding vector):
:ignore-https-errors — boolean, default false (standalone mode only).
All other options accepted by start-browser (:headless, :output-dir,
:slow-mo) are forwarded when starting a standalone context.
Examples:
;; Standalone REST-only test — full trace produced (deftest config-fetch-test (with-api [client] (let [resp (.get client "https://jsonplaceholder.typicode.com/posts/1" nil)] (is (= 200 (.status resp))))))
;; Inside with-pw — HTTP recorded in the same browser trace (deftest mixed-test (with-pw [page] (step "Load UI" #(.navigate page "https://example.com")) (with-api [client] (let [resp (.get client "https://jsonplaceholder.typicode.com/posts/1" nil)] (is (= 200 (.status resp)))))))
Provide a Playwright APIRequestContext bound to `client-binding`.
Behaviour depends on whether a browser session is already active:
* Inside `with-pw` (`*current-page*` is bound):
BrowserContext.request() returns the context's own APIRequestContext
directly — no factory step needed. HTTP calls appear in the trace.
The browser lifecycle is untouched.
* Standalone (no `*current-page*`):
Starts a fresh headless Chromium + BrowserContext with tracing enabled,
produces a trace.zip just like `with-pw`, then closes everything.
Uses Playwright.request().newContext(opts) to create the REST client.
Options map (optional second element of binding vector):
:ignore-https-errors — boolean, default false (standalone mode only).
All other options accepted by `start-browser` (:headless, :output-dir,
:slow-mo) are forwarded when starting a standalone context.
Examples:
;; Standalone REST-only test — full trace produced
(deftest config-fetch-test
(with-api [client]
(let [resp (.get client "https://jsonplaceholder.typicode.com/posts/1" nil)]
(is (= 200 (.status resp))))))
;; Inside with-pw — HTTP recorded in the same browser trace
(deftest mixed-test
(with-pw [page]
(step "Load UI" #(.navigate page "https://example.com"))
(with-api [client]
(let [resp (.get client "https://jsonplaceholder.typicode.com/posts/1" nil)]
(is (= 200 (.status resp)))))))
(with-pw binding & body)Deprecated alias for with-ui. Use with-ui instead.
Deprecated alias for `with-ui`. Use `with-ui` instead.
(with-ui [page-binding & [opts]] & body)Main test wrapper that provides a Playwright Page bound to page-binding
and full tracing lifecycle management. Also binds *current-page* so
step and pw-screenshot can be used without passing page explicitly.
Forms:
(with-ui [page] (step "Do something" #(.navigate page "https://...")) (step "Verify" #(is (= "Title" (.title page)))))
;; with explicit options: (with-ui [page {:headless false :output-dir "target/my-traces"}] ...)
Options (second element of binding vector, optional map): :headless — false to show a browser window (default true) :output-dir — directory for trace zips (default 'target/pw-traces') :slow-mo — ms delay between actions when headed (default 80)
What happens:
page bound to the Playwright Page and
*current-page* dynamically bound to the same Page.Viewing a trace: npx playwright show-trace target/pw-traces/<name>.zip
Main test wrapper that provides a Playwright Page bound to `page-binding`
and full tracing lifecycle management. Also binds `*current-page*` so
`step` and `pw-screenshot` can be used without passing page explicitly.
Forms:
(with-ui [page]
(step "Do something" #(.navigate page "https://..."))
(step "Verify" #(is (= "Title" (.title page)))))
;; with explicit options:
(with-ui [page {:headless false :output-dir "target/my-traces"}]
...)
Options (second element of binding vector, optional map):
:headless — false to show a browser window (default true)
:output-dir — directory for trace zips (default 'target/pw-traces')
:slow-mo — ms delay between actions when headed (default 80)
What happens:
1. Chromium is launched, tracing started (screenshots + snapshots + sources).
2. Body forms execute with `page` bound to the Playwright Page and
`*current-page*` dynamically bound to the same Page.
3. Tracing stops and trace.zip is written to output-dir.
4. Browser is closed regardless of test outcome.
5. On success/failure the trace path is printed for quick opening.
Viewing a trace:
npx playwright show-trace target/pw-traces/<name>.zip
# or drag-drop the zip to https://trace.playwright.dev
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 |