Playwright API testing — APIRequest, APIRequestContext, APIResponse.
Provides idiomatic Clojure wrappers for Playwright's built-in HTTP client.
Supports all HTTP methods, form data, query params, custom headers, and
lifecycle management via with-api-context.
Usage: (require '[com.blockether.spel.core :as pw] '[com.blockether.spel.api :as api])
(core/with-playwright [playwright] (api/with-api-context [ctx (-> playwright api/api-request (api/new-api-context {:base-url "https://api.example.com"}))] (let [resp (api/get ctx "/users" {:params {:page 1 :limit 10}})] (println (api/response->map resp)))))
;; Response map: ;; {:status 200 ;; :status-text "OK" ;; :url "https://api.example.com/users?page=1&limit=10" ;; :ok? true ;; :headers {"content-type" "application/json"} ;; :body "{...}"}
All HTTP methods accept either a RequestOptions object or a Clojure map:
(api/post ctx "/users" {:headers {"Content-Type" "application/json"} :data "{"name": "Alice"}" :timeout 5000})
(api/put ctx "/users/1" {:form (api/map->form-data {:name "Bob" :email "bob@example.com"})})
(api/delete ctx "/users/1")
Playwright API testing — APIRequest, APIRequestContext, APIResponse.
Provides idiomatic Clojure wrappers for Playwright's built-in HTTP client.
Supports all HTTP methods, form data, query params, custom headers, and
lifecycle management via `with-api-context`.
Usage:
(require '[com.blockether.spel.core :as pw]
'[com.blockether.spel.api :as api])
(core/with-playwright [playwright]
(api/with-api-context [ctx (-> playwright api/api-request
(api/new-api-context {:base-url "https://api.example.com"}))]
(let [resp (api/get ctx "/users" {:params {:page 1 :limit 10}})]
(println (api/response->map resp)))))
;; Response map:
;; {:status 200
;; :status-text "OK"
;; :url "https://api.example.com/users?page=1&limit=10"
;; :ok? true
;; :headers {"content-type" "application/json"}
;; :body "{...}"}
All HTTP methods accept either a RequestOptions object or a Clojure map:
(api/post ctx "/users"
{:headers {"Content-Type" "application/json"}
:data "{\"name\": \"Alice\"}"
:timeout 5000})
(api/put ctx "/users/1"
{:form (api/map->form-data {:name "Bob" :email "bob@example.com"})})
(api/delete ctx "/users/1")Map of hook functions invoked during the API request lifecycle.
All hooks are optional (nil = no-op). Each hook receives context about the current operation and can observe or transform it.
Keys:
:on-request (fn [method url opts]) -> opts Called before every request. Receives the HTTP method keyword (:get, :post, ...), the URL string, and the opts map. Return value replaces opts. Return nil to keep original. Use for: logging, auth token injection, request transformation.
:on-response (fn [method url response]) -> response
Called after every successful request. Receives the method,
URL, and the APIResponse (or response map from request!).
Return value replaces the response. Return nil to keep original.
Use for: logging, metrics, response transformation.
:on-error (fn [method url anomaly]) -> anomaly Called when a request returns an anomaly (network error, timeout, etc.). Return value replaces the anomaly. Return nil to keep original. Use for: error logging, alerting, error transformation.
:on-retry (fn [{:keys [attempt max-attempts delay-ms result]}]) Called before each retry sleep. Side-effect only, return value ignored. Use for: logging retry attempts, metrics.
Examples:
;; Global logging (alter-var-root #'api/hooks (constantly {:on-request (fn [m url _] (println "→" m url)) :on-response (fn [m url r] (println "←" m url (.status r)) r)}))
;; Per-scope auth injection (binding [api/hooks {:on-request (fn [_ _ opts] (update opts :headers assoc "Authorization" (str "Bearer " (get-token))))}] (api-get ctx "/protected"))
Map of hook functions invoked during the API request lifecycle.
All hooks are optional (nil = no-op). Each hook receives context
about the current operation and can observe or transform it.
Keys:
:on-request (fn [method url opts]) -> opts
Called before every request. Receives the HTTP method keyword
(:get, :post, ...), the URL string, and the opts map.
Return value replaces opts. Return nil to keep original.
Use for: logging, auth token injection, request transformation.
:on-response (fn [method url response]) -> response
Called after every successful request. Receives the method,
URL, and the APIResponse (or response map from `request!`).
Return value replaces the response. Return nil to keep original.
Use for: logging, metrics, response transformation.
:on-error (fn [method url anomaly]) -> anomaly
Called when a request returns an anomaly (network error,
timeout, etc.). Return value replaces the anomaly.
Return nil to keep original.
Use for: error logging, alerting, error transformation.
:on-retry (fn [{:keys [attempt max-attempts delay-ms result]}])
Called before each retry sleep. Side-effect only, return
value ignored.
Use for: logging retry attempts, metrics.
Examples:
;; Global logging
(alter-var-root #'api/*hooks*
(constantly
{:on-request (fn [m url _] (println "→" m url))
:on-response (fn [m url r] (println "←" m url (.status r)) r)}))
;; Per-scope auth injection
(binding [api/*hooks* {:on-request (fn [_ _ opts]
(update opts :headers
assoc "Authorization" (str "Bearer " (get-token))))}]
(api-get ctx "/protected"))Function that encodes Clojure data to a JSON string. Bind to your preferred JSON library's encode function.
Must accept any Clojure data (maps, vectors, strings, numbers, etc.) and return a String.
Examples: ;; cheshire (binding [api/json-encoder cheshire.core/generate-string] (api/api-post ctx "/users" {:json {:name "Alice"}}))
;; charred (binding [api/json-encoder charred.api/write-json-str] (api/api-post ctx "/users" {:json {:name "Alice"}}))
;; jsonista (binding [api/json-encoder jsonista.core/write-value-as-string] (api/api-post ctx "/users" {:json {:name "Alice"}}))
;; Set globally for convenience (alter-var-root #'api/json-encoder (constantly cheshire.core/generate-string))
Function that encodes Clojure data to a JSON string.
Bind to your preferred JSON library's encode function.
Must accept any Clojure data (maps, vectors, strings, numbers, etc.)
and return a String.
Examples:
;; cheshire
(binding [api/*json-encoder* cheshire.core/generate-string]
(api/api-post ctx "/users" {:json {:name "Alice"}}))
;; charred
(binding [api/*json-encoder* charred.api/write-json-str]
(api/api-post ctx "/users" {:json {:name "Alice"}}))
;; jsonista
(binding [api/*json-encoder* jsonista.core/write-value-as-string]
(api/api-post ctx "/users" {:json {:name "Alice"}}))
;; Set globally for convenience
(alter-var-root #'api/*json-encoder* (constantly cheshire.core/generate-string))(api-delete ctx url)(api-delete ctx url opts)Sends a DELETE request.
Params:
ctx - APIRequestContext instance.
url - String. URL path.
opts - RequestOptions or map, optional. See request-options.
Returns: APIResponse or anomaly map.
Sends a DELETE request. Params: `ctx` - APIRequestContext instance. `url` - String. URL path. `opts` - RequestOptions or map, optional. See `request-options`. Returns: APIResponse or anomaly map.
(api-dispose! ctx)Disposes the APIRequestContext and all responses.
Params:
ctx - APIRequestContext instance.
Returns: nil.
Disposes the APIRequestContext and all responses. Params: `ctx` - APIRequestContext instance. Returns: nil.
(api-fetch ctx url)(api-fetch ctx url opts)Sends a request with custom method (set via :method in opts).
Params:
ctx - APIRequestContext instance.
url - String. URL path.
opts - RequestOptions or map, optional. See request-options.
Returns: APIResponse or anomaly map.
Examples: (api-fetch ctx "/resource" {:method "OPTIONS"})
Sends a request with custom method (set via :method in opts).
Params:
`ctx` - APIRequestContext instance.
`url` - String. URL path.
`opts` - RequestOptions or map, optional. See `request-options`.
Returns:
APIResponse or anomaly map.
Examples:
(api-fetch ctx "/resource" {:method "OPTIONS"})(api-get ctx url)(api-get ctx url opts)Sends a GET request.
Params:
ctx - APIRequestContext instance.
url - String. URL path (resolved against base-url if set).
opts - RequestOptions or map, optional. See request-options.
Returns: APIResponse or anomaly map.
Examples: (api-get ctx "/users") (api-get ctx "/users" {:params {:page 1 :limit 10} :headers {"Authorization" "Bearer token"}})
Sends a GET request.
Params:
`ctx` - APIRequestContext instance.
`url` - String. URL path (resolved against base-url if set).
`opts` - RequestOptions or map, optional. See `request-options`.
Returns:
APIResponse or anomaly map.
Examples:
(api-get ctx "/users")
(api-get ctx "/users" {:params {:page 1 :limit 10}
:headers {"Authorization" "Bearer token"}})(api-head ctx url)(api-head ctx url opts)Sends a HEAD request.
Params:
ctx - APIRequestContext instance.
url - String. URL path.
opts - RequestOptions or map, optional. See request-options.
Returns: APIResponse or anomaly map.
Sends a HEAD request. Params: `ctx` - APIRequestContext instance. `url` - String. URL path. `opts` - RequestOptions or map, optional. See `request-options`. Returns: APIResponse or anomaly map.
(api-patch ctx url)(api-patch ctx url opts)Sends a PATCH request.
Params:
ctx - APIRequestContext instance.
url - String. URL path.
opts - RequestOptions or map, optional. See request-options.
Returns: APIResponse or anomaly map.
Sends a PATCH request. Params: `ctx` - APIRequestContext instance. `url` - String. URL path. `opts` - RequestOptions or map, optional. See `request-options`. Returns: APIResponse or anomaly map.
(api-post ctx url)(api-post ctx url opts)Sends a POST request.
Params:
ctx - APIRequestContext instance.
url - String. URL path.
opts - RequestOptions or map, optional. See request-options.
Returns: APIResponse or anomaly map.
Examples: (api-post ctx "/users" {:data "{"name": "Alice"}" :headers {"Content-Type" "application/json"}}) (api-post ctx "/login" {:form (map->form-data {:user "admin" :pass "secret"})})
Sends a POST request.
Params:
`ctx` - APIRequestContext instance.
`url` - String. URL path.
`opts` - RequestOptions or map, optional. See `request-options`.
Returns:
APIResponse or anomaly map.
Examples:
(api-post ctx "/users" {:data "{\"name\": \"Alice\"}"
:headers {"Content-Type" "application/json"}})
(api-post ctx "/login" {:form (map->form-data {:user "admin" :pass "secret"})})(api-put ctx url)(api-put ctx url opts)Sends a PUT request.
Params:
ctx - APIRequestContext instance.
url - String. URL path.
opts - RequestOptions or map, optional. See request-options.
Returns: APIResponse or anomaly map.
Sends a PUT request. Params: `ctx` - APIRequestContext instance. `url` - String. URL path. `opts` - RequestOptions or map, optional. See `request-options`. Returns: APIResponse or anomaly map.
(api-request pw)Returns the APIRequest for the Playwright instance.
Params:
pw - Playwright instance.
Returns: APIRequest instance.
Returns the APIRequest for the Playwright instance. Params: `pw` - Playwright instance. Returns: APIRequest instance.
(api-response->map resp)Converts an APIResponse to a Clojure map.
Reads the response body as text and includes all metadata. Useful for logging, debugging, and test assertions.
Params:
resp - APIResponse instance.
Returns: Map with keys: :status - Long. HTTP status code. :status-text - String. HTTP status text. :url - String. Response URL. :ok? - Boolean. True if status is 2xx. :headers - Map. Response headers. :body - String. Response body text (nil on read failure).
Examples: (let [resp (api-get ctx "/users")] (api-response->map resp)) ;; => {:status 200 ;; :status-text "OK" ;; :url "https://api.example.com/users" ;; :ok? true ;; :headers {"content-type" "application/json"} ;; :body "{"users": [...]}"}
Converts an APIResponse to a Clojure map.
Reads the response body as text and includes all metadata.
Useful for logging, debugging, and test assertions.
Params:
`resp` - APIResponse instance.
Returns:
Map with keys:
:status - Long. HTTP status code.
:status-text - String. HTTP status text.
:url - String. Response URL.
:ok? - Boolean. True if status is 2xx.
:headers - Map. Response headers.
:body - String. Response body text (nil on read failure).
Examples:
(let [resp (api-get ctx "/users")]
(api-response->map resp))
;; => {:status 200
;; :status-text "OK"
;; :url "https://api.example.com/users"
;; :ok? true
;; :headers {"content-type" "application/json"}
;; :body "{\"users\": [...]}"}(api-response-body resp)Returns the response body as bytes. Passes through anomaly maps unchanged.
Returns the response body as bytes. Passes through anomaly maps unchanged.
(api-response-dispose! resp)Disposes the APIResponse. No-op on anomaly maps.
Params:
resp - APIResponse instance.
Returns: nil.
Disposes the APIResponse. No-op on anomaly maps. Params: `resp` - APIResponse instance. Returns: nil.
(api-response-headers resp)Returns the response headers. Passes through anomaly maps unchanged.
Returns the response headers. Passes through anomaly maps unchanged.
(api-response-headers-array resp)Returns the response headers as a vector of {:name :value} maps.
Preserves duplicate headers (unlike api-response-headers which
keeps only the last value for each name).
Passes through anomaly maps unchanged.
Params:
resp - APIResponse instance.
Returns: Vector of maps, or anomaly map.
Returns the response headers as a vector of {:name :value} maps.
Preserves duplicate headers (unlike `api-response-headers` which
keeps only the last value for each name).
Passes through anomaly maps unchanged.
Params:
`resp` - APIResponse instance.
Returns:
Vector of maps, or anomaly map.(api-response-ok? resp)Returns whether the response is OK (2xx). Passes through anomaly maps unchanged.
Returns whether the response is OK (2xx). Passes through anomaly maps unchanged.
(api-response-status resp)Returns the HTTP status code. Passes through anomaly maps unchanged.
Returns the HTTP status code. Passes through anomaly maps unchanged.
(api-response-status-text resp)Returns the HTTP status text. Passes through anomaly maps unchanged.
Returns the HTTP status text. Passes through anomaly maps unchanged.
(api-response-text resp)Returns the response body as text. Passes through anomaly maps unchanged.
Returns the response body as text. Passes through anomaly maps unchanged.
(api-response-url resp)Returns the response URL. Passes through anomaly maps unchanged.
Returns the response URL. Passes through anomaly maps unchanged.
(context-api ctx)Returns the APIRequestContext for a BrowserContext.
The returned context shares cookies and storage with the browser context. API calls through it appear in Playwright traces automatically.
Params:
ctx - BrowserContext instance.
Returns: APIRequestContext bound to the browser context.
Examples: (core/with-playwright [pw] (core/with-browser [browser (core/launch-chromium pw)] (core/with-context [ctx (core/new-context browser {:base-url "https://api.example.com"})] (let [resp (api-get (context-api ctx) "/users")] (api-response-status resp)))))
Returns the APIRequestContext for a BrowserContext.
The returned context shares cookies and storage with the browser context.
API calls through it appear in Playwright traces automatically.
Params:
`ctx` - BrowserContext instance.
Returns:
APIRequestContext bound to the browser context.
Examples:
(core/with-playwright [pw]
(core/with-browser [browser (core/launch-chromium pw)]
(core/with-context [ctx (core/new-context browser {:base-url "https://api.example.com"})]
(let [resp (api-get (context-api ctx) "/users")]
(api-response-status resp)))))Default retry configuration.
:max-attempts - Total attempts including the first (default: 3). :delay-ms - Initial delay between retries in ms (default: 200). :backoff - Backoff strategy (default: :exponential). :fixed — constant delay. :linear — delay * attempt. :exponential — delay * 2^attempt. :max-delay-ms - Ceiling on delay (default: 10000). :retry-when - Predicate (fn [result] -> truthy to retry). Default: retries on anomalies and 5xx status codes.
Default retry configuration.
:max-attempts - Total attempts including the first (default: 3).
:delay-ms - Initial delay between retries in ms (default: 200).
:backoff - Backoff strategy (default: :exponential).
:fixed — constant delay.
:linear — delay * attempt.
:exponential — delay * 2^attempt.
:max-delay-ms - Ceiling on delay (default: 10000).
:retry-when - Predicate (fn [result] -> truthy to retry).
Default: retries on anomalies and 5xx status codes.(fd-append fd name value)Appends a field to FormData.
Params:
fd - FormData instance.
name - String. Field name.
value - String. Field value.
Returns: FormData instance.
Appends a field to FormData. Params: `fd` - FormData instance. `name` - String. Field name. `value` - String. Field value. Returns: FormData instance.
(fd-set fd name value)Sets a field in FormData.
Params:
fd - FormData instance.
name - String. Field name.
value - String. Field value.
Returns: FormData instance.
Sets a field in FormData. Params: `fd` - FormData instance. `name` - String. Field name. `value` - String. Field value. Returns: FormData instance.
(form-data)Creates a new FormData instance.
Returns: FormData instance.
Creates a new FormData instance. Returns: FormData instance.
(map->form-data m)Converts a Clojure map to FormData.
Params:
m - Map of string->string.
Returns: FormData instance.
Converts a Clojure map to FormData. Params: `m` - Map of string->string. Returns: FormData instance.
(new-api-context api-req)(new-api-context api-req opts)Creates a new APIRequestContext.
Params:
api-req - APIRequest instance (from api-request).
opts - Map, optional. Context options:
:base-url - String. Base URL for all requests.
:extra-http-headers - Map. Headers sent with every request.
:ignore-https-errors - Boolean. Ignore SSL certificate errors.
:timeout - Double. Default timeout in ms (default: 30000).
:user-agent - String. User-Agent header.
:max-redirects - Long. Max redirects to follow (default: 20).
:fail-on-status-code - Boolean. Throw on non-2xx/3xx status.
:storage-state - String. Storage state JSON or path.
Returns: APIRequestContext or anomaly map.
Examples: (new-api-context (api-request pw) {:base-url "https://api.example.com" :extra-http-headers {"Authorization" "Bearer token"} :timeout 10000})
Creates a new APIRequestContext.
Params:
`api-req` - APIRequest instance (from `api-request`).
`opts` - Map, optional. Context options:
:base-url - String. Base URL for all requests.
:extra-http-headers - Map. Headers sent with every request.
:ignore-https-errors - Boolean. Ignore SSL certificate errors.
:timeout - Double. Default timeout in ms (default: 30000).
:user-agent - String. User-Agent header.
:max-redirects - Long. Max redirects to follow (default: 20).
:fail-on-status-code - Boolean. Throw on non-2xx/3xx status.
:storage-state - String. Storage state JSON or path.
Returns:
APIRequestContext or anomaly map.
Examples:
(new-api-context (api-request pw)
{:base-url "https://api.example.com"
:extra-http-headers {"Authorization" "Bearer token"}
:timeout 10000})(page-api pg)Returns the APIRequestContext for a Page.
The returned context shares cookies and storage with the page's browser context. API calls through it appear in Playwright traces automatically.
Params:
pg - Page instance.
Returns: APIRequestContext bound to the page's browser context.
Examples: (core/with-testing-page [pg] (page/navigate pg "https://example.com/login") ;; API calls share the browser session (cookies, auth) (let [resp (api-get (page-api pg) "/api/me")] (api-response-status resp)))
Returns the APIRequestContext for a Page.
The returned context shares cookies and storage with the page's browser
context. API calls through it appear in Playwright traces automatically.
Params:
`pg` - Page instance.
Returns:
APIRequestContext bound to the page's browser context.
Examples:
(core/with-testing-page [pg]
(page/navigate pg "https://example.com/login")
;; API calls share the browser session (cookies, auth)
(let [resp (api-get (page-api pg) "/api/me")]
(api-response-status resp)))(request! pw method url)(request! pw method url opts)Fire-and-forget HTTP request. Creates an ephemeral context, makes the request, reads the full response into a Clojure map, and disposes everything. No context management needed.
Params:
pw - Playwright instance.
method - Keyword. :get :post :put :patch :delete :head.
url - String. Full URL (not relative — no base-url).
opts - Map, optional. See request-options for all keys.
Returns:
Response map (same shape as api-response->map) or anomaly map.
Examples: ;; Simple GET — no setup, no cleanup (request! pw :get "https://api.example.com/health") ;; => {:status 200 :ok? true :body "OK" ...}
;; POST with JSON body (request! pw :post "https://api.example.com/users" {:data "{"name": "Alice"}" :headers {"Content-Type" "application/json" "Authorization" "Bearer token"}})
;; Hit multiple domains without any context setup (let [users (request! pw :get "https://users.example.com/me" {:headers {"Authorization" "Bearer user-token"}}) invoices (request! pw :get "https://billing.example.com/invoices" {:headers {"X-API-Key" "billing-key"}})] [users invoices])
Fire-and-forget HTTP request. Creates an ephemeral context, makes the
request, reads the full response into a Clojure map, and disposes
everything. No context management needed.
Params:
`pw` - Playwright instance.
`method` - Keyword. :get :post :put :patch :delete :head.
`url` - String. Full URL (not relative — no base-url).
`opts` - Map, optional. See `request-options` for all keys.
Returns:
Response map (same shape as `api-response->map`) or anomaly map.
Examples:
;; Simple GET — no setup, no cleanup
(request! pw :get "https://api.example.com/health")
;; => {:status 200 :ok? true :body "OK" ...}
;; POST with JSON body
(request! pw :post "https://api.example.com/users"
{:data "{\"name\": \"Alice\"}"
:headers {"Content-Type" "application/json"
"Authorization" "Bearer token"}})
;; Hit multiple domains without any context setup
(let [users (request! pw :get "https://users.example.com/me"
{:headers {"Authorization" "Bearer user-token"}})
invoices (request! pw :get "https://billing.example.com/invoices"
{:headers {"X-API-Key" "billing-key"}})]
[users invoices])(request-options opts)Creates RequestOptions from a map.
Params:
opts - Map with optional:
:method - String. HTTP method override.
:headers - Map. HTTP headers ({"Authorization" "Bearer ..."}).
:data - String, bytes, or Object. Request body.
Objects are auto-serialized to JSON by Playwright.
:json - Any Clojure data. Encoded via *json-encoder*,
sets Content-Type to application/json automatically.
Mutually exclusive with :data, :form, :multipart.
:form - FormData. URL-encoded form data.
:multipart - FormData. Multipart form data (file uploads).
:timeout - Double. Timeout in ms (default: 30000).
:params - Map. Query parameters.
:max-redirects - Long. Max redirects to follow (default: 20).
:max-retries - Long. Retry network errors (default: 0).
:fail-on-status-code - Boolean. Throw on non-2xx/3xx status.
:ignore-https-errors - Boolean. Ignore SSL certificate errors.
The :json key requires *json-encoder* to be bound:
(binding [api/json-encoder cheshire.core/generate-string] (api/api-post ctx "/users" {:json {:name "Alice" :age 30}}))
Returns: RequestOptions instance.
Creates RequestOptions from a map.
Params:
`opts` - Map with optional:
:method - String. HTTP method override.
:headers - Map. HTTP headers ({"Authorization" "Bearer ..."}).
:data - String, bytes, or Object. Request body.
Objects are auto-serialized to JSON by Playwright.
:json - Any Clojure data. Encoded via `*json-encoder*`,
sets Content-Type to application/json automatically.
Mutually exclusive with :data, :form, :multipart.
:form - FormData. URL-encoded form data.
:multipart - FormData. Multipart form data (file uploads).
:timeout - Double. Timeout in ms (default: 30000).
:params - Map. Query parameters.
:max-redirects - Long. Max redirects to follow (default: 20).
:max-retries - Long. Retry network errors (default: 0).
:fail-on-status-code - Boolean. Throw on non-2xx/3xx status.
:ignore-https-errors - Boolean. Ignore SSL certificate errors.
The :json key requires `*json-encoder*` to be bound:
(binding [api/*json-encoder* cheshire.core/generate-string]
(api/api-post ctx "/users" {:json {:name "Alice" :age 30}}))
Returns:
RequestOptions instance.(retry f)(retry f opts)Execute f (a no-arg function) with retry logic.
Params:
f - No-arg function that returns a result.
opts - Map, optional. Override keys from default-retry-opts:
:max-attempts - Total attempts (default: 3).
:delay-ms - Initial delay in ms (default: 200).
:backoff - :fixed, :linear, or :exponential (default).
:max-delay-ms - Max delay ceiling in ms (default: 10000).
:retry-when - (fn [result]) → truthy to retry.
Returns: The result of the last attempt.
Examples: ;; Retry a flaky endpoint (retry #(api-get ctx "/flaky"))
;; Custom: retry on 429 Too Many Requests with linear backoff (retry #(api-get ctx "/rate-limited") {:max-attempts 5 :delay-ms 1000 :backoff :linear :retry-when (fn [r] (= 429 (:status (api-response->map r))))})
Execute `f` (a no-arg function) with retry logic.
Params:
`f` - No-arg function that returns a result.
`opts` - Map, optional. Override keys from `default-retry-opts`:
:max-attempts - Total attempts (default: 3).
:delay-ms - Initial delay in ms (default: 200).
:backoff - :fixed, :linear, or :exponential (default).
:max-delay-ms - Max delay ceiling in ms (default: 10000).
:retry-when - (fn [result]) → truthy to retry.
Returns:
The result of the last attempt.
Examples:
;; Retry a flaky endpoint
(retry #(api-get ctx "/flaky"))
;; Custom: retry on 429 Too Many Requests with linear backoff
(retry #(api-get ctx "/rate-limited")
{:max-attempts 5
:delay-ms 1000
:backoff :linear
:retry-when (fn [r] (= 429 (:status (api-response->map r))))})(run-with-page-api pg opts f)Functional core of with-page-api. Creates an APIRequestContext from a Page
with custom options (base-url, headers, etc.) while sharing cookies via
storage-state.
Copies storage-state (cookies, localStorage) from the page's browser context, creates a new APIRequestContext with the provided opts, and disposes it after.
Params:
pg - Page instance to extract cookies from.
opts - Map. Options for the new APIRequestContext:
:base-url - String. Base URL for all requests.
:extra-http-headers - Map. Headers sent with every request.
:ignore-https-errors - Boolean.
:json-encoder - Function. Binds *json-encoder* for the body.
f - Function receiving the new APIRequestContext.
Returns:
Result of calling f.
Examples: (run-with-page-api pg {:base-url "https://api.example.com"} (fn [ctx] (api-get ctx "/users")))
Functional core of `with-page-api`. Creates an APIRequestContext from a Page
with custom options (base-url, headers, etc.) while sharing cookies via
storage-state.
Copies storage-state (cookies, localStorage) from the page's browser context,
creates a new APIRequestContext with the provided opts, and disposes it after.
Params:
`pg` - Page instance to extract cookies from.
`opts` - Map. Options for the new APIRequestContext:
:base-url - String. Base URL for all requests.
:extra-http-headers - Map. Headers sent with every request.
:ignore-https-errors - Boolean.
:json-encoder - Function. Binds `*json-encoder*` for the body.
`f` - Function receiving the new APIRequestContext.
Returns:
Result of calling `f`.
Examples:
(run-with-page-api pg {:base-url "https://api.example.com"}
(fn [ctx]
(api-get ctx "/users")))(run-with-testing-api opts f)Functional core of with-testing-api. Sets up a complete Playwright stack
for API testing and calls (f api-request-context).
Creates: Playwright → Browser (headless Chromium) → BrowserContext → APIRequestContext.
The APIRequestContext comes from BrowserContext.request(), so all API calls
share cookies with the context and appear in Playwright traces.
When the Allure reporter is active, automatically enables tracing (DOM snapshots + sources) and HAR recording — zero configuration.
Opts:
:base-url - String. Base URL for all requests.
:extra-http-headers - Map. Headers sent with every request.
:ignore-https-errors - Boolean. Ignore SSL certificate errors.
:json-encoder - Function. Binds *json-encoder* for the body.
:storage-state - String. Storage state JSON or path.
core/new-context (:locale, :timezone-id, etc.)Examples: (run-with-testing-api {:base-url "https://api.example.com"} (fn [ctx] (api-get ctx "/users")))
Functional core of `with-testing-api`. Sets up a complete Playwright stack
for API testing and calls `(f api-request-context)`.
Creates: Playwright → Browser (headless Chromium) → BrowserContext → APIRequestContext.
The APIRequestContext comes from `BrowserContext.request()`, so all API calls
share cookies with the context and appear in Playwright traces.
When the Allure reporter is active, automatically enables tracing
(DOM snapshots + sources) and HAR recording — zero configuration.
Opts:
:base-url - String. Base URL for all requests.
:extra-http-headers - Map. Headers sent with every request.
:ignore-https-errors - Boolean. Ignore SSL certificate errors.
:json-encoder - Function. Binds `*json-encoder*` for the body.
:storage-state - String. Storage state JSON or path.
+ any key accepted by `core/new-context` (:locale, :timezone-id, etc.)
Examples:
(run-with-testing-api {:base-url "https://api.example.com"}
(fn [ctx]
(api-get ctx "/users")))(with-api-context [sym expr] & body)Binds a single APIRequestContext and ensures disposal.
Usage: (with-api-context [ctx (new-api-context (api-request pw) {:base-url "https://api.example.com"})] (api-get ctx "/users"))
Binds a single APIRequestContext and ensures disposal.
Usage:
(with-api-context [ctx (new-api-context (api-request pw) {:base-url "https://api.example.com"})]
(api-get ctx "/users"))(with-api-contexts bindings & body)Binds multiple APIRequestContexts and disposes all on exit.
Same shape as with-open — flat pairs of [name expr].
Usage: (with-api-contexts [users (new-api-context (api-request pw) {:base-url "https://users.example.com" :extra-http-headers {"Authorization" "Bearer token"}}) billing (new-api-context (api-request pw) {:base-url "https://billing.example.com" :extra-http-headers {"X-API-Key" "key"}}) public (new-api-context (api-request pw) {:base-url "https://public.example.com"})] (api-get users "/me") (api-get billing "/invoices") (api-get public "/catalog"))
Binds multiple APIRequestContexts and disposes all on exit.
Same shape as `with-open` — flat pairs of [name expr].
Usage:
(with-api-contexts [users (new-api-context (api-request pw)
{:base-url "https://users.example.com"
:extra-http-headers {"Authorization" "Bearer token"}})
billing (new-api-context (api-request pw)
{:base-url "https://billing.example.com"
:extra-http-headers {"X-API-Key" "key"}})
public (new-api-context (api-request pw)
{:base-url "https://public.example.com"})]
(api-get users "/me")
(api-get billing "/invoices")
(api-get public "/catalog"))(with-hooks hooks & body)Execute body with the given hooks merged into *hooks*.
Merges with (not replaces) any existing hooks so outer bindings are preserved for keys you don't override.
Usage: (with-hooks {:on-request (fn [m url opts] (log/info m url) opts) :on-response (fn [m url resp] (metrics/inc! :api-calls) resp)} (api-get ctx "/users") (api-post ctx "/users" {:json {:name "Alice"}}))
Execute body with the given hooks merged into `*hooks*`.
Merges with (not replaces) any existing hooks so outer bindings
are preserved for keys you don't override.
Usage:
(with-hooks {:on-request (fn [m url opts] (log/info m url) opts)
:on-response (fn [m url resp] (metrics/inc! :api-calls) resp)}
(api-get ctx "/users")
(api-post ctx "/users" {:json {:name "Alice"}}))(with-page-api pg opts binding-vec & body)Create an APIRequestContext from a Page with custom options.
Copies cookies from the page's browser context (via storage-state), creates a new APIRequestContext with the provided options, and disposes it after.
This lets you use a custom base-url while still sharing the browser's login session / cookies.
Params:
pg - Page instance (shares cookies from its browser context).
opts - Map of options for the new context:
:base-url - String. Base URL for all requests.
:extra-http-headers - Map. Headers sent with every request.
:ignore-https-errors - Boolean.
:json-encoder - Function. Binds *json-encoder* for the body.
Usage: ;; API calls to different domain, sharing browser cookies (with-testing-page [pg] (page/navigate pg "https://example.com/login") ;; ... login via UI ... (with-page-api pg {:base-url "https://api.example.com"} [ctx] ;; ctx has cookies from the browser session (api-get ctx "/me")))
;; With JSON encoder (with-page-api pg {:base-url "https://api.example.com" :json-encoder cheshire.core/generate-string} [ctx] (api-post ctx "/users" {:json {:name "Alice"}}))
Create an APIRequestContext from a Page with custom options.
Copies cookies from the page's browser context (via storage-state), creates
a new APIRequestContext with the provided options, and disposes it after.
This lets you use a custom base-url while still sharing the browser's
login session / cookies.
Params:
`pg` - Page instance (shares cookies from its browser context).
`opts` - Map of options for the new context:
:base-url - String. Base URL for all requests.
:extra-http-headers - Map. Headers sent with every request.
:ignore-https-errors - Boolean.
:json-encoder - Function. Binds `*json-encoder*` for the body.
Usage:
;; API calls to different domain, sharing browser cookies
(with-testing-page [pg]
(page/navigate pg "https://example.com/login")
;; ... login via UI ...
(with-page-api pg {:base-url "https://api.example.com"} [ctx]
;; ctx has cookies from the browser session
(api-get ctx "/me")))
;; With JSON encoder
(with-page-api pg {:base-url "https://api.example.com"
:json-encoder cheshire.core/generate-string} [ctx]
(api-post ctx "/users" {:json {:name "Alice"}}))(with-retry opts-or-body & body)Execute body with retry logic.
Usage: ;; Default: 3 attempts, exponential backoff, retry on anomalies + 5xx (with-retry {} (api-get ctx "/flaky-endpoint"))
;; Custom retry config (with-retry {:max-attempts 5 :delay-ms 500 :backoff :linear :retry-when (fn [r] (and (map? r) (>= (:status r) 500)))} (api-post ctx "/idempotent-endpoint" {:json {:action "process"}}))
;; Retry standalone requests too (with-retry {:max-attempts 3} (request! pw :get "https://api.example.com/health"))
Execute body with retry logic.
Usage:
;; Default: 3 attempts, exponential backoff, retry on anomalies + 5xx
(with-retry {}
(api-get ctx "/flaky-endpoint"))
;; Custom retry config
(with-retry {:max-attempts 5
:delay-ms 500
:backoff :linear
:retry-when (fn [r] (and (map? r) (>= (:status r) 500)))}
(api-post ctx "/idempotent-endpoint"
{:json {:action "process"}}))
;; Retry standalone requests too
(with-retry {:max-attempts 3}
(request! pw :get "https://api.example.com/health"))(with-testing-api opts-or-binding & args)All-in-one macro for API testing with automatic resource management.
Creates a complete Playwright stack (playwright → browser → context),
extracts the context-bound APIRequestContext, binds it to sym,
executes body, and tears everything down.
The APIRequestContext comes from BrowserContext.request(), so all API
calls share cookies with the context and appear in Playwright traces.
When the Allure reporter is active, tracing (DOM snapshots + sources) and HAR recording are enabled automatically — zero configuration.
Opts (an optional map expression, evaluated at runtime):
:base-url - String. Base URL for all requests.
:extra-http-headers - Map. Headers sent with every request.
:ignore-https-errors - Boolean. Ignore SSL certificate errors.
:json-encoder - Function. Binds *json-encoder* for the body.
:storage-state - String. Storage state JSON or path.
core/new-context (:locale, :timezone-id, etc.)Usage: ;; Minimal — hit a base URL (with-testing-api {:base-url "https://api.example.com"} [ctx] (api-get ctx "/users"))
;; With custom headers and JSON encoder (with-testing-api {:base-url "https://api.example.com" :extra-http-headers {"Authorization" "Bearer token"} :json-encoder cheshire.core/generate-string} [ctx] (api-post ctx "/users" {:json {:name "Alice"}}))
;; Minimal — no opts (with-testing-api [ctx] (api-get ctx "https://api.example.com/health"))
All-in-one macro for API testing with automatic resource management.
Creates a complete Playwright stack (playwright → browser → context),
extracts the context-bound APIRequestContext, binds it to `sym`,
executes body, and tears everything down.
The APIRequestContext comes from `BrowserContext.request()`, so all API
calls share cookies with the context and appear in Playwright traces.
When the Allure reporter is active, tracing (DOM snapshots + sources)
and HAR recording are enabled automatically — zero configuration.
Opts (an optional map expression, evaluated at runtime):
:base-url - String. Base URL for all requests.
:extra-http-headers - Map. Headers sent with every request.
:ignore-https-errors - Boolean. Ignore SSL certificate errors.
:json-encoder - Function. Binds `*json-encoder*` for the body.
:storage-state - String. Storage state JSON or path.
+ any key accepted by `core/new-context` (:locale, :timezone-id, etc.)
Usage:
;; Minimal — hit a base URL
(with-testing-api {:base-url "https://api.example.com"} [ctx]
(api-get ctx "/users"))
;; With custom headers and JSON encoder
(with-testing-api {:base-url "https://api.example.com"
:extra-http-headers {"Authorization" "Bearer token"}
:json-encoder cheshire.core/generate-string} [ctx]
(api-post ctx "/users" {:json {:name "Alice"}}))
;; Minimal — no opts
(with-testing-api [ctx]
(api-get ctx "https://api.example.com/health"))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 |