// @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() }) })