"""Keycloak initial setup scenario. Automates the first-time setup for a fresh Keycloak installation. This scenario: 1. Navigates to the Keycloak admin console 2. Logs in with the admin credentials (set via env vars) 3. Creates a "letsbe" realm 4. Configures basic realm settings """ from typing import Any from playwright.async_api import Page from app.playwright_scenarios import register_scenario from app.playwright_scenarios.base import BaseScenario, ScenarioOptions, ScenarioResult @register_scenario class KeycloakInitialSetup(BaseScenario): """Automate Keycloak initial realm setup. This scenario handles the initial configuration after Keycloak is deployed. It logs into the admin console and creates the "letsbe" realm with appropriate settings. Keycloak admin credentials are set via environment variables during deployment (KEYCLOAK_ADMIN / KEYCLOAK_ADMIN_PASSWORD), so this scenario only needs to create the realm. Required inputs: base_url: The Keycloak instance URL (e.g., https://auth.example.com) admin_user: Admin username (set during deployment) admin_password: Admin password (set during deployment) Optional inputs: realm_name: Name of the realm to create (default: "letsbe") Result data: login_successful: Whether admin login succeeded realm_created: Whether the realm was created realm_name: Name of the created realm already_configured: True if realm already exists """ @property def name(self) -> str: return "keycloak_initial_setup" @property def required_inputs(self) -> list[str]: return ["base_url", "admin_user", "admin_password"] @property def optional_inputs(self) -> list[str]: return ["realm_name"] @property def description(self) -> str: return "Automate Keycloak admin login and realm creation" async def execute( self, page: Page, inputs: dict[str, Any], options: ScenarioOptions, ) -> ScenarioResult: """Execute the Keycloak initial setup. Args: page: Playwright Page object inputs: Scenario inputs (base_url, admin_user, admin_password) options: Scenario options Returns: ScenarioResult with setup status """ base_url = inputs["base_url"].rstrip("/") admin_user = inputs["admin_user"] admin_password = inputs["admin_password"] realm_name = inputs.get("realm_name", "letsbe") screenshots = [] result_data = { "login_successful": False, "realm_created": False, "realm_name": realm_name, "already_configured": False, } try: # Navigate to Keycloak admin console admin_url = f"{base_url}/admin/master/console/" await page.goto(admin_url, wait_until="networkidle") # Keycloak redirects to login page # Wait for the login form username_input = page.locator('input#username, input[name="username"]') await username_input.wait_for(state="visible", timeout=15000) # Fill login form await username_input.fill(admin_user) password_input = page.locator('input#password, input[name="password"]') await password_input.fill(admin_password) # Click login button login_button = page.locator( 'button#kc-login, ' 'input#kc-login, ' 'button[type="submit"], ' 'input[type="submit"]' ) await login_button.click() # Wait for admin console to load try: await page.wait_for_url( lambda url: "/admin" in url and "login" not in url.lower(), timeout=30000, ) result_data["login_successful"] = True except Exception: # Check for error message error_el = page.locator('.alert-error, .kc-feedback-text, #input-error') if await error_el.count() > 0: error_text = await error_el.first.text_content() return ScenarioResult( success=False, data=result_data, screenshots=screenshots, error=f"Login failed: {error_text}", ) return ScenarioResult( success=False, data=result_data, screenshots=screenshots, error="Login failed - could not reach admin console", ) # Check if realm already exists by navigating to realm selector # Look for the realm dropdown or realm list realm_selector = page.locator( '[data-testid="realmSelector"], ' '.pf-c-dropdown__toggle, ' '#realm-select' ) if await realm_selector.count() > 0: await realm_selector.first.click() await page.wait_for_timeout(1000) # Check if our realm already exists in the dropdown existing_realm = page.locator( f'a:has-text("{realm_name}"), ' f'button:has-text("{realm_name}"), ' f'[data-testid="realmSelector"] >> text="{realm_name}"' ) if await existing_realm.count() > 0: result_data["already_configured"] = True result_data["realm_created"] = True # Click away to close dropdown await page.keyboard.press("Escape") return ScenarioResult( success=True, data=result_data, screenshots=screenshots, error=None, ) # Close dropdown await page.keyboard.press("Escape") # Create new realm # Navigate to realm creation page create_realm_button = page.locator( 'a:has-text("Create Realm"), ' 'button:has-text("Create Realm"), ' 'a:has-text("Create realm"), ' 'button:has-text("Create realm"), ' '[data-testid="add-realm"]' ) if await create_realm_button.count() > 0: await create_realm_button.first.click() else: # Try navigating directly await page.goto( f"{base_url}/admin/master/console/#/create/realm", wait_until="networkidle", ) await page.wait_for_timeout(2000) # Fill in realm name realm_name_input = page.locator( 'input#kc-realm, ' 'input[name="realm"], ' 'input[data-testid="realmName"], ' 'input#name' ) await realm_name_input.wait_for(state="visible", timeout=10000) await realm_name_input.fill(realm_name) # Ensure realm is enabled enabled_toggle = page.locator( 'input[name="enabled"], ' '[data-testid="realmEnabled"]' ) if await enabled_toggle.count() > 0: is_checked = await enabled_toggle.first.is_checked() if not is_checked: await enabled_toggle.first.click() # Take screenshot before creating if options.screenshot_on_success and options.artifacts_dir: pre_create_path = options.artifacts_dir / "keycloak_pre_create.png" await page.screenshot(path=str(pre_create_path)) screenshots.append(str(pre_create_path)) # Click Create button create_button = page.locator( 'button:has-text("Create"), ' 'button[type="submit"]' ).first await create_button.click() # Wait for realm to be created (redirects to realm settings) await page.wait_for_timeout(3000) # Verify realm was created by checking URL or page content current_url = page.url if realm_name in current_url or "realm-settings" in current_url: result_data["realm_created"] = True else: # Check for success notification success_el = page.locator( '.pf-c-alert.pf-m-success, ' '[class*="success"], ' ':has-text("Realm created")' ) if await success_el.count() > 0: result_data["realm_created"] = True # Take final screenshot if options.screenshot_on_success and options.artifacts_dir: final_path = options.artifacts_dir / "keycloak_setup_complete.png" await page.screenshot(path=str(final_path)) screenshots.append(str(final_path)) return ScenarioResult( success=result_data["realm_created"], data=result_data, screenshots=screenshots, error=None if result_data["realm_created"] else "Realm creation may not have completed", ) except Exception as e: # Take error screenshot if options.screenshot_on_failure and options.artifacts_dir: error_path = options.artifacts_dir / "keycloak_setup_error.png" await page.screenshot(path=str(error_path)) screenshots.append(str(error_path)) return ScenarioResult( success=False, data=result_data, screenshots=screenshots, error=f"Keycloak setup failed: {str(e)}", )