LetsBeBiz-Redesign/letsbe-sysadmin-agent/app/playwright_scenarios/keycloak/initial_setup.py

273 lines
9.9 KiB
Python
Raw Normal View History

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