232 lines
8.4 KiB
Python
232 lines
8.4 KiB
Python
"""Nextcloud initial setup scenario.
|
|
|
|
Automates the first-time setup wizard for a fresh Nextcloud installation.
|
|
This scenario:
|
|
1. Navigates to the Nextcloud instance
|
|
2. Creates the admin account
|
|
3. Optionally skips recommended apps installation
|
|
4. Verifies successful login to the dashboard
|
|
"""
|
|
|
|
from typing import Any
|
|
|
|
from playwright.async_api import Page, expect
|
|
|
|
from app.playwright_scenarios import register_scenario
|
|
from app.playwright_scenarios.base import BaseScenario, ScenarioOptions, ScenarioResult
|
|
|
|
|
|
@register_scenario
|
|
class NextcloudInitialSetup(BaseScenario):
|
|
"""Automate Nextcloud first-time setup wizard.
|
|
|
|
This scenario handles the initial admin account creation when
|
|
Nextcloud is freshly installed. It's idempotent - if setup is
|
|
already complete, it will detect this and succeed.
|
|
|
|
Required inputs:
|
|
base_url: The Nextcloud instance URL (e.g., https://cloud.example.com)
|
|
admin_username: Username for the admin account
|
|
admin_password: Password for the admin account
|
|
|
|
Optional inputs:
|
|
skip_recommended_apps: Skip the recommended apps step (default: True)
|
|
|
|
Result data:
|
|
admin_created: Whether a new admin was created (False if already setup)
|
|
login_successful: Whether login to dashboard succeeded
|
|
setup_skipped: True if Nextcloud was already configured
|
|
"""
|
|
|
|
@property
|
|
def name(self) -> str:
|
|
return "nextcloud_initial_setup"
|
|
|
|
@property
|
|
def required_inputs(self) -> list[str]:
|
|
return ["base_url", "admin_username", "admin_password"]
|
|
|
|
@property
|
|
def optional_inputs(self) -> list[str]:
|
|
return ["skip_recommended_apps"]
|
|
|
|
@property
|
|
def description(self) -> str:
|
|
return "Automate Nextcloud first-time admin setup wizard"
|
|
|
|
async def execute(
|
|
self,
|
|
page: Page,
|
|
inputs: dict[str, Any],
|
|
options: ScenarioOptions,
|
|
) -> ScenarioResult:
|
|
"""Execute the Nextcloud initial setup.
|
|
|
|
Args:
|
|
page: Playwright Page object
|
|
inputs: Scenario inputs (base_url, admin_username, admin_password)
|
|
options: Scenario options
|
|
|
|
Returns:
|
|
ScenarioResult with setup status
|
|
"""
|
|
base_url = inputs["base_url"].rstrip("/")
|
|
admin_username = inputs["admin_username"]
|
|
admin_password = inputs["admin_password"]
|
|
skip_recommended_apps = inputs.get("skip_recommended_apps", True)
|
|
|
|
screenshots = []
|
|
result_data = {
|
|
"admin_created": False,
|
|
"login_successful": False,
|
|
"setup_skipped": False,
|
|
}
|
|
|
|
try:
|
|
# Navigate to Nextcloud
|
|
await page.goto(base_url, wait_until="networkidle")
|
|
|
|
# Check if we're on the setup page or login page
|
|
current_url = page.url
|
|
|
|
# Detect if setup is already complete (redirects to login)
|
|
if "/login" in current_url or await page.locator('input[name="user"]').count() > 0:
|
|
# Already configured, try to login
|
|
result_data["setup_skipped"] = True
|
|
login_success = await self._try_login(
|
|
page, admin_username, admin_password
|
|
)
|
|
result_data["login_successful"] = login_success
|
|
|
|
return ScenarioResult(
|
|
success=login_success,
|
|
data=result_data,
|
|
screenshots=screenshots,
|
|
error=None if login_success else "Login failed - check credentials",
|
|
)
|
|
|
|
# We're on the setup page - create admin account
|
|
# Wait for the setup form to be visible
|
|
admin_user_input = page.locator('input[id="adminlogin"], input[name="adminlogin"]')
|
|
await admin_user_input.wait_for(state="visible", timeout=10000)
|
|
|
|
# Fill in admin credentials
|
|
await admin_user_input.fill(admin_username)
|
|
|
|
admin_pass_input = page.locator('input[id="adminpass"], input[name="adminpass"]')
|
|
await admin_pass_input.fill(admin_password)
|
|
|
|
# Check for data directory input (may or may not be present)
|
|
data_dir_input = page.locator('input[id="directory"]')
|
|
if await data_dir_input.count() > 0 and await data_dir_input.is_visible():
|
|
# Keep default data directory
|
|
pass
|
|
|
|
# Click install/finish setup button
|
|
# Nextcloud uses various button texts depending on version
|
|
install_button = page.locator(
|
|
'input[type="submit"][value*="Install"], '
|
|
'input[type="submit"][value*="Finish"], '
|
|
'button:has-text("Install"), '
|
|
'button:has-text("Finish setup")'
|
|
)
|
|
await install_button.click()
|
|
|
|
# Wait for installation to complete (this can take a while)
|
|
# Look for either dashboard or recommended apps screen
|
|
try:
|
|
await page.wait_for_url(
|
|
lambda url: "/apps" in url or "/index.php" in url or "dashboard" in url.lower(),
|
|
timeout=120000, # 2 minutes for installation
|
|
)
|
|
except Exception:
|
|
# May be on recommended apps screen
|
|
pass
|
|
|
|
result_data["admin_created"] = True
|
|
|
|
# Handle recommended apps screen if present
|
|
if skip_recommended_apps:
|
|
skip_button = page.locator(
|
|
'button:has-text("Skip"), '
|
|
'a:has-text("Skip"), '
|
|
'.skip-button'
|
|
)
|
|
if await skip_button.count() > 0:
|
|
await skip_button.first.click()
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# Verify we're logged in by checking for user menu or dashboard elements
|
|
dashboard_indicators = page.locator(
|
|
'#user-menu, '
|
|
'.user-menu, '
|
|
'[data-id="dashboard"], '
|
|
'#nextcloud, '
|
|
'.app-dashboard'
|
|
)
|
|
|
|
try:
|
|
await dashboard_indicators.first.wait_for(state="visible", timeout=30000)
|
|
result_data["login_successful"] = True
|
|
except Exception:
|
|
# Try one more check - look for any indication we're logged in
|
|
if await page.locator('.header-menu').count() > 0:
|
|
result_data["login_successful"] = True
|
|
|
|
# Take a screenshot of the final state if requested
|
|
if options.screenshot_on_success and options.artifacts_dir:
|
|
screenshot_path = options.artifacts_dir / "setup_complete.png"
|
|
await page.screenshot(path=str(screenshot_path))
|
|
screenshots.append(str(screenshot_path))
|
|
|
|
success = result_data["admin_created"] and result_data["login_successful"]
|
|
return ScenarioResult(
|
|
success=success,
|
|
data=result_data,
|
|
screenshots=screenshots,
|
|
error=None if success else "Setup completed but verification failed",
|
|
)
|
|
|
|
except Exception as e:
|
|
return ScenarioResult(
|
|
success=False,
|
|
data=result_data,
|
|
screenshots=screenshots,
|
|
error=f"Nextcloud setup failed: {str(e)}",
|
|
)
|
|
|
|
async def _try_login(self, page: Page, username: str, password: str) -> bool:
|
|
"""Attempt to login to an already-configured Nextcloud.
|
|
|
|
Args:
|
|
page: Playwright Page object (should be on login page)
|
|
username: Username to login with
|
|
password: Password to login with
|
|
|
|
Returns:
|
|
True if login succeeded, False otherwise
|
|
"""
|
|
try:
|
|
# Fill login form
|
|
await page.locator('input[name="user"]').fill(username)
|
|
await page.locator('input[name="password"]').fill(password)
|
|
|
|
# Submit login
|
|
await page.locator('input[type="submit"], button[type="submit"]').click()
|
|
|
|
# Wait for redirect to dashboard
|
|
await page.wait_for_url(
|
|
lambda url: "/login" not in url,
|
|
timeout=30000,
|
|
)
|
|
|
|
# Check for login error message
|
|
error_msg = page.locator('.warning, .error, [class*="error"]')
|
|
if await error_msg.count() > 0 and await error_msg.first.is_visible():
|
|
return False
|
|
|
|
return True
|
|
|
|
except Exception:
|
|
return False
|