Finanzmanager/tests/e2e/specs/smoke.spec.js
fds2610-bot 896a1e36ae Cockpit: Vermögensentwicklung als gestapeltes Balkendiagramm
Die Kachel war ein Platzhalter und zeigt jetzt die Kontostände der
letzten zwölf Monate je Monatsende – ein Balken pro Monat, ein Farbton
pro Konto. Gezählt wird, was auch in der Kontenübersicht in die Summe
zählt: Konten mit Σ-Kennzeichen.

Der Schalter „nur aktive“ oben rechts blendet stillgelegte Konten aus
und merkt sich seinen Zustand über die Einstellungen. Mehr Konten als
Farbtöne fasst „Übrige Konten (n)“ zusammen – zwei Konten in derselben
Farbe wären in der Legende nicht auseinanderzuhalten.

Eigenes SVG, keine Diagrammbibliothek. Die Farben stehen als Klasse am
Element und folgen damit dem hellen wie dem dunklen Thema.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 13:13:56 +02:00

130 lines
4.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('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/)
})
})