#!/usr/bin/env python3 # Covers frontend button/API interaction audit: auth, navigation, config modals, # registry/cluster health buttons, chart launch modes, copy action, instances, and mobile overflow. import os from playwright.sync_api import expect, sync_playwright BASE_URL = os.environ.get("FRONTEND_URL", "http://localhost:18080") ADMIN_USER = os.environ.get("ADMIN_USER", "admin") ADMIN_PASS = os.environ["ADMIN_PASS"] def login(page): page.goto(BASE_URL, wait_until="networkidle") if page.locator("input[type='password']").count() == 0: return page.locator("input:not([type='password'])").first.fill(ADMIN_USER) page.locator("input[type='password']").first.fill(ADMIN_PASS) page.get_by_role("button", name="Login").last.click() page.wait_for_url("**/home", timeout=15000) page.wait_for_load_state("networkidle") def click_nav(page, name, index=0): button = page.get_by_role("button", name=name).nth(index) try: button.click(timeout=5000) except Exception: button.evaluate("element => element.click()") page.wait_for_load_state("networkidle") page.wait_for_timeout(400) def click_if_present(locator, timeout=2500): if locator.count() == 0: return False locator.first.click(timeout=timeout) return True def collect_console_error(errors, msg): text = msg.text ignored = [ "Failed to load resource: the server responded with a status of 404", "[LaunchModal] Failed to load values schema", "No chart repositories found in this registry", ] if msg.type == "error" and not any(item in text for item in ignored): errors.append(text) def assert_no_overlay_overflow(page): overflow = page.evaluate( "document.documentElement.scrollWidth > document.documentElement.clientWidth + 2" ) assert not overflow, "page has horizontal overflow" with sync_playwright() as p: browser = p.chromium.launch(headless=True) context = browser.new_context( viewport={"width": 1440, "height": 950}, permissions=["clipboard-read", "clipboard-write"], ) page = context.new_page() errors = [] page.on("pageerror", lambda exc: errors.append(str(exc))) page.on("console", lambda msg: collect_console_error(errors, msg)) page.on("dialog", lambda dialog: dialog.dismiss()) login(page) for label, nav_index, assertion in [ ("Home", 0, "Operations Workbench"), ("Clusters", 0, "Configuration - Clusters"), ("Registries", 0, "Configuration - Registries"), ("Cluster Monitoring", 0, "Cluster"), ("Launch Instance", 0, "Chart Browser"), ("Instances", 0, "Instance"), ]: click_nav(page, label, nav_index) expect(page.locator("body")).to_contain_text(assertion, timeout=15000) assert_no_overlay_overflow(page) click_nav(page, "Registries", 0) page.get_by_role("button", name="Refresh").first.click() page.wait_for_load_state("networkidle") page.get_by_role("button", name="Add Registry").click() expect(page.get_by_text("Add Registry Configuration")).to_be_visible(timeout=5000) page.get_by_role("button", name="Cancel").click() if page.locator("button[title='Edit']").count() > 0: page.locator("button[title='Edit']").first.click() expect(page.get_by_text("Edit Registry Configuration")).to_be_visible(timeout=5000) if page.get_by_role("button", name="Test Connection").count() > 0: page.get_by_role("button", name="Test Connection").click() page.wait_for_timeout(800) page.get_by_role("button", name="Cancel").click() if page.locator("button[title='Delete']").count() > 0: page.locator("button[title='Delete']").first.click() page.wait_for_timeout(300) click_nav(page, "Clusters", 0) page.get_by_role("button", name="Refresh").first.click() page.wait_for_load_state("networkidle") page.get_by_role("button", name="Add Cluster").click() expect(page.get_by_text("Add Cluster Configuration")).to_be_visible(timeout=5000) page.get_by_role("button", name="Cancel").click() if page.locator("button[title='Test Connection']").count() > 0: page.locator("button[title='Test Connection']").first.click() page.wait_for_timeout(800) if page.locator("button[title='Edit']").count() > 0: page.locator("button[title='Edit']").first.click() expect(page.get_by_text("Edit Cluster Configuration")).to_be_visible(timeout=5000) page.get_by_role("button", name="Cancel").click() if page.locator("button[title='Delete']").count() > 0: page.locator("button[title='Delete']").first.click() page.wait_for_timeout(300) click_nav(page, "Launch Instance") expect(page.get_by_text("Chart Browser")).to_be_visible(timeout=15000) page.get_by_role("button", name="Refresh").first.click() page.wait_for_load_state("networkidle") if page.get_by_role("button", name="All tags").count() > 0: page.get_by_role("button", name="All tags").click() page.wait_for_load_state("networkidle") page.get_by_role("button", name="Charts", exact=True).click() page.wait_for_load_state("networkidle") if page.get_by_role("button", name="Copy").count() > 0: page.get_by_role("button", name="Copy").first.click() page.wait_for_timeout(300) if page.get_by_role("button", name="Launch").count() > 0: page.get_by_role("button", name="Launch").first.click() if page.get_by_role("button", name="Cancel").count() > 0: expect(page.get_by_role("heading", name="Launch Instance")).to_be_visible(timeout=10000) expect(page.get_by_role("button", name="Quick")).to_be_visible() page.get_by_role("button", name="YAML").click() page.locator("textarea").last.fill("replicaCount: 1\n") if page.get_by_role("button", name="Guided").is_enabled(): page.get_by_role("button", name="Guided").click() page.get_by_role("button", name="Cancel").click() click_nav(page, "Instances", 0) page.get_by_role("button", name="Refresh").first.click() page.wait_for_load_state("networkidle") if page.get_by_role("button", name="Entries").count() > 0: page.get_by_role("button", name="Entries").first.click() expect(page.locator("body")).to_contain_text("Entries", timeout=5000) click_if_present(page.get_by_role("button", name="Close")) if page.get_by_role("button", name="Modify").count() > 0: page.get_by_role("button", name="Modify").first.click() expect(page.locator("body")).to_contain_text("Modify Instance", timeout=5000) if page.get_by_role("button", name="YAML").count() > 0: page.get_by_role("button", name="YAML").click() page.get_by_role("button", name="Cancel").click() if page.get_by_role("button", name="Delete", exact=True).count() > 0: delete_button = page.get_by_role("button", name="Delete", exact=True).first box = delete_button.bounding_box() viewport = page.viewport_size or {"width": 1440, "height": 950} assert box and box["x"] >= 0 and box["x"] + box["width"] <= viewport["width"], "Delete instance button overflows horizontally" delete_button.click() page.wait_for_timeout(300) mobile = browser.new_page(viewport={"width": 390, "height": 844}, is_mobile=True) mobile.on("pageerror", lambda exc: errors.append(str(exc))) mobile.on("console", lambda msg: collect_console_error(errors, msg)) login(mobile) click_nav(mobile, "Launch Instance") expect(mobile.get_by_text("Chart Browser")).to_be_visible(timeout=15000) assert_no_overlay_overflow(mobile) browser.close() if errors: raise AssertionError("\n".join(errors[:12]))