This commit is contained in:
2025-11-17 18:51:08 +01:00
parent 14d6f9aa73
commit 7fb0d2212a
318 changed files with 35761 additions and 0 deletions

7
nextcloud-aio/php/tests/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View File

@@ -0,0 +1,97 @@
{
"name": "e2e",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "e2e",
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.51.1",
"@types/node": "^22.13.10"
}
},
"node_modules/@playwright/test": {
"version": "1.51.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.51.1.tgz",
"integrity": "sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.51.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@types/node": {
"version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/playwright": {
"version": "1.51.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.1.tgz",
"integrity": "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.51.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.51.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.1.tgz",
"integrity": "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"license": "MIT"
}
}
}

View File

@@ -0,0 +1,8 @@
{
"name": "nextcloud-aio-mastercontainer-tests",
"version": "1.0.0",
"license": "AGPL-3.0-or-later",
"devDependencies": {
"@playwright/test": "^1.51.1"
}
}

View File

@@ -0,0 +1,29 @@
import { defineConfig, devices } from '@playwright/test'
/**
* @see https://playwright.dev/docs/test-configuration
*/
export default defineConfig({
testDir: './tests',
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: 0,
workers: 1,
reporter: [
['list'],
['html'],
],
use: {
baseURL: process.env.BASE_URL ?? 'http://localhost:8080',
trace: 'on',
},
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
ignoreHTTPSErrors: true,
},
},
],
})

View File

@@ -0,0 +1,96 @@
import { test, expect } from '@playwright/test';
import { writeFileSync } from 'node:fs'
test('Initial setup', async ({ page: setupPage }) => {
test.setTimeout(10 * 60 * 1000)
// Extract initial password
await setupPage.goto('./setup');
const password = await setupPage.locator('#initial-password').innerText()
const containersPagePromise = setupPage.waitForEvent('popup');
await setupPage.getByRole('link', { name: 'Open Nextcloud AIO login ↗' }).click();
const containersPage = await containersPagePromise;
// Log in and wait for redirect
await containersPage.locator('#master-password').click();
await containersPage.locator('#master-password').fill(password);
await containersPage.getByRole('button', { name: 'Log in' }).click();
await containersPage.waitForURL('./containers');
// Reject IP addresses
await containersPage.locator('#domain').click();
await containersPage.locator('#domain').fill('1.1.1.1');
await containersPage.getByRole('button', { name: 'Submit domain' }).click();
await expect(containersPage.locator('body')).toContainText('Please enter a domain and not an IP-address!');
// Accept example.com (requires disabled domain validation)
await containersPage.locator('#domain').click();
await containersPage.locator('#domain').fill('example.com');
await containersPage.getByRole('button', { name: 'Submit domain' }).click();
// Disable all additional containers
await containersPage.locator('#talk').uncheck();
await containersPage.getByRole('checkbox', { name: 'Whiteboard' }).uncheck();
await containersPage.getByRole('checkbox', { name: 'Imaginary' }).uncheck();
await containersPage.getByRole('checkbox', { name: 'Collabora' }).uncheck();
await containersPage.getByRole('button', { name: 'Save changes' }).click();
await expect(containersPage.locator('#talk')).not.toBeChecked()
await expect(containersPage.getByRole('checkbox', { name: 'Whiteboard' })).not.toBeChecked()
await expect(containersPage.getByRole('checkbox', { name: 'Imaginary' })).not.toBeChecked()
await expect(containersPage.getByRole('checkbox', { name: 'Collabora' })).not.toBeChecked()
// Reject invalid time zones
await containersPage.locator('#timezone').click();
await containersPage.locator('#timezone').fill('Invalid time zone');
containersPage.once('dialog', dialog => {
console.log(`Dialog message: ${dialog.message()}`)
dialog.accept()
});
await containersPage.getByRole('button', { name: 'Submit timezone' }).click();
await expect(containersPage.locator('body')).toContainText('The entered timezone does not seem to be a valid timezone!')
// Accept valid time zone
await containersPage.locator('#timezone').click();
await containersPage.locator('#timezone').fill('Europe/Berlin');
containersPage.once('dialog', dialog => {
console.log(`Dialog message: ${dialog.message()}`)
dialog.accept()
});
await containersPage.getByRole('button', { name: 'Submit timezone' }).click();
// Start containers and wait for starting message
await containersPage.getByRole('button', { name: 'Download and start containers' }).click();
await expect(containersPage.getByRole('main')).toContainText('Containers are currently starting.', { timeout: 5 * 60 * 1000 });
await expect(containersPage.getByRole('link', { name: 'Open your Nextcloud ↗' })).toBeVisible({ timeout: 3 * 60 * 1000 });
await expect(containersPage.getByRole('link', { name: 'Open your Nextcloud ↗' })).toHaveAttribute('href', 'https://example.com');
// Extract initial nextcloud password
await expect(containersPage.getByRole('main')).toContainText('Initial Nextcloud password:')
const initialNextcloudPassword = await containersPage.locator('#initial-nextcloud-password').innerText();
// Set backup location and create backup
const borgBackupLocation = `/mnt/test/aio-${Math.floor(Math.random() * 2147483647)}`
await containersPage.locator('#borg_backup_host_location').click();
await containersPage.locator('#borg_backup_host_location').fill(borgBackupLocation);
await containersPage.getByRole('button', { name: 'Submit backup location' }).click();
containersPage.once('dialog', dialog => {
console.log(`Dialog message: ${dialog.message()}`)
dialog.accept()
});
await containersPage.getByRole('button', { name: 'Create backup' }).click();
await expect(containersPage.getByRole('main')).toContainText('Backup container is currently running:', { timeout: 3 * 60 * 1000 });
await expect(containersPage.getByRole('main')).toContainText('Last backup successful on', { timeout: 3 * 60 * 1000 });
await containersPage.getByText('Click here to reveal all backup options').click();
await expect(containersPage.locator('#borg-backup-password')).toBeVisible();
const borgBackupPassword = await containersPage.locator('#borg-backup-password').innerText();
// Assert that all containers are stopped
await expect(containersPage.getByRole('button', { name: 'Start containers' })).toBeVisible();
// Save passwords for restore backup test
writeFileSync('test_data.json', JSON.stringify({
initialNextcloudPassword,
borgBackupLocation,
borgBackupPassword,
}))
});

