Finanzmanager/tests/e2e/specs/smoke.spec.js
fds2610-bot 048bbf3894 Cockpit: Übrige Konten aufklappbar, Balken rücken auseinander
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 14:22:06 +02:00

154 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @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('Vermögensentwicklung zeigt zwölf gestapelte Monatsbalken', async ({ page }) => {
await anmelden(page)
const kachel = page.locator('.kachel', { hasText: 'Vermögensentwicklung' })
await expect(kachel.locator('svg.diagramm')).toBeVisible()
// Zwölf Monate, jeder mit eigener Beschriftung unter dem Balken.
await expect(kachel.locator('.diagramm-beschriftung:not(.schwach)')).toHaveCount(12)
await expect(kachel.locator('.diagramm rect').first()).toBeVisible()
// Jedes Balkenstück gehört zu einem Eintrag der Legende, keiner doppelt.
const farben = await kachel.locator('.legendenfarbe:not(.leer)').evaluateAll(
knoten => knoten.map(k => getComputedStyle(k).backgroundColor)
)
expect(farben.length).toBeGreaterThan(0)
expect(new Set(farben).size).toBe(farben.length)
})
test('„Übrige Konten“ führt eine Ebene tiefer und wieder zurück', async ({ page }) => {
await anmelden(page)
const kachel = page.locator('.kachel', { hasText: 'Vermögensentwicklung' })
const sammel = kachel.locator('.legendenname.tiefer')
// Der Testbestand hat mehr Konten als Farbtöne; sonst gäbe es nichts
// aufzuklappen und der Test hätte hier nichts zu prüfen.
await expect(sammel).toHaveText(/Übrige Konten \(\d+\)/)
const oben = await kachel.locator('.legendenname').allTextContents()
await sammel.click()
await expect(page).toHaveURL(/ebene=1/)
await expect(kachel.locator('.diagrammpfad')).toContainText('Ebene 2 von')
// Eine Ebene tiefer stehen andere Konten in der Legende.
const unten = await kachel.locator('.legendenname').allTextContents()
expect(unten.filter(name => oben.includes(name) && !name.startsWith('Übrige'))).toHaveLength(0)
await kachel.locator('.diagrammpfad a').first().click()
await expect(page).toHaveURL(/ebene=0/)
expect(await kachel.locator('.legendenname').allTextContents()).toEqual(oben)
})
test('Schalter „nur aktive“ behält seinen Zustand', async ({ page }) => {
await anmelden(page)
const schalter = page.locator('.kachel', { hasText: 'Vermögensentwicklung' })
.locator('.schalter')
await expect(schalter).toContainText('nur aktive')
// Ein früherer Lauf kann den Schalter umgelegt haben.
if (await schalter.getAttribute('aria-checked') === 'false') {
await schalter.click()
}
await expect(schalter).toHaveAttribute('aria-checked', 'true')
await schalter.click()
await expect(schalter).toHaveAttribute('aria-checked', 'false')
// Gespeichert, nicht nur angezeigt: Der Zustand überlebt den Neuaufruf.
await page.goto('/')
await expect(schalter).toHaveAttribute('aria-checked', 'false')
await schalter.click()
await expect(schalter).toHaveAttribute('aria-checked', 'true')
})
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/)
})
})