PANELITH DOCS Open the interactive docs ↗

Guide · Panelith 0.1.0 · plain HTML edition; the interactive edition has search and live examples

Testing an app

On this page 3 sections
  1. Python: the app through its API
  2. The browser: Playwright against the real shell
  3. Keeping docs honest

Two layers, both cheap: Python tests against the App through FastAPI's test client, and a headless browser against the real shell for the things only a browser can tell you.

Python: the app through its API#

An App is an ordinary object. Build it, register a pane, send events, and assert on the actions that come back; the server mirrors state, so a second event sees what the first one did.

from fastapi.testclient import TestClient
from panelith.server import make_server
from myapp import MyApp

def test_bump():
    c = TestClient(make_server(MyApp()))
    c.get("/api/manifest")                                   # the session cookie
    out = c.post("/api/panes", json={"cid": "c1", "kind": "counter"}).json()
    assert out["state"]["items"][0]["value"] == 0
    out = c.post("/api/panes/c1/event", json={"name": "bump", "payload": {"by": 2}}).json()
    assert out["actions"][0]["state"]["items"][0]["value"] == 2

A blocks kind is even easier: view(state) is a pure function returning dicts, so assert on the dict. panelith manifest module:App is the quick check that a kind, command or list is declared the way you meant.

The browser: Playwright against the real shell#

The framework's own suite (tests/e2e/ in the repository) serves an app on a spare port, drives headless Chromium through the command bar, and asserts no console errors on desktop and phone viewports in both colour schemes. The pattern is small enough to copy:

def open_command(page, title):
    page.keyboard.press("Alt+Shift+Period")
    page.wait_for_selector(".cmdbar-input")
    page.fill(".cmdbar-input", "> " + title)      # keep the sigil, or the list is empty
    page.wait_for_selector(f".cmdbar-row:has-text('{title}')")
    page.keyboard.press("Enter")

def test_about_opens(server, new_page):
    page = new_page(viewport={"width": 1400, "height": 900})
    page.goto(server.url + "/")
    open_command(page, "About")
    page.wait_for_selector(".pane:has-text('Hello')")
    assert page.errors == []

Useful selectors: .pane[data-kind="<kind>"] for a pane by kind, [data-pl="topbar.list:<id>"] and the other spotlight hooks for shell controls, .widget-<name> for a widget body, .blk-* for blocks, .toast for a notice. A new_page fixture that records console errors and pageerror events onto page.errors turns "no red in the console" into an assertion.

Keeping docs honest#

These docs test themselves: every demo= id must exist, every relative link must resolve to a page and heading, every src= fence is filled from inspect.getsource so it cannot drift, and every public symbol must have a reference entry and a docstring. The same idea applies to any app with documentation of its own.