View File

@@ -0,0 +1,83 @@
import { test, expect } from '@playwright/test';
import { readFileSync } from 'node:fs';
test('Restore instance', async ({ page: setupPage }) => {
test.setTimeout(10 * 60 * 1000)
// Load passwords from previous test
const {
initialNextcloudPassword,
borgBackupLocation,
borgBackupPassword,
} = JSON.parse(readFileSync('test_data.json'))
// Extract initial password
await setupPage.goto('./setup');
const password = await setupPage.locator('#initial-password').innerText()
const containersPagePromise = setupPage.waitForEvent('popup');
await setupPage.getByRole('link', { name: 'Open Nextcloud AIO login ↗' }).click();
const containersPage = await containersPagePromise;
// Log in and wait for redirect
await containersPage.locator('#master-password').click();
await containersPage.locator('#master-password').fill(password);
await containersPage.getByRole('button', { name: 'Log in' }).click();
await containersPage.waitForURL('./containers');
// Reject example.com (requires enabled domain validation)
await containersPage.locator('#domain').click();
await containersPage.locator('#domain').fill('example.com');
await containersPage.getByRole('button', { name: 'Submit domain' }).click();
await expect(containersPage.locator('body')).toContainText('Domain does not point to this server or the reverse proxy is not configured correctly.', { timeout: 15 * 1000 });
// Reject invalid backup location
await containersPage.locator('#borg_restore_host_location').click();
await containersPage.locator('#borg_restore_host_location').fill('/mnt/test/aio-incorrect-path');
await containersPage.locator('#borg_restore_password').click();
await containersPage.locator('#borg_restore_password').fill(borgBackupPassword);
await containersPage.getByRole('button', { name: 'Submit location and encryption password' }).click()
await containersPage.getByRole('button', { name: 'Test path and encryption' }).click();
await expect(containersPage.getByRole('main')).toContainText('Last test failed!', { timeout: 60 * 1000 });
// Reject invalid backup password
await containersPage.locator('#borg_restore_host_location').click();
await containersPage.locator('#borg_restore_host_location').fill(borgBackupLocation);
await containersPage.locator('#borg_restore_password').click();
await containersPage.locator('#borg_restore_password').fill('foobar');
await containersPage.getByRole('button', { name: 'Submit location and encryption password' }).click()
await containersPage.getByRole('button', { name: 'Test path and encryption' }).click();
await expect(containersPage.getByRole('main')).toContainText('Last test failed!', { timeout: 60 * 1000 });
// Accept correct backup location and password
await containersPage.locator('#borg_restore_host_location').click();
await containersPage.locator('#borg_restore_host_location').fill(borgBackupLocation);
await containersPage.locator('#borg_restore_password').click();
await containersPage.locator('#borg_restore_password').fill(borgBackupPassword);
await containersPage.getByRole('button', { name: 'Submit location and encryption password' }).click()
await containersPage.getByRole('button', { name: 'Test path and encryption' }).click();
// Check integrity and restore backup
await containersPage.getByRole('button', { name: 'Check backup integrity' }).click();
await expect(containersPage.getByRole('main')).toContainText('Last check successful!', { timeout: 5 * 60 * 1000 });
containersPage.once('dialog', dialog => {
console.log(`Dialog message: ${dialog.message()}`)
dialog.accept()
});
await containersPage.getByRole('button', { name: 'Restore selected backup' }).click();
await expect(containersPage.getByRole('main')).toContainText('Backup container is currently running:', { timeout: 1 * 60 * 1000 });
// Verify a successful backup restore
await expect(containersPage.getByRole('main')).toContainText('Last restore successful!', { timeout: 3 * 60 * 1000 });
await expect(containersPage.getByRole('main')).toContainText('⚠️ Container updates are available. Click on Stop containers and Start and update containers to update them. You should consider creating a backup first.');
containersPage.once('dialog', dialog => {
console.log(`Dialog message: ${dialog.message()}`)
dialog.accept()
});
await containersPage.getByRole('button', { name: 'Start and update containers' }).click();
await expect(containersPage.getByRole('link', { name: 'Open your Nextcloud ↗' })).toBeVisible({ timeout: 5 * 60 * 1000 });
await expect(containersPage.getByRole('main')).toContainText(initialNextcloudPassword);
// Verify that containers are all stopped
await containersPage.getByRole('button', { name: 'Stop containers' }).click();
await expect(containersPage.getByRole('button', { name: 'Start containers' })).toBeVisible({ timeout: 60 * 1000 });
});