Finanzmanager/tests/e2e/specs/smoke.spec.js
fds2610-bot a2867bd806 Fix #130: Marke der Anmeldeseite an die Seitenleiste angleichen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:46:01 +02:00

177 lines
6.6 KiB
JavaScript
Raw Permalink 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, .anmelde-marke')).toContainText(/Finanzmanager|Anmeldung/)
await expect(page.locator('#benutzername')).toBeVisible()
})
test('Anmeldeseite zeigt dieselbe Marke wie die Seitenleiste', async ({ page }) => {
const stil = loc => loc.evaluate(k => {
const s = getComputedStyle(k)
return { familie: s.fontFamily, gewicht: s.fontWeight }
})
await page.goto('/login')
const marke = page.locator('.anmelde-marke')
await expect(marke.locator('img.marken-symbol')).toHaveAttribute('src', '/static/coinstack.png')
await expect(marke.locator('img.marken-symbol')).toBeVisible()
await expect(marke.locator('.marken-text')).toHaveText('Finanzmanager')
const anmeldung = await stil(marke.locator('.marken-text'))
// Die zweite Worthälfte hebt sich farblich ab, wie in der Seitenleiste.
const farben = await marke.locator('.marken-text').evaluate(k => [
getComputedStyle(k).color, getComputedStyle(k.querySelector('span')).color,
])
expect(farben[0]).not.toBe(farben[1])
await anmelden(page)
const leiste = await stil(page.locator('.seitenleisten-marke .marken-text'))
expect(anmeldung).toEqual(leiste)
})
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/)
})
})