Skip to content

HTTP, cassette, and browser API

StableSince v0.1.0Requires playwright

Use this when you need the member list for a page or HTTP fixture, the cassette mode semantics, or the serializer contract behind /vcr. The task guides are HTTP mocking and test servers, Recording HTTP cassettes, and DOM and browser testing.

Browser fixtures and the browser webPage backend are experimental; the server, mock, and cassette surfaces are stable. playwright is optional and loaded only when a requested fixture needs it.

webPage: one fixture, two backends

page always uses happy-dom, and browserPage always uses Playwright. Their meaning never changes. To write a test that runs on either, request webPage and choose the backend with BUN_TEST_UTILS_WEB_ENV: dom (the default) or browser (or playwright).

Partial — one step of a longer file or workflow — surrounding lines are omitted

test("renders the button", async ({ webPage }) => {
await webPage.setContent(`<button id="save">Save</button>`);
await webPage.click("#save");
expect(webPage.mode).toMatch(/^(dom|browser)$/);
});
MemberBehaviour
mode"dom" or "browser": the backend this test runs on.
goto(url)Loads a page. On the DOM backend, it fetches the URL and sets the body.
setContent(html) / mount(html)Replaces the page’s content.
click(selector) / type(selector, text)Interacts with an element.
textContent(selector) / html()Reads the page.
evaluate(fn)Runs fn in the page on the browser backend. On the DOM backend, fn runs with this set to the happy-dom window.
rawThe underlying happy-dom window or Playwright Page. Use it only for backend-specific assertions.

Write portable tests with the helper methods; reach for raw, mode, or browserPage only when a test is deliberately backend-specific.

httpMock: fetch handlers

httpMock patches fetch for the current test. It exposes an MSW-style API: a method per HTTP verb plus use for anything else, and a matcher that is a string, a RegExp, or a predicate.

Partial — one step of a longer file or workflow — surrounding lines are omitted

test("loads mocked data", async ({ httpMock }) => {
httpMock.get("/api/user", () => Response.json({ name: "Ada" }));
httpMock.post(/\/api\/events$/, async (request) =>
Response.json({ received: await request.json() }),
);
expect(await fetch("https://app.test/api/user").then((r) => r.json())).toEqual({
name: "Ada",
});
expect(httpMock.calls()[0]).toMatchObject({ method: "GET", handled: true });
httpMock.reset();
});
MemberBehaviour
get, post, put, patch, delete, head, options, useRegisters a responder. Return undefined from a responder to pass the request through.
passthrough(matcher?)Lets matching requests reach the network.
reset()Clears the handlers. Use it between phases of one test.
calls()Lists every intercepted request: method, url, request, and whether a handler matched (handled).
install(page)Installs the same handlers on a Playwright page or context. Resolves to a cleanup function.

Matchers. A string that starts with http://, https://, or data: is compared against the whole URL. Any other string matches the pathname (or a URL suffix). A RegExp is tested against the whole URL. A function receives a clone of the Request.

Ordering. Handlers are consulted in registration order; the first match answers. The fixture built last wraps the others around globalThis.fetch, so listing httpMock after networkGuard in a test parameter makes mocks win. See combining them.

testServer and serverUrl

MemberBehaviour
testServer.urlOrigin of the ephemeral server, e.g. http://localhost:35833. Changes per test.
testServer.portThe bound port.
testServer.serverThe Bun Server object.
testServer.handle(fn)Replaces the request handler: (req: Request) => Response | Promise<Response>.
serverUrlThe same string as testServer.url, for fixtures that depend on the origin without the server object.

The server starts on a random free port and is stopped at teardown, so parallel test files cannot collide.

cassette: callbacks and HTTP recordings

