# SwiftBrowse > SwiftBrowse is the AI browser for browser automation and end-to-end testing: a native engine (Rust, driving Chromium over the DevTools Protocol) with one contract across a JSON test runner (`swiftbrowse test`), TypeScript and Python SDKs, and an MCP server for AI agents. Version 0.1.2. Install with `npm install swiftbrowse` or `pip install swiftbrowse`, then `npx playwright install chromium chromium-headless-shell` for a browser. The complete text of every page follows the index below; it is also published as one Markdown file at https://steady-ember-82q7.here.now/docs.md. ## Pages ### Start - [Overview](https://steady-ember-82q7.here.now/index.html) - [Getting started](https://steady-ember-82q7.here.now/getting-started.html) ### Writing tests - [Test files](https://steady-ember-82q7.here.now/writing-tests.html) - [Targets and locators](https://steady-ember-82q7.here.now/locators.html) - [Assertions](https://steady-ember-82q7.here.now/assertions.html) - [Network mocking](https://steady-ember-82q7.here.now/network.html) - [Saved logins](https://steady-ember-82q7.here.now/auth.html) - [Tabs, frames and uploads](https://steady-ember-82q7.here.now/tabs-frames-uploads.html) ### Running tests - [The test runner](https://steady-ember-82q7.here.now/test-runner.html) - [Traces, video and reports](https://steady-ember-82q7.here.now/debugging.html) - [Continuous integration](https://steady-ember-82q7.here.now/ci.html) ### Tests in code - [TypeScript and JavaScript](https://steady-ember-82q7.here.now/typescript.html) - [Python](https://steady-ember-82q7.here.now/python.html) ### AI agents - [MCP server](https://steady-ember-82q7.here.now/mcp.html) ### Reference - [Operations](https://steady-ember-82q7.here.now/operations.html) - [Command line](https://steady-ember-82q7.here.now/cli.html) - [Results and errors](https://steady-ember-82q7.here.now/results.html) - [Configuration and security](https://steady-ember-82q7.here.now/configuration.html) ### More - [Benchmarks](https://steady-ember-82q7.here.now/benchmarks.html) - [Limitations and roadmap](https://steady-ember-82q7.here.now/limitations.html) ## Full documentation ## SwiftBrowse The AI browser for browser automation and end-to-end testing. A native engine (Rust, driving Chromium over the DevTools Protocol) with one contract across a test runner, TypeScript and Python SDKs, and an MCP server for AI agents. ```sh npm install -g swiftbrowse # or: pip install swiftbrowse npx playwright install chromium chromium-headless-shell swiftbrowse doctor ``` ### What it is SwiftBrowse does one job three ways, with the same operations and the same results everywhere: - **An end-to-end test runner.** `swiftbrowse test` runs tests written as JSON files: parallel workers, retries and flaky detection, sharding, saved logins, network mocking, traces, video, and JUnit/HTML reports. - **Tests in code.** The [TypeScript](https://steady-ember-82q7.here.now/typescript.html) and [Python](https://steady-ember-82q7.here.now/python.html) SDKs expose every operation as a typed method, for use with `node:test`, Vitest, Jest, pytest or unittest. - **A browser for AI agents.** `swiftbrowse mcp` serves the Model Context Protocol. A flow an agent works out over MCP can be saved as a test unchanged. A step in a test file, an MCP tool call and an SDK method call are the same request: ```json { "op": "click", "target": "Sign in", "expect": { "type": "url_contains", "value": "/dashboard" } } ``` ```ts await page.click('Sign in', { expect: { type: 'url_contains', value: '/dashboard' } }); ``` ### Why use it - **Fast.** On the same 29-test suite, `swiftbrowse test` finishes in a third to a half of Playwright Test's time. New sessions attach to a warm browser in about 50 ms. See [Benchmarks](https://steady-ember-82q7.here.now/benchmarks.html). - **Reliable actions.** Every click waits until its target is visible, enabled, not covered and not moving. The click is checked again as the mouse goes down and up, so a layout shift can't send it to the wrong element. - **Honest results.** An action can verify its own outcome (`expect`). Ambiguous locators are errors, never silent guesses. Every failure says what kind it is and whether retrying could help. - **Evidence on failure.** You get a screenshot, the page as text, console errors and failed requests. Optionally there's a trace with a live, inspectable copy of the page at every step, and a video. - **Small footprint.** A single native binary, with no Node runtime needed to run JSON tests. The Python SDK uses the standard library only; the TypeScript SDK has no dependencies. ### Where to go next - [Getting started](https://steady-ember-82q7.here.now/getting-started.html): Install, run your first test, try the SDK and MCP. - [Test files](https://steady-ember-82q7.here.now/writing-tests.html): Steps, hooks, variables, tags, setup files. - [Targets and locators](https://steady-ember-82q7.here.now/locators.html): How SwiftBrowse finds elements. - [Assertions](https://steady-ember-82q7.here.now/assertions.html): Auto-waiting conditions for URL, text, elements and more. - [Network mocking](https://steady-ember-82q7.here.now/network.html): Fulfill, fail, delay or watch requests. - [Saved logins](https://steady-ember-82q7.here.now/auth.html): Log in once, start every test signed in. - [Traces, video and reports](https://steady-ember-82q7.here.now/debugging.html): See exactly what happened when a test failed. - [Continuous integration](https://steady-ember-82q7.here.now/ci.html): GitHub Actions, sharding, JUnit. - [Operations reference](https://steady-ember-82q7.here.now/operations.html): Every operation and argument, generated from the engine. ### Platforms Prebuilt binaries are available for Linux (x64 and arm64, glibc 2.35+), macOS (Apple Silicon and Intel) and Windows x64. They ship inside the npm and PyPI packages and as archives on the [GitHub releases page](https://github.com/swiftbrowse/SwiftBrowse/releases). SwiftBrowse drives Chromium; other browser engines are not supported yet (see [Limitations](https://steady-ember-82q7.here.now/limitations.html)). > SwiftBrowse is young (version 0.1.2). If something breaks or the docs leave you guessing, please [open an issue](https://github.com/swiftbrowse/SwiftBrowse/issues). Include the output of `swiftbrowse doctor`. --- ## Getting started Install SwiftBrowse, give it a Chromium to drive, and run a first test as a JSON file, in TypeScript or in Python. ### Install Pick one. Both ship the native `swiftbrowse` binary for your platform. ```sh npm install -g swiftbrowse # global CLI; or `npm install -D swiftbrowse` in a project, then `npx swiftbrowse` ``` ```sh pip install swiftbrowse # installs the `swiftbrowse` command and the Python SDK ``` On npm, the binary comes from a platform package (`swiftbrowse-linux-x64`, `swiftbrowse-linux-arm64`, `swiftbrowse-darwin-arm64`, `swiftbrowse-darwin-x64` or `swiftbrowse-windows-x64`), which npm installs automatically. On PyPI, the wheel for your platform contains it. You can also download an archive from the [releases page](https://github.com/swiftbrowse/SwiftBrowse/releases), or build from source with `cargo build --release` (Rust 1.89+). ### Get a browser SwiftBrowse drives Chromium. The easiest way to get the right build is Playwright's installer, which SwiftBrowse finds automatically: ```sh npx playwright install chromium chromium-headless-shell ``` The headless shell is what headless sessions use; it starts about three times faster than full Chromium. Full Chromium is used for headed sessions (`--headed`). An installed Chrome or Chromium on your `PATH` or in a standard location also works. To pick a binary yourself, set `SWIFTBROWSE_CHROMIUM=/path/to/chrome`. Check what SwiftBrowse found: ```sh swiftbrowse doctor ``` `doctor` lists the operating system, the browsers it found and their versions, ffmpeg (needed only for video), and warnings, such as running as root (Chromium's sandbox is then off). ### Your first test Make a folder with a tiny page to test, `site/index.html`: ```html Hello

