Files
Ferdinand Thiessen 128560917d test(core): drive the setup wizard against the settings app list routes
The recommended apps view reads the app list from `settings/apps/list` and
enables every selected app in a single `settings/apps/enable` request, then
redirects to the default page. Stub those routes and assert the redirect
instead of an inline per-app result.

Assisted-by: ClaudeCode:claude-opus-5
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2026-09-16 21:04:48 +02:00

128 lines
4.3 KiB
TypeScript

/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Locator, Page } from '@playwright/test'
import { expect } from '@playwright/test'
/**
* The database engines offered by the installation wizard, keyed by their
* visible (accessible) radio label. `oci` (Oracle) is only offered when the
* server is configured to allow all databases.
*/
export type DatabaseName = 'SQLite' | 'MySQL/MariaDB' | 'PostgreSQL' | 'Oracle'
/** Connection details for a non-SQLite database backend. */
export interface DatabaseConnection {
user: string
password: string
name: string
host: string
}
/**
* The Nextcloud installation wizard (`core/src/views/Setup.vue`) and the
* recommended-apps screen shown right after a successful install
* (`core/src/components/setup/RecommendedApps.vue`).
*
* Locators are role/label based; the two view containers have no accessible
* name, so they keep their product-owned `data-cy` hooks.
*/
export class SetupPage {
constructor(private readonly page: Page) {}
/** Open the installation wizard (served at the instance root when uninstalled). */
async open(): Promise<void> {
await this.page.goto('/')
await this.form().waitFor({ state: 'visible' })
}
form(): Locator {
return this.page.locator('[data-cy-setup-form]')
}
adminLoginField(): Locator {
return this.page.getByRole('textbox', { name: 'Administration account name' })
}
adminPasswordField(): Locator {
// A password <input> has no textbox role, so address it by its label
return this.page.getByLabel('Administration account password')
}
dataFolderField(): Locator {
return this.page.getByRole('textbox', { name: 'Data folder' })
}
databaseRadio(name: DatabaseName): Locator {
return this.page.getByRole('radio', { name })
}
submitButton(): Locator {
return this.page.getByRole('button', { name: 'Install' })
}
/** Select a database engine and confirm the radio actually toggled. */
async selectDatabase(name: DatabaseName): Promise<void> {
const radio = this.databaseRadio(name)
// The radio input is visually hidden inside NcCheckboxRadioSwitch, so force it
await radio.check({ force: true })
await expect(radio).toBeChecked()
}
/** Fill the connection fields for a non-SQLite database backend. */
async fillDatabaseConnection(connection: DatabaseConnection): Promise<void> {
await this.page.getByRole('textbox', { name: 'Database user' }).fill(connection.user)
await this.page.getByLabel('Database password').fill(connection.password)
await this.page.getByRole('textbox', { name: 'Database name' }).fill(connection.name)
await this.page.getByRole('textbox', { name: 'Database host' }).fill(connection.host)
}
/**
* Fill the administration account and submit the wizard, then wait for the
* install to finish and land on the recommended-apps screen.
*/
async install(adminUser: string, adminPassword: string): Promise<void> {
await this.adminLoginField().fill(adminUser)
await this.adminPasswordField().fill(adminPassword)
await this.submitButton().click()
await this.page.waitForURL(/\/core\/apps\/recommended/)
await this.recommendedApps().waitFor({ state: 'visible' })
}
// --- Recommended apps screen -------------------------------------------
recommendedApps(): Locator {
return this.page.locator('[data-cy-setup-recommended-apps]')
}
recommendedAppsHeading(): Locator {
return this.page.getByRole('heading', { name: 'Recommended apps' })
}
skipButton(): Locator {
return this.page.getByRole('button', { name: 'Skip' })
}
installRecommendedButton(): Locator {
return this.page.getByRole('button', { name: 'Install recommended apps' })
}
/**
* Install the recommended apps and confirm the password dialog that
* `@nextcloud/password-confirmation` raises for the enable request. All
* selected apps are enabled in one bulk request, so a single dialog is shown.
*/
async installRecommendedApps(password: string): Promise<void> {
await this.installRecommendedButton().click()
const dialog = this.page.getByRole('dialog', { name: 'Authentication required' })
await dialog.waitFor({ state: 'visible', timeout: 10_000 })
await dialog.locator('input[type="password"]').fill(password)
await dialog.getByRole('button', { name: 'Confirm' }).click()
}
}