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.8
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.8.tar.gz | 532.1 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| fastbrowser-0.1.8-cp38-abi3-win_amd64.whl | CPython 3.8 | abi3 | Windows x86-64 | Details |
| fastbrowser-0.1.8-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.8-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.8 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| fastbrowser-0.1.8-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl | CPython 3.8 | abi3 | macOS 10.12+ universal2 (ARM64, x86-64), macOS 11.0+ ARM64, macOS 10.12+ x86-64 | Details |
Total release size: 16.7 MB
Release files / fastbrowser-0.1.8.tar.gz
| Download URL | fastbrowser-0.1.8.tar.gz |
|---|---|
| Size | 532.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e9700b2f24741067df38b171514006263b5ce0f86f718a405f0e68e7fdc27f02
|
|
BLAKE2b-256 checksum How to use checksums |
c246d73420f105bac52ea144c22c038a05672a6814e8fe4e886ec744553647e0
|
| 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.8-cp38-abi3-win_amd64.whl
| Download URL | fastbrowser-0.1.8-cp38-abi3-win_amd64.whl |
|---|---|
| Size | 2.7 MB |
| Tags | CPython 3.8 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
f32fa6e3e0bd365e97c74f831696aa839dda18acc283858e819c7ecc2206d1e4
|
|
BLAKE2b-256 checksum How to use checksums |
7261fd2abacfa8411c45c17ac9c8057b4ee3d48ddfb665b1d8d0d98d08ce0019
|
| 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.8-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | fastbrowser-0.1.8-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 |
1fe6777893f1ec3664853ab98abca2c1a90c02d776c02441b9a89bcd40264fd7
|
|
BLAKE2b-256 checksum How to use checksums |
fe34145bf39b40036e46293d5187b681377ee0a53dbb1e7c8d3addd710a29338
|
| 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.8-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | fastbrowser-0.1.8-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 |
4d9687b950886c6242cd405e37596605f25e21d0788e87734f007e44734fe2ed
|
|
BLAKE2b-256 checksum How to use checksums |
22f718ddeadf151b12cfba6e68ce902804a58699bd151f930e9ba1ce8032d6ea
|
| 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.8-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
| Download URL | fastbrowser-0.1.8-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl |
|---|---|
| Size | 5.1 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 |
1139b87208b2c8e418d72722fe1828e795a294298133eeb2364331ff70686f55
|
|
BLAKE2b-256 checksum How to use checksums |
91e4ed9051e9a213b3ac2256b87d9d7923f20331c4f72601b278f2a703c32ef6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|