0d5c49c19a
Testing / test (push) Failing after 10m21s
- compat.py → compat/ package: auth.py (auth-field normalizers), commands.py (command-format shims), __init__.py (re-exports) - Add _auth_0_9_3 transformer: normalizes pubkey to lowercase before auth so clients < 0.9.3 sending uppercase hex are accepted - adapt_auth() now called before auth check in serve.py; command extracted after adapt_auth so future transformers can rename commands safely - serve.py: deduplicate _recv_exact (import from client), unify resp/resp_payload across Windows/Unix branches, require lowercase hex pubkey (re.fullmatch), reorganize imports, drop unused os import - client.py: move payload/framed construction inside branches (remote path no longer serializes JSON it never uses); fix _is_valid_key_spec operator precedence; import MAX_MSG_BYTES from version_manager - auth.py: narrow except clause (ValueError instead of bare Exception) - Bump version 0.9.2 → 0.9.3 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
209 lines
7.4 KiB
Python
209 lines
7.4 KiB
Python
import click
|
|
from browser_cli.commands import _handle
|
|
from rich.console import Console
|
|
from rich.table import Table
|
|
import json
|
|
|
|
console = Console()
|
|
|
|
|
|
@click.group("dom")
|
|
def dom_group():
|
|
"""Query and interact with page DOM elements."""
|
|
|
|
|
|
@dom_group.command("query")
|
|
@click.argument("selector")
|
|
def dom_query(selector):
|
|
"""Return elements matching CSS SELECTOR (like mini DevTools)."""
|
|
elements = _handle("dom.query", {"selector": selector})
|
|
if not elements:
|
|
console.print("[yellow]No elements found[/yellow]")
|
|
return
|
|
table = Table(show_header=True, header_style="bold cyan")
|
|
table.add_column("Tag", width=12)
|
|
table.add_column("Text", width=40)
|
|
table.add_column("Attributes")
|
|
for el in elements:
|
|
attrs = ", ".join(f"{k}={v!r}" for k, v in (el.get("attrs") or {}).items())
|
|
table.add_row(el.get("tag", ""), (el.get("text") or "")[:60], attrs[:80])
|
|
console.print(table)
|
|
|
|
|
|
@dom_group.command("click")
|
|
@click.argument("selector")
|
|
def dom_click(selector):
|
|
"""Click the first element matching CSS SELECTOR."""
|
|
_handle("dom.click", {"selector": selector})
|
|
console.print(f"[green]Clicked:[/green] {selector}")
|
|
|
|
|
|
@dom_group.command("type")
|
|
@click.argument("selector")
|
|
@click.argument("text")
|
|
def dom_type(selector, text):
|
|
"""Type TEXT into the element matching CSS SELECTOR."""
|
|
_handle("dom.type", {"selector": selector, "text": text})
|
|
console.print(f"[green]Typed into:[/green] {selector}")
|
|
|
|
|
|
@dom_group.command("attr")
|
|
@click.argument("selector")
|
|
@click.argument("attr_name")
|
|
def dom_attr(selector, attr_name):
|
|
"""Get attribute ATTR_NAME from elements matching CSS SELECTOR."""
|
|
values = _handle("dom.attr", {"selector": selector, "attr": attr_name})
|
|
for v in (values or []):
|
|
console.print(v)
|
|
|
|
|
|
@dom_group.command("text")
|
|
@click.argument("selector")
|
|
def dom_text(selector):
|
|
"""Get text content of elements matching CSS SELECTOR."""
|
|
values = _handle("dom.text", {"selector": selector})
|
|
for v in (values or []):
|
|
console.print(v)
|
|
|
|
|
|
@dom_group.command("exists")
|
|
@click.argument("selector")
|
|
def dom_exists(selector):
|
|
"""Check if an element matching CSS SELECTOR exists on the page."""
|
|
exists = _handle("dom.exists", {"selector": selector})
|
|
if exists:
|
|
console.print(f"[green]exists[/green]: {selector}")
|
|
else:
|
|
console.print(f"[red]not found[/red]: {selector}")
|
|
raise SystemExit(1)
|
|
|
|
|
|
@dom_group.command("scroll")
|
|
@click.argument("selector", required=False)
|
|
@click.option("--x", type=int, default=None, help="Horizontal scroll position (px)")
|
|
@click.option("--y", type=int, default=None, help="Vertical scroll position (px)")
|
|
def dom_scroll(selector, x, y):
|
|
"""Scroll to a CSS SELECTOR or to an X/Y coordinate."""
|
|
_handle("dom.scroll", {"selector": selector, "x": x, "y": y})
|
|
target = selector or f"({x or 0}, {y or 0})"
|
|
console.print(f"[green]Scrolled to:[/green] {target}")
|
|
|
|
|
|
@dom_group.command("select")
|
|
@click.argument("selector")
|
|
@click.argument("value")
|
|
def dom_select(selector, value):
|
|
"""Set the VALUE of a <select> dropdown matching CSS SELECTOR."""
|
|
_handle("dom.select", {"selector": selector, "value": value})
|
|
console.print(f"[green]Selected '{value}' in:[/green] {selector}")
|
|
|
|
|
|
@dom_group.command("eval")
|
|
@click.argument("code")
|
|
@click.option("--tab", "tab_id", type=int, default=None, help="Tab ID (default: active tab)")
|
|
def dom_eval(code, tab_id):
|
|
"""Evaluate JavaScript CODE in the page and print the result."""
|
|
result = _handle("dom.eval", {"code": code, "tabId": tab_id})
|
|
if result is None:
|
|
console.print("[dim]null[/dim]")
|
|
else:
|
|
console.print(json.dumps(result, indent=2) if isinstance(result, (dict, list)) else str(result))
|
|
|
|
|
|
@dom_group.command("wait-for")
|
|
@click.argument("selector")
|
|
@click.option("--timeout", type=float, default=10.0, show_default=True, help="Max seconds to wait")
|
|
@click.option("--visible", is_flag=True, help="Wait until element is visible (non-zero size)")
|
|
@click.option("--hidden", is_flag=True, help="Wait until element is absent or hidden")
|
|
@click.option("--tab", "tab_id", type=int, default=None, help="Tab ID (default: active tab)")
|
|
def dom_wait_for(selector, timeout, visible, hidden, tab_id):
|
|
"""Wait until CSS SELECTOR appears (or disappears) in the DOM."""
|
|
_handle("dom.wait_for", {
|
|
"selector": selector,
|
|
"timeout": int(timeout * 1000),
|
|
"visible": visible,
|
|
"hidden": hidden,
|
|
"tabId": tab_id,
|
|
})
|
|
state = "hidden" if hidden else ("visible" if visible else "present")
|
|
console.print(f"[green]Ready ({state}):[/green] {selector}")
|
|
|
|
|
|
@dom_group.command("key")
|
|
@click.argument("key")
|
|
@click.option("--selector", default=None, help="CSS selector to target (default: focused element)")
|
|
def dom_key(key, selector):
|
|
"""Dispatch a keyboard KEY event (e.g. Enter, Tab, Escape, ArrowDown)."""
|
|
_handle("dom.key", {"key": key, "selector": selector})
|
|
target = selector or "active element"
|
|
console.print(f"[green]Key '{key}' sent to:[/green] {target}")
|
|
|
|
|
|
@dom_group.command("hover")
|
|
@click.argument("selector")
|
|
def dom_hover(selector):
|
|
"""Dispatch mouseover/mouseenter on the element matching CSS SELECTOR."""
|
|
_handle("dom.hover", {"selector": selector})
|
|
console.print(f"[green]Hovered:[/green] {selector}")
|
|
|
|
|
|
@dom_group.command("check")
|
|
@click.argument("selector")
|
|
def dom_check(selector):
|
|
"""Check a checkbox matching CSS SELECTOR."""
|
|
_handle("dom.check", {"selector": selector})
|
|
console.print(f"[green]Checked:[/green] {selector}")
|
|
|
|
|
|
@dom_group.command("uncheck")
|
|
@click.argument("selector")
|
|
def dom_uncheck(selector):
|
|
"""Uncheck a checkbox matching CSS SELECTOR."""
|
|
_handle("dom.uncheck", {"selector": selector})
|
|
console.print(f"[green]Unchecked:[/green] {selector}")
|
|
|
|
|
|
@dom_group.command("clear")
|
|
@click.argument("selector")
|
|
def dom_clear(selector):
|
|
"""Clear the value of an input matching CSS SELECTOR."""
|
|
_handle("dom.clear", {"selector": selector})
|
|
console.print(f"[green]Cleared:[/green] {selector}")
|
|
|
|
|
|
@dom_group.command("focus")
|
|
@click.argument("selector")
|
|
def dom_focus(selector):
|
|
"""Focus the element matching CSS SELECTOR."""
|
|
_handle("dom.focus", {"selector": selector})
|
|
console.print(f"[green]Focused:[/green] {selector}")
|
|
|
|
|
|
@dom_group.command("submit")
|
|
@click.argument("selector")
|
|
def dom_submit(selector):
|
|
"""Submit the form that contains the element matching CSS SELECTOR."""
|
|
_handle("dom.submit", {"selector": selector})
|
|
console.print(f"[green]Submitted form for:[/green] {selector}")
|
|
|
|
|
|
@dom_group.command("poll")
|
|
@click.argument("selector")
|
|
@click.argument("pattern")
|
|
@click.option("--attr", default=None, help="Attribute or property to read (default: textContent/value)")
|
|
@click.option("--timeout", type=float, default=30.0, show_default=True, help="Max seconds to wait")
|
|
@click.option("--interval", type=float, default=0.5, show_default=True, help="Poll interval in seconds")
|
|
@click.option("--tab", "tab_id", type=int, default=None, help="Tab ID (default: active tab)")
|
|
def dom_poll(selector, pattern, attr, timeout, interval, tab_id):
|
|
"""Poll SELECTOR until its text/value matches regex PATTERN."""
|
|
result = _handle("dom.poll", {
|
|
"selector": selector,
|
|
"pattern": pattern,
|
|
"attr": attr,
|
|
"timeout": int(timeout * 1000),
|
|
"interval": int(interval * 1000),
|
|
"tabId": tab_id,
|
|
})
|
|
value = result.get("value", "") if isinstance(result, dict) else ""
|
|
console.print(f"[green]Matched:[/green] {selector!r} = {value!r}")
|