Files
zeekay 450f65932d browser teaches the snapshot loop in its description and the rest on help
The tool put ~120 actions in a Literal enum and ~60 parameters in its
schema, all of it in every agent's context, and most of it Playwright
locator builders that returned a count. Three hand-kept lists (the enum,
extension_actions, _zap_method_for) disagreed: snapshot, wait and
select_option routed to the extension under names it did not answer.

ACTIONS is now the one table: each action once, its topic, its usage
line, and the extension method that serves it. The schema carries the
core loop (navigate, snapshot, click, fill, type, press, read,
screenshot, evaluate, wait, tabs, help) with an open `action` string;
help prints the rest by topic, and their parameters travel in `args`.

snapshot, click-by-ref and friends go to the extension's page engine
(hanzo.snapshot / hanzo.act / hanzo.read / hanzo.wait / hanzo.navigate,
screenshot annotate=true to hanzo.annotate). A tree or a page answers as
text; a refusal ("Stale ref @e4 … run snapshot again", "covered by
<div#consent>") is the error, not a success wrapping "ERR:", and is
never retried on Playwright, which has no refs to act on.

Aliases and no-ops are gone: select_option (select), get_inner_text and
get_value (get_text), clear (fill), content (get_html), new_page
(new_tab), press_key, mouse_wheel (scroll), list_browsers and
list_mcp_instances (browsers), use_browser, set_default_browser,
claim/release_browser, the locator/get_by_*/first/last/nth/filter/all
builders, is_hidden, frame, main_frame, on and off.

Claude-Session: https://claude.ai/code/session_01Dc8DXc3fEQCRHUa6XWK4Ku
2026-09-29 13:05:21 -07:00

58 lines
2.0 KiB
Python

"""Playwright-pinned browser tool.
Same action surface as ``BrowserTool`` but with ``backend="playwright"``
forced — never dispatches through the extension or CDP bridge. Use this
when you want deterministic headless automation regardless of whether a
browser extension is connected to this MCP.
The high-level ``browser`` tool auto-detects transport. Calling
``playwright`` is the explicit "I want Playwright, period" path.
"""
from __future__ import annotations
import logging
from typing import Optional
from hanzo_tools.browser.browser_tool import BrowserTool
logger = logging.getLogger(__name__)
class PlaywrightTool(BrowserTool):
"""Browser automation pinned to the Playwright backend.
Skips the in-process ZAP server (extension dispatch) and the legacy
CDP HTTP bridge entirely — every action goes through Playwright's
Chromium driver. Suitable for headless test runs, CI pipelines, and
any context where attaching to a user-controlled browser is wrong.
The action surface (navigate, click, fill, screenshot, expect_*, ...)
is identical to ``BrowserTool`` — only the transport pin differs.
"""
name = "playwright"
def __init__(
self,
headless: bool = True,
cdp_endpoint: Optional[str] = None,
):
# Force backend="playwright" — overrides BROWSER_BACKEND env, ignores
# extension config, skips ZAP/CDP-bridge lifecycle bootstrap.
super().__init__(
headless=headless,
cdp_endpoint=cdp_endpoint,
backend="playwright",
)
@property
def description(self) -> str:
return """Headless Playwright Chromium, never the user's browser: the `browser`
tool's actions, pinned to Playwright for deterministic runs. It has no snapshot
refs (those come from the Hanzo extension through `browser`), so act with CSS
selectors: snapshot answers an aria tree to read them from.
Core: navigate, snapshot, click, fill, type, press, read, screenshot, evaluate,
wait, tabs. action="help" lists the rest; their parameters go in args."""