# Steuerkram — E2E-Tests (Playwright) Browser-Tests für die Ebene, die die PHP-Testsuite nicht erreicht: Routing, Controller, Views und das JavaScript in den Formularen. Sie laufen **im Container** des generischen [e2e-runner-Projekts](../../../e2e-runner/) (`~/projects/e2e-runner/`) — auf dem Host wird nichts installiert (kein Node, kein Browser, kein npm). ## Warum diese Ebene Die PHP-Suite (`tests/NN_*.php`) prüft Models und Services und ist dort gründlich. Sie ruft aber nie einen Controller über HTTP auf. Genau dort saßen die zuletzt aufgetretenen Fehler: - **`/accounts/new` und `/assets/new` lieferten HTTP 500** — gefunden vom ersten Smoke-Lauf. Ursache: undefinierte Variablen, die seit der Fehler-Härtung (`Fix #142`) keine stille Warning mehr sind, sondern eine Exception. - **Netto/Brutto-Umschalter** (`Fix #137`): Ein submit-Handler rechnet den eingegebenen Bruttowert vor dem Absenden in Netto um. Serverseitig ist nur das Ergebnis sichtbar — eine kaputte Umrechnung fiele nie auf. - **DOM-XSS in der Bankimport-Vorschau** (`Fix #133`): Die Vorschau wird vollständig im Browser aus einer `fetch`-Antwort gebaut. ## Voraussetzungen 1. **Steuerkram läuft** unter `http://localhost:8080` (`docker compose up -d`). 2. **Testnutzer existiert** (einmalig, idempotent wiederholbar): ```bash docker compose exec web php scripts/e2e-user.php ``` Legt `e2e` / `e2e-test-Pass123!` an bzw. setzt Passwort und Sperrstatus zurück. Abweichende Zugangsdaten als Argumente oder per `E2E_USER` / `E2E_PASSWORD` (Defaults: `helpers/env.js`). Entfernen mit `--delete`. 3. **Testmodus aktiv** — sonst lassen sich Testbuchungen und -rechnungen nicht wieder löschen (Einstellungen → Testmodus). ## Ausführen ```bash tests/e2e/run.sh ``` Das Skript prüft vorab, ob Docker, das e2e-runner-Projekt (Pfad überschreibbar via `E2E_RUNNER_DIR`) und das Playwright-Image vorhanden sind. Fehlt etwas, wird **nur gewarnt und übersprungen** (Exit 0) — die E2E-Tests sind optional und blockieren keine Umgebung ohne Runner. Andere Zielinstanz: `BASE_URL=http://host.docker.internal:9000 tests/e2e/run.sh` Einzelne Datei oder UI-Modus zum Debuggen: ```bash cd ~/projects/e2e-runner TEST_DIR=~/projects/Steuerkram/tests/e2e docker compose run --rm playwright \ sh -c "npm ci && npx playwright test smoke.spec.js" # UI-Modus, danach http://localhost:8077 öffnen: TEST_DIR=~/projects/Steuerkram/tests/e2e docker compose run --rm --service-ports playwright-ui ``` HTML-Report, Screenshots und Traces landen in `playwright-report/` und `test-results/`. ## Umgang mit Testdaten Die Tests laufen gegen eine Instanz mit **echten Arbeitsdaten**. Deshalb gilt: - Sie legen ausschließlich eigene Datensätze an, markiert mit `E2E-TEST--` (`helpers/app.js`, `uniqueTag`), und räumen sie im `afterEach` wieder ab. - Bestehende Belege, Buchungen und Rechnungen werden nie verändert oder gelöscht. - Der Bankimport-Test bricht **vor** dem Bestätigen ab — die Vorschau allein legt keine Transaktionen an. Reste nach einem abgebrochenen Lauf entfernen: ```bash docker compose exec web php scripts/e2e-cleanup.php # nur anzeigen docker compose exec web php scripts/e2e-cleanup.php --force # löschen ``` ### Nummernkreise `rechnung-export.spec.js` finalisiert eine Rechnung und vergibt dabei eine Rechnungs- und eine Belegnummer. Beide gehen beim Aufräumen wieder zurück: `Invoice::delete()` und `Beleg::delete()` zählen den jeweiligen Nummernkreis zurück, wenn der gelöschte Datensatz die **zuletzt vergebene** Nummer trug. Nach einem vollständigen Testlauf stehen die Zähler also wieder auf ihrem Ausgangswert; der Test `Löschen einer finalisierten Rechnung gibt die Nummer wieder frei` weist das nach. War die Nummer nicht die letzte (z.B. weil parallel eine echte Rechnung finalisiert wurde), bleibt der Zähler stehen — eine Nummer wird nie zweimal vergeben. In dem Fall entsteht wie bisher eine Lücke im Nummernkreis. Bricht ein Lauf mittendrin ab, bleiben Testdatensätze liegen; `scripts/e2e-cleanup.php --force` räumt sie nachträglich ab und arbeitet dabei absteigend, damit möglichst viele Nummern zurückwandern. ## Aufbau | Datei | Inhalt | |---|---| | `specs/smoke.spec.js` | Alle Hauptseiten rendern ohne Serverfehler; 404, Login-Zwang, CSV-Export | | `specs/beleg-buchung.spec.js` | Upload → Buchungsmaske → gespeicherte Buchung, inkl. Netto/Brutto-Umschalter | | `specs/rechnung-export.spec.js` | Entwurf → Finalisieren → PDF- und XML-Export (XRechnung 3.0) | | `specs/bank-import.spec.js` | Bankimport-Vorschau inkl. XSS-Regression | | `helpers/app.js` | Upload, Aufräumen, Betragsparsing, `gotoOk` | | `helpers/env.js` | Zugangsdaten und Basis-URL | | `global-setup.js` | Einmaliger Login, legt `.auth/user.json` an | ## Fallstricke - **Submit-Buttons immer auf ihr Formular scopen.** In der Sidebar steht das Logout-Formular (`layout/nav.php`); ein ungescopetes `button[type="submit"]` trifft dessen Button zuerst und beendet die Session — alle folgenden Tests laufen dann gegen die Login-Seite. - **Reihenfolge in der Buchungsmaske:** Die Auswahl des Kategorie-Kontos ruft `updateAutoUst()` und setzt den USt-Satz passend zum Steuercode. Den Satz deshalb **nach** dem Konto setzen. - **Ein Redirect auf `/login` endet mit Status 200.** `gotoOk()` prüft deshalb zusätzlich den Pfad — sonst schlagen Tests mit irreführenden Meldungen fehl. - **Belegverzeichnisse aus PHP-Testläufen:** `docker compose exec web php tests/run.php` läuft als *root* und kann in `data/belege//` ein Verzeichnis anlegen, das root gehört, während die Transaktion die Belegnummer zurückrollt. Trifft später ein echter Upload auf dieselbe Nummer, scheitert `move_uploaded_file()` mit HTTP 500. Solche Leichen finden: `find data/belege -maxdepth 2 -user root`