Merge pull request 'Fix #62: E2E-Tests an den Typeahead im Kontoblatt angepasst' (#63) from fds2610-bot/Finanzmanager:main into main
Reviewed-on: https://codeberg.org/fds2610/Finanzmanager/pulls/63
This commit is contained in:
commit
f4fa4f0e3a
7 changed files with 99 additions and 20 deletions
28
Changes.md
28
Changes.md
|
|
@ -1,5 +1,33 @@
|
|||
# Änderungen
|
||||
|
||||
## E2E-Tests an den Typeahead im Kontoblatt angepasst (04.09.2026)
|
||||
|
||||
Zwölf E2E-Tests standen seit dem Typeahead-Umbau (Fix #41) auf Rot – nicht, weil die
|
||||
Anwendung etwas falsch machte, sondern weil die Specs den alten Stand prüften. Vier
|
||||
Ursachen lagen darunter:
|
||||
|
||||
- **Die Kombobox versteckt ihr `<select>`.** `kombobox.js` lässt das ursprüngliche Feld
|
||||
zwar im Formular stehen, setzt es aber auf `hidden`; `selectOption()` wartet darauf
|
||||
vergeblich, bis der Test nach 30 Sekunden abbricht. Der neue Helfer
|
||||
`tests/e2e/specs/hilfen.js` bedient stattdessen das Eingabefeld: Er tippt die
|
||||
Beschriftung ein – das ist zugleich die Probe auf die Filterung – und klickt den genau
|
||||
passenden Treffer an. Exakt muss der Vergleich sein, weil `[Depot]` sonst auch das
|
||||
`[Depot] …` des Wertpapiergeschäfts träfe.
|
||||
- **Zwei Erfolgsmeldungen übereinander.** Seit dem Schnellspeichern ohne Neuladen
|
||||
(Fix #40) bleibt die Meldung des Anlegens stehen und die neue kommt darunter. Die Tests
|
||||
meinen die neue und greifen jetzt zu `.last()`.
|
||||
- **Die Adressliste ist eine Bearbeitungstabelle.** Seit Fix #51 stehen die Namen in
|
||||
Eingabefeldern, wo `toContainText` nicht hinsieht. Geprüft wird nun der Feldwert – die
|
||||
Adresse selbst wurde die ganze Zeit korrekt gespeichert.
|
||||
- **Zwei Folgefehler.** Playwright startet nach jedem Fehlschlag einen frischen Worker;
|
||||
damit wird das Spec-Modul neu geladen, seine Marke neu gezogen und `beforeAll` legt neue,
|
||||
leere Konten an. Die Tests „Kontoblatt lässt sich durchsuchen“ und „Löschen fragt nach“
|
||||
suchten deshalb eine Buchung, die auf dem Konto des vorigen Workers lag. Sie brauchten
|
||||
keine Änderung.
|
||||
|
||||
Die Suite läuft wieder vollständig durch: 87 Tests grün, und ohne die Timeouts in 2,7
|
||||
statt 7,1 Minuten.
|
||||
|
||||
## FinTS-Bibliothek auf den aktuellen Upstream-Stand gehoben (04.09.2026)
|
||||
|
||||
Der Fork `nemiah/php-fints` steht jetzt auf `v4.1.4` und damit auf dem Upstream-Stand vom
|
||||
|
|
|
|||
|
|
@ -51,6 +51,20 @@ Hierher gehört nur, was einen Browser braucht: Formularabläufe, Redirects,
|
|||
gerenderte Views, JavaScript. Rechen- und Model-Logik gehört in die PHP-Suite
|
||||
(`tests/NN_*.php`) – schneller und isoliert.
|
||||
|
||||
## Gemeinsame Helfer
|
||||
|
||||
`specs/hilfen.js` liegt neben den Specs, endet aber nicht auf `.spec.js` und wird
|
||||
deshalb nicht als Testdatei eingesammelt. Dort steht `komboboxWaehlen()`: Die
|
||||
Typeahead-Felder aus `public/static/kombobox.js` lassen ihr `<select>` versteckt
|
||||
im Formular stehen, `selectOption()` läuft darauf in einen Timeout. Der Helfer
|
||||
tippt stattdessen die Beschriftung ein und klickt den genau passenden Treffer an.
|
||||
|
||||
```js
|
||||
const { komboboxWaehlen } = require('./hilfen')
|
||||
|
||||
await komboboxWaehlen(zeile.locator('#zuordnungsauswahl select'), 'Lebensmittel')
|
||||
```
|
||||
|
||||
## Berichte
|
||||
|
||||
HTML-Report, Screenshots und Traces landen nach einem Lauf in
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// @ts-check
|
||||
const { test, expect } = require('@playwright/test')
|
||||
const { komboboxWaehlen } = require('./hilfen')
|
||||
|
||||
/*
|
||||
* Der Abgleich mit dem Bargeld-Tool im Browser: die Kachel in der Verwaltung,
|
||||
|
|
@ -167,11 +168,12 @@ test.describe('Bargeld-Tool', () => {
|
|||
|
||||
const zeile = page.locator('tr', { hasText: empfaenger })
|
||||
await zeile.locator('a.zuordnungsfeld').click()
|
||||
await zeile.locator('#zuordnungsauswahl select').selectOption({ label: KATEGORIE })
|
||||
await komboboxWaehlen(zeile.locator('#zuordnungsauswahl select'), KATEGORIE)
|
||||
|
||||
// Die Zuordnung steht, der gescheiterte Abruf hat sie nicht aufgehalten.
|
||||
await expect(page.locator('.meldung-erfolg')).toContainText('Zuordnung gespeichert')
|
||||
await expect(page.locator('.meldung-erfolg')).toContainText('fehlgeschlagen')
|
||||
// Über der Meldung steht noch die des Anlegens – gemeint ist die neue.
|
||||
await expect(page.locator('.meldung-erfolg').last()).toContainText('Zuordnung gespeichert')
|
||||
await expect(page.locator('.meldung-erfolg').last()).toContainText('fehlgeschlagen')
|
||||
|
||||
const danach = page.locator('tr', { hasText: empfaenger })
|
||||
await expect(danach).toContainText(KATEGORIE)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// @ts-check
|
||||
const { test, expect } = require('@playwright/test')
|
||||
const { komboboxWaehlen } = require('./hilfen')
|
||||
|
||||
const BENUTZER = process.env.E2E_USER || 'e2e'
|
||||
const PASSWORT = process.env.E2E_PASSWORD || 'e2e-testpasswort'
|
||||
|
|
@ -172,11 +173,15 @@ test.describe('Buchungen', () => {
|
|||
await zeile.locator('a.zuordnungsfeld').click()
|
||||
|
||||
// Die Auswahlliste gibt es einmal und wandert in die angeklickte Zelle.
|
||||
const liste = zeile.locator('#zuordnungsauswahl select')
|
||||
// Sichtbar wird das Formular; das <select> darin bleibt hinter der
|
||||
// Kombobox verborgen.
|
||||
const liste = zeile.locator('#zuordnungsauswahl')
|
||||
await expect(liste).toBeVisible()
|
||||
await liste.selectOption({ label: KATEGORIE })
|
||||
await komboboxWaehlen(liste.locator('select'), KATEGORIE)
|
||||
|
||||
await expect(page.locator('.meldung-erfolg')).toContainText('Zuordnung')
|
||||
// Die Meldung des Anlegens steht noch da: Das Schnellspeichern lädt
|
||||
// die Seite nicht neu, sondern hängt seine eigene darunter.
|
||||
await expect(page.locator('.meldung-erfolg').last()).toContainText('Zuordnung')
|
||||
await expect(page.locator('tr', { hasText: `Ohne Kategorie ${MARKE}` }))
|
||||
.toContainText(KATEGORIE)
|
||||
})
|
||||
|
|
@ -202,9 +207,9 @@ test.describe('Buchungen', () => {
|
|||
|
||||
const zeile = page.locator('tr', { hasText: `Kein Neuladen ${MARKE}` })
|
||||
await zeile.locator('a.zuordnungsfeld').click()
|
||||
await zeile.locator('#zuordnungsauswahl select').selectOption({ label: KATEGORIE })
|
||||
await komboboxWaehlen(zeile.locator('#zuordnungsauswahl select'), KATEGORIE)
|
||||
|
||||
await expect(page.locator('.meldung-erfolg')).toContainText('Zuordnung')
|
||||
await expect(page.locator('.meldung-erfolg').last()).toContainText('Zuordnung')
|
||||
await expect(zeile).toContainText(KATEGORIE)
|
||||
|
||||
// Ein echtes Neuladen hätte sowohl die Markierung gelöscht als auch die
|
||||
|
|
@ -229,7 +234,7 @@ test.describe('Buchungen', () => {
|
|||
await expect(liste).toBeVisible()
|
||||
await liste.selectOption({ label: KLASSE })
|
||||
|
||||
await expect(page.locator('.meldung-erfolg')).toContainText('Klasse gespeichert')
|
||||
await expect(page.locator('.meldung-erfolg').last()).toContainText('Klasse gespeichert')
|
||||
await expect(page.locator('tr', { hasText: `Ohne Klasse ${MARKE}` }))
|
||||
.toContainText(KLASSE)
|
||||
|
||||
|
|
@ -238,7 +243,7 @@ test.describe('Buchungen', () => {
|
|||
await danach.locator('a.klassenfeld').click()
|
||||
await danach.locator('#klassenauswahl select').selectOption('')
|
||||
|
||||
await expect(page.locator('.meldung-erfolg')).toContainText('Klasse gelöscht')
|
||||
await expect(page.locator('.meldung-erfolg').last()).toContainText('Klasse gelöscht')
|
||||
await expect(page.locator('tr', { hasText: `Ohne Klasse ${MARKE}` }))
|
||||
.not.toContainText(KLASSE)
|
||||
})
|
||||
|
|
@ -253,9 +258,9 @@ test.describe('Buchungen', () => {
|
|||
|
||||
const zeile = page.locator('tr', { hasText: `Nachträglich ${MARKE}` })
|
||||
await zeile.locator('a.zuordnungsfeld').click()
|
||||
await zeile.locator('#zuordnungsauswahl select').selectOption({ label: `[${SPAR}]` })
|
||||
await komboboxWaehlen(zeile.locator('#zuordnungsauswahl select'), `[${SPAR}]`)
|
||||
|
||||
await expect(page.locator('.meldung-erfolg')).toContainText('Zuordnung')
|
||||
await expect(page.locator('.meldung-erfolg').last()).toContainText('Zuordnung')
|
||||
await expect(page.locator('tr', { hasText: `Nachträglich ${MARKE}` }))
|
||||
.toContainText(`[${SPAR}]`)
|
||||
|
||||
|
|
@ -275,7 +280,7 @@ test.describe('Buchungen', () => {
|
|||
|
||||
const zeile = page.locator('tr', { hasText: `Aufzuteilen ${MARKE}` })
|
||||
await zeile.locator('a.zuordnungsfeld').click()
|
||||
await zeile.locator('#zuordnungsauswahl select').selectOption('split')
|
||||
await komboboxWaehlen(zeile.locator('#zuordnungsauswahl select'), 'Splitbuchung …')
|
||||
|
||||
const dialog = page.locator('#splitdialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
|
|
@ -292,7 +297,7 @@ test.describe('Buchungen', () => {
|
|||
// Der Dialog hat zwei Submit-Knöpfe: Speichern und Löschen.
|
||||
await dialog.locator('button[type="submit"].primaer').click()
|
||||
|
||||
await expect(page.locator('.meldung-erfolg')).toContainText('Splitbuchung')
|
||||
await expect(page.locator('.meldung-erfolg').last()).toContainText('Splitbuchung')
|
||||
await expect(page.locator('tr', { hasText: `Aufzuteilen ${MARKE}` }).first())
|
||||
.toContainText('Splitbuchung')
|
||||
|
||||
|
|
@ -320,19 +325,19 @@ test.describe('Buchungen', () => {
|
|||
|
||||
const dialog = page.locator('#splitdialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
await expect(page.locator('#zuordnungsauswahl select')).toBeHidden()
|
||||
await expect(page.locator('#zuordnungsauswahl')).toBeHidden()
|
||||
await expect(dialog.locator('input[name="split[0][betrag]"]')).toHaveValue('-30,00')
|
||||
|
||||
await dialog.locator('#splitloeschen').click()
|
||||
|
||||
await expect(page.locator('.meldung-erfolg')).toContainText('Splitbuchung gelöscht')
|
||||
await expect(page.locator('.meldung-erfolg').last()).toContainText('Splitbuchung gelöscht')
|
||||
const danach = page.locator('tr', { hasText: `Wieder einfach ${MARKE}` })
|
||||
await expect(danach).not.toContainText('Splitbuchung')
|
||||
await expect(danach).not.toContainText(KATEGORIE)
|
||||
|
||||
// Und die Zuordnung geht jetzt wieder über die Auswahlliste.
|
||||
await danach.locator('a.zuordnungsfeld').click()
|
||||
await expect(danach.locator('#zuordnungsauswahl select')).toBeVisible()
|
||||
await expect(danach.locator('#zuordnungsauswahl')).toBeVisible()
|
||||
})
|
||||
|
||||
test('Splitbuchung mit zwei Zeilen', async ({ page }) => {
|
||||
|
|
|
|||
26
tests/e2e/specs/hilfen.js
Normal file
26
tests/e2e/specs/hilfen.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// @ts-check
|
||||
|
||||
/* Gemeinsame Helfer der E2E-Specs. Die Datei endet nicht auf `.spec.js` und
|
||||
* wird deshalb nicht als Testdatei eingesammelt. */
|
||||
|
||||
/**
|
||||
* Wählt in einer Typeahead-Kombobox (public/static/kombobox.js) den Eintrag
|
||||
* mit genau dieser Beschriftung.
|
||||
*
|
||||
* Seit dem Umbau steht das ursprüngliche `<select>` nur noch versteckt im
|
||||
* Formular; bedient wird das Eingabefeld daneben. `selectOption()` läuft
|
||||
* darauf in einen Timeout. Der Helfer tippt die Beschriftung ein – das ist
|
||||
* zugleich die Probe auf die Filterung – und klickt den Treffer an.
|
||||
*
|
||||
* @param {import('@playwright/test').Locator} select das versteckte <select>
|
||||
* @param {string} beschriftung Text des Eintrags, exakt
|
||||
*/
|
||||
async function komboboxWaehlen(select, beschriftung) {
|
||||
const kombobox = select.locator('xpath=ancestor::div[contains(@class,"kombobox")][1]')
|
||||
|
||||
await kombobox.locator('.kombobox-eingabe').fill(beschriftung)
|
||||
// Exakt, sonst träfe „[Depot]“ auch das „[Depot] …“ des Wertpapiergeschäfts.
|
||||
await kombobox.getByRole('option', { name: beschriftung, exact: true }).click()
|
||||
}
|
||||
|
||||
module.exports = { komboboxWaehlen }
|
||||
|
|
@ -323,8 +323,11 @@ test.describe('Module der Phasen 4 bis 7', () => {
|
|||
await expect(page.locator('.meldung-erfolg')).toContainText('erfasst')
|
||||
|
||||
// Die Adresse landet wirklich im Adressbuch – nicht nur der Auftrag.
|
||||
// Die Liste ist eine Bearbeitungstabelle: Die Namen stehen in
|
||||
// Eingabefeldern, nicht als Text.
|
||||
await page.goto('/vertraege/adressen')
|
||||
await expect(page.locator('table.liste')).toContainText(empfaenger)
|
||||
await expect(page.locator(`table.liste input[name="name"][value="${empfaenger}"]`))
|
||||
.toHaveCount(1)
|
||||
})
|
||||
|
||||
test('Die Empfängerauswahl im Zahlungsverkehr ist ein Typeahead aus dem Adressbuch', async ({ page }) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// @ts-check
|
||||
const { test, expect } = require('@playwright/test')
|
||||
const { komboboxWaehlen } = require('./hilfen')
|
||||
|
||||
/*
|
||||
* Der Weg, den die Wertpapierabrechnung nimmt: Sie kommt als Buchung aufs
|
||||
|
|
@ -107,7 +108,7 @@ test.describe('Wertpapiergeschäft aus dem Kontoblatt', () => {
|
|||
|
||||
const zeile = page.locator('tr', { hasText: empfaenger })
|
||||
await zeile.locator('a.zuordnungsfeld').click()
|
||||
await zeile.locator('#zuordnungsauswahl select').selectOption({ label: `[${DEPOT}] …` })
|
||||
await komboboxWaehlen(zeile.locator('#zuordnungsauswahl select'), `[${DEPOT}] …`)
|
||||
|
||||
const dialog = page.locator('#wertpapierdialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
|
|
@ -166,7 +167,7 @@ test.describe('Wertpapiergeschäft aus dem Kontoblatt', () => {
|
|||
|
||||
const zeile = page.locator('tr', { hasText: empfaenger })
|
||||
await zeile.locator('a.zuordnungsfeld').click()
|
||||
await zeile.locator('#zuordnungsauswahl select').selectOption({ label: `[${DEPOT}] …` })
|
||||
await komboboxWaehlen(zeile.locator('#zuordnungsauswahl select'), `[${DEPOT}] …`)
|
||||
|
||||
await expect(page.locator('#wertpapierdialog')).toBeVisible()
|
||||
await page.fill('#wp_name', `Sparplan-Fonds ${MARKE}`)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue