"""Browser regression test for Bliss template saves inside an hx-get child page. Run with: .venv/bin/python tests/test_inspector_sheepgpt.py """ from __future__ import annotations import re from dataclasses import dataclass, field from urllib.parse import urlparse from playwright.sync_api import Page, Request, sync_playwright BASE_URL = "https://brb.city" SHEEPGPT_URL = f"{BASE_URL}/sheepgpt/" MESSAGE_RAW_TEMPLATE_ID = "28" @dataclass class NetworkTrace: requests: list[tuple[str, str]] = field(default_factory=list) def record(self, request: Request) -> None: self.requests.append((request.method, urlparse(request.url).path)) def after(self, start: int) -> list[tuple[str, str]]: return self.requests[start:] class BlissInspector: """Small page-object API for the inspector's normal user workflow.""" def __init__(self, page: Page) -> None: self.page = page def open_slideout(self) -> None: self.page.get_by_role("button", name="🔍").click() self.page.locator("#bliss-live-editor").wait_for() def open_template(self, name: str, template_id: str) -> None: self.page.get_by_role("button", name=re.compile(rf"^{re.escape(name)}(?: |$)")).click() self.page.locator( f'[data-bliss-artifact-editor][data-kind="template"][data-id="{template_id}"]' ).wait_for() def save_template(self) -> None: self.page.locator("[data-bliss-artifact-editor] [data-save]").click() def main() -> None: with sync_playwright() as playwright: browser = playwright.chromium.launch() page = browser.new_page() trace = NetworkTrace() page.on("request", trace.record) page.goto(SHEEPGPT_URL, wait_until="networkidle") inspector = BlissInspector(page) inspector.open_slideout() inspector.open_template("message-raw", MESSAGE_RAW_TEMPLATE_ID) chat_before = page.locator("#chat").element_handle() assert chat_before is not None, "the embedded chat did not render" start = len(trace.requests) with page.expect_response(re.compile(r"/_bliss/render-template$")) as rendered: inspector.save_template() render_response = rendered.value print(f"template render response: {render_response.status} {render_response.url}") print(f"template render body: {render_response.request.post_data}") print("requests after message-raw save:") for method, path in trace.after(start): print(f" {method} {path}") assert render_response.status == 200, render_response.text() page.wait_for_timeout(200) requests = trace.after(start) # Saving a child template must never replay the outer sheepgpt route. assert ("GET", "/sheepgpt/") not in requests, requests # The embedded chat itself must stay mounted; only matching template # instances should be patched. assert page.locator("#chat").element_handle() == chat_before assert page.locator("#bliss-live-editor").is_visible() browser.close() if __name__ == "__main__": main()