MemberBehaviour
cassette(callback)Get-or-record callback wrapper. Local auto replays a hit and refreshes only a missing source with a visible warning; CI, explicit replay, ambiguity, and serializer errors stay strict.
record(callback)Runs the callback once and stores its serializable result. Returns the result.
replay(callback)Returns the stored result without running the callback.
addSerializer(serializer)Registers a reversible, versioned serializer for this test’s callback values.
redactHeader(name)Redacts the named request header in recordings. Available, but outside the stable contract.
mode, setMode(mode)The mode for this test: auto, record, replay, or passthrough. A mode explicitly set to replay keeps the callable strict.
ModeMissing cassetteExisting cassette
auto (default)Records, unless CI is setReplays
recordRecords and overwrites at end of runOverwrites at end of run
replayFails with CASSETTE_NOT_FOUNDReplays
passthroughGoes to the networkGoes to the network

Storage. HTTP entries live in __cassettes__/<test-slug>.json next to the test file; callback results live in the sibling __cassettes__/<test-slug>.callbacks.json. Snapshot baselines live in __snapshots__/<test-slug>.snap.json. The locations are fixed in 0.1.x: there is no base-directory override.

HTTP matching. The uppercase request method and the full URL must both match exactly. Partial URLs, regular expressions, request bodies, and custom predicates are not part of the stable contract. A request missing from an existing cassette fails with CASSETTE_MISMATCH; it is never recorded silently.

Callback identity. Within a run, a callback is identified by its object; across runs, by its source text. Two closures from one factory are separate callbacks. Captured values are not part of the identity, which is why hidden inputs can require an explicit VCR_MODE=record.

/vcr: global callback serializers

The helper-only /vcr subpath exports no fixture. Use it from a preload when a serializer should apply to every cassette in a Bun process.

ExportBehaviour
defineCallbackSerializer(serializer)Returns a reusable reversible serializer definition unchanged. Its name and positive integer version are stored in recordings.
registerCallbackSerializer(serializer)Validates, registers, and returns the same object. Registrations persist for the process and are shared between the /vcr preload bundle and the root cassette bundle.
unregisterCallbackSerializer(serializer)Removes every registration of that exact object. Returns true if it removed one or more, otherwise false.
CallbackSerializer<T>The { name, version, test, serialize, deserialize } contract for a callback value shape.

Selection order: newest fixture-local cassette.addSerializer registration, then newest global registration, then built-ins. Exact (name, version) decoding is required at every level: a same-name, different-version serializer causes CALLBACK_SERIALIZER_NOT_FOUND rather than an automatic refresh.

Browser fixtures

Experimental; requires the playwright peer and an installed Chromium build.

FixtureScopeProvides
browsersessionThe launched headless Chromium.
browserContexttestA Playwright BrowserContext.
browserPagetestA Playwright Page.
browserHttpMocktesthttpMock handlers installed on the context.
webPage with BUN_TEST_UTILS_WEB_ENV=browsertestThe portable page API on a real page.

Launch is headless-only and Chromium-only; there is no headed mode. A launch failure raises FIXTURE_SETUP_FAILED with the underlying error as cause. Recovery steps are in DOM and browser testing and Browser tests in CI.

Errors

CodeRaised when
CASSETTE_NOT_FOUNDReplay mode found no cassette for the test.
CASSETTE_MISMATCHA request is missing from an existing cassette. The message names the command that clears it.
CALLBACK_AMBIGUOUSOne callback source text was recorded from more than one closure.
CALLBACK_NOT_RECORDEDA callback’s source text has no recording.
CALLBACK_NOT_SERIALIZABLEA callback returned a value that is not plain data and that no serializer handles.
CALLBACK_SERIALIZER_NOT_FOUNDA recording needs a serializer that is not registered.
CALLBACK_SERIALIZER_FAILEDA serializer threw. The original error is the cause.
CALLBACK_STORE_INVALIDA callback sidecar file is corrupt.
FIXTURE_SETUP_FAILEDA browser could not launch. The original error is the cause.
MISSING_OPTIONAL_DEPENDENCYplaywright is not installed. The message names the install command.

Docs for bun-test-utils v0.1.0 Release notes Report a docs issue