feat: add browser automation commands (v0.6.0)
Testing / test (push) Successful in 24s
Package Extension / package-extension (push) Successful in 9s
Build & Publish Package / publish (push) Successful in 21s

Navigation: open-wait (open + block until loaded)
DOM: key, hover, check/uncheck, clear, focus, submit, poll, scroll, select, eval, wait-for
Tabs: pin/unpin, screenshot, watch-url (block until URL matches regex)
New command groups: page info, storage get/set, cookies list/get/set
Extension: add cookies permission
This commit is contained in:
2026-04-16 14:21:19 +02:00
parent fc4ce8f74d
commit 1c5fd0ffee
11 changed files with 922 additions and 9 deletions
+52
View File
@@ -1,3 +1,4 @@
import base64
import click
from browser_cli.client import BrowserNotConnected, active_browser_targets, send_command
from rich.console import Console
@@ -236,3 +237,54 @@ def tabs_unmute(tab_id):
result = _handle("tabs.unmute", {"tabId": tab_id})
target = result.get("tabId", tab_id) if isinstance(result, dict) else tab_id
console.print(f"[green]Unmuted tab {target}[/green]")
@tabs_group.command("pin")
@click.argument("tab_id", type=int, required=False)
def tabs_pin(tab_id):
"""Pin the active tab or a specific tab."""
result = _handle("tabs.pin", {"tabId": tab_id})
target = result.get("tabId", tab_id) if isinstance(result, dict) else tab_id
console.print(f"[green]Pinned tab {target}[/green]")
@tabs_group.command("unpin")
@click.argument("tab_id", type=int, required=False)
def tabs_unpin(tab_id):
"""Unpin the active tab or a specific tab."""
result = _handle("tabs.unpin", {"tabId": tab_id})
target = result.get("tabId", tab_id) if isinstance(result, dict) else tab_id
console.print(f"[green]Unpinned tab {target}[/green]")
@tabs_group.command("watch-url")
@click.argument("pattern")
@click.option("--tab", "tab_id", type=int, default=None, help="Tab ID (default: active tab)")
@click.option("--timeout", type=float, default=30.0, show_default=True, help="Max seconds to wait")
def tabs_watch_url(pattern, tab_id, timeout):
"""Wait until the active (or specified) tab URL matches regex PATTERN."""
result = _handle("tabs.watch_url", {"pattern": pattern, "tabId": tab_id, "timeout": int(timeout * 1000)})
url = result.get("url", "") if isinstance(result, dict) else ""
console.print(f"[green]URL matched:[/green] {url}")
@tabs_group.command("screenshot")
@click.argument("output", required=False, metavar="FILE")
@click.option("--tab", "tab_id", type=int, default=None, help="Tab ID (default: active tab)")
@click.option("--format", "fmt", type=click.Choice(["png", "jpeg"]), default="png", show_default=True)
@click.option("--quality", type=int, default=None, help="JPEG quality 0-100")
def tabs_screenshot(output, tab_id, fmt, quality):
"""Capture a screenshot of the active (or specified) tab.
Saves to FILE if given, otherwise prints the base64 data URL.
"""
result = _handle("tabs.screenshot", {"tabId": tab_id, "format": fmt, "quality": quality})
data_url = result.get("dataUrl", "") if isinstance(result, dict) else ""
if output:
header = f"data:image/{fmt};base64,"
raw = base64.b64decode(data_url[len(header):])
with open(output, "wb") as f:
f.write(raw)
console.print(f"[green]Screenshot saved:[/green] {output}")
else:
console.print(data_url)