Hello

``` and a test, `hello.test.json`: ```json { "name": "hello page", "tests": [ { "name": "greets by name", "steps": [ { "op": "navigate", "url": "index.html" }, { "op": "expect", "condition": { "type": "visible", "target": { "by": "role", "role": "heading", "name": "Hello" } } }, { "op": "fill", "target": "Name", "value": "Ada" }, { "op": "click", "target": "Greet", "expect": { "type": "text_visible", "text": "Hi, Ada" } } ] } ] } ``` Run it: ```sh swiftbrowse test --serve site ``` `--serve site` serves the folder on a free local port and resolves relative URLs such as `index.html` against it. For a real app, use `--base-url http://localhost:3000/` or let the runner start your dev server with `--web-server` (see [The test runner](https://steady-ember-82q7.here.now/test-runner.html)). `swiftbrowse test` finds every `*.test.json` and `*.spec.json` under the current directory (or the paths you give it). It runs the tests in parallel, each in its own isolated browser context, and prints a list of results. Results, failure screenshots and page snapshots go to `test-results/`. Now break it on purpose: change `"Hi, Ada"` to `"Hi, Bob"` and run again. The failure shows what was expected, what the page actually had, a screenshot, and the page as text. Then try the other outputs: ```sh swiftbrowse test --serve site --reporter dot --html report.html # compact output plus an HTML report swiftbrowse test --serve site --trace on # a step-by-step trace.html per test ``` Read [Test files](https://steady-ember-82q7.here.now/writing-tests.html) next for everything a test file can contain. ### The example suite The repository has a complete example: a small demo app and 29 tests covering forms, tabs, popups, iframes, uploads, network mocking and a saved login. ```sh git clone https://github.com/swiftbrowse/SwiftBrowse cd SwiftBrowse/examples/e2e swiftbrowse test tests --serve site # serves the demo app and runs every test ``` ### Tests in code If you'd rather write tests in a programming language, use an SDK. It runs the same engine. Serve the `site` folder from above (for example `python3 -m http.server 3000 -d site`) and then: TypeScript or JavaScript (`npm install -D swiftbrowse`), run with `node --test`: ```js import { test } from 'node:test'; import { launch } from 'swiftbrowse'; test('greets by name', async () => { const browser = await launch(); const page = await browser.newSession(); await page.navigate('http://localhost:3000/'); await page.fill('Name', 'Ada'); await page.click('Greet', { expect: { type: 'text_visible', text: 'Hi, Ada' } }); await page.expect({ type: 'title_equals', value: 'Hello' }); await browser.close(); }); ``` Python (`pip install swiftbrowse`), run with `pytest`: ```python from swiftbrowse import launch def test_greets_by_name(): with launch() as browser, browser.new_session() as page: page.navigate("http://localhost:3000/") page.fill("Name", "Ada") page.click("Greet", expect={"type": "text_visible", "text": "Hi, Ada"}) page.expect({"type": "title_equals", "value": "Hello"}) ``` See [TypeScript and JavaScript](https://steady-ember-82q7.here.now/typescript.html) and [Python](https://steady-ember-82q7.here.now/python.html). ### For AI agents Point an MCP client at `swiftbrowse mcp`: ```json { "mcpServers": { "swiftbrowse": { "command": "swiftbrowse", "args": ["mcp"] } } } ``` See [MCP server](https://steady-ember-82q7.here.now/mcp.html). ### Quick commands ```sh swiftbrowse snapshot https://your-app.test # the page as compact text, with element refs swiftbrowse screenshot https://your-app.test --full-page swiftbrowse run script.json # run a list of steps, print every result swiftbrowse daemon status # the warm browsers new sessions attach to ``` ### Something not working? 1. Run `swiftbrowse doctor` and check that a browser was found. 2. Run the failing test with `--trace on` and open the `trace.html` it prints. 3. [Open an issue](https://github.com/swiftbrowse/SwiftBrowse/issues). Include your OS, how you installed SwiftBrowse, the command you ran, its output, and `swiftbrowse doctor --json`. --- ## Test files A test file is JSON. The runner picks up every `*.test.json` and `*.spec.json`, plus `*.setup.json` files that run before everything else. Each test is a list of steps, and each step is one [operation](https://steady-ember-82q7.here.now/operations.html). ### A complete file ```json { "name": "sign in", "base_url": "http://localhost:3000/", "vars": { "email": "ada@example.com" }, "tags": ["auth"], "before_each": [ { "op": "navigate", "url": "login" } ], "after_each": [ { "op": "expect", "condition": { "type": "no_console_errors" } } ], "tests": [ { "name": "signs in", "tags": ["smoke"], "steps": [ { "op": "fill", "target": "Email", "value": "${email}" }, { "op": "fill", "target": "Password", "value": "${env.TEST_PASSWORD}" }, { "op": "click", "target": { "by": "role", "role": "button", "name": "Sign in" }, "expect": { "type": "url_contains", "value": "dashboard" } }, { "op": "expect", "condition": { "type": "text_equals", "target": { "by": "test_id", "id": "hello" }, "text": "Welcome, ${email}" } } ] }, { "name": "rejects a wrong password", "retries": 2, "timeout_ms": 10000, "steps": [ { "op": "fill", "target": "Email", "value": "${email}" }, { "op": "fill", "target": "Password", "value": "wrong" }, { "op": "click", "target": "Sign in" }, { "op": "expect", "condition": { "type": "text_visible", "text": "Wrong email or password" } } ] }, { "name": "resets the password", "skip": "waiting for the mail server", "steps": [] } ] } ``` ### File fields | Field | Meaning | |---|---| | `name` | Suite name shown in reports. Default: the file name without `.test.json`. | | `base_url` | Relative `navigate` and `new_tab` URLs are resolved against it. `--base-url` and `--serve` override it. | | `vars` | Values for `${name}` placeholders anywhere in the file. `--var name=value` overrides them. | | `tags` | Tags added to every test in the file. | | `before_each` | Steps run before every test, in the test's session. | | `after_each` | Steps run after every test, **even when the test failed**. | | `storage_state` | Name of a saved state to start every test from: see [Saved logins](https://steady-ember-82q7.here.now/auth.html). | | `tests` | The tests. | | `steps` | Instead of `tests`: a file that is one test (the same format as a `swiftbrowse run` script). | Unknown fields are an error, so a typo such as `befor_each` is reported instead of being ignored. ### Test fields | Field | Meaning | |---|---| | `name` | Required. Shown as `suite › name`, with tags appended as `@tag`. | | `steps` | Required. The steps, in order. The test stops at the first failing step. | | `tags` | Tags for filtering with `--grep @smoke`. | | `skip` | `true` or a reason string. The test is reported as skipped. | | `only` | Run only the tests marked `only` (across all files). `--forbid-only` makes this an error on CI. | | `retries` | Retries for this test, overriding `--retries`. | | `timeout_ms` | Time budget for one attempt of this test, including hooks. Default 30 s (`--test-timeout-ms`). | ### Steps A step is an object with `op` plus the operation's arguments: ```json { "op": "select_option", "target": "Country", "option": "Norway" } ``` Every [operation](https://steady-ember-82q7.here.now/operations.html) can be a step: `navigate`, `click`, `fill`, `type_text`, `press_key`, `select_option`, `check`, `hover`, `upload_file`, `go_back`, `go_forward`, `reload`, `evaluate`, `wait_for`, `route`, `switch_tab`, `switch_frame`, `save_storage_state`, `get_screenshot` and the rest. The test runner adds one more: **`expect`**, an auto-waiting assertion with either one `condition` or a list of `conditions`. It retries until every condition holds or the expect timeout (5 s, `--expect-timeout-ms`, or `timeout_ms` on the step) runs out. See [Assertions](https://steady-ember-82q7.here.now/assertions.html). ```json { "op": "expect", "conditions": [ { "type": "url_contains", "value": "/cart" }, { "type": "count", "target": { "by": "css", "selector": ".item" }, "count": 3 } ] } ``` #### Verifying an action's outcome `click`, `hover` and `press_key` accept an `expect` condition of their own. The action counts as successful only if the condition holds afterwards: ```json { "op": "click", "target": "Add to cart", "expect": { "type": "text_visible", "text": "1 item" } } ``` `fill`, `select_option`, `check` and `upload_file` always verify their result (the field's value, the selected option, the checked state, the chosen files). ### Variables - `${name}`: from the file's `vars`, overridden by `--var name=value`. - `${env.NAME}`: from the environment. Use it for secrets so they stay out of test files. An undefined variable is an error before any test runs (exit code 2). Variables work in any string, including URLs, targets and conditions. ### Setup files A file named `*.setup.json` runs before all other tests. If any setup test fails, the other tests are skipped. Use it to log in once and save the state; see [Saved logins](https://steady-ember-82q7.here.now/auth.html). ### Scripts A file with top-level `steps` instead of `tests` is a single test. That's also the format of `swiftbrowse run`, which runs one script and prints every result as JSON: ```json { "name": "check the homepage", "steps": [ { "op": "navigate", "url": "http://localhost:3000/" }, { "op": "assert_page_state", "conditions": [ { "type": "title_equals", "value": "Hello" } ] } ] } ``` ```sh swiftbrowse run check.json ``` A script stops at its first failing step unless it sets `"stop_on_failure": false` (only `run` supports that). ### Isolation Every test runs in a fresh, isolated browser context with its own cookies, localStorage, cache and tabs. Tests don't share state unless you share it on purpose with a [saved state](https://steady-ember-82q7.here.now/auth.html). Tests in different files and tests in the same file run in parallel up to `--workers`. ### Policy inside tests Tests target test environments, so the runner is permissive where the MCP server is careful: - `evaluate` (running JavaScript in the page) is allowed. - Clicks on buttons such as "Delete" or "Pay" need no approval. - `upload_file` may read files from the working directory and the test file's directory. Relative paths are resolved against the test file. See [Configuration and security](https://steady-ember-82q7.here.now/configuration.html) for the full policy. --- ## Targets and locators Every operation that acts on an element takes a `target`: either a plain string or a locator object. ### Strings A string is the quickest way to point at something: ```json { "op": "fill", "target": "Email", "value": "ada@example.com" } { "op": "click", "target": "Sign in" } ``` A string tries these strategies in order and uses the first that matches: 1. accessible name (what a screen reader announces: button text, `aria-label`, `alt`, …) 2. form label (`