"""Chatwoot initial setup scenario. Automates the first-time setup for a fresh Chatwoot installation. This scenario: 1. Navigates to the Chatwoot installation wizard 2. Fills in admin account details (name, company, email, password) 3. Unchecks the newsletter subscription 4. Completes the setup """ 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 ChatwootInitialSetup(BaseScenario): """Automate Chatwoot first-time setup wizard. This scenario handles the initial super admin account creation when Chatwoot is freshly installed. It fills in the account details, unchecks the newsletter subscription, and completes the setup. Required inputs: base_url: The Chatwoot instance URL (e.g., https://chatwoot.example.com) admin_name: Full name for the admin account company_name: Company/organization name admin_email: Email address for the admin account Optional inputs: admin_password: Password for admin account (auto-generated if not provided) 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 Chatwoot was already set up """ @property def name(self) -> str: return "chatwoot_initial_setup" @property def required_inputs(self) -> list[str]: return ["base_url", "admin_name", "company_name", "admin_email"] @property def optional_inputs(self) -> list[str]: return ["admin_password"] @property def description(self) -> str: return "Automate Chatwoot first-time admin account setup" async def execute( self, page: Page, inputs: dict[str, Any], options: ScenarioOptions, ) -> ScenarioResult: """Execute the Chatwoot initial setup. Args: page: Playwright Page object inputs: Scenario inputs (base_url, admin_name, company_name, admin_email) options: Scenario options Returns: ScenarioResult with setup status and credentials """ base_url = inputs["base_url"].rstrip("/") admin_name = inputs["admin_name"] company_name = inputs["company_name"] admin_email = inputs["admin_email"] # Generate password if not provided admin_password = inputs.get("admin_password") or generate_secure_password() screenshots = [] result_data = { "setup_completed": False, "admin_name": admin_name, "company_name": company_name, "admin_email": admin_email, "admin_password": admin_password, # Return for secure storage "already_configured": False, } try: # Navigate to Chatwoot await page.goto(base_url, wait_until="networkidle") current_url = page.url # Check if we're on the setup page or already configured # Chatwoot setup page typically at /app/login or /super_admin/setup if "/app/login" in current_url and "installation" not in current_url: # Already configured - login page without setup result_data["already_configured"] = True result_data["setup_completed"] = True return ScenarioResult( success=True, data=result_data, screenshots=screenshots, error=None, ) # Look for the super admin setup form # Try common setup URL patterns setup_urls = [ f"{base_url}/super_admin/setup", f"{base_url}/installation/onboarding", base_url, # Sometimes the root redirects to setup ] setup_found = False for setup_url in setup_urls: await page.goto(setup_url, wait_until="networkidle") # Check for setup form elements name_input = page.locator('input[name="name"], input[placeholder*="name" i]') if await name_input.count() > 0: setup_found = True break if not setup_found: # Check if already configured if "/app" in page.url or "/dashboard" in page.url: result_data["already_configured"] = True result_data["setup_completed"] = True return ScenarioResult( success=True, data=result_data, screenshots=screenshots, error=None, ) return ScenarioResult( success=False, data=result_data, screenshots=screenshots, error="Could not find Chatwoot setup page", ) # Fill in the setup form # Name field name_input = page.locator( 'input[name="name"], ' 'input[placeholder*="name" i], ' 'input[id*="name" i]' ).first await name_input.wait_for(state="visible", timeout=10000) await name_input.fill(admin_name) # Company name field company_input = page.locator( 'input[name="company_name"], ' 'input[name="account_name"], ' 'input[placeholder*="company" i], ' 'input[placeholder*="account" i]' ).first if await company_input.count() > 0: await company_input.fill(company_name) # Email field email_input = page.locator( 'input[name="email"], ' 'input[type="email"], ' 'input[placeholder*="email" i]' ).first await email_input.fill(admin_email) # Password field password_input = page.locator( 'input[name="password"], ' 'input[type="password"]' ).first await password_input.fill(admin_password) # Uncheck newsletter subscription if present newsletter_checkbox = page.locator( 'input[type="checkbox"][name*="subscribe" i], ' 'input[type="checkbox"][name*="newsletter" i], ' 'input[type="checkbox"][id*="subscribe" i], ' 'label:has-text("Subscribe") input[type="checkbox"], ' 'label:has-text("newsletter") input[type="checkbox"]' ) if await newsletter_checkbox.count() > 0: checkbox = newsletter_checkbox.first is_checked = await checkbox.is_checked() if is_checked: await checkbox.uncheck() # Take screenshot before submitting if requested if options.screenshot_on_success and options.artifacts_dir: pre_submit_path = options.artifacts_dir / "chatwoot_pre_submit.png" await page.screenshot(path=str(pre_submit_path)) screenshots.append(str(pre_submit_path)) # Click Finish Setup / Submit button submit_button = page.locator( 'button:has-text("Finish"), ' 'button:has-text("Setup"), ' 'button:has-text("Create"), ' 'button[type="submit"], ' 'input[type="submit"]' ).first await submit_button.click() # Wait for setup to complete - should redirect to login or dashboard try: await page.wait_for_url( lambda url: "/app" in url or "/dashboard" in url or "/login" in url, timeout=60000, ) result_data["setup_completed"] = True except Exception: # Check if there's an error message error_el = page.locator('.error, .alert-danger, [class*="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"Setup failed: {error_text}", ) # Check if we're on a success page success_indicators = page.locator( ':has-text("success"), ' ':has-text("Welcome"), ' ':has-text("Dashboard")' ) if await success_indicators.count() > 0: result_data["setup_completed"] = True # Take final screenshot if options.screenshot_on_success and options.artifacts_dir: final_path = options.artifacts_dir / "chatwoot_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: # Take error screenshot if options.screenshot_on_failure and options.artifacts_dir: error_path = options.artifacts_dir / "chatwoot_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"Chatwoot setup failed: {str(e)}", )