"""Uptime Kuma initial setup scenario. Automates the first-time setup for a fresh Uptime Kuma installation. This scenario: 1. Navigates to the Uptime Kuma setup page 2. Creates the admin account with username and password """ import secrets import string 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 def generate_secure_password(length: int = 24) -> str: """Generate a cryptographically secure password. Args: length: Password length (default: 24) Returns: A secure random password with mixed characters """ alphabet = string.ascii_letters + string.digits + "!@#$%^&*" password = [ secrets.choice(string.ascii_lowercase), secrets.choice(string.ascii_uppercase), secrets.choice(string.digits), secrets.choice("!@#$%^&*"), ] password.extend(secrets.choice(alphabet) for _ in range(length - 4)) password_list = list(password) secrets.SystemRandom().shuffle(password_list) return "".join(password_list) @register_scenario class UptimeKumaInitialSetup(BaseScenario): """Automate Uptime Kuma first-time admin account setup. This scenario handles the initial admin account creation when Uptime Kuma is freshly installed. On first launch, Uptime Kuma shows a setup page to create the admin account. Required inputs: base_url: The Uptime Kuma instance URL (e.g., https://status.example.com) Optional inputs: admin_username: Username for the admin account (default: "admin") admin_password: Password for admin account (auto-generated if not provided) Result data: setup_completed: Whether initial setup was completed admin_username: The configured admin username admin_password: The password (generated or provided) - STORE SECURELY already_configured: True if Uptime Kuma was already set up """ @property def name(self) -> str: return "uptime_kuma_initial_setup" @property def required_inputs(self) -> list[str]: return ["base_url"] @property def optional_inputs(self) -> list[str]: return ["admin_username", "admin_password"] @property def description(self) -> str: return "Automate Uptime Kuma first-time admin account setup" async def execute( self, page: Page, inputs: dict[str, Any], options: ScenarioOptions, ) -> ScenarioResult: """Execute the Uptime Kuma initial setup. Args: page: Playwright Page object inputs: Scenario inputs (base_url) options: Scenario options Returns: ScenarioResult with setup status and credentials """ base_url = inputs["base_url"].rstrip("/") admin_username = inputs.get("admin_username", "admin") admin_password = inputs.get("admin_password") or generate_secure_password() screenshots = [] result_data = { "setup_completed": False, "admin_username": admin_username, "admin_password": admin_password, "already_configured": False, } try: # Navigate to Uptime Kuma await page.goto(base_url, wait_until="networkidle") current_url = page.url # Uptime Kuma shows setup page on first visit, login page after # Check if we're on the setup page setup_heading = page.locator( 'h1:has-text("Setup"), ' ':has-text("Create your admin account")' ) # Check if already configured (shows login form) login_form = page.locator( 'form:has(input[autocomplete="username"]), ' 'h1:has-text("Login")' ) if await login_form.count() > 0 and await setup_heading.count() == 0: result_data["already_configured"] = True result_data["setup_completed"] = True return ScenarioResult( success=True, data=result_data, screenshots=screenshots, error=None, ) # We're on the setup page - fill in the admin account # Username field username_input = page.locator( 'input[autocomplete="username"], ' 'input[name="username"], ' 'input[id="floatingInput"], ' 'input[placeholder*="username" i]' ).first await username_input.wait_for(state="visible", timeout=10000) await username_input.fill(admin_username) # Password field password_input = page.locator( 'input[type="password"][autocomplete="new-password"], ' 'input[name="password"], ' 'input[type="password"]' ).first await password_input.fill(admin_password) # Confirm password field (Uptime Kuma requires password confirmation) confirm_input = page.locator( 'input[type="password"][autocomplete="new-password"]' ) if await confirm_input.count() > 1: # Second password field is the confirm field await confirm_input.nth(1).fill(admin_password) else: # Try alternative selector confirm_input = page.locator( 'input[name="repeatPassword"], ' 'input[name="confirm_password"], ' 'input[placeholder*="repeat" i], ' 'input[placeholder*="confirm" i]' ).first if await confirm_input.count() > 0: await confirm_input.fill(admin_password) # Take screenshot before submitting if options.screenshot_on_success and options.artifacts_dir: pre_submit_path = options.artifacts_dir / "uptime_kuma_pre_submit.png" await page.screenshot(path=str(pre_submit_path)) screenshots.append(str(pre_submit_path)) # Click Create / Submit button submit_button = page.locator( 'button:has-text("Create"), ' 'button:has-text("Submit"), ' 'button:has-text("Register"), ' 'button[type="submit"]' ).first await submit_button.click() # Wait for redirect to dashboard try: await page.wait_for_url( lambda url: "/dashboard" in url or "/setup" not in url, timeout=30000, ) result_data["setup_completed"] = True except Exception: # Check if on dashboard by looking for dashboard elements dashboard_el = page.locator( '.dashboard, ' '[class*="dashboard"], ' ':has-text("Add New Monitor")' ) if await dashboard_el.count() > 0: result_data["setup_completed"] = True # Take final screenshot if options.screenshot_on_success and options.artifacts_dir: final_path = options.artifacts_dir / "uptime_kuma_setup_complete.png" await page.screenshot(path=str(final_path)) screenshots.append(str(final_path)) return ScenarioResult( success=result_data["setup_completed"], data=result_data, screenshots=screenshots, error=None if result_data["setup_completed"] else "Setup may not have completed", ) except Exception as e: if options.screenshot_on_failure and options.artifacts_dir: error_path = options.artifacts_dir / "uptime_kuma_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"Uptime Kuma setup failed: {str(e)}", )