Finanzmanager/tests/e2e/specs/planung.spec.js
fds2610-bot 932d17c128 Fix n.n.: „+ Neu“ öffnet das Formular zur Neuanlage als modalen Dialog
Elf Listenansichten (Klassen, Buchungsvorlagen, Regelmäßige Buchungen,
Erinnerungen, Verträge, Adressen, Sparziele, Kredite, Abgleichregeln,
Budgets, Berichtsgruppen) zeigten das Formular unter der Liste. Jetzt
öffnet der Knopf „+ Neu“ es als Dialog.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 09:50:08 +02:00

170 lines
7.2 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'
// Eindeutige Namen je Lauf: die Tests arbeiten auf einer bestehenden Instanz
// und dürfen sich nicht an Resten eines früheren Laufs stoßen.
const MARKE = Date.now().toString().slice(-6)
// Ein Jahr weit in der Zukunft: dort gibt es keine Buchungen, die Ist-Werte
// bleiben leer und die geprüften Summen damit über die Jahre stabil.
const JAHR = new Date().getFullYear() + 5
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(/\/$/)
}
/**
* Legt ein Budget an und liefert dessen Id. Nach dem Anlegen steht sie in der
* URL des Planers – die Auswahlliste enthält alle Budgets der Instanz und
* taugt deshalb nicht zum Wiederfinden.
*/
async function budgetAnlegen(page, name) {
await page.goto('/planung/budget/verwaltung')
await page.click('#neu-knopf')
await page.fill('#name', name)
await page.fill('#jahr', String(JAHR))
await page.click('.formular button:has-text("Budget anlegen")')
await expect(page.locator('.meldung-erfolg')).toBeVisible()
await expect(page).toHaveURL(/budget=\d+/)
return new URL(page.url()).searchParams.get('budget')
}
/** Die erste Kategoriezeile mit Eingabefeldern – eine Blattkategorie. */
function ersteBeplanbareZeile(page) {
return page.locator('tr.kategoriezeile:not(.summenzeile-kategorie)').first()
}
test.describe('Planung', () => {
test.beforeEach(async ({ page }) => {
await anmelden(page)
})
test('Planungsübersicht führt zu Budget und Monatsabschluss', async ({ page }) => {
await page.click('.seitenleiste a[href="/planung"]')
await expect(page.locator('h1')).toContainText('Planung')
await expect(page.locator('a[href="/planung/budget"]').first()).toBeVisible()
await expect(page.locator('a[href="/planung/monatsabschluss"]').first()).toBeVisible()
})
test('Budget anlegen, Monatswert eintragen und wiederfinden', async ({ page }) => {
const id = await budgetAnlegen(page, `E2E-Budget ${MARKE}`)
await page.goto(`/planung/budget?budget=${id}`)
const zeile = ersteBeplanbareZeile(page)
const kategorie = (await zeile.locator('.baumname').innerText()).trim()
await zeile.locator('input.betragsfeld').first().fill('250,00')
await page.click('button:has-text("Budget speichern")')
await expect(page.locator('.meldung-erfolg')).toContainText('Budget gespeichert')
// Nach dem Speichern steht der Wert wieder im Feld – und in der Jahressumme.
const wieder = page.locator('tr.kategoriezeile', { hasText: kategorie }).first()
await expect(wieder.locator('input.betragsfeld').first()).toHaveValue('250,00')
await expect(wieder).toContainText('250,00')
})
test('Die drei Ansichten zeigen Budget, Ist und Saldo je Monat', async ({ page }) => {
const id = await budgetAnlegen(page, `E2E-Ansichten ${MARKE}`)
// Alles: zwölf Monate à drei Wertspalten, dazu die Jahresspalten.
await page.goto(`/planung/budget?budget=${id}`)
const zeile = ersteBeplanbareZeile(page)
await expect(zeile.locator('td')).toHaveCount(1 + 3 + 12 * 3)
await expect(zeile.locator('input.betragsfeld')).toHaveCount(12)
// Kein Monat ist zusammengefasst: Januar steht als eigene Spalte da.
await expect(page.locator('th.gruppenkopf', { hasText: 'Jan' })).toHaveCount(1)
await page.click('a:has-text("Nur Budget")')
await expect(ersteBeplanbareZeile(page).locator('td')).toHaveCount(1 + 1 + 12)
await expect(ersteBeplanbareZeile(page).locator('input.betragsfeld')).toHaveCount(12)
// Nur IST ist eine reine Leseansicht – ohne Eingabefelder und ohne Speichern.
await page.click('a:has-text("Nur IST")')
await expect(ersteBeplanbareZeile(page).locator('td')).toHaveCount(1 + 1 + 12)
await expect(page.locator('input.betragsfeld')).toHaveCount(0)
await expect(page.locator('button:has-text("Budget speichern")')).toHaveCount(0)
})
test('Zahnrad überträgt den Wert auf das ganze und das restliche Jahr', async ({ page }) => {
const id = await budgetAnlegen(page, `E2E-Zahnrad ${MARKE}`)
await page.goto(`/planung/budget?budget=${id}&ansicht=budget`)
const zeile = ersteBeplanbareZeile(page)
const kategorie = (await zeile.locator('.baumname').innerText()).trim()
const felder = zeile.locator('input.betragsfeld')
// Ganzes Jahr: der Wert aus dem März steht danach auch im Januar.
await felder.nth(2).fill('100,00')
await zeile.locator('.zahnrad').nth(2).click()
await page.click('.zahnradmenue button:has-text("Ganzes Jahr")')
await expect(felder.nth(0)).toHaveValue('100,00')
await expect(felder.nth(11)).toHaveValue('100,00')
// Restliches Jahr: die Monate davor bleiben stehen.
await felder.nth(5).fill('40,00')
await zeile.locator('.zahnrad').nth(5).click()
await page.click('.zahnradmenue button:has-text("Restliches Jahr")')
await expect(felder.nth(4)).toHaveValue('100,00')
await expect(felder.nth(6)).toHaveValue('40,00')
await expect(felder.nth(11)).toHaveValue('40,00')
// Erst das Speichern schreibt die verteilten Werte weg.
await page.click('button:has-text("Budget speichern")')
await expect(page.locator('.meldung-erfolg')).toContainText('Budget gespeichert')
const wieder = page.locator('tr.kategoriezeile', { hasText: kategorie }).first()
// Fünf Monate à 100,00 und sieben à 40,00 ergeben 780,00 im Jahr.
await expect(wieder).toContainText('780,00')
})
test('Monat abschließen, Snapshot ansehen und wiedereröffnen', async ({ page }) => {
// Ein Monat weit in der Vergangenheit, den kein anderer Test anfasst.
const jahr = new Date().getFullYear() - 9
// Rückfragen bejahen: Wiedereröffnen und Abschließen fragen beide nach.
page.on('dialog', d => d.accept())
await page.goto(`/planung/monatsabschluss?jahr=${jahr}&monat=6`)
await expect(page.locator('h2').first()).toContainText('Prüflauf')
// Ein früherer Lauf kann den Monat abgeschlossen zurückgelassen haben.
if (await page.locator('button:has-text("Monat wiedereröffnen")').count()) {
await page.click('button:has-text("Monat wiedereröffnen")')
await expect(page.locator('button:has-text("Monat abschließen")')).toBeVisible()
}
await page.fill('#notiz', `E2E ${MARKE}`)
await page.click('button:has-text("Monat abschließen")')
await expect(page.locator('.meldung-erfolg')).toContainText('abgeschlossen')
// Der abgeschlossene Monat weist die Sperre aus und bietet den Snapshot an.
await expect(page.locator('.phasenmarke')).toContainText('abgeschlossen')
await page.click('a:has-text("Snapshot ansehen")')
await expect(page.locator('h1')).toContainText('Abschluss')
await expect(page.locator('.summenzeile')).toContainText('Summe Kontostände')
await page.goto(`/planung/monatsabschluss?jahr=${jahr}&monat=6`)
await page.click('button:has-text("Monat wiedereröffnen")')
await expect(page.locator('.meldung-erfolg')).toContainText('wieder offen')
await expect(page.locator('button:has-text("Monat abschließen")')).toBeVisible()
})
})