Standardize on the .agents/ directory (shared with project skills): phases/, user_stories/, reports/, screenshots/, validate.sh, and phase-sessions/ + pipeline.log all move to .agents/ (git mv preserves history; runtime artifacts move alongside). Updates every reference in AGENTS.md, README.md, .gitignore, app docstrings, and test story headers. Historical KB content in data/ and the runtime pipeline.log transcript are left untouched.
178 lines
7.4 KiB
Python
178 lines
7.4 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.
|
|
|
|
Phase 40 fixed it with the ship-hidden / reveal-for-admin contract
|
|
(the admin-only nav links). The owner then asked for the button to go
|
|
away entirely (2026-08-28): the navbar ``#steering-toggle`` is now
|
|
ABSENT from every page — for the admin AND anonymous — so the
|
|
never-visible contract holds by construction, and note management
|
|
lives on ``/tuning.html``. The anonymous end-state is unchanged: the
|
|
``#steering-panel`` is REMOVED from the DOM (phase 16 "absent, not
|
|
hidden") and ``/api/steering`` is never fetched. This suite proves the
|
|
browser-level contract:
|
|
|
|
* ``#steering-toggle`` is absent from the DOM on every page, for
|
|
anonymous AND admin — nothing can flash, because nothing ships;
|
|
* the anonymous end-state survives the removal: ``#steering-panel``
|
|
is removed from the DOM, not just hidden;
|
|
* the admin UX around the removal is intact: the header panel section
|
|
still ships hidden (never opened from the header anymore), and the
|
|
ship-hidden nav-link contract this phase relies on
|
|
(``#nav-sources`` / ``#nav-git-sources`` / ``#nav-tuning``) holds:
|
|
hidden for anonymous, revealed for admin.
|
|
|
|
Story: ``.agents/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 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_removal.py)
|
|
#: — the three pages here are the other pages an anonymous visitor
|
|
#: actually lands on.
|
|
OTHER_PAGES = ("/sources.html", "/tuning.html", "/login.html")
|
|
|
|
#: Admin-only nav links — the ship-hidden / reveal-for-admin family the
|
|
#: steering toggle belonged to (phase 19/29/35 contract).
|
|
NAV_IDS = ("#nav-sources", "#nav-git-sources", "#nav-tuning")
|
|
|
|
|
|
def _assert_no_toggle(page: Page, path: str) -> None:
|
|
"""The navbar toggle is absent from the DOM for one page — nothing
|
|
to flash, for either role."""
|
|
assert page.locator("#steering-toggle").count() == 0, (
|
|
f"{path}: #steering-toggle must be ABSENT from the DOM "
|
|
"(removed from the navbar at owner request, 2026-08-28)"
|
|
)
|
|
|
|
|
|
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
|
|
steering panel)."""
|
|
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()
|
|
|
|
|
|
def _wait_header_settled_admin(page: Page) -> None:
|
|
"""Whoami resolved for a signed-in admin on the current document."""
|
|
page.wait_for_load_state("networkidle")
|
|
expect(page.locator("#sign-out-btn")).to_be_visible(timeout=15_000)
|
|
expect(page.locator("#sign-in-link")).to_be_hidden()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. Anonymous chat load: zero toggle, panel removed (never visible)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_anonymous_never_sees_toggle(
|
|
page: Page, app_url: str, mock_llm: int, db_ready: None
|
|
) -> None:
|
|
page.set_default_timeout(30_000)
|
|
page.goto(app_url + "/")
|
|
_wait_header_settled_anonymous(page)
|
|
_assert_no_toggle(page, "/")
|
|
# The phase-16 end-state survives the removal: the panel is REMOVED
|
|
# from the DOM, not just hidden.
|
|
assert page.locator("#steering-panel").count() == 0, (
|
|
"/: #steering-panel must be removed from the DOM for anonymous "
|
|
"(phase 16 'absent, not hidden')"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. Anonymous loads of the other pages: same no-toggle 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)
|
|
for path in OTHER_PAGES:
|
|
page.goto(app_url + path)
|
|
_wait_header_settled_anonymous(page)
|
|
_assert_no_toggle(page, path)
|
|
assert page.locator("#steering-panel").count() == 0, (
|
|
f"{path}: #steering-panel must be removed from the DOM for "
|
|
"anonymous (phase 16 'absent, not hidden')"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. Admin: the toggle is gone too — the panel still ships hidden and
|
|
# the nav links are revealed
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
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)
|
|
login(page, app_url, next="/")
|
|
_wait_header_settled_admin(page)
|
|
|
|
# The navbar toggle was removed at owner request (2026-08-28) —
|
|
# absent for the admin as well; nothing in the header can flash
|
|
# it back, because nothing ships it anymore.
|
|
_assert_no_toggle(page, "/")
|
|
# The header panel section survives (kept fresh by the chat page's
|
|
# per-bubble Tune form) but ships hidden — no header control opens
|
|
# it anymore; note management lives on /tuning.html.
|
|
assert page.locator("#steering-panel").count() == 1
|
|
expect(page.locator("#steering-panel")).to_be_hidden()
|
|
# The surviving path to the notes: the admin-only Tuning nav link
|
|
# is revealed.
|
|
expect(page.locator("#nav-tuning")).to_be_visible()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 4. Nav-contract regression (phase 19/34): the ship-hidden family the
|
|
# toggle used to belong 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 mirrored before it was removed.
|
|
login(page, app_url, next="/")
|
|
_wait_header_settled_admin(page)
|
|
for nav in NAV_IDS:
|
|
expect(page.locator(nav)).to_be_visible()
|