Files
brain-of-reese/tests/e2e/test_tuning_toggle_flash.py
T
ducoterra 6f9e033117 fix(header): ship the tuning toggle hidden — no anonymous flash
#steering-toggle (the header 'Tuning' button) shipped visible in all
six pages and was only removed after /api/whoami resolved, so
anonymous visitors saw it flash for the whole round-trip (TODO.md L3).
It now ships hidden on every page and initSharedHeader unhides it only
for admin — the same ship-hidden / reveal-for-admin contract as the
admin-only nav links; the anonymous end-state (removed from the DOM,
phase-16 'absent, not hidden') is unchanged.

Adds the story E2E suite (MutationObserver proves zero visible frames
for anonymous on every page, admin reveal + panel + count badge,
nav-contract regression) and the source-level unit pins. Also fixes
test_steering.py's BASE_SCRIPT_COUNT (2 → 3: brand.js + markdown.js +
app.js, since phase 39).
2026-08-27 22:38:58 -04:00

254 lines
10 KiB
Python

"""Phase 40 E2E (Playwright): the anonymous "Tuning" flash is gone.
The owner report (``TODO.md`` L3): loading a page briefly showed the
header "Tuning" button (``#steering-toggle``, the steering-notes
toggle) to ANONYMOUS visitors — it shipped VISIBLE in all six pages'
markup and ``assets/header.js`` removed it only after ``/api/whoami``
resolved, so the button flashed for the whole whoami round-trip.
The fix mirrors the admin-only nav links (phase 19/29/35 contract):
the toggle now SHIPS ``hidden`` in every page and ``initSharedHeader``
unhides it only when whoami says admin; the anonymous end-state is
unchanged (toggle + panel REMOVED from the DOM — phase 16 "absent,
not hidden"). This suite proves the browser-level contract:
* a MutationObserver (installed via ``add_init_script`` before any
page code runs) records every frame in which ``#steering-toggle``
is both in the DOM and visible (``offsetParent !== null`` or
``!hidden``) — an anonymous load records ZERO such frames, on
every page, from first paint to the settled state;
* after the whoami round-trip the toggle is ABSENT from the DOM for
anonymous visitors (removed, not hidden);
* the admin UX is untouched (phase 15/34 behavior): the toggle is
revealed, clicking opens ``#steering-panel``
(``aria-expanded="true"``) and the count badge matches the note
list — including a self-check that the observer records the admin
reveal, so the zero-frame anonymous assertions are not vacuous;
* the ship-hidden nav-link contract this phase relies on
(``#nav-sources`` / ``#nav-git-sources`` / ``#nav-tuning``) is
intact: hidden for anonymous, revealed for admin.
Story: ``.agent/user_stories/tuning-toggle-flash.md``
Run in isolation (DB must be up: ``podman compose up -d db``):
uv run pytest tests/e2e/test_tuning_toggle_flash.py -v --no-cov
Test → story mapping (Playwright Mapping Rule):
1. ``test_anonymous_never_sees_toggle``
2. ``test_anonymous_other_pages_never_flash``
3. ``test_admin_toggle_revealed_and_working``
4. ``test_nav_contract_regression``
"""
from __future__ import annotations
from playwright.sync_api import Page, expect
from sqlalchemy import text
from app.db import SessionLocal
from e2e.auth_helpers import login
#: The pages the contract must hold on besides the chat page (mapping
#: rule 2). document.html / git-sources.html are covered by the
#: source-level unit pins (tests/unit/test_steering_toggle_visibility.py)
#: — the four pages here are the ones an anonymous visitor actually
#: lands on.
OTHER_PAGES = ("/sources.html", "/tuning.html", "/login.html")
ADMIN_NOTE = "PHASE40-E2E note — badge check"
#: Admin-only nav links — the ship-hidden / reveal-for-admin family the
#: steering toggle now belongs to (phase 19/29/35 contract).
NAV_IDS = ("#nav-sources", "#nav-git-sources", "#nav-tuning")
#: Runs in every new document BEFORE any page script (addInitScript):
#: arms a MutationObserver over the whole DOM and records every frame
#: in which #steering-toggle is attached AND visible — visible meaning
#: rendered (offsetParent !== null) OR carrying no [hidden] attribute
#: (!el.hidden). A shipped-VISIBLE toggle (the old bug) is recorded the
#: moment the parser inserts it; a shipped-hidden toggle that is later
#: revealed is recorded at the reveal mutation. The array is fresh per
#: document, so each navigation asserts its own frames.
VISIBILITY_OBSERVER_JS = """
window.__tuningVisibleFrames = [];
(() => {
const visible = (el) =>
!!el && el.isConnected && (el.offsetParent !== null || !el.hidden);
const check = () => {
if (visible(document.getElementById("steering-toggle"))) {
window.__tuningVisibleFrames.push({
at: Math.round(performance.now()),
href: location.pathname,
});
}
};
const start = () => {
check();
new MutationObserver(check).observe(document.documentElement, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["hidden"],
});
};
if (document.documentElement) start();
else document.addEventListener("DOMContentLoaded", start);
})();
"""
def install_visibility_observer(page: Page) -> None:
"""Arm the never-visible frame counter on every document this page
creates — the initial load, the post-login redirect, re-gotos."""
page.add_init_script(VISIBILITY_OBSERVER_JS)
def visible_frames(page: Page) -> list[dict[str, object]]:
"""The frames the observer recorded in the CURRENT document."""
return page.evaluate("() => window.__tuningVisibleFrames || []")
def _assert_no_flash(page: Page, path: str) -> None:
"""Zero visible frames + the phase-16 absent end-state, for one
anonymously loaded page."""
frames = visible_frames(page)
assert frames == [], f"{path}: the toggle was visible {len(frames)}x: {frames!r}"
assert page.locator("#steering-toggle").count() == 0, (
f"{path}: #steering-toggle must be REMOVED from the DOM for "
"anonymous (phase 16 'absent, not hidden')"
)
assert page.locator("#steering-panel").count() == 0, (
f"{path}: #steering-panel must be removed together with the toggle"
)
def _wait_header_settled_anonymous(page: Page) -> None:
"""Whoami resolved on the current document: the anonymous state
reveals the Sign in link and keeps Sign out hidden (the pair is
decided by the SAME initSharedHeader pass that removes the
toggle)."""
page.wait_for_load_state("networkidle")
expect(page.locator("#sign-in-link")).to_be_visible(timeout=15_000)
expect(page.locator("#sign-out-btn")).to_be_hidden()
# ---------------------------------------------------------------------------
# 1. Anonymous chat load: zero visible frames, toggle absent afterwards
# ---------------------------------------------------------------------------
def test_anonymous_never_sees_toggle(
page: Page, app_url: str, mock_llm: int, db_ready: None
) -> None:
page.set_default_timeout(30_000)
install_visibility_observer(page)
page.goto(app_url + "/")
_wait_header_settled_anonymous(page)
_assert_no_flash(page, "/")
# ---------------------------------------------------------------------------
# 2. Anonymous loads of the other pages: same zero-flash contract
# ---------------------------------------------------------------------------
def test_anonymous_other_pages_never_flash(
page: Page, app_url: str, mock_llm: int, db_ready: None
) -> None:
page.set_default_timeout(30_000)
install_visibility_observer(page)
for path in OTHER_PAGES:
page.goto(app_url + path)
_wait_header_settled_anonymous(page)
_assert_no_flash(page, path)
# ---------------------------------------------------------------------------
# 3. Admin: revealed, clickable, count badge matches the list
# ---------------------------------------------------------------------------
def test_admin_toggle_revealed_and_working(
page: Page, app_url: str, mock_llm: int, db_ready: None
) -> None:
page.set_default_timeout(30_000)
with SessionLocal() as db:
db.execute(text("TRUNCATE steering_notes"))
db.commit()
install_visibility_observer(page)
login(page, app_url, next="/")
page.wait_for_load_state("networkidle")
expect(page.locator("#sign-out-btn")).to_be_visible(timeout=15_000)
toggle = page.locator("#steering-toggle")
panel = page.locator("#steering-panel")
# Revealed — the [hidden] attribute is gone and the button renders.
expect(toggle).to_be_visible()
assert toggle.get_attribute("hidden") is None
# Observer self-check: the reveal IS a recorded visible frame, so
# the zero-frame anonymous assertions above cannot be vacuous.
assert visible_frames(page) != [], "the admin reveal was not observed"
# Click: the panel opens, aria-expanded tracks it (phase 15/34).
toggle.click()
expect(panel).to_be_visible()
expect(toggle).to_have_attribute("aria-expanded", "true")
# Count badge matches the list — first the empty state…
expect(page.locator("#steering-count")).to_have_text("0")
expect(page.locator("#steering-list .steering-note")).to_have_count(0)
expect(page.locator("#steering-empty")).to_be_visible()
# …then with one note created through the real admin API.
page.evaluate(
"""async (note) => {
const r = await fetch("/api/steering", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({note}),
});
if (!r.ok) throw new Error("steering POST failed: " + r.status);
}""",
ADMIN_NOTE,
)
toggle.click() # close
toggle.click() # re-open (refreshes the list)
expect(panel).to_be_visible()
expect(toggle).to_have_attribute("aria-expanded", "true")
expect(page.locator("#steering-count")).to_have_text("1")
expect(page.locator("#steering-list .steering-note")).to_have_count(1)
expect(page.locator("#steering-list .steering-note-text")).to_have_text(ADMIN_NOTE)
# ---------------------------------------------------------------------------
# 4. Nav-contract regression (phase 19/34): the ship-hidden family the
# toggle now belongs to is intact
# ---------------------------------------------------------------------------
def test_nav_contract_regression(
page: Page, app_url: str, mock_llm: int, db_ready: None
) -> None:
page.set_default_timeout(30_000)
# Anonymous: every admin-only nav link stays hidden on the chat
# page (ships [hidden], never revealed).
page.goto(app_url + "/")
_wait_header_settled_anonymous(page)
for nav in NAV_IDS:
assert page.locator(nav).count() == 1, f"{nav} missing from the chat header"
expect(page.locator(nav)).to_be_hidden()
assert page.locator(nav + "[hidden]").count() == 1, (
f"{nav} must stay [hidden] for anonymous"
)
# Admin: the same links are revealed — the exact contract the
# steering toggle now mirrors.
login(page, app_url, next="/")
page.wait_for_load_state("networkidle")
expect(page.locator("#sign-out-btn")).to_be_visible(timeout=15_000)
for nav in NAV_IDS:
expect(page.locator(nav)).to_be_visible()