From 6181de8071a74e3ad1b4bf0c3bc6e56c8fab275d Mon Sep 17 00:00:00 2001 From: emmaaroche Date: Thu, 5 Feb 2026 17:14:56 +0000 Subject: [PATCH] refactor: Update login flow for UI tests Signed-off-by: emmaaroche --- pyproject.toml | 1 + testsuite/kubernetes/client.py | 5 + .../ui/console_plugin/conftest.py | 109 ++++++++++++++++++ .../ui/console_plugin/policies/conftest.py | 58 ---------- 4 files changed, 115 insertions(+), 58 deletions(-) create mode 100644 testsuite/tests/singlecluster/ui/console_plugin/conftest.py delete mode 100644 testsuite/tests/singlecluster/ui/console_plugin/policies/conftest.py diff --git a/pyproject.toml b/pyproject.toml index 94afe1ed..b9e6f11b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,7 @@ markers = [ "coredns_two_primaries: CoreDNS multicluster tests using two primary clusters", "defaults_overrides: Defaults and Overrides feature tests", "observability: Tracing, metrics and logging tests", + "ui: Test uses browser automation via Playwright to test the console plugin UI", ] filterwarnings = [ "ignore: WARNING the new order is not taken into account:UserWarning", diff --git a/testsuite/kubernetes/client.py b/testsuite/kubernetes/client.py index b0f18fec..ebda095f 100644 --- a/testsuite/kubernetes/client.py +++ b/testsuite/kubernetes/client.py @@ -66,6 +66,11 @@ def apps_url(self): hostname = urlparse(self.api_url).hostname return "apps." + hostname.split(".", 1)[1] + @cached_property + def console_url(self): + """Return URL of the OpenShift console""" + return f"https://console-openshift-console.{self.apps_url}" + @property def project(self): """Returns real Kubernetes namespace name""" diff --git a/testsuite/tests/singlecluster/ui/console_plugin/conftest.py b/testsuite/tests/singlecluster/ui/console_plugin/conftest.py new file mode 100644 index 00000000..a3301424 --- /dev/null +++ b/testsuite/tests/singlecluster/ui/console_plugin/conftest.py @@ -0,0 +1,109 @@ +"""Conftest for UI tests""" + +import os +import tempfile + +import pytest +from httpx import Timeout + +from testsuite.gateway.exposers import OpenShiftExposer +from testsuite.page_objects.nav_bar import NavBar +from testsuite.page_objects.navigator import Navigator + + +@pytest.fixture(scope="session") +def auth_state_file(browser, cluster, testconfig, request): + """Login once and save authentication state (cookies, etc.) to a file for reuse""" + # Create temporary file for auth state + state_file = tempfile.NamedTemporaryFile() # pylint: disable=consider-using-with + + # Register cleanup to close and auto-delete file after session ends + request.addfinalizer(state_file.close) + + # Create temporary context for login + temp_context = browser.new_context() + page = temp_context.new_page() + + # Perform login + page.goto(cluster.console_url, timeout=60000) + page.locator("//a[@title='Log in with HTPasswd']").click() + + username = testconfig.get("console.username") or os.getenv("KUBE_USER", "admin") + password = testconfig.get("console.password") or os.getenv("KUBE_PASSWORD") + + page.locator("//input[@name='username']").fill(username) + page.locator("//input[@name='password']").fill(password) + page.locator("//button[@type='submit']").click() + + # Wait for successful login by checking URL changed from auth page + page.wait_for_url(f"{cluster.console_url}/**", timeout=30000) + + # Give console a moment to initialize session + page.wait_for_timeout(2000) + + # Save cookies and session storage to file + temp_context.storage_state(path=state_file.name) + + # Cleanup browser resources + page.close() + temp_context.close() + + return state_file.name + + +@pytest.fixture(scope="session") +def browser_context_args(browser_context_args, auth_state_file): + """Configure browser context to load saved authentication""" + return { + **browser_context_args, + "storage_state": auth_state_file, # Load cookies/session from file + } + + +@pytest.fixture(scope="module", autouse=True) +def commit(): + """Skip parent commit of auth/rate limit""" + return None + + +@pytest.fixture(scope="module") +def client(route, hostname): # pylint: disable=unused-argument + """Returns httpx client with increased timeout""" + client = hostname.client(timeout=Timeout(connect=30.0, read=10.0, write=10.0, pool=10.0)) + yield client + client.close() + + +@pytest.fixture(autouse=True) +def navigate_console(page, exposer, cluster, skip_or_fail): + """Navigate to OpenShift console and verify console plugin is enabled and visible""" + if not isinstance(exposer, OpenShiftExposer): + pytest.skip("UI tests require OpenShift exposer (OpenShift console not available)") + + # Check if console plugin resource exists in cluster + plugin_check = cluster.do_action("get", "consoleplugin", "kuadrant-console-plugin", auto_raise=False) + if plugin_check.status() != 0: + skip_or_fail( + "Kuadrant console plugin resource not found. Please install it via Helm charts before running UI tests." + ) + + # Check if plugin is enabled in console operator + console_config = cluster.do_action( + "get", "console.operator.openshift.io", "cluster", "-o", "jsonpath={.spec.plugins}", auto_raise=False + ) + if console_config.status() == 0 and "kuadrant-console-plugin" not in console_config.out(): + skip_or_fail( + "Kuadrant console plugin is installed but not enabled. " + "Please enable it in the console operator configuration." + ) + + page.goto(cluster.console_url, timeout=60000) # OpenShift console can be slow to fully load + + # Wait for console plugin nav element (plugin is enabled at this point, so should be available) + NavBar(page).kuadrant_nav.wait_for(state="visible", timeout=30000) + + +@pytest.fixture +def navigator(page): + """Return a Navigator bound to the current Playwright page""" + return Navigator(page) diff --git a/testsuite/tests/singlecluster/ui/console_plugin/policies/conftest.py b/testsuite/tests/singlecluster/ui/console_plugin/policies/conftest.py deleted file mode 100644 index 860b3dc9..00000000 --- a/testsuite/tests/singlecluster/ui/console_plugin/policies/conftest.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Conftest for Policies page UI tests""" - -import os - -import pytest - -from testsuite.page_objects.nav_bar import NavBar -from testsuite.page_objects.navigator import Navigator - - -@pytest.fixture(scope="session") -def browser_context_args(browser_context_args): - """Configure browser context to ignore HTTPS certificate errors""" - return { - **browser_context_args, - "ignore_https_errors": True, - } - - -@pytest.fixture(scope="module", autouse=True) -def commit(): - """Skip parent commit of auth/rate limit""" - return None - - -@pytest.fixture(autouse=True) -def login(page, base_domain, testconfig): - """Log into the OpenShift console using HTPasswd credentials""" - page.goto( - f"https://console-openshift-console.{base_domain}", - timeout=60000, # OpenShift console can be slow to fully load - ) - page.locator("//a[@title='Log in with HTPasswd']").click() - - # Get credentials from configuration or environment variables (for nightly pipeline) - username = testconfig.get("console.username") or os.getenv("KUBE_USER", "admin") - password = testconfig.get("console.password") or os.getenv("KUBE_PASSWORD") - - page.locator("//input[@name='username']").fill(username) - page.locator("//input[@name='password']").fill(password) - page.locator("//button[@type='submit']").click() - return NavBar(page) - - -@pytest.fixture(autouse=True) -def dynamic_plugin(login, skip_or_fail): - """Verify the console plugin is enabled""" - if not login.kuadrant_nav.is_visible: - skip_or_fail( - "Kuadrant console plugin is not enabled. " - "Please enable it via Helm charts or OpenShift console before running UI tests." - ) - - -@pytest.fixture -def navigator(page): - """Return a Navigator bound to the current Playwright page""" - return Navigator(page)