- dashboard.html: each section sits in OPTIONAL:<name> BEGIN/END blocks in the style, markup and script, with sample answers so a page with no bot shows it working. Core change: setUser records the role on <body>, so CSS hides admin-only parts from operators. - templates/sections.py keeps the chosen sections and deletes the rest, markers included; it stops on a broken marker. - templates/sections.md: what each section needs from the host (events, actions, desktop slot snippets, the role re-check, the login hand-off). - SKILL.md step 5 asks one multi-select question and runs sections.py. - Page check: checkpoint states, admin visibility by role and assign/remove, login theme and hand-off to auth or the stub. Cases skip on a generated page without the section; the template must carry all three. The harness signs in when the page has a login screen. - tests/test_sections.py: no sections leave no trace, and that page still passes the page check. - Eval runner: a-empty-sections variant (all three) and per-section checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
147 lines
5.7 KiB
Python
147 lines
5.7 KiB
Python
"""Optional sections (see templates/sections.md). On a generated page, a case skips when the
|
|
page was built without its section. The template itself must carry every section."""
|
|
import os
|
|
import re
|
|
|
|
import pytest
|
|
from playwright.sync_api import expect
|
|
from test_contract import colour_of, open_tab
|
|
|
|
|
|
def need(locator, section):
|
|
if not locator.count():
|
|
if not os.environ.get("PAGE_CHECK_HTML"):
|
|
pytest.fail(f"the template has no {section}")
|
|
pytest.skip(f"page built without the {section}")
|
|
return locator
|
|
|
|
|
|
def fits_width(page, locator):
|
|
box = locator.bounding_box()
|
|
return box["x"] >= 0 and box["x"] + box["width"] <= page.viewport_size["width"]
|
|
|
|
|
|
CP_SHOWS = {"pending": ("Pending", "grey"), "active": ("In progress", "amber"),
|
|
"done": ("Done", "green"), "stuck": ("Stuck", "red")}
|
|
|
|
|
|
def test_checkpoint_boxes_show_each_state(page, push):
|
|
track = need(page.get_by_role("list", name="Checkpoints", include_hidden=True), "checkpoint tracker")
|
|
open_tab(page, "Run")
|
|
boxes = track.get_by_role("listitem")
|
|
n = boxes.count()
|
|
assert n >= 2, "the tracker needs at least two checkpoints"
|
|
|
|
def expect_boxes(*kinds):
|
|
for i, kind in enumerate(kinds):
|
|
label, colour = CP_SHOWS[kind]
|
|
expect(boxes.nth(i)).to_have_text(re.compile(rf"^\s*{i + 1}(?!\d).*{label}\s*$"))
|
|
assert colour_of(boxes.nth(i)) == colour, (i + 1, kind)
|
|
|
|
expect_boxes(*["pending"] * n)
|
|
push("cpstate", {"kind": "active", "cp": 2}) # earlier boxes are done, later ones pending
|
|
expect_boxes("done", "active", *["pending"] * (n - 2))
|
|
push("cpstate", {"kind": "stuck", "cp": 2})
|
|
expect_boxes("done", "stuck")
|
|
push("cpstate", {"kind": "done", "cp": 2})
|
|
expect_boxes("done", "done")
|
|
push("cpstate", {"kind": "pending", "cp": 2})
|
|
expect_boxes("done", "pending")
|
|
assert fits_width(page, boxes.last)
|
|
|
|
|
|
ADA = {"username": "ada", "emp_id": "E1", "role": "admin"}
|
|
OLIVE = {"username": "olive", "emp_id": "E2", "role": "operator"}
|
|
|
|
|
|
def admin_nav(page):
|
|
return need(page.get_by_role("navigation").get_by_role("button", name="Admin", exact=True, include_hidden=True),
|
|
"admin screen")
|
|
|
|
|
|
def test_admin_screen_only_for_admins(page, push):
|
|
nav = admin_nav(page)
|
|
push("set_user", OLIVE)
|
|
expect(nav).to_be_hidden()
|
|
push("set_user", ADA)
|
|
expect(nav).to_be_visible()
|
|
nav.click()
|
|
users = page.get_by_role("table", name="Users with access")
|
|
push("admin_data", {"rows": [ADA, OLIVE]})
|
|
rows = users.locator("tbody").get_by_role("row")
|
|
expect(rows).to_have_count(2)
|
|
expect(rows.nth(1)).to_contain_text("olive")
|
|
expect(rows.nth(1)).to_contain_text(re.compile("operator", re.I))
|
|
|
|
push("set_user", OLIVE) # operators see neither the nav item nor the screen
|
|
expect(nav).to_be_hidden()
|
|
expect(users).to_be_hidden()
|
|
|
|
|
|
def test_admin_assigns_and_removes_roles(page, push):
|
|
nav = admin_nav(page)
|
|
push("set_user", ADA)
|
|
nav.click()
|
|
sample = page.evaluate("SAMPLE_DATA")
|
|
form = page.get_by_role("form", name="Assign access")
|
|
form.get_by_role("textbox", name="Username or employee ID").fill("grace")
|
|
form.get_by_role("combobox", name="Role").select_option("admin")
|
|
form.get_by_role("button", name="Assign").click()
|
|
rows = page.get_by_role("table", name="Users with access").locator("tbody").get_by_role("row")
|
|
if not sample: # wired page: the host answers with fresh admin_data
|
|
assert ["assign_role", "grace", "admin"] in page.evaluate("window.__calls")
|
|
push("admin_data", {"rows": [ADA, {"username": "grace", "emp_id": "", "role": "admin"}]})
|
|
grace = rows.filter(has_text="grace")
|
|
expect(grace).to_contain_text(re.compile("admin", re.I))
|
|
|
|
grace.get_by_role("button", name="Remove").click()
|
|
if not sample:
|
|
assert ["remove_role", "grace"] in page.evaluate("window.__calls")
|
|
push("admin_data", {"rows": [ADA]})
|
|
expect(grace).to_have_count(0)
|
|
|
|
|
|
def login_form(page):
|
|
return need(page.get_by_role("form", name="Sign in", include_hidden=True), "login page")
|
|
|
|
|
|
def sign_out(page):
|
|
page.get_by_role("button", name="Sign out", exact=True).click()
|
|
|
|
|
|
def light_text(locator):
|
|
rgb = re.findall(r"\d+", locator.evaluate("el => getComputedStyle(el).color"))[:3]
|
|
return sum(int(x) for x in rgb) / 3 > 128
|
|
|
|
|
|
def test_login_page_renders_in_the_theme(page, request):
|
|
form = login_form(page)
|
|
sign_out(page)
|
|
expect(form).to_be_visible()
|
|
assert fits_width(page, form)
|
|
assert light_text(form) == (request.node.callspec.params["page"] == "dark")
|
|
expect(form.get_by_role("button", name="Sign in")).to_be_visible()
|
|
|
|
|
|
def test_login_hands_off_to_auth_or_stub(page, push):
|
|
form = login_form(page)
|
|
sample = page.evaluate("SAMPLE_DATA")
|
|
sign_out(page)
|
|
form.get_by_role("textbox", name="Username").fill("alice")
|
|
form.get_by_label("Password").fill("secret")
|
|
form.get_by_role("button", name="Sign in").click()
|
|
if sample: # no auth yet: the stub signs anyone in
|
|
expect(form).to_be_hidden()
|
|
expect(page.get_by_text("alice", exact=True)).to_have_count(1)
|
|
sign_out(page)
|
|
else: # the host checks it with the project's auth
|
|
calls = page.evaluate("window.__calls")
|
|
assert ["logout"] in calls and ["login", "alice", "secret"] in calls
|
|
|
|
push("login_result", {"ok": False, "msg": "Wrong password"})
|
|
expect(form.get_by_role("alert")).to_have_text("Wrong password")
|
|
expect(form).to_be_visible()
|
|
push("login_result", {"ok": True, "user": {"username": "bob", "emp_id": "7", "role": "operator"}})
|
|
expect(form).to_be_hidden()
|
|
expect(page.get_by_text("bob", exact=True)).to_have_count(1)
|