Bridgic Browser is a Python library for LLM-driven browser automation built on Playwright. It includes CLI tools, Python tools and skills for AI agents.
- Comprehensive CLI Tools - 67 tools organized into 15 categories; Designed to integrate with any AI agent
- Python-based Tools - Used for agent / workflow code generation; Easier integration with Bridgic
- Snapshot with Semantic Invariance - A representation of page snapshot based on accessibility tree and a specially designed ref-generation algorithm that ensures element refs remain unchanged across page reloads
- Skills - Used for guided exploration and code generation; Compatible with most of coding agents
- Stealth Mode (Enabled by Default) - Mode-aware anti-detection covering 24+ JS/CDP fingerprint vectors. Verified against the public bot-detection benchmark suite — see Anti-Detection below
- Persistent & Ephemeral Sessions - Persistent profile by default (
$BRIDGIC_HOME/bridgic-browser/user_data/, default~/.bridgic/...); passclear_user_data=Truefor an ephemeral session with no profile - Nested iframe Support - Supports DOM element operations within multi-level nested iframes
The easiest way to use Bridgic Browser is with a coding agent or AI assistant (such as Claude Code, Cursor, Codex, or OpenClaw). You can use it in two ways: via a Skill or a Plugin. In both cases, Bridgic Browser is installed automatically.
Method 1: Use AI to directly control the browser and complete tasks in real time.
Karpathy.mp4
To use this method, install the Skill provided by Bridgic Browser.
npx skills add bitsky-tech/bridgic-browser --skill bridgic-browserAfter installation, the Skill will appear in your agent directories (for example, Claude Code typically under .claude/skills/bridgic-browser/, and Cursor under .agents/skills/bridgic-browser/).
Method 2: Let AI generate repeatable browser automation scripts with minimal token usage.
To use this method, install the Plugin provided by AmphiLoop, a brand new methodology, tech stack and toolchain for building AI agents with natural language.
pip install bridgic-browserAfter installation, install Playwright browsers:
playwright install chromiumbridgic-browser open --headed https://example.com
bridgic-browser snapshot
# 'f0201d1c' is the ref value of the 'Learn more' link
bridgic-browser click f0201d1c
bridgic-browser screenshot page.png
bridgic-browser closeFirst, build tools:
from bridgic.browser.session import Browser
from bridgic.browser.tools import BrowserToolSetBuilder, ToolCategory
# create a browser instance
browser = Browser(headless=False)
async def create_tools(browser):
# Build a focused tool set for your agent
builder = BrowserToolSetBuilder.for_categories(
browser,
ToolCategory.NAVIGATION,
ToolCategory.SNAPSHOT,
ToolCategory.ELEMENT_INTERACTION,
ToolCategory.CAPTURE,
ToolCategory.WAIT,
)
tools = builder.build()["tool_specs"]
return toolsSecond (optional), build a Bridgic agent that uses this tool set:
import os
from bridgic.llms.openai import OpenAILlm, OpenAIConfiguration
async def create_llm():
_api_key = os.environ.get("OPENAI_API_KEY")
_model_name = os.environ.get("OPENAI_MODEL_NAME")
llm = OpenAILlm(
api_key=_api_key,
configuration=OpenAIConfiguration(model=_model_name),
timeout=60,
)
return llm
from bridgic.core.agentic.recent import ReCentAutoma, StopCondition
from bridgic.core.automa import RunningOptions
async def create_agent(llm, tools):
browser_agent = ReCentAutoma(
llm=llm,
tools=tools,
stop_condition=StopCondition(max_iteration=10, max_consecutive_no_tool_selected=1),
running_options=RunningOptions(debug=True),
)
return browser_agent
async def main():
tools = await create_tools(browser)
llm = await create_llm()
agent = await create_agent(llm, tools)
result = await agent.arun(
goal=(
"Summarize the 'Learn more' page of example.com for me"
),
guidance=(
"Do the following steps one by one:\n"
"1. Navigate to https://example.com\n"
"2. Click the 'Learn more' link\n"
"3. Take a screenshot of the 'Learn more' page\n"
"4. Summarize the page content in one sentence and tell me how to access the screenshot.\n"
),
)
print("\n\n*** Final Result: ***\n\n")
print(result)
await browser.close()
if __name__ == "__main__":
import asyncio
asyncio.run(main())You can also directly call the underlying Browser API to control the browser.
from bridgic.browser.session import Browser
browser = Browser(headless=False)
async def main():
await browser.navigate_to("https://example.com")
snapshot = await browser.get_snapshot()
print(snapshot.tree) # Tree format: - role "name" [ref=f0201d1c]
for ref, data in snapshot.refs.items():
if data.name == "Learn more":
learn_more_ref = ref
break
print(f"Found ref for 'Learn more': {learn_more_ref}")
await browser.click_element_by_ref(learn_more_ref)
await browser.take_screenshot(filename="page.png")
await browser.close()
if __name__ == "__main__":
import asyncio
asyncio.run(main())bridgic-browser ships with a command-line interface for controlling a browser from the terminal (67 tools organized into 15 categories). A persistent daemon process holds a browser instance; each CLI invocation connects over a Unix domain socket and exits immediately.
Browser options are automatically loaded from the following sources (both CLI daemon and SDK Browser()), in priority order (highest last wins):
| Source | Example |
|---|---|
| Defaults | headless=True, clear_user_data=False (persistent profile) |
$BRIDGIC_HOME/bridgic-browser/bridgic-browser.json |
User-level persistent config (default ~/.bridgic/...) |
./bridgic-browser.json |
Project-local config (in cwd at daemon start) |
| Environment variables | See skills/bridgic-browser/references/env-vars.md |
Headed browser note:
When headless=false and stealth is enabled, bridgic auto-switches to system Chrome
(if installed) for better anti-detection (Chrome for Testing is blocked by Google OAuth).
To override, set:
channel: e.g.”chrome”,”msedge”executable_path: absolute path to a browser binary
bridgic includes an industrial-grade stealth layer that defeats most JS-fingerprint-based bot detection without a custom Chromium binary, proxy, or CAPTCHA solver. The strategy is mode-aware — headed mode leverages the real system Chrome's TLS authenticity, headless mode applies a fuller JS
- CDP patch suite. See
docs/INTERNALS.md#mode-aware-stealth-designfor the architecture.
Last verified 2026-05-12 (Playwright Chromium 143 / system Chrome 147 on macOS).
| Site | bridgic Result |
|---|---|
bot.sannysoft.com |
0 / 57 fail (both modes) |
bot.incolumitas.com |
0 fail (both modes) |
browserscan.net/bot-detection |
0 abnormal / 19 normal (both modes) |
demo.fingerprint.com/web-scraping |
Pass (headed mode) |
recaptcha-demo.appspot.com (reCAPTCHA v3) |
score = 0.9 (both modes) |
24+ detection vectors patched at the JS + CDP layer:
- Anti-introspection foundation —
Function.prototype.toStringinterception defeats.toString()probes - navigator —
webdriver(deleted fromNavigator.prototypeto match--disable-blink-features=AutomationControlledsemantics;'webdriver' in navigatorreturnsfalse),plugins&mimeTypes(with nativePluginArray/MimeTypeArrayprototypes;item(i)truncatesito uint32 per Web IDL §3.2.4),languages,deviceMemory,hardwareConcurrency,connection,permissions.query - window / document —
chrome(runtime/csi/loadTimes),outerWidth/Height,hasFocus/hidden/visibilityState,Notification.permission - WebGL — UNMASKED_VENDOR / UNMASKED_RENDERER (replaces SwiftShader / generic-vendor leaks)
- UA / Sec-CH-UA (headless-only via CDP
Emulation.setUserAgentOverride) —navigator.userAgent,userAgentData.brands - Web Worker / SharedWorker / Service Worker (race-proof injection via
constructor wrap +
importScripts) — main↔worker consistency fordeviceMemory,languages,vendor,productSub,vendorSub, WebGL - CDP-attach detection —
Debugger.setSkipAllPauses,console.*Errorpre-stringify (blockserror.stackgetter probes) - Anti devtools-detector —
console.tabletiming neutralization,devtoolsFormatterslockout,Function-constructordebuggerstrip
Per-vector implementation details:
docs/INTERNALS.md#stealth-js-init-script--patched-properties.
The JSON sources accept any Browser constructor parameter:
{
"headless": false,
"proxy": {"server": "http://proxy:8080", "username": "u", "password": "p"},
"viewport": {"width": 1280, "height": 720},
"locale": "zh-CN",
"timezone_id": "Asia/Shanghai"
}# One-shot env override
BRIDGIC_BROWSER_JSON='{"headless":false,"locale":"zh-CN"}' bridgic-browser open URL
# One-shot ephemeral session (no persistent profile)
BRIDGIC_BROWSER_JSON='{"clear_user_data":true}' bridgic-browser open URLBy default all state lives under ~/.bridgic. Set BRIDGIC_HOME to run multiple independent daemon instances in parallel — each gets its own socket, logs, user data, and config:
# Instance 1 (default)
bridgic-browser open https://site-a.com
# Instance 2 (separate home)
BRIDGIC_HOME=/tmp/b2 bridgic-browser open https://site-b.com
# Each instance operates independently
bridgic-browser snapshot # site-a snapshot
BRIDGIC_HOME=/tmp/b2 bridgic-browser snapshot # site-b snapshot
# Close each instance separately
bridgic-browser close
BRIDGIC_HOME=/tmp/b2 bridgic-browser closeFor SDK multi-instance isolation within the same process, use Browser(user_data_dir=...) per instance. For full process-level isolation, set BRIDGIC_HOME before spawning a subprocess. See skills/bridgic-browser/references/env-vars.md for details.
Export cookies and localStorage from one browser instance and import them into another — useful for sharing login sessions across instances or persisting auth state for later runs:
# 1. Log into a website in instance A
bridgic-browser open https://github.com --headed
# ... complete login in the browser ...
# 2. Export storage state (cookies + localStorage)
bridgic-browser storage-save /tmp/github-login.json
# 3. Import into another instance (even with a different BRIDGIC_HOME)
BRIDGIC_HOME=/tmp/b2 bridgic-browser open https://github.com --headed
BRIDGIC_HOME=/tmp/b2 bridgic-browser storage-load /tmp/github-login.json
BRIDGIC_HOME=/tmp/b2 bridgic-browser reload # apply the imported cookies
# Instance B is now logged in with the same session as instance AThe exported JSON file contains all cookies (including HttpOnly / Secure) and localStorage entries for every origin the browser has visited.
The storage state file is cross-mode compatible — you can export from a headed session and import into a headless one (or vice versa), and the login session will carry over. This is especially useful for automating workflows that require authentication: log in once in headed mode where you can interact with CAPTCHAs and 2FA prompts, export the storage state, and then reuse it in headless automation runs.
SDK usage:
import asyncio
from bridgic.browser.session import Browser
async def main():
# 1. Export: log in interactively, then save storage state
async with Browser(headless=False) as browser:
await browser.navigate_to("https://github.com")
# ... complete login in the browser ...
await browser.save_storage_state("/tmp/github-login.json")
# 2. Import: reuse the login session in a new (headless) instance
async with Browser(headless=True, user_data_dir="/tmp/sdk-profile") as browser:
await browser.restore_storage_state("/tmp/github-login.json")
await browser.navigate_to("https://github.com")
snap = await browser.get_snapshot(interactive=True)
print(snap.tree) # Dashboard — logged in
asyncio.run(main())Instead of launching a new browser, bridgic-browser can connect to an already-running Chrome/Chromium instance via the Chrome DevTools Protocol.
There are two ways to start Chrome with a remote debugging endpoint exposed.
Option A — Chrome 144+ in-browser UI (no relaunch). Open chrome://inspect/#remote-debugging in your everyday Chrome window and follow the dialog to allow incoming debugging connections. Chrome opens a local endpoint and writes the connection info to a DevToolsActivePort file at the root of the user data directory:
| Platform | Path |
|---|---|
| macOS | ~/Library/Application Support/Google/Chrome/DevToolsActivePort |
| Linux | ~/.config/google-chrome/DevToolsActivePort |
| Windows | %LOCALAPPDATA%\Google\Chrome\User Data\DevToolsActivePort |
The file is exactly two lines — port and browser-level WebSocket path:
9222
/devtools/browser/f8632266-41b6-4eb8-8239-d48a86bb44b1
Because bridgic's --cdp auto already scans these standard profile directories for DevToolsActivePort, you can connect immediately with no extra arguments:
bridgic-browser open https://example.com --cdp autoWhile the session is active Chrome shows a "Chrome is being controlled by automated test software" banner, and Chrome may prompt you to confirm each new debugging session. Sources: Chrome DevTools MCP blog post, chrome-devtools-mcp README. See docs/CDP_MODE.md for more.
Option B — launch flag (Chrome <144, or a dedicated profile). Start Chrome with --remote-debugging-port:
# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 --user-data-dir=/tmp/cdp-profile
# Linux
google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/cdp-profileThen connect with --cdp:
bridgic-browser open https://example.com --cdp 9222
bridgic-browser open https://example.com --cdp ws://localhost:9222/devtools/browser/...
bridgic-browser open https://example.com --cdp wss://cloud.example.com/chromium?token=...
bridgic-browser open https://example.com --cdp auto| Format | Description |
|---|---|
9222 |
Bare port number -- queries localhost:9222/json/version to discover the WebSocket URL |
ws://... / wss://... |
Direct WebSocket URL (raw CDP or Playwright WS protocol), passed through as-is |
http://host:port |
HTTP discovery endpoint -- queries /json/version on that host |
auto |
Auto-scan local Chrome/Chromium/Brave profile directories (+ Canary variants) for an active DevToolsActivePort file |
Tab visibility: when attached to a user's running Chrome, bridgic only sees pages it itself opens — the brand-new tab created at attach time, anything spawned via new-tab, and popups triggered from those pages by a plain left-click on a <a target="_blank"> link or by a JavaScript window.open() call (adopted via Page.opener()). Tabs the user opens with Cmd+click (macOS) / Ctrl+click (Win/Linux) / middle-click / Cmd+T / address bar are not adopted — for Cmd/Ctrl/middle-click Chromium clears the opener at the browser-process level (the "background tab" navigation path); Cmd+T and the address bar have no opener relationship to begin with. Either way bridgic cannot see them. Your other tabs are deliberately invisible to bridgic's tabs / switch-tab / close-tab. This is a privacy boundary that prevents an LLM driving bridgic from switching to or closing your private work tabs. To work with a page you already have open, navigate to that URL through bridgic instead. By default a popup whose opener is bridgic's current tab becomes the new active tab (auto_follow_popups=True); set Browser(auto_follow_popups=False) to keep the active pointer fixed. See docs/CDP_MODE.md#tab-ownership-in-cdp-mode for the full adoption truth table.
Closing behavior: bridgic-browser close disconnects from the remote browser but does not terminate the Chrome process. The browser keeps running and can be reconnected.
Use cases:
- Reuse an existing Chrome session with its login state and extensions
- Connect to cloud browser services (Browserless, Steel.dev, etc.)
- Automate Electron apps that expose a CDP port
SDK equivalent:
browser = Browser(cdp="ws://localhost:9222/devtools/browser/...")| Category | Commands |
|---|---|
| Navigation | open, back, forward, reload, search, info |
| Snapshot | snapshot [-i] [-f|-F] [-l N] [-s FILE] |
| Element Interaction | click, double-click, hover, focus, fill, select, options, check, uncheck, scroll-to, drag, upload, fill-form |
| Keyboard | press, type, key-down, key-up |
| Mouse | scroll, mouse-move, mouse-click, mouse-drag, mouse-down, mouse-up |
| Wait | wait [SECONDS] [TEXT] [--gone] |
| Tabs | tabs, new-tab, switch-tab, close-tab |
| Evaluate | eval, eval-on |
| Capture | screenshot, pdf |
| Network | network-start, network-stop, network, wait-network |
| Dialog | dialog-setup, dialog, dialog-remove |
| Storage | storage-save, storage-load, cookies-clear, cookies, cookie-set |
| Verify | verify-visible, verify-text, verify-value, verify-state, verify-url, verify-title |
| Developer | console-start, console-stop, console, trace-start, trace-stop, trace-chunk, video-start, video-stop |
| Lifecycle | close, resize |
Use -h or --help on any command for details:
bridgic-browser -h
bridgic-browser scroll -hBridgic Browser provides 67 tools organized into 15 categories. Use BrowserToolSetBuilder with category/name selection for scenario-focused tool sets.
from bridgic.browser.tools import BrowserToolSetBuilder, ToolCategory
# Focused set for your specific agent flows
builder = BrowserToolSetBuilder.for_categories(
browser,
ToolCategory.NAVIGATION,
ToolCategory.ELEMENT_INTERACTION,
ToolCategory.CAPTURE,
)
tools = builder.build()["tool_specs"]
# Include all available tools
builder = BrowserToolSetBuilder.for_categories(browser, ToolCategory.ALL)
tools = builder.build()["tool_specs"]# Select by tool function names
builder = BrowserToolSetBuilder.for_tool_names(
browser,
"search",
"navigate_to",
"click_element_by_ref",
)
tools = builder.build()["tool_specs"]
# Enable strict mode to catch typos and missing browser methods early
builder = BrowserToolSetBuilder.for_tool_names(
browser,
"search",
"navigate_to",
strict=True,
)
tools = builder.build()["tool_specs"]builder1 = BrowserToolSetBuilder.for_categories(
browser,
ToolCategory.NAVIGATION,
ToolCategory.ELEMENT_INTERACTION,
ToolCategory.CAPTURE,
)
builder2 = BrowserToolSetBuilder.for_tool_names(
browser, "verify_url", "verify_title"
)
tools = [*builder1.build()["tool_specs"], *builder2.build()["tool_specs"]]Navigation (6 tools):
navigate_to(url)- Navigate to URLsearch(query, engine)- Search using search engineget_current_page_info()- Get current page info (URL, title, etc.)reload_page()- Reload current pagego_back()/go_forward()- Browser history navigation
Snapshot (1 tool):
get_snapshot_text(limit=10000, interactive=False, full_page=True, file=None)- Get page state string for LLM (accessibility tree with refs). limit (default 10000) controls the maximum characters returned. When the snapshot exceeds limit or file is explicitly provided, full content is saved to file (auto-generated under$BRIDGIC_HOME/bridgic-browser/snapshot/ifNoneand over limit) and only a notice with the file path is returned. interactive and full_page matchget_snapshot(interactive-only or full-page by default).
Element Interaction (13 tools) - by ref:
click_element_by_ref(ref)- Click elementinput_text_by_ref(ref, text)- Input textfill_form(fields)- Fill multiple form fieldsscroll_element_into_view_by_ref(ref)- Scroll element into viewselect_dropdown_option_by_ref(ref, value)- Select dropdown optionget_dropdown_options_by_ref(ref)- Get dropdown optionscheck_checkbox_or_radio_by_ref(ref)/uncheck_checkbox_by_ref(ref)- Checkbox controlfocus_element_by_ref(ref)- Focus elementhover_element_by_ref(ref)- Hover over elementdouble_click_element_by_ref(ref)- Double clickupload_file_by_ref(ref, path)- Upload filedrag_element_by_ref(start_ref, end_ref)- Drag and drop
Tabs (4 tools):
get_tabs()/new_tab(url)/switch_tab(page_id)/close_tab(page_id)- Tab management
Evaluate (2 tools):
evaluate_javascript(code)- Execute JavaScriptevaluate_javascript_on_ref(ref, code)- Execute JavaScript on element
Keyboard (4 tools):
type_text(text)- Type text character by character (key events, no ref — acts on focused element)press_key(key)- Press keyboard shortcut (e.g."Enter","Control+A")key_down(key)/key_up(key)- Key control
Mouse (6 tools) - Coordinate-based:
mouse_wheel(delta_x, delta_y)- Scroll wheelmouse_click(x, y)- Click at positionmouse_move(x, y)- Move mousemouse_drag(start_x, start_y, end_x, end_y)- Drag operationmouse_down()/mouse_up()- Mouse button control
Wait (1 tool):
wait_for(time_seconds, text, text_gone, selector, state, timeout)- Wait for conditions
Capture (2 tools):
take_screenshot(filename=None, ref=None, full_page=False, type="png")- Capture screenshotsave_pdf(filename)- Save page as PDF
Network (4 tools):
start_network_capture()/stop_network_capture()/get_network_requests()- Network monitoringwait_for_network_idle()- Wait for network idle
Dialog (3 tools):
setup_dialog_handler(default_action)- Set up auto dialog handlerhandle_dialog(accept, prompt_text)- Handle dialogremove_dialog_handler()- Remove dialog handler
Storage (5 tools):
get_cookies()/set_cookie()/clear_cookies()- Cookie management (expires=0is valid and preserved)save_storage_state(filename)/restore_storage_state(filename)- Session persistence
Verify (6 tools):
verify_text_visible(text)- Check text visibilityverify_element_visible(role, accessible_name)- Check element visibility by role and accessible nameverify_url(expected_url, exact=False)/verify_title(expected_title, exact=False)- URL/title verificationverify_element_state(ref, state)- Check element stateverify_value(ref, value)- Check element value
Developer (8 tools):
start_console_capture()/stop_console_capture()/get_console_messages()- Console monitoringstart_tracing()/stop_tracing()/add_trace_chunk()- Performance tracingstart_video()/stop_video()- Video recording
Lifecycle (2 tools):
close()- Close browserbrowser_resize(width, height)- Resize viewport
| CLI command | SDK tool method |
|---|---|
open |
navigate_to |
search |
search |
info |
get_current_page_info |
reload |
reload_page |
back |
go_back |
forward |
go_forward |
snapshot |
get_snapshot_text |
click |
click_element_by_ref |
fill |
input_text_by_ref |
fill-form |
fill_form |
scroll-to |
scroll_element_into_view_by_ref |
select |
select_dropdown_option_by_ref |
options |
get_dropdown_options_by_ref |
check |
check_checkbox_or_radio_by_ref |
uncheck |
uncheck_checkbox_by_ref |
focus |
focus_element_by_ref |
hover |
hover_element_by_ref |
double-click |
double_click_element_by_ref |
upload |
upload_file_by_ref |
drag |
drag_element_by_ref |
tabs |
get_tabs |
new-tab |
new_tab |
switch-tab |
switch_tab |
close-tab |
close_tab |
eval |
evaluate_javascript |
eval-on |
evaluate_javascript_on_ref |
press |
press_key |
type |
type_text |
key-down |
key_down |
key-up |
key_up |
scroll |
mouse_wheel |
mouse-click |
mouse_click |
mouse-move |
mouse_move |
mouse-drag |
mouse_drag |
mouse-down |
mouse_down |
mouse-up |
mouse_up |
wait |
wait_for |
screenshot |
take_screenshot |
pdf |
save_pdf |
network-start |
start_network_capture |
network |
get_network_requests |
network-stop |
stop_network_capture |
wait-network |
wait_for_network_idle |
dialog-setup |
setup_dialog_handler |
dialog |
handle_dialog |
dialog-remove |
remove_dialog_handler |
cookies |
get_cookies |
cookie-set |
set_cookie |
cookies-clear |
clear_cookies |
storage-save |
save_storage_state |
storage-load |
restore_storage_state |
verify-text |
verify_text_visible |
verify-visible |
verify_element_visible |
verify-url |
verify_url |
verify-title |
verify_title |
verify-state |
verify_element_state |
verify-value |
verify_value |
console-start |
start_console_capture |
console |
get_console_messages |
console-stop |
stop_console_capture |
trace-start |
start_tracing |
trace-chunk |
add_trace_chunk |
trace-stop |
stop_tracing |
video-start |
start_video |
video-stop |
stop_video |
close |
close |
resize |
browser_resize |
The main class for browser automation with automatic launch mode selection:
from bridgic.browser.session import Browser
# Persistent session (default — profile saved to $BRIDGIC_HOME/bridgic-browser/user_data/)
browser = Browser(
headless=True,
viewport={"width": 1600, "height": 900},
)
# Persistent session with custom profile path
browser = Browser(
headless=False,
user_data_dir="./user_data",
stealth=True, # Enabled by default
)
# Ephemeral session (no persistent profile)
browser = Browser(
headless=True,
clear_user_data=True,
)Key Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
headless |
bool | True | Run in headless mode |
viewport |
dict | 1600x900 | Browser viewport size |
user_data_dir |
str/Path | None | Custom path for persistent profile (ignored when clear_user_data=True) |
clear_user_data |
bool | False | If True, use ephemeral session (no profile); if False, use persistent profile |
stealth |
bool/StealthConfig | True | Stealth mode configuration |
cdp |
str | None | Connect to an existing Chrome via CDP (skips launch). Accepts port number, ws:// / wss:// URL, http://host:port, or "auto" — mirrors the CLI --cdp flag. |
auto_follow_popups |
bool | True | When a bridgic-owned page spawns a popup (<a target="_blank"> click, window.open()), automatically move self._page to the popup. Set False to keep the active-page pointer fixed; the popup is still adopted into the owned set. |
channel |
str | None | Browser channel (chrome, msedge, etc.) |
proxy |
dict | None | Proxy settings |
downloads_path |
str/Path | None | Download directory. Priority: explicit value > bridgic-browser.json > (CDP-borrowed CLI only) the CLI client's CWD > ~/Downloads. See Downloads. |
Snapshot: Use get_snapshot(interactive=False, full_page=True) to get an EnhancedSnapshot with .tree (accessibility tree string) and .refs (ref → locator data). By default full_page=True includes all elements regardless of viewport position. Pass interactive=True for clickable/editable elements only (flattened output), or full_page=False to limit to viewport-only elements. Use get_element_by_ref(ref) to get a Playwright Locator from a ref (e.g. "1f79fe5e") for click, fill, etc.
Configure stealth mode for bypassing bot detection:
from bridgic.browser.session import StealthConfig, Browser
# Custom stealth configuration
config = StealthConfig(
enabled=True,
disable_security=False,
)
browser = Browser(stealth=config, headless=False)bridgic preserves the original filename, suppresses the "Save As" dialog, and keeps the API the same across modes. Internally there are two pipelines — DownloadManager for non-CDP / CDP-owned, and CdpDownloadRenamer for CDP-borrowed (page-level CDP routing of setDownloadBehavior(allowAndName)). See CLAUDE.md → Downloads for the full design.
| Caller | Mode | downloads_path explicit |
Effective path |
|---|---|---|---|
CLI (bridgic-browser ...) |
non-CDP | yes | the explicit value |
| CLI | non-CDP | no | ~/Downloads (daemon auto-default) |
| CLI | CDP (--cdp ...) |
yes | the explicit value |
| CLI | CDP | no | the CLI client's working directory at command time (os.getcwd()) — curl -O-style ergonomics |
SDK (Browser(...)) |
non-CDP | yes | the explicit value |
| SDK | non-CDP | no | downloads not captured (Playwright wipes the temp dir on close — pass downloads_path) |
| SDK | CDP (Browser(cdp=...)) |
yes | the explicit value |
| SDK | CDP | no | ~/Downloads (SDK has no CLI CWD hint) |
# Non-CDP (DownloadManager pipeline)
browser = Browser(downloads_path="./downloads", headless=True)
await browser.navigate_to("https://example.com")
# Programmatic access to completed downloads
for f in browser.download_manager.downloaded_files:
print(f"Downloaded: {f.file_name} ({f.file_size} bytes)")
# CDP-borrowed (CdpDownloadRenamer pipeline; downloads land at downloads_path
# with real filenames; download_manager is None — wait_for_download is
# unsupported here).
browser = Browser(cdp="auto", downloads_path="./downloads")Stealth mode is enabled by default and includes:
- Headless mode: 50+ Chrome args + JS init script + Web/Service/Shared Worker injection + CDP UA-CH override. See Anti-Detection for the full coverage list.
- Headed mode: minimal ~11 flags + system Chrome (
channel="chrome") for real TLS authenticity. The main JS init script is skipped entirely so cross-origin iframes (e.g. Cloudflare Turnstile) see unmodified native APIs. See Anti-Detection.
# Stealth is ON by default
browser = Browser() # stealth=True
# Disable stealth if needed
browser = Browser(stealth=False)
# Custom stealth settings
from bridgic.browser.session import create_stealth_config
config = create_stealth_config(
disable_security=True,
)
browser = Browser(stealth=config)SDK and CLI share one structured error protocol.
- Base type:
BridgicBrowserError - Stable fields:
code,message,details,retryable - Behavior subclasses:
InvalidInputError(invalid arguments/user input)StateError(invalid runtime state, e.g. no active page/session)OperationError(operation execution failures)VerificationError(assertion/verification failures)
Why keep a small number of behavior subclasses:
- Lets callers catch by behavior when needed (e.g. retry only
StateError) - Encodes default retry semantics close to the failure source
- Avoids a large, hard-to-maintain class hierarchy while keeping error handling predictable
Daemon protocol is also structured:
- Success:
{"success": true, "result": "..."} - Failure:
{"success": false, "error_code": "...", "result": "...", "data": {...}, "meta": {"retryable": false}}
CLI client converts daemon failures into BridgicBrowserCommandError, and CLI output keeps machine code visible as Error[CODE]: ....
- Python 3.10+
- Playwright 1.57+
- Pydantic 2.11+
Join us to share feedback, ask questions, and keep up with what's new:
- 🐦 Twitter / X: @bridgic
- 💬 Discord: Join our server
MIT License
- Browser Tools Guide – Tool selection, ref vs coordinate, wait strategies, patterns.
- Snapshot and Page State – SnapshotOptions, EnhancedSnapshot, get_snapshot_text, get_element_by_ref.
- API Summary – Session and DownloadManager API reference.
- Known Limitations – Known issues and upstream bugs (e.g. Chrome "Show in Folder" not working).