fastbrowser
Python bindings for fastbrowser — a cross-platform browser-automation kernel for AI agents, written in Rust. This package exposes the kernel through PyO3.
- Engine-agnostic: mock (no deps) /
chromium(CDP) /bundled(Chrome for Testing) /system(system/default Chrome/Edge/Chromium) /webview/cef, unified behind oneBrowserEnginetrait.autodegrades through them (never silently tomock). - AI-native: 95 LLM-friendly tools (
navigate,click,type,extract_text,wait_for_element,fill_form,screenshot, …), all JSON in/out. - Multi-tab concurrency: per-tab locks — different tabs run in parallel, same tab serialized (mirrors a real browser's per-process / single-main-thread model).
Install
pip install fastbrowser
Prebuilt wheels are published for CPython 3.8+ on macOS (arm64/x86_64), Linux (manylinux x86_64/aarch64) and Windows x64.
Quickstart (mock engine — no browser required)
import fastbrowser as fb
b = fb.FastBrowser()
b.init() # default: mock engine, zero deps
out = b.open("https://example.com")
print(out["title"]) # "Example Page"
print(b.snapshot()["title"]) # interactive-element snapshot for LLMs
print(b.tool_call("get_current_url")) # {"url": "https://example.com"}
w, h, rgba = b.screenshot() # raw RGBA (w, h, w*h*4 bytes)
b.save_screenshot("page.png") # PNG
b.shutdown()
Examples
1. Snapshot-driven interaction (the core agent loop)
snapshot() returns the interactive elements (a/b/c ids) the LLM acts on; click/type
target those ids:
b = fb.FastBrowser(); b.init()
b.open("https://example.com")
snap = b.snapshot()
for el in snap["interactive"]:
print(el["id"], el["tag"], el.get("text"), el.get("href"))
# click a link, type into a field, press Enter
b.tool_call("click", {"id": "c"})
b.tool_call("type", {"id": "e", "text": "hello"})
b.tool_call("press", {"key": "Enter"})
2. Content extraction
b = fb.FastBrowser(); b.init()
b.open("https://example.com")
print(b.tool_call("extract_links")) # {"links": [{"text": "Learn more", "url": "..."}]}
print(b.tool_call("extract_text")) # {"text": "..."}
print(b.tool_call("get_page_text")) # {"text": "..."}
print(b.tool_call("extract_table")) # {"table": [["Name", "Value"], ...]}
print(b.tool_call("get_page_title")) # {"title": "Example Page"}
3. Form filling
b = fb.FastBrowser(); b.init()
b.open("https://example.com")
# fill by snapshot id
b.tool_call("fill_form", {"values": {"e": "alice"}}) # {"filled": ["e"], "ok": true}
# or field-by-field
b.tool_call("type", {"id": "e", "text": "bob", "clear": True})
b.tool_call("checkbox", {"id": "d", "checked": True})
# on a real engine: dropdown / radio / file upload
b.tool_call("select_option", {"id": "e", "value": "pro"})
b.tool_call("radio", {"id": "r"})
4. Multi-tab
Every tool accepts {"tab": N} — cross-tab operations don't depend on the active tab:
b = fb.FastBrowser(); b.init()
t1 = b.open("https://example.com")["tab"]
t2 = b.tool_call("new_tab", {"url": "https://example.com/login"})["tab"]
print(b.tool_call("list_tabs")) # {"tabs": [1, 2]}
print(b.tool_call("get_page_title", {"tab": t1})) # {"title": "Example Page"}
print(b.tool_call("get_page_title", {"tab": t2})) # {"title": "Login"}
b.tool_call("switch_tab", {"tab": t1})
b.tool_call("close_tab", {"tab": t2})
5. Waiting
b.tool_call("wait_for_element", {"selector": "button", "timeout_ms": 5000})
b.tool_call("wait_for_navigation", {"timeout_ms": 10000})
6. Execute JavaScript
print(b.tool_call("execute_js", {"script": "document.title"})) # {"result": "Example Page"}
# on a real engine, any script runs against the real page:
print(b.tool_call("execute_js", {"script": "document.links.length"})) # {"result": <count>}
7. Screenshots
w, h, rgba = b.screenshot() # raw RGBA
png = b.screenshot_png() # PNG bytes (stdlib-encoded, no Pillow)
b.save_screenshot("page.png") # save PNG to disk
b.set_viewport(390, 844) # mobile viewport
8. Event callbacks
import json
events = []
b.register_event_callback(lambda tab, ev: events.append((tab, json.loads(ev))))
b.open("https://example.com")
b.navigate("https://example.com/login")
print(events) # navigation / console / dom events
9. OSR frame streaming
import time, json
frames = []
b.register_frame_callback(lambda tab, f: frames.append((tab, json.loads(f))))
b.open("https://example.com")
b.start_frame_stream(1, fps=10)
time.sleep(0.5)
b.stop_frame_stream(1)
print(frames[0][1]["width"], frames[0][1]["height"]) # frame dims
10. Session state, cookies & storage
b = fb.FastBrowser(); b.init()
b.open("https://example.com")
b.tool_call("cookie_set", {"name": "sid", "value": "abc", "domain": "example.com"})
b.tool_call("storage_set", {"key": "token", "value": "t1"})
b.session_save("/tmp/state.json") # {"tabs": 1}
b.shutdown()
b2 = fb.FastBrowser(); b2.init()
b2.session_load("/tmp/state.json") # {"tabs": 1}
b2.open("https://example.com")
print(b2.tool_call("cookie_get", {"domain": "example.com"})) # {"cookies": [{"value": "abc", ...}]}
print(b2.tool_call("storage_get", {"key": "token"})) # {"value": "t1"}
11. Audit trail
b.tool_call("get_current_url")
print(b.audit()) # [{"tool":..., "ok":..., ...}, ...]
b.clear_audit()
Async (asyncio)
import asyncio
import fastbrowser as fb
async def main():
b = fb.AsyncFastBrowser()
await b.init()
t1 = (await b.open("https://example.com"))["tab"]
t2 = (await b.open("https://example.com/login"))["tab"]
# multi-tab concurrency — the two calls run truly in parallel
r1, r2 = await asyncio.gather(
b.tool_call("get_page_title", {"tab": t1}),
b.tool_call("get_page_title", {"tab": t2}),
)
print(r1["title"], r2["title"])
asyncio.run(main())
Blocking operations (open/tool_call/snapshot/screenshot…) are offloaded to a thread
pool in the async interface, and the kernel releases the GIL, so gather runs concurrent tabs
truly in parallel.
Real browser (Chromium)
Point at your own Chrome via CDP:
b = fb.FastBrowser()
b.init({
"engine": "chromium",
"cdp_url": "ws://127.0.0.1:9222/devtools/browser/<id>", # start Chrome with --remote-debugging-port=9222
})
b.open("https://example.com")
print(b.snapshot()["title"])
Or use the bundled Chrome for Testing (downloads separately, not shipped in the wheel):
b.init({"engine": "bundled"}) # auto-launches vendor/chromium (or CHROME_PATH)
Or let the kernel find and launch a system/default browser for you (system), or try
everything automatically (auto) — which never silently falls back to mock:
b.init({"engine": "auto"}) # cdp_url → bundled → system/default browser → webview → mock
used = b.status()["engine_used"] # "chromium" | "bundled" | "system" | "webview" | "mock"
if b.status().get("degraded"):
print("hint:", b.status().get("hint")) # why it degraded + how to get a real browser
auto only uses CDP-capable browsers (Chrome/Edge/Chromium/Brave — not Safari/Firefox).
A real browser launches headless with an isolated temp profile by default. Point
browser_path (or CHROME_PATH) at a specific binary (e.g. playwright install chromium).
With a real engine, click/type use real coordinate-level input with Playwright-style
actionability, execute_js runs in the real page, screenshot captures real pixels, and
cookies/storage/downloads hit real browser state.
API
| Area | Methods |
|---|---|
| Lifecycle | init(config?), is_initialized, shutdown |
| Navigation | open(url), navigate(url) |
| Tools | tool_call(name, params?), tool_list(), tool_count() |
| Content | snapshot(), screenshot(), screenshot_png(), save_screenshot(path), get_page_* via tools |
| Rendering | set_viewport(w,h), get_view(), set_rendering_mode(mode), start_frame_stream(tab, fps), stop_frame_stream(tab) |
| Callbacks | register_event_callback(cb), register_frame_callback(cb) |
| State | status(), get_info(), audit(), clear_audit() |
| Session | session_save(path), session_load(path), clear_state() |
Notes
- Single-instance semantics: one
FastBrowserper process (the kernel is a process-wide singleton behind a shared runtime). Use one instance and drive tabs concurrently. - Callbacks (
register_event_callback/register_frame_callback) fire on a dedicated dispatcher thread outside the engine lock — safe to re-enter the browser from inside them. - License: Apache-2.0 (matches the kernel).
Test
python -m pytest tests/ -q # mock engine (no browser)
TMPDIR=/tmp python -m pytest tests/test_chromium.py -q # real Chromium (needs vendor/chromium)
Metadata
Release files for fastbrowser 0.1.7
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fastbrowser-0.1.7.tar.gz | 515.7 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| fastbrowser-0.1.7-cp38-abi3-win_amd64.whl | CPython 3.8 | abi3 | Windows x86-64 | Details |
| fastbrowser-0.1.7-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.8 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| fastbrowser-0.1.7-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.8 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| fastbrowser-0.1.7-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl | CPython 3.8 | abi3 | macOS 11.0+ ARM64, macOS 10.12+ x86-64, macOS 10.12+ universal2 (ARM64, x86-64) | Details |
Total release size: 16.7 MB
Release files / fastbrowser-0.1.7.tar.gz
| Download URL | fastbrowser-0.1.7.tar.gz |
|---|---|
| Size | 515.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bc193d7fff60b6962a60eebcd188e8b389601f8bfc7463dba2ea2ccf92ee0e99
|
|
BLAKE2b-256 checksum How to use checksums |
4bcf188836d0096ef8111e709119850bb5a97f9b782d421bc39b78b71fe60ca2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / fastbrowser-0.1.7-cp38-abi3-win_amd64.whl
| Download URL | fastbrowser-0.1.7-cp38-abi3-win_amd64.whl |
|---|---|
| Size | 2.7 MB |
| Tags | CPython 3.8 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
93c8cf035a70aac1355e4a7f109bdb4f0081cd10ff732d63578f8fdead595247
|
|
BLAKE2b-256 checksum How to use checksums |
bbc6b0cc1e30d8d57772dd78eeba6ae139b8f5fb12fc85a0aebdb4f1d30d523b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / fastbrowser-0.1.7-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | fastbrowser-0.1.7-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 4.3 MB |
| Tags | CPython 3.8 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
4b694540f5c5d1737e3939d874785ccc1326a42b379e7893b549d11188d8b1c8
|
|
BLAKE2b-256 checksum How to use checksums |
84846ea2faaf1400efeaff38d377da532b1c8eca4286ba2f723d19e23cc00dc0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / fastbrowser-0.1.7-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | fastbrowser-0.1.7-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 4.1 MB |
| Tags | CPython 3.8 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
1aeeeb1910ed5f39ced99d8e7ebc7305144dd5e69fc3ce4957df56cf369c8ff9
|
|
BLAKE2b-256 checksum How to use checksums |
247a9431b0f13c0ab81ac6663a572d380a7f8c556a3d67beb69cd4359045c659
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / fastbrowser-0.1.7-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
| Download URL | fastbrowser-0.1.7-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl |
|---|---|
| Size | 5.0 MB |
| Tags | CPython 3.8 abi3 macOS 10.12+ universal2 (ARM64, x86-64) macOS 10.12+ x86-64 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
b2d96c96a6d3f11ebafa58ac7b632872ed16a72ea07cee03ce35ee2c43b91d6d
|
|
BLAKE2b-256 checksum How to use checksums |
9b2a4e95675402a8b11736adc8349ecb00dc72bcc33ade3041f73e572a87907d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|