Files
browser-cli/browser_cli/commands/dom.py
T
daniel156161 0d5c49c19a
Testing / test (push) Failing after 10m21s
refactor: split compat into package, harden serve proxy (v0.9.3)
- 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>
2026-05-03 10:12:55 +02:00

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}")