Docker Compose mit drei Services (nginx auf Port 8000), Migrationsmechanik, Update- und Backup-Skript. Anwendung: Router mit CSRF-Prüfung, Anmeldung und Ersteinrichtung, zentraler Einstellungsdialog mit Reitern, ein- und ausschaltbares Debug-Log mit Maskierung von Zugangsdaten. Layout nach Vorbild des FinanzManagers: feste Navigation links, Werkzeugleiste oben, Tab-Leiste; Farbschema hell/dunkel/System. Tests: 51 PHP-Prüfungen und 7 Playwright-Tests, alle grün.
88 lines
3 KiB
JavaScript
88 lines
3 KiB
JavaScript
// @ts-check
|
||
const { test, expect } = require('@playwright/test')
|
||
|
||
const BENUTZER = process.env.E2E_USER || 'e2e'
|
||
const PASSWORT = process.env.E2E_PASSWORD || 'e2e-testpasswort'
|
||
|
||
/**
|
||
* Meldet sich an. Die Anwendung leitet nicht angemeldete Aufrufe auf /login um;
|
||
* bei einer frischen Installation stattdessen auf /einrichtung.
|
||
*/
|
||
async function anmelden(page) {
|
||
await page.goto('/login')
|
||
|
||
if (page.url().includes('/einrichtung')) {
|
||
throw new Error(
|
||
'Instanz ist noch nicht eingerichtet. Zuerst einen Benutzer anlegen: ' +
|
||
'docker compose exec -u www-data php php scripts/e2e-benutzer.php'
|
||
)
|
||
}
|
||
|
||
await page.fill('#benutzername', BENUTZER)
|
||
await page.fill('#passwort', PASSWORT)
|
||
await page.click('button[type="submit"]')
|
||
await expect(page).toHaveURL(/\/$/)
|
||
}
|
||
|
||
test.describe('Grundgerüst', () => {
|
||
test('Anmeldeseite ist erreichbar', async ({ page }) => {
|
||
await page.goto('/login')
|
||
await expect(page.locator('h1, .marke')).toContainText(/Finanzmanager|Anmeldung/)
|
||
await expect(page.locator('#benutzername')).toBeVisible()
|
||
})
|
||
|
||
test('Ohne Anmeldung führt jeder Weg zur Anmeldung', async ({ page }) => {
|
||
await page.goto('/einstellungen')
|
||
await expect(page).toHaveURL(/\/(login|einrichtung)/)
|
||
})
|
||
|
||
test('Falsches Passwort wird abgewiesen', async ({ page }) => {
|
||
await page.goto('/login')
|
||
test.skip(page.url().includes('/einrichtung'), 'Instanz noch nicht eingerichtet')
|
||
|
||
await page.fill('#benutzername', BENUTZER)
|
||
await page.fill('#passwort', 'ganz sicher falsch')
|
||
await page.click('button[type="submit"]')
|
||
|
||
await expect(page.locator('.meldung-fehler')).toBeVisible()
|
||
await expect(page).toHaveURL(/\/login/)
|
||
})
|
||
|
||
test('Anmeldung führt ins Cockpit', async ({ page }) => {
|
||
await anmelden(page)
|
||
await expect(page.locator('h1')).toContainText('Cockpit')
|
||
await expect(page.locator('.seitenleiste')).toContainText('Kontenübersicht')
|
||
})
|
||
|
||
test('Einstellungsdialog zeigt alle Reiter', async ({ page }) => {
|
||
await anmelden(page)
|
||
await page.click('a[href="/einstellungen"]')
|
||
|
||
await expect(page.locator('h1')).toContainText('Einstellungen')
|
||
for (const reiter of ['Allgemein', 'Darstellung', 'Buchhaltung', 'Diagnose']) {
|
||
await expect(page.locator('.tableiste')).toContainText(reiter)
|
||
}
|
||
})
|
||
|
||
test('Einstellung lässt sich speichern', async ({ page }) => {
|
||
await anmelden(page)
|
||
await page.goto('/einstellungen?bereich=darstellung')
|
||
|
||
await page.fill('#zeilen_pro_seite', '250')
|
||
// Eindeutig das Einstellungsformular treffen – im Kopfbereich steht
|
||
// ebenfalls ein Submit-Knopf (Abmelden), und der kommt im DOM zuerst.
|
||
await page.click('.formular.breit button[type="submit"]')
|
||
|
||
await expect(page.locator('.meldung-erfolg')).toBeVisible()
|
||
await expect(page.locator('#zeilen_pro_seite')).toHaveValue('250')
|
||
})
|
||
|
||
test('Abmelden beendet die Sitzung', async ({ page }) => {
|
||
await anmelden(page)
|
||
await page.click('form[action="/logout"] button')
|
||
|
||
await expect(page).toHaveURL(/\/login/)
|
||
await page.goto('/')
|
||
await expect(page).toHaveURL(/\/login/)
|
||
})
|
||
})
|