Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
title: Run Vitest Browser Mode tests on Browser Run
description: Use the Browser Run Vitest provider to run Vitest Browser Mode test suites in hosted Chromium over CDP.
products:
- browser-run
date: 2026-07-03
---

You can now run [Vitest Browser Mode](https://vitest.dev/guide/browser/) test suites on [Browser Run](/browser-run/) hosted Chromium using `@cloudflare/vitest-browser-run-provider`. The provider connects Vitest's Playwright browser provider to Browser Run over the [Chrome DevTools Protocol (CDP)](/browser-run/cdp/), so browser tests can run against a managed Chromium environment from local development or CI.

Use this for tests that need browser APIs, user interactions, screenshots, or visual regression. For tests that need Workers runtime APIs or bindings, continue to use the [Workers Vitest integration](/workers/testing/vitest-integration/).

Here is a minimal Browser Run provider configuration:

```ts title="vitest.browser-run.config.ts"
import { cloudflare } from "@cloudflare/vite-plugin";
import { browserRunCdp } from "@cloudflare/vitest-browser-run-provider";
import { defineConfig } from "vitest/config";

export default defineConfig({
plugins: [cloudflare({ tunnel: { autoStart: true } })],
test: {
browser: {
enabled: true,
headless: true,
provider: browserRunCdp(),
instances: [{ browser: "chromium" }],
},
},
});
```

After configuring the provider, you can write Vitest Browser Mode tests that run in Browser Run hosted Chromium. For example, the following test captures and compares a reference screenshot:

```ts title="test/browser/hero.browser.test.ts"
import { expect, test } from "vitest";
import { page } from "vitest/browser";

test("hero section", async () => {
document.body.innerHTML = `
<main>
<section data-testid="hero">
<h1>Build with Cloudflare</h1>
</section>
</main>
`;

await expect.element(page.getByTestId("hero")).toMatchScreenshot("hero");
});
```

Before running tests, set `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN`. The token needs `Browser Rendering - Edit` permission.

To get started, refer to [Using with Vitest Browser Mode (CDP)](/browser-run/cdp/vitest-browser-mode/).
6 changes: 3 additions & 3 deletions src/content/docs/browser-run/cdp/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Render } from "~/components";

The `/devtools` endpoints provide session management capabilities that follow the [Chrome DevTools Protocol (CDP)](https://chromedevtools.github.io/devtools-protocol/). These endpoints allow you to create persistent browser sessions, manage multiple tabs, and interact with browsers using CDP commands. This is useful for advanced automation, debugging, and remote browser control.

CDP endpoints can be accessed from any environment that supports WebSocket connections, including local development machines, external servers, and CI/CD pipelines. This means you can connect to Browser Run from Node.js scripts, Puppeteer, Playwright, or any CDP-compatible client.
CDP endpoints can be accessed from any environment that supports WebSocket connections, including local development machines, external servers, and CI/CD pipelines. This means you can connect to Browser Run from Node.js scripts, Puppeteer, Playwright, Vitest Browser Mode, or any CDP-compatible client.

<Render file="cdp-api-token-permissions" product="browser-run" />

Expand All @@ -29,15 +29,15 @@ The browser sessions endpoints enable you to:
- **Open, close, and list browser tabs (targets)** — Manage multiple debuggable targets (pages, iframes, etc.) within a single browser instance
- **Connect via WebSocket to send CDP commands** — Automate browser actions programmatically
- **View live browser sessions using Chrome DevTools UI** — Debug and inspect remote browser sessions visually
- **Integrate with existing CDP clients** — Use standard CDP clients like Puppeteer or custom WebSocket implementations
- **Integrate with existing CDP clients** — Use standard CDP clients like Puppeteer, Vitest Browser Mode, or custom WebSocket implementations

## How it works

Once you acquire a browser session, you can interact with it in two ways:

### CDP over WebSocket

Connect to the WebSocket endpoint `/devtools/browser` to acquire a session and send [CDP commands](https://chromedevtools.github.io/devtools-protocol/) directly over the connection. This is the standard way to use CDP and works with any CDP client, including [Puppeteer](/browser-run/cdp/puppeteer/), [Playwright](/browser-run/cdp/playwright/), and [MCP clients](/browser-run/cdp/mcp-clients/).
Connect to the WebSocket endpoint `/devtools/browser` to acquire a session and send [CDP commands](https://chromedevtools.github.io/devtools-protocol/) directly over the connection. This is the standard way to use CDP and works with any CDP client, including [Puppeteer](/browser-run/cdp/puppeteer/), [Playwright](/browser-run/cdp/playwright/), [Vitest Browser Mode](/browser-run/cdp/vitest-browser-mode/), and [MCP clients](/browser-run/cdp/mcp-clients/).

### HTTP API

Expand Down
272 changes: 272 additions & 0 deletions src/content/docs/browser-run/cdp/vitest-browser-mode.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
---
pcx_content_type: how-to
title: Use Vitest Browser Mode (CDP)
description: Run Vitest Browser Mode tests on Browser Run using hosted Chromium over the Chrome DevTools Protocol.
sidebar:
order: 5
label: Vitest Browser Mode
products:
- browser-run
---

import { PackageManagers, Render, TypeScriptExample } from "~/components";

Use [Vitest Browser Mode](https://vitest.dev/guide/browser/) with Browser Run to run browser tests, user interaction tests, and visual regression tests in a hosted Chromium browser. The `@cloudflare/vitest-browser-run-provider` package connects Vitest to Browser Run through the Chrome DevTools Protocol (CDP).

<Render file="cdp-api-token-permissions" product="browser-run" />

## Prerequisites

- A Vite project that uses [Vitest Browser Mode](https://vitest.dev/guide/browser/)
- A Cloudflare account with Browser Run enabled
- A Browser Run API token with `Browser Rendering - Edit` permission
- The [Cloudflare Vite plugin](/workers/vite-plugin/), if Browser Run needs to reach a local Vite dev server

## Install packages

Install Vitest Browser Mode, the Playwright browser provider, the Browser Run provider, and the Cloudflare Vite plugin:

<PackageManagers
pkg="vitest @vitest/browser @vitest/browser-playwright @cloudflare/vitest-browser-run-provider @cloudflare/vite-plugin playwright@npm:playwright-core"
dev
/>

## Configure credentials

Set the Browser Run credentials in your shell, CI environment, or a `.env` file that is ignored by Git. By default, `browserRunCdp()` reads `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN`:

```txt title=".env"
CLOUDFLARE_ACCOUNT_ID="<ACCOUNT_ID>"
CLOUDFLARE_API_TOKEN="<API_TOKEN>"
CLOUDFLARE_BROWSER_RUN_CONCURRENCY="8"
```

`CLOUDFLARE_BROWSER_RUN_CONCURRENCY` controls how many Vitest browser test files can run at the same time. It does not create that many Browser Run browser instances.

## Configure Vitest

Create a Vitest config for Browser Run tests:

<TypeScriptExample filename="vitest.browser-run.config.ts">

```ts
import { cloudflare } from "@cloudflare/vite-plugin";
import { browserRunCdp } from "@cloudflare/vitest-browser-run-provider";
import { defineConfig } from "vitest/config";

const browserApiHost = process.env.VITEST_BROWSER_API_HOST ?? "0.0.0.0";
const browserApiPort = Number(process.env.VITEST_BROWSER_API_PORT ?? "63315");
const browserRunConcurrency = Number(
process.env.CLOUDFLARE_BROWSER_RUN_CONCURRENCY ?? "8",
);

export default defineConfig({
plugins: [
cloudflare({
tunnel: { autoStart: true },
}),
],
server: {
host: browserApiHost,
port: browserApiPort,
strictPort: true,
allowedHosts: true,
},
test: {
include: ["test/browser/**/*.browser.test.ts"],
fileParallelism: true,
maxWorkers: browserRunConcurrency,
browser: {
enabled: true,
headless: true,
fileParallelism: true,
provider: browserRunCdp(),
api: {
host: browserApiHost,
port: browserApiPort,
allowExec: true,
allowWrite: true,
},
expect: {
toMatchScreenshot: {
comparatorName: "pixelmatch",
comparatorOptions: {
threshold: 0.2,
allowedMismatchedPixelRatio: 0.005,
},
},
},
instances: [
{
browser: "chromium",
viewport: { width: 1280, height: 800 },
},
],
},
},
});
```

</TypeScriptExample>

The Cloudflare Vite plugin starts a tunnel and sets `CLOUDFLARE_TUNNEL_URL`. The Browser Run provider uses that public origin so hosted Chromium can reach the local Vitest browser API.

If you expose the Vitest browser API with a different tunnel, set `VITEST_BROWSER_PUBLIC_ORIGIN` to its public origin.

This configuration sets `test.browser.api.allowExec` and `test.browser.api.allowWrite` to `true` so browser tests can use APIs such as `cdp()` and write screenshot artifacts. If your tests do not use `cdp()` or write artifacts, remove the corresponding option.

By default, `tunnel: { autoStart: true }` creates a short-lived [Quick Tunnel](/tunnel/setup/#quick-tunnels-development). Anyone with the tunnel URL can reach your dev server while the tunnel is active. Use this mode for local runs only after you review what the dev server exposes. For more information, refer to [Share a local dev server](/workers/local-development/local-dev-tunnels/#security-considerations).

For a stable hostname or stricter access control, use a [named tunnel](/tunnel/setup/#create-a-tunnel) with the Cloudflare Vite plugin:

<TypeScriptExample filename="vitest.browser-run.config.ts">

```ts
cloudflare({
tunnel: {
name: "vitest-browser-run",
autoStart: true,
},
}),
```

</TypeScriptExample>

You can protect the named tunnel hostname by adding a [self-hosted Access application](/cloudflare-one/access-controls/applications/http-apps/self-hosted-public-app/). For non-interactive test runs, use a [Service Auth policy](/cloudflare-one/access-controls/policies/#service-auth) and an [Access service token](/cloudflare-one/access-controls/service-credentials/service-tokens/). Browser Run must be able to load the Vitest browser runner through that Access policy; service tokens authenticate HTTP requests with the `CF-Access-Client-Id` and `CF-Access-Client-Secret` headers.

The `test.browser.expect.toMatchScreenshot` block configures shared defaults for visual regression assertions. The fixed viewport keeps screenshots the same size across local and CI runs.

## Write a browser test

Create a browser test file under the configured `test/browser/` directory:

```ts title="test/browser/greeting.browser.test.ts"
import { describe, expect, it } from "vitest";
import { cdp, userEvent } from "vitest/browser";

describe("Browser Run Vitest test", () => {
it("runs in hosted Chromium", async () => {
const userAgent = (await cdp().send("Runtime.evaluate", {
expression: "navigator.userAgent",
returnByValue: true,
})) as { result: { value: string } };

expect(userAgent.result.value).toContain("Chrome");

document.body.innerHTML = `
<main>
<label for="name">Name</label>
<input id="name" />
<button type="button">Greet</button>
<p data-testid="greeting"></p>
</main>
`;

const input = document.querySelector<HTMLInputElement>("#name");
const button = document.querySelector<HTMLButtonElement>("button");
const greeting = document.querySelector<HTMLElement>(
"[data-testid='greeting']",
);

expect(input).not.toBeNull();
expect(button).not.toBeNull();
expect(greeting).not.toBeNull();

button!.addEventListener("click", () => {
greeting!.textContent = `Hello, ${input!.value}!`;
});

await userEvent.type(input!, "Cloudflare");
await userEvent.click(button!);

expect(greeting!.textContent).toBe("Hello, Cloudflare!");
});
});
```

## Add visual regression tests

Use `expect.element(...).toMatchScreenshot()` to compare a page element against a reference screenshot:

```ts title="test/browser/hero.browser.test.ts"
import { expect, test } from "vitest";
import { page } from "vitest/browser";

test("hero section", async () => {
document.body.innerHTML = `
<main>
<section data-testid="hero">
<h1>Build with Cloudflare</h1>
<p>Run browser tests in hosted Chromium.</p>
</section>
</main>
`;

await expect
.element(page.getByTestId("hero"))
.toMatchScreenshot("hero-section");
});
```

When you run a visual test for the first time, Vitest creates a reference screenshot in a `__screenshots__` directory and fails the test. Review the screenshot, commit it, and run the test again.

To intentionally update reference screenshots, run Vitest with `--update`:

<PackageManagers
type="exec"
pkg="vitest"
args="run --config vitest.browser-run.config.ts --update"
/>

Review updated screenshots before committing them. Keep `test.browser.api.allowWrite` set to `true` when tests need to create or update screenshots.

## Run tests

Run Vitest with the Browser Run config:

<PackageManagers
type="exec"
pkg="vitest"
args="run --config vitest.browser-run.config.ts"
/>

To run fewer test files at the same time, lower `CLOUDFLARE_BROWSER_RUN_CONCURRENCY`:

<PackageManagers
type="exec"
pkg="vitest"
args="run --config vitest.browser-run.config.ts"
prefix="CLOUDFLARE_BROWSER_RUN_CONCURRENCY=2"
/>

## Parallelism model

Vitest schedules browser test files across `test.maxWorkers`. The Browser Run provider connects to one hosted Chromium browser and opens isolated pages or contexts for Vitest browser sessions.

This means `CLOUDFLARE_BROWSER_RUN_CONCURRENCY=8` runs up to eight browser test files at once inside one shared Browser Run browser. It does not launch eight separate Browser Run browsers.

If your tests need separate browser processes for isolation, run separate Vitest invocations or lower concurrency. Each new Browser Run browser is subject to [Browser Run limits](/browser-run/limits/).

## Configure provider options

`browserRunCdp()` reads credentials and Browser Run options from environment variables. You can also pass options directly to the provider, but avoid hardcoding secrets in committed files.

| Option | Environment variable | Description |
| -------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `accountId` | `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account ID used to create the Browser Run URL |
| `apiToken` | `CLOUDFLARE_API_TOKEN` | API token used in the Browser Run authorization header |
| `publicOrigin` | `VITEST_BROWSER_PUBLIC_ORIGIN` or `CLOUDFLARE_TUNNEL_URL` | Public origin for the Vitest browser API |
| `wsEndpoint` | `CLOUDFLARE_BROWSER_RUN_WS_ENDPOINT` | Custom Browser Run CDP WebSocket endpoint |
| `keepAliveMs` | `CLOUDFLARE_BROWSER_RUN_KEEP_ALIVE_MS` | Browser inactivity timeout in milliseconds. Defaults to `600000` |
| `recording` | `CLOUDFLARE_BROWSER_RUN_RECORDING` | Set to `true` to enable [session recording](/browser-run/features/session-recording/) |

For example:

```ts title="vitest.browser-run.config.ts"
provider: browserRunCdp({
keepAliveMs: 600000,
recording: true,
}),
```

<Render file="faq" product="browser-run" />
5 changes: 5 additions & 0 deletions src/content/docs/browser-run/examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ Use [CDP](/browser-run/cdp/) to connect to Browser Run from any environment usin
description="Run Puppeteer scripts against Browser Run from Node.js on your local machine, CI/CD, or any external server."
href="/browser-run/cdp/puppeteer/"
/>
<LinkCard
title="Run Vitest Browser Mode tests"
description="Run browser tests and visual regression tests in Browser Run hosted Chromium."
href="/browser-run/cdp/vitest-browser-mode/"
/>
<LinkCard
title="Configure AI agents with MCP"
description="Set up Claude Desktop, Claude Code, Cursor, or other MCP clients to control browsers via Browser Run."
Expand Down
2 changes: 2 additions & 0 deletions src/content/docs/browser-run/playwright/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ export default {
};
```

To run Vitest Browser Mode tests against Browser Run hosted Chromium, refer to [Use Vitest Browser Mode (CDP)](/browser-run/cdp/vitest-browser-mode/).

### Storage state

Playwright supports [storage state](https://playwright.dev/docs/api/class-browsercontext#browsercontext-storage-state) to obtain and persist cookies and other storage data. In this example, you will use storage state to persist cookies and other storage data in [Workers KV](/kv).
Expand Down
2 changes: 2 additions & 0 deletions src/content/docs/workers/testing/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { Render, LinkButton } from "~/components";

The Workers platform has a variety of ways to test your applications, depending on your requirements. We recommend using the [Vitest integration](/workers/testing/vitest-integration), which allows you to run tests _inside_ the Workers runtime, and unit test individual functions within your Worker.

For browser tests that need DOM APIs, user interactions, screenshots, or visual regression in a real browser, use [Vitest Browser Mode with Browser Run](/browser-run/cdp/vitest-browser-mode/). That setup runs tests in hosted Chromium instead of inside the Workers runtime.

<LinkButton href="/workers/testing/vitest-integration/write-your-first-test/">
Get started with Vitest
</LinkButton>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
pcx_content_type: navigation
title: Browser Mode with Browser Run
description: Run Vitest Browser Mode tests in hosted Chromium with Browser Run.
external_link: /browser-run/cdp/vitest-browser-mode/
sidebar:
order: 3.5
products:
- workers
---
Loading