"""Cal.com initial setup scenario. Automates the first-time setup for a fresh Cal.com installation. This scenario: 1. Navigates to the Cal.com setup page 2. Creates the admin account 3. Completes onboarding steps """ 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 CalcomInitialSetup(BaseScenario): """Automate Cal.com first-time admin account setup. This scenario handles the initial account creation when Cal.com is freshly installed. It navigates to the signup page, fills in account details, and completes the onboarding wizard. Required inputs: base_url: The Cal.com instance URL (e.g., https://cal.example.com) admin_email: Email address for the admin account Optional inputs: admin_password: Password for admin account (auto-generated if not provided) admin_username: Username for the admin account (default: "admin") admin_name: Display name for the admin account (default: "Admin") Result data: setup_completed: Whether initial setup was completed admin_email: The configured admin email address admin_password: The password (generated or provided) - STORE SECURELY already_configured: True if Cal.com was already set up """ @property def name(self) -> str: return "calcom_initial_setup" @property def required_inputs(self) -> list[str]: return ["base_url", "admin_email"] @property def optional_inputs(self) -> list[str]: return ["admin_password", "admin_username", "admin_name"] @property def description(self) -> str: return "Automate Cal.com first-time admin account setup" async def execute( self, page: Page, inputs: dict[str, Any], options: ScenarioOptions, ) -> ScenarioResult: """Execute the Cal.com initial setup. Args: page: Playwright Page object inputs: Scenario inputs (base_url, admin_email) options: Scenario options Returns: ScenarioResult with setup status and credentials """ base_url = inputs["base_url"].rstrip("/") admin_email = inputs["admin_email"] admin_password = inputs.get("admin_password") or generate_secure_password() admin_username = inputs.get("admin_username", "admin") admin_name = inputs.get("admin_name", "Admin") screenshots = [] result_data = { "setup_completed": False, "admin_email": admin_email, "admin_password": admin_password, "already_configured": False, } try: # Navigate to Cal.com await page.goto(base_url, wait_until="networkidle") current_url = page.url # Check if already configured (redirects to login) if "/auth/login" in current_url: result_data["already_configured"] = True result_data["setup_completed"] = True return ScenarioResult( success=True, data=result_data, screenshots=screenshots, error=None, ) # Navigate to signup page signup_url = f"{base_url}/signup" await page.goto(signup_url, wait_until="networkidle") # If redirected to login, the instance may already be set up if "/auth/login" in page.url and "/signup" not in page.url: result_data["already_configured"] = True result_data["setup_completed"] = True return ScenarioResult( success=True, data=result_data, screenshots=screenshots, error=None, ) # Fill in the signup form # Username username_input = page.locator( 'input[name="username"], ' 'input[id="username"], ' 'input[placeholder*="username" i]' ).first if await username_input.count() > 0: await username_input.wait_for(state="visible", timeout=10000) await username_input.fill(admin_username) # Full name name_input = page.locator( 'input[name="name"], ' 'input[name="full_name"], ' 'input[placeholder*="name" i]' ).first if await name_input.count() > 0: await name_input.fill(admin_name) # Email email_input = page.locator( 'input[name="email"], ' 'input[type="email"], ' 'input[placeholder*="email" i]' ).first await email_input.wait_for(state="visible", timeout=10000) await email_input.fill(admin_email) # Password password_input = page.locator( 'input[name="password"], ' 'input[type="password"]' ).first await password_input.fill(admin_password) # Take screenshot before submitting if options.screenshot_on_success and options.artifacts_dir: pre_submit_path = options.artifacts_dir / "calcom_pre_submit.png" await page.screenshot(path=str(pre_submit_path)) screenshots.append(str(pre_submit_path)) # Click Sign up / Create Account button submit_button = page.locator( 'button:has-text("Sign up"), ' 'button:has-text("Create"), ' 'button:has-text("Register"), ' 'button[type="submit"]' ).first await submit_button.click() # Wait for onboarding or dashboard await page.wait_for_timeout(3000) # Cal.com has an onboarding wizard after signup # Skip through onboarding steps for _ in range(5): skip_button = page.locator( 'button:has-text("Skip"), ' 'a:has-text("Skip"), ' 'button:has-text("Next"), ' 'button:has-text("Continue"), ' 'button:has-text("Finish")' ) if await skip_button.count() > 0: await skip_button.first.click() await page.wait_for_timeout(2000) else: break # Check if we reached the dashboard or event types page await page.wait_for_timeout(2000) current_url = page.url if any(kw in current_url for kw in ["/event-types", "/dashboard", "/bookings", "/settings"]): result_data["setup_completed"] = True else: # Check for dashboard indicators dashboard_el = page.locator( '[class*="event-type"], ' '[class*="dashboard"], ' ':has-text("Event Types")' ) 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 / "calcom_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 / "calcom_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"Cal.com setup failed: {str(e)}", )