"""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)