Test interactive Prefab UIs without a browser using the headless Simulator.
The Simulator API predates Prefab’s current architecture. It was written when every UI originated as an MCP tool call, and its invoke / handler model reflects that assumption. Expect this API to change significantly as we update it to support Prefab’s broader usage patterns.
Prefab UIs are declarative — they’re JSON trees with state and actions. The Simulator reimplements the renderer’s client-side loop in Python, so you can test the full interaction cycle (load UI → find components → simulate clicks → assert on state) entirely in pytest.
The Simulator takes an action handler — a function that responds to toolCall actions the same way your server tools would.
Copy
Ask AI
from prefab_ui.testing import Simulator, ActionResultasync def handler(name: str, arguments: dict) -> ActionResult: if name == "search_users": query = arguments.get("q", "") users = [{"name": "Alice", "email": "[email protected]"}] results = [u for u in users if query.lower() in u["name"].lower()] return ActionResult(content={ "state": {"results": results}, "view": {"type": "Text", "content": "Found users"}, }) return ActionResult(is_error=True, error_text=f"Unknown tool: {name}")sim = Simulator(handler)
ActionResult wraps a tool’s response. Set is_error=True with error_text for failures. On success, content should be a dict matching the Prefab envelope structure (state, view).
# Find the first match (raises ComponentNotFoundError if missing)button = sim.find("Button", label="Search")input_field = sim.find("Input", name="query")# Find all matchescards = sim.find_all("Card")buttons = sim.find_all("Button", variant="destructive")
The returned values are raw JSON dicts (the component nodes from the tree), which you can inspect or pass to interaction methods.
Three interaction methods mirror what a user does in the browser:
Copy
Ask AI
# Click a button — executes its onClick action chainawait sim.click(button)# Type into an input — sets the value, syncs named state, runs onChangeawait sim.set_value(input_field, "alice")# Submit a form — executes its onSubmit action chainform = sim.find("Form")await sim.submit(form)
Each method resolves {{ templates }} against current state, executes actions (including toolCall round-trips through your handler), handles onSuccess/onError callbacks, and updates sim.state with results.
After interactions, check sim.state for expected values:
Copy
Ask AI
# State was seeded by invokeassert sim.state["query"] == ""# set_value auto-synced the named inputawait sim.set_value(input_field, "alice")assert sim.state["query"] == "alice"# toolCall wrote results via resultKeyawait sim.click(button)assert len(sim.state["results"]) == 1assert sim.state["results"][0]["name"] == "Alice"