letsbe-sysadmin/app/playwright_scenarios/chatwoot/initial_setup.py

292 lines
11 KiB
Python

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