Fix #62: E2E-Tests an den Typeahead im Kontoblatt angepasst
Zwölf Tests standen auf Rot, ohne dass die Anwendung etwas falsch machte – die Specs prüften den Stand vor dem Umbau. Vier Ursachen lagen darunter: Die Kombobox lässt ihr <select> zwar im Formular stehen, setzt es aber auf hidden; selectOption() wartet darauf vergeblich. Der neue Helfer tests/e2e/specs/hilfen.js bedient das Eingabefeld, tippt die Beschriftung ein und klickt den genau passenden Treffer an – exakt, weil [Depot] sonst auch das [Depot] … des Wertpapiergeschäfts träfe. Seit dem Schnellspeichern ohne Neuladen stehen zwei Erfolgsmeldungen übereinander; gemeint ist die neue, also .last(). Die Adressliste ist seit der Adressverwaltung eine Bearbeitungstabelle: Die Namen stehen in Eingabefeldern, wo toContainText nicht hinsieht. Geprüft wird jetzt der Feldwert – gespeichert wurde die Adresse die ganze Zeit korrekt. Die beiden übrigen Fehlschläge waren Folgefehler: Playwright startet nach jedem Fehlschlag einen frischen Worker, das Spec-Modul wird neu geladen, seine Marke neu gezogen und beforeAll legt leere Konten an. Sie brauchten keine Änderung. 87 Tests grün, und ohne die Timeouts in 2,7 statt 7,1 Minuten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AQ57wQWZ4xKHcKmp26bzgr
This commit is contained in:
parent
b74cbe5416
commit
5491bb69f6
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