fd5447cbb9
Testing / remote-protocol-compat (0.9.3) (push) Successful in 42s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 44s
Package Extension / package-extension (push) Successful in 43s
Build & Publish Package / publish (push) Successful in 43s
Testing / test (push) Successful in 45s
Restructure the Python API and internals around composable namespaces and a standalone transport/endpoint layer. Bump to 0.12.0. Python API: - Replace flat methods (b.tabs_list(), b.group_list()) with namespaces: b.nav, b.tabs, b.groups, b.windows, b.dom, b.extract, b.page, b.storage, b.cookies, b.session, b.perf, b.extension. - Shrink browser_cli/__init__.py to a thin composition root; move all behaviour into browser_cli/sdk/ (one module per namespace + factories, base, routing). Internals: - Add browser_cli/transport.py and remote_transport.py to isolate IPC from command logic; client.py now delegates instead of owning transport. - Add browser_cli/endpoints.py for endpoint resolution and browser_cli/errors.py for shared error types. - Extract markdown rendering into browser_cli/markdown.py (out of extract). - Add USER_AGENT to version_manager. Tooling & tests: - Add justfile with common dev tasks. - Update CLI commands and demo to the namespaced API. - Rework tests for the new layout; add test_transport.py and test_refactor_boundaries.py to lock in module boundaries. BREAKING CHANGE: flat API methods are removed in favour of namespaces (e.g. b.tabs_list() -> b.tabs.list(), b.group_list() -> b.groups.list()).
110 lines
3.9 KiB
Python
110 lines
3.9 KiB
Python
"""
|
|
browser-cli Python SDK demo
|
|
----------------------------
|
|
Shows how to manage your running browser from a Python script.
|
|
|
|
Commands are grouped into namespaces on the client: b.tabs, b.nav, b.groups,
|
|
b.dom, b.extract, b.session, ...
|
|
|
|
Run with:
|
|
uv run python examples/demo.py
|
|
"""
|
|
from browser_cli import BrowserCLI
|
|
|
|
b = BrowserCLI()
|
|
|
|
|
|
# ── 1. See what's open ────────────────────────────────────────────────────────
|
|
print("=== Open tabs ===")
|
|
tabs = b.tabs.list()
|
|
for tab in tabs:
|
|
active = " <-- active" if tab.active else ""
|
|
print(f" [{tab.id}] (window {tab.window_id}) {tab.title[:50]}{active}")
|
|
|
|
|
|
# ── 2. Count tabs per domain ──────────────────────────────────────────────────
|
|
print("\n=== Tabs per domain ===")
|
|
from urllib.parse import urlparse
|
|
from collections import Counter
|
|
|
|
domains = Counter(
|
|
urlparse(t.url).netloc
|
|
for t in tabs
|
|
if t.url and not t.url.startswith("chrome")
|
|
)
|
|
for domain, count in domains.most_common(5):
|
|
print(f" {count:>3}x {domain}")
|
|
|
|
|
|
# ── 3. Open a group and add tabs to it ───────────────────────────────────────
|
|
print("\n=== Creating 'demo' tab group ===")
|
|
group = b.groups.create("demo")
|
|
group_id = group.id
|
|
print(f" Created group id: {group_id}")
|
|
|
|
urls = [
|
|
"https://example.com",
|
|
"https://wikipedia.org",
|
|
]
|
|
first_tab_id = None
|
|
for url in urls:
|
|
b.nav.open(url, background=True)
|
|
# find the tab we just opened and move it into the group
|
|
fresh = b.tabs.list()
|
|
new_tab = next((t for t in reversed(fresh) if t.url.startswith(url[:20])), None)
|
|
if new_tab:
|
|
b.tabs.move(new_tab.id, group_id=group_id)
|
|
print(f" Added {url} → tab {new_tab.id}")
|
|
if first_tab_id is None:
|
|
first_tab_id = new_tab.id
|
|
|
|
# Activate the first opened tab so DOM/extract have a real page to work with
|
|
if first_tab_id:
|
|
b.tabs.activate(first_tab_id)
|
|
print(f" Switched to tab {first_tab_id} for DOM demo")
|
|
|
|
|
|
# ── 4. Extract links from the active tab ─────────────────────────────────────
|
|
# Re-fetch tabs so we have the current active tab (it may have changed)
|
|
current_tabs = b.tabs.list()
|
|
active_tab = next((t for t in current_tabs if t.active), None)
|
|
active_url = active_tab.url if active_tab else ""
|
|
scriptable = active_url.startswith("http://") or active_url.startswith("https://")
|
|
|
|
print("\n=== Links on active tab ===")
|
|
if not scriptable:
|
|
print(f" Skipped — active tab is {active_url!r} (not a web page)")
|
|
else:
|
|
links = b.extract.links()
|
|
for link in links[:5]:
|
|
print(f" {link['text'][:30]:<32} {link['href'][:60]}")
|
|
if len(links) > 5:
|
|
print(f" ... and {len(links) - 5} more")
|
|
|
|
|
|
# ── 5. Check if an element exists, then read its text ────────────────────────
|
|
print("\n=== DOM: page heading ===")
|
|
if not scriptable:
|
|
print(f" Skipped — active tab is {active_url!r} (not a web page)")
|
|
elif b.dom.exists("h1"):
|
|
headings = b.dom.text("h1")
|
|
for h in headings:
|
|
print(f" <h1> {h}")
|
|
else:
|
|
print(" No <h1> found on active tab")
|
|
|
|
|
|
# ── 6. Save current session and show how to restore ──────────────────────────
|
|
print("\n=== Saving session as 'demo-session' ===")
|
|
b.session.save("demo-session")
|
|
print(" Saved current session")
|
|
print(" Restore later with: b.session.load('demo-session')")
|
|
|
|
|
|
# ── 7. Clean up: close duplicate tabs ────────────────────────────────────────
|
|
print("\n=== Closing duplicate tabs ===")
|
|
closed = b.tabs.close_duplicates()
|
|
print(f" Closed {closed} duplicate tab(s)")
|
|
|
|
print("\nDone!")
|