"""Helpers shared by the end-to-end tests: HTTP, sessions, UI waiters.
The suite drives the real server and the built webclient, so the helpers stay
deliberately small: create a session over REST, open it in the browser, type a
prompt, and wait for the tool cards the agent is supposed to produce.
"""
from __future__ import annotations
import json
import os
from urllib import request
# How long one agent turn may take. A real cloud model plans, thinks and calls
# tools, so this is minutes rather than seconds by design.
TURN_TIMEOUT_MS = int(os.environ.get("E2E_TURN_TIMEOUT_MS", "300000"))
# How long the fresh backend may take to answer /health.
SERVER_TIMEOUT = float(os.environ.get("E2E_SERVER_TIMEOUT", "120"))
def post_json(base_url: str, path: str, payload: dict) -> dict:
data = json.dumps(payload).encode()
req = request.Request(
base_url + path,
data=data,
method="POST",
headers={"Content-Type": "application/json"},
)
with request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read().decode())
def create_session(base_url: str, profile: str = "assistant") -> str:
"""Create a session as the anonymous admin (auth is off in the harness)."""
return post_json(base_url, "/sessions", {"profile_id": profile})["session_id"]
def open_session(page, base_url: str, session_id: str) -> None:
page.goto(f"{base_url}/#{session_id}", wait_until="domcontentloaded")
page.wait_for_selector(".input-textarea", timeout=30_000)
def send_message(page, text: str) -> None:
page.fill(".input-textarea", text)
page.press(".input-textarea", "Enter")
def tool_card(page, name: str):
return page.locator(f'.tool-card:has(.tool-name:text-is("{name}"))').first
def wait_for_tools(page, names: list[str], timeout_ms: int) -> None:
"""Wait until every named tool card exists and the turn has finished.
``.msg-meta-row`` is rendered only for a completed assistant message (the
stats/timestamp row), so it is the end-of-turn marker the UI already owns.
"""
for name in names:
page.wait_for_selector(
f'.tool-card:has(.tool-name:text-is("{name}"))',
timeout=timeout_ms,
)
page.wait_for_selector(".msg-meta-row", timeout=timeout_ms)
def assert_ok(card) -> None:
classes = card.get_attribute("class") or ""
assert "is-success" in classes, f"tool card did not succeed: {classes}"
def result_text(card) -> str:
return card.locator(".tool-result").text_content() or ""