// @ts-check const { test, expect } = require('@playwright/test') const BENUTZER = process.env.E2E_USER || 'e2e' const PASSWORT = process.env.E2E_PASSWORD || 'e2e-testpasswort' 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(/\/$/) } /** * Die Module aus den Phasen 4 bis 7: Cockpit-Kacheln, Online-Banking, * Wertpapiere und die Nebenmodule. * * Geprüft wird, dass jeder Bereich erreichbar ist und seine Kernansicht * aufbaut – nicht die Rechenlogik. Die steckt in der PHP-Suite, wo sie * schneller und isoliert läuft. */ test.describe('Module der Phasen 4 bis 7', () => { test.beforeEach(async ({ page }) => { await anmelden(page) }) test('Jeder Navigationspunkt führt in ein gebautes Modul', async ({ page }) => { const punkte = [ ['/konten', 'Kontenübersicht'], ['/banking', 'Bankverbindungen'], ['/zahlungsverkehr', 'Zahlungsverkehr'], ['/auswertungen', 'Auswertungen'], ['/planung', 'Planung'], ['/termine', 'Regelmäßige Buchungen'], ['/vertraege', 'Verträge'], ['/wertpapiere', 'Wertpapiere'], ['/verwaltung', 'Verwaltung'], ] // Jeder Bereich hat sein Modul: Die Überschrift gehört zum Modul, nicht // zur früheren Platzhalterseite. for (const [pfad, titel] of punkte) { await page.goto(pfad) await expect(page.locator('h1')).toContainText(titel) } }) test('Das Cockpit zeichnet seine Kacheln', async ({ page }) => { await page.goto('/') // Über die Überschriften, nicht über den Kacheltext: „Kontostände“ kommt // auch im Hinweis der Vermögenskachel vor. const ueberschriften = page.locator('.kachel h2') for (const titel of [ 'Vermögensentwicklung', 'Einnahmen und Ausgaben', 'Meine Ausgaben', 'Kontostände', ]) { await expect(ueberschriften.filter({ hasText: titel })).toHaveCount(1) } }) test('Abgewählte Kacheln verschwinden und kommen zurück', async ({ page }) => { await page.goto('/einstellungen?bereich=darstellung') const kachel = page.locator('input[name="cockpit_kacheln[]"][value="ausgaben"]') await kachel.uncheck() await page.click('button:has-text("Speichern")') await page.goto('/') await expect(page.locator('.kachel h2', { hasText: 'Meine Ausgaben' })).toHaveCount(0) await expect(page.locator('.kachelfuss')).toContainText('abgeschaltet') await page.goto('/einstellungen?bereich=darstellung') await page.locator('input[name="cockpit_kacheln[]"][value="ausgaben"]').check() await page.click('button:has-text("Speichern")') await page.goto('/') await expect(page.locator('.kachel h2', { hasText: 'Meine Ausgaben' })).toHaveCount(1) }) test('Eine Bankverbindung lässt sich anlegen und wieder löschen', async ({ page }) => { await page.goto('/banking/neu') await page.fill('#name', 'E2E-Testbank') await page.fill('#blz', '12030000') await page.fill('#fints_url', 'https://banking.example.org/fints') await page.fill('#benutzerkennung', 'e2e-kunde') await page.fill('#pin', 'geheim') // Schritt 1 speichert nur die Zugangsdaten. await expect(page.locator('.schrittmarke')).toContainText('Schritt 1 von 3') await page.click('button:has-text("Weiter")') // Schritt 2 fragt die Bank selbst nach ihren TAN-Verfahren. Die Adresse // im Test führt nirgendwohin – geprüft wird deshalb der Fehlerweg: Die // Seite erklärt, was zu tun ist, statt stehen zu bleiben. await expect(page.locator('.schrittmarke')).toContainText('Schritt 2 von 3') await expect(page.locator('h1')).toContainText('E2E-Testbank') await expect(page.locator('.meldung-fehler')).toBeVisible() await expect(page.locator('a:has-text("Zugangsdaten prüfen")')).toHaveCount(1) // Wer die Einrichtung vorzeitig verlässt, landet in der Übersicht: Die // Zugangsmaske böte nur noch an, was gerade durchlaufen wurde. await page.click('a:has-text("Später einstellen")') await expect(page.locator('h1')).toContainText('Bankverbindungen') await expect(page.locator('td a:has-text("E2E-Testbank")')).toHaveCount(1) await page.click('td a:has-text("E2E-Testbank")') await expect(page.locator('h1')).toContainText('E2E-Testbank') // Die PIN wird nie wieder angezeigt. await expect(page.locator('#pin')).toHaveValue('') await expect(page.locator('#pin')).toHaveAttribute('placeholder', /unverändert/) // Ohne Wahl bleibt der Hinweis stehen, und die Seite ist wieder erreichbar. await expect(page.locator('a:has-text("TAN-Verfahren einstellen")')).toHaveCount(1) // Schritt 3: die Kontenzuordnung. Ohne erreichbare Bank kennt sie noch // kein Konto und bietet die Abfrage an, statt eine leere Tabelle zu zeigen. await page.click('a:has-text("Konten zuordnen")') await expect(page.locator('h1')).toContainText('Konten zuordnen') await expect(page.locator('.platzhalter')).toContainText('Noch kein Konto bekannt') await expect(page.locator('button:has-text("Konten bei der Bank abfragen")')).toHaveCount(1) await page.click('a:has-text("Zurück zur Bankverbindung")') await expect(page.locator('h1')).toContainText('E2E-Testbank') page.once('dialog', dialog => dialog.accept()) await page.click('button:has-text("Bankverbindung löschen")') await expect(page.locator('.meldung')).toContainText('gelöscht') }) test('Eine falsche IBAN wird beim Anlegen einer Adresse abgewiesen', async ({ page }) => { await page.goto('/vertraege/adressen') await page.fill('#name', 'E2E-Falschtest') await page.fill('#iban', 'DE02120300000000202052') await page.click('button:has-text("Adresse anlegen")') await expect(page.locator('.meldung-fehler')).toContainText('IBAN') }) test('Ein Kredit zeigt seinen gerechneten Tilgungsplan', async ({ page }) => { await page.goto('/vertraege/kredite') await page.fill('#name', 'E2E-Darlehen') await page.fill('#darlehensbetrag', '100000') await page.fill('#zinssatz', '3,0') await page.fill('#rate', '500') await page.click('button:has-text("Kredit anlegen")') await expect(page.locator('h3', { hasText: 'Tilgungsplan' })).toContainText('E2E-Darlehen') await expect(page.locator('.kennzahltitel', { hasText: 'Zinsen gesamt' })).toHaveCount(1) // Erste Rate: 250 Zinsen, 250 Tilgung. Die erste Tabelle ist die // Kreditliste, der Plan steht in der zweiten. const ersteRate = page.locator('table.liste').last().locator('tbody tr').first() await expect(ersteRate).toContainText('250,00') page.once('dialog', dialog => dialog.accept()) await page.locator('tr', { hasText: 'E2E-Darlehen' }).locator('button:has-text("Löschen")').first().click() await expect(page.locator('.meldung')).toContainText('gelöscht') }) test('Der Finanzkalender ist erreichbar und lässt sich einstellen', async ({ page }) => { await page.goto('/termine/kalender') await expect(page.locator('h1')).toContainText('Finanzkalender') await page.selectOption('#tage', '90') await page.click('button:has-text("Anzeigen")') await expect(page).toHaveURL(/tage=90/) }) test('Der Zahlungsverkehr weist eine falsche IBAN ab', async ({ page }) => { await page.goto('/zahlungsverkehr/neu') await page.fill('#empfaenger', 'E2E-Empfänger') await page.fill('#iban', 'DE00000000000000000000') await page.fill('#betrag', '10,00') await page.click('button:has-text("Erfassen")') // Die Bankverbindung ist bewusst nicht Pflicht: Ein Auftrag darf erfasst // werden, bevor eine eingerichtet ist. Geprüft wird sie erst beim Senden. await expect(page.locator('.meldung-fehler')).toContainText('IBAN') }) })