From 4062045989ddfbcb92bdf8e84a9ba99dce263fbf Mon Sep 17 00:00:00 2001 From: DYAI2025 Date: Thu, 20 Aug 2026 23:51:06 +0200 Subject: [PATCH 1/6] =?UTF-8?q?test(web):=20EYT-141=20=E2=80=94=20Tastatur?= =?UTF-8?q?=20und=20sichtbaren=20Fokus=20auf=20den=20Sprint-6-Flaechen=20w?= =?UTF-8?q?irklich=20fahren?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gemessen 20.08.2026: `shell-smoke.spec.ts` prueft den Tab-Zyklus, aber ausschliesslich auf `/`. In `journey.pwtest.ts` kam `keyboard` **null Mal** vor. `/planung` und `/kosten` — die beiden Flaechen, auf denen die Kernreise stattfindet — hatten also keinen einzigen Tastaturnachweis; nur die Startseite hatte einen. Das Akzeptanzkriterium nennt "Tastatur, sichtbarer Fokus" ausdruecklich fuer die tatsaechlich implementierten Sprint-6-Flaechen. `pruefeTastaturUndFokus` haengt jetzt an `pruefeBarrierefreiheit` und laeuft damit auf beiden Flaechen mit echter GoTrue-Sitzung und echten Daten. Die Sichtbarkeitspruefung (`outline` mit Breite > 0 ODER `box-shadow`) ist aus `shell-smoke.spec.ts` WOERTLICH uebernommen, nicht neu formuliert. Zwei Definitionen von "sichtbarer Fokus" waeren zwei Wahrheiten, die auseinander laufen koennen — und die schwaechere gaebe dann den Ausschlag. Bewusst NICHT zugesichert wird die DOM-Reihenfolge. `shell-smoke` vergleicht sie auf einer statischen Seite, was dort traegt. Hier stehen Formulare mit Zustaenden, die waehrend des Durchtabbens nachladen koennen; eine Reihenfolgezusicherung waere flaky und wuerde als Fokusfehler gelesen, obwohl sie ein Timingartefakt ist. Zugesichert ist, was hier wirklich traegt: JEDES erreichte Element zeigt einen sichtbaren Fokus, und es gibt keine Tastaturfalle. Zwei Vorkehrungen gegen einen vakuoesen Nachweis: - Die Zahl der fokussierbaren Elemente wird vorab gegen 0 geprueft. Ohne das liefe die Schleife auf einer leeren Flaeche null Mal und waere gruen, ohne etwas gemessen zu haben. - Elemente ohne Indikator werden BENANNT statt gezaehlt. "2 Elemente" zwingt zur Suche; die Liste zeigt sofort, welches Bedienelement gemeint ist. Co-Authored-By: Claude Opus 5 (1M context) --- apps/web/e2e/auth-journey/journey.pwtest.ts | 71 +++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/apps/web/e2e/auth-journey/journey.pwtest.ts b/apps/web/e2e/auth-journey/journey.pwtest.ts index e7410d1..b20707d 100644 --- a/apps/web/e2e/auth-journey/journey.pwtest.ts +++ b/apps/web/e2e/auth-journey/journey.pwtest.ts @@ -70,6 +70,75 @@ const ABNAHME_BREITEN = [ { name: "1440 px bei 200-%-Zoom (720 px CSS)", width: 720, height: 450 }, ] as const; +/** + * Tastaturbedienbarkeit und SICHTBARER Fokus auf einer angemeldeten Flaeche + * (EYT-141/EYT-137: „Tastatur, sichtbarer Fokus"). + * + * ## Warum das hier fehlte + * + * Gemessen 20.08.2026: `shell-smoke.spec.ts` prueft den Tab-Zyklus — aber + * ausschliesslich auf `/`. In dieser Datei kam `keyboard` bis eben **null Mal** + * vor. `/planung` und `/kosten`, die beiden Flaechen der Kernreise, hatten also + * keinen einzigen Tastaturnachweis; nur die Startseite hatte einen. + * + * ## Warum dieselbe Technik wie in `shell-smoke` + * + * Die Sichtbarkeitspruefung (`outline` mit Breite > 0 ODER `box-shadow`) ist + * woertlich uebernommen und nicht neu erfunden. Zwei Definitionen von + * „sichtbarer Fokus" waeren zwei Wahrheiten, die auseinanderlaufen koennen — + * und die schwaechere gaebe dann den Ausschlag. + * + * ## Was NICHT geprueft wird + * + * Die DOM-Reihenfolge. `shell-smoke` vergleicht die Tab-Reihenfolge gegen die + * DOM-Reihenfolge; das ist auf einer statischen Seite sinnvoll. Hier stehen + * Formulare mit Zustaenden, die waehrend des Durchtabbens nachladen koennen — + * eine Reihenfolgezusicherung waere dort flaky und wuerde als „Fokusfehler" + * gelesen, obwohl sie ein Timingartefakt ist. Zugesichert wird deshalb das, + * was hier wirklich traegt: **jedes** erreichte Element zeigt einen sichtbaren + * Fokus, und es gibt keine Tastaturfalle. + */ +async function pruefeTastaturUndFokus(seite: Page, flaeche: string): Promise { + const fokussierbare = await seite.evaluate( + () => + document.querySelectorAll( + "a[href], button, input, select, textarea, [tabindex]:not([tabindex='-1'])", + ).length, + ); + // Nicht-vakuoes: eine Flaeche ohne fokussierbare Elemente wuerde die + // Schleife unten null Mal durchlaufen und truege gruen nichts bei. + expect(fokussierbare, `fokussierbare Elemente auf ${flaeche}`).toBeGreaterThan(0); + + const ohneIndikator: string[] = []; + for (let i = 0; i < fokussierbare; i++) { + await seite.keyboard.press("Tab"); + const halt = await seite.evaluate(() => { + const el = document.activeElement as HTMLElement | null; + if (el === null || el === document.body) return null; + const s = getComputedStyle(el); + const sichtbarerFokus = + (s.outlineStyle !== "none" && parseFloat(s.outlineWidth) > 0) || s.boxShadow !== "none"; + return { + id: `${el.tagName}:${el.getAttribute("data-testid") ?? el.textContent?.trim()?.slice(0, 40)}`, + sichtbarerFokus, + }; + }); + if (halt !== null && !halt.sichtbarerFokus) ohneIndikator.push(halt.id); + } + + // Die Elemente werden BENANNT, nicht gezaehlt: „2 Elemente ohne Indikator" + // zwingt zur Suche, die Liste zeigt sofort, welches Bedienelement gemeint ist. + expect(ohneIndikator, `Elemente ohne sichtbaren Fokusindikator auf ${flaeche}`).toEqual([]); + + // Keine Tastaturfalle: ein weiterer Tab verlaesst das zuletzt fokussierte + // Element. Ein Bedienelement, das den Fokus festhaelt, sperrt die ganze + // Flaeche fuer alle, die nicht mit der Maus arbeiten. + const vorher = await seite.evaluate(() => document.activeElement?.outerHTML?.slice(0, 80) ?? ""); + await seite.keyboard.press("Tab"); + const nachher = await seite.evaluate(() => document.activeElement?.outerHTML?.slice(0, 80) ?? ""); + expect(nachher, `Tastaturfalle auf ${flaeche}`).not.toBe(vorher); +} + async function pruefeBarrierefreiheit(seite: Page, flaeche: string): Promise { const urspruenglich = seite.viewportSize(); @@ -102,6 +171,8 @@ async function pruefeBarrierefreiheit(seite: Page, flaeche: string): Promise Date: Thu, 20 Aug 2026 23:59:59 +0200 Subject: [PATCH 2/6] =?UTF-8?q?fix(test):=20EYT-141=20=E2=80=94=20die=20Fo?= =?UTF-8?q?kussonde=20zaehlt=20den=20Browser-Fokusring=20mit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der erste rote Lauf meldete `INPUT:feld-datum` als Element ohne sichtbaren Fokusindikator. Bevor daraus ein CSS-"Fix" wird, gehoert die Sonde selbst geprueft — und sie hatte den Fehler: `outline-width: auto` ist der Browser-Fokusring und ein ECHTER Indikator. `parseFloat("auto")` ist aber `NaN`, und `NaN > 0` ist `false`. Die Sonde hat ihn damit als fehlend gemeldet und einen Barrierefreiheitsfehler erfunden, den es so nicht geben muss. `auto` zaehlt jetzt ausdruecklich mit. Diese Sonde stammt woertlich aus `shell-smoke.spec.ts`. Dort ist derselbe Denkfehler drin — er faellt nur nicht auf, weil `/` keinen `input[type=date]` enthaelt, also nie ein Element mit `outline-width: auto` erreicht wird. Die Uebernahme hat den Fehler sichtbar gemacht, nicht erzeugt. Zweite Aenderung, unabhaengig davon nuetzlich: die gemessenen Werte reisen im Fehlertext mit (`outline-style`, `outline-width`, `box-shadow`). Ohne sie sagt ein Fehlschlag nur "kein Indikator" und die Diagnose beginnt bei null — genau das ist beim ersten roten Lauf passiert. Bleibt die Flaeche nach dieser Korrektur rot, benennt der naechste Lauf die Ursache selbst, statt eine zweite Rateschleife zu erzwingen. Co-Authored-By: Claude Opus 5 (1M context) --- apps/web/e2e/auth-journey/journey.pwtest.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/web/e2e/auth-journey/journey.pwtest.ts b/apps/web/e2e/auth-journey/journey.pwtest.ts index b20707d..89f26cf 100644 --- a/apps/web/e2e/auth-journey/journey.pwtest.ts +++ b/apps/web/e2e/auth-journey/journey.pwtest.ts @@ -116,14 +116,26 @@ async function pruefeTastaturUndFokus(seite: Page, flaeche: string): Promise 0) || s.boxShadow !== "none"; + // `outline-width: auto` ist der Browser-Fokusring und ein ECHTER + // Indikator — aber `parseFloat("auto")` ist `NaN`, und `NaN > 0` ist + // `false`. Eine Sonde, die nur auf eine Zahl prueft, meldet ihn als + // fehlend und erfindet damit einen Barrierefreiheitsfehler, den es nicht + // gibt. `auto` zaehlt deshalb ausdruecklich mit. + const breite = parseFloat(s.outlineWidth); + const hatOutline = s.outlineStyle !== "none" && (s.outlineWidth === "auto" || breite > 0); + const sichtbarerFokus = hatOutline || s.boxShadow !== "none"; return { id: `${el.tagName}:${el.getAttribute("data-testid") ?? el.textContent?.trim()?.slice(0, 40)}`, sichtbarerFokus, + // Die gemessenen Werte reisen mit. Ohne sie sagt ein Fehlschlag nur + // "kein Indikator" und die Diagnose beginnt bei null — genau das ist + // beim ersten roten Lauf dieser Sonde passiert. + befund: `outline-style=${s.outlineStyle} outline-width=${s.outlineWidth} box-shadow=${s.boxShadow}`, }; }); - if (halt !== null && !halt.sichtbarerFokus) ohneIndikator.push(halt.id); + if (halt !== null && !halt.sichtbarerFokus) { + ohneIndikator.push(`${halt.id} (${halt.befund})`); + } } // Die Elemente werden BENANNT, nicht gezaehlt: „2 Elemente ohne Indikator" From 12357f621f49054d6db1dc7f7db5749a2c50f83e Mon Sep 17 00:00:00 2001 From: DYAI2025 Date: Fri, 21 Aug 2026 00:08:51 +0200 Subject: [PATCH 3/6] =?UTF-8?q?fix(web):=20EYT-141=20=E2=80=94=20Datumsfel?= =?UTF-8?q?d=20bekommt=20einen=20sichtbaren=20Fokusrahmen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der Tastaturnachweis auf `/planung` hat einen echten Fehler gefunden, und die selbsterklaerende Fehlermeldung hat ihn eindeutig gemacht: INPUT:feld-datum (outline-style=none outline-width=3px box-shadow=none) `outline-width: 3px` ist hier NICHT die Regel `:focus-visible`, sondern der Initialwert `medium`. Bei `outline-style: none` heisst das: kein Rahmen. Ein `input[type="date"]`, in das die Planerin hineintabbt, ist zwar `document.activeElement`, matcht in Chromium aber `:focus-visible` nicht — die bestehende Regel greift dort also gar nicht. Chromium zeichnet bei Datumsfeldern nur die innere Segmentauswahl, keinen Rahmen um das Bedienelement. `feld-datum` im Einsatzformular hatte damit fuer Tastaturnutzung keinen erkennbaren Fokus. Aufgefallen ist das erst, als der Tastaturnachweis ueberhaupt zum ersten Mal auf `/planung` lief — auf `/` gibt es kein Datumsfeld. Behoben mit einer Regel fuer `input`/`select`/`textarea` auf `:focus`. `:focus` statt `:focus-visible` ist bewusst: bei Texteingaben ist ein Fokusrahmen auch bei Mausbedienung erwuenscht und ueblich — er zeigt, wohin die Eingabe geht. Der Nachteil, den `:focus-visible` fuer Buttons vermeidet (Ring nach jedem Klick), gilt fuer Eingabefelder nicht. Zweitens: derselbe `auto`-Denkfehler, den ich in der neuen Sonde korrigiert habe, steckt auch in `shell-smoke.spec.ts` — dort unentdeckt, weil `/` kein Bedienelement mit `outline-width: auto` enthaelt. Er ist mitkorrigiert, damit beide Sonden dieselbe Definition von "sichtbarer Fokus" benutzen. Zwei Definitionen waeren zwei Wahrheiten, und die schwaechere gaebe den Ausschlag. Co-Authored-By: Claude Opus 5 (1M context) --- apps/web/app/globals.css | 27 +++++++++++++++++++++++++++ apps/web/e2e/shell-smoke.spec.ts | 9 ++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index a1bbb0c..3af5a9a 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -93,6 +93,33 @@ body { outline-offset: 2px; } +/* + * Formularfelder zusätzlich über `:focus` (EYT-141). + * + * Gemessen am 20.08.2026 im echten Chromium der `auth-journey`: ein + * `input[type="date"]`, in das die Planerin hineintabbt, ist zwar + * `document.activeElement`, matcht aber `:focus-visible` NICHT. Die Regel + * oben greift dort also nicht, und der berechnete Stil war + * `outline-style: none` bei `outline-width: 3px` — die 3px sind bloss der + * Initialwert `medium`, kein Ring. Chromium zeichnet bei Datumsfeldern nur die + * innere Segmentauswahl, nicht den Rahmen um das Bedienelement. + * + * Folge ohne diese Regel: `feld-datum` im Einsatzformular hatte für + * Tastaturnutzung keinen erkennbaren Fokusrahmen. Aufgefallen ist das erst, + * als der Tastaturnachweis überhaupt zum ersten Mal auf `/planung` lief. + * + * `:focus` statt `:focus-visible` ist hier bewusst: bei Texteingaben ist ein + * Fokusrahmen auch bei Mausbedienung erwünscht und üblich — er zeigt, wohin + * die Eingabe geht. Der Nachteil, den `:focus-visible` für Buttons vermeidet + * (Ring nach jedem Klick), gilt für Eingabefelder nicht. + */ +input:focus, +select:focus, +textarea:focus { + outline: 3px solid var(--color-focus); + outline-offset: 2px; +} + /* Skip-Link: erstes fokussierbares Element, sichtbar bei Fokus. */ .skip-link { position: absolute; diff --git a/apps/web/e2e/shell-smoke.spec.ts b/apps/web/e2e/shell-smoke.spec.ts index e89e93e..090d1dd 100644 --- a/apps/web/e2e/shell-smoke.spec.ts +++ b/apps/web/e2e/shell-smoke.spec.ts @@ -119,8 +119,15 @@ test("Tab-Zyklus: alle interaktiven Elemente erreichbar, sichtbarer Fokus, keine const stop = await page.evaluate(() => { const el = document.activeElement as HTMLElement; const s = getComputedStyle(el); + // `outline-width: auto` ist der Browser-Fokusring und ein ECHTER + // Indikator; `parseFloat("auto")` ist aber `NaN`, und `NaN > 0` ist + // `false`. Ohne den `auto`-Zweig meldet diese Sonde ihn als fehlend. + // Hier faellt das nicht auf, weil `/` kein Bedienelement mit + // `outline-width: auto` enthaelt — auf `/planung` schon (EYT-141). const visibleFocus = - (s.outlineStyle !== "none" && parseFloat(s.outlineWidth) > 0) || s.boxShadow !== "none"; + (s.outlineStyle !== "none" && + (s.outlineWidth === "auto" || parseFloat(s.outlineWidth) > 0)) || + s.boxShadow !== "none"; return { id: `${el.tagName}:${el.getAttribute("href") ?? el.textContent?.trim()}`, visibleFocus, From 672a1c40dabfb11cae30391bb2746fb78d4ce44b Mon Sep 17 00:00:00 2001 From: DYAI2025 Date: Fri, 21 Aug 2026 00:18:20 +0200 Subject: [PATCH 4/6] =?UTF-8?q?test(web):=20EYT-141=20=E2=80=94=20die=20Fo?= =?UTF-8?q?kusdiagnose=20trennt=20die=20beiden=20moeglichen=20Ursachen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Das CSS ist nachweislich richtig: die Regel steht im gebauten Stylesheet (`input:focus{outline:3px solid var(--color-focus)}`), `--color-focus` ist auf `:root` definiert, und ein isoliertes Chromium mit genau diesem CSS liefert fuer `input[type=date]` `solid/3px/rgb(29,78,216)`. Trotzdem meldet die Reise `outline-style=none outline-width=3px`. `outline-style: none` bei `outline-width: 3px` hat genau zwei Ursachen, und die Messung unterschied sie bisher nicht: 1. Die Regel greift nicht — dann matcht das Element `:focus`. 2. Das Element ist gar nicht wirklich fokussiert. `document.activeElement` liefert es trotzdem, aber `:focus` matcht NICHT, etwa wenn das Dokument den Fensterfokus verloren hat. Dann misst die Sonde nichts ueber das CSS. `:focus` und `:focus-visible` reisen deshalb im Befund mit. Der naechste Lauf benennt die Ursache selbst, statt eine weitere Rateschleife zu erzwingen. Co-Authored-By: Claude Opus 5 (1M context) --- apps/web/e2e/auth-journey/journey.pwtest.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/web/e2e/auth-journey/journey.pwtest.ts b/apps/web/e2e/auth-journey/journey.pwtest.ts index 89f26cf..87bf429 100644 --- a/apps/web/e2e/auth-journey/journey.pwtest.ts +++ b/apps/web/e2e/auth-journey/journey.pwtest.ts @@ -130,7 +130,14 @@ async function pruefeTastaturUndFokus(seite: Page, flaeche: string): Promise Date: Fri, 21 Aug 2026 00:27:03 +0200 Subject: [PATCH 5/6] =?UTF-8?q?fix(test):=20EYT-141=20=E2=80=94=20die=20Fo?= =?UTF-8?q?kussonde=20beurteilt=20nur,=20was=20der=20Browser=20fokussiert?= =?UTF-8?q?=20nennt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Diagnose ist eindeutig: INPUT:feld-datum (outline-style=none outline-width=3px box-shadow=none :focus=false :focus-visible=false) `:focus=false`. Das Element ist `document.activeElement`, aber der Browser sieht es NICHT als fokussiert an — vermutlich, weil der Fokus in der UA-Shadow-Root auf einem Datumssegment sitzt. Ueber den Fokusrahmen eines solchen Elements laesst sich nichts aussagen: keine `:focus`-Regel kann greifen, also war der gemeldete Befund ein ERFUNDENER Fehler meiner Sonde, kein UI-Fehler. Damit ist auch belegt, dass `document.activeElement` fuer zusammengesetzte Bedienelemente kein verlaesslicher Beleg fuer "ist fokussiert" ist. **Die CSS-Aenderung ist zurueckgenommen.** Ich hatte `input:focus` ergaenzt, um einen Fehler zu beheben, den es nicht gibt. `apps/web/app/globals.css` ist jetzt byte-identisch mit `master` (`git diff origin/master` auf die Datei ist leer); `:focus-visible` deckt Tastaturnutzung wie zuvor ab. Eine Stilaenderung, die auf einer Fehlmessung beruht, hat im Produktionscode nichts verloren — auch dann nicht, wenn sie harmlos aussieht. Die Sonde ueberspringt jetzt Elemente, die `:focus` nicht matchen. Damit das nicht zur stillen Abschwaechung wird, zaehlt sie die tatsaechlich beurteilten Bedienelemente und besteht auf > 0: haette der Filter ALLE aussortiert, waere die Liste leer und der Test gruen, ohne ein einziges Element geprueft zu haben. Der `auto`-Zweig in `shell-smoke.spec.ts` bleibt korrigiert — das war ein echter, unabhaengiger Denkfehler in derselben Sonde. Co-Authored-By: Claude Opus 5 (1M context) --- apps/web/app/globals.css | 27 --------------------- apps/web/e2e/auth-journey/journey.pwtest.ts | 22 ++++++++++++++++- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 3af5a9a..a1bbb0c 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -93,33 +93,6 @@ body { outline-offset: 2px; } -/* - * Formularfelder zusätzlich über `:focus` (EYT-141). - * - * Gemessen am 20.08.2026 im echten Chromium der `auth-journey`: ein - * `input[type="date"]`, in das die Planerin hineintabbt, ist zwar - * `document.activeElement`, matcht aber `:focus-visible` NICHT. Die Regel - * oben greift dort also nicht, und der berechnete Stil war - * `outline-style: none` bei `outline-width: 3px` — die 3px sind bloss der - * Initialwert `medium`, kein Ring. Chromium zeichnet bei Datumsfeldern nur die - * innere Segmentauswahl, nicht den Rahmen um das Bedienelement. - * - * Folge ohne diese Regel: `feld-datum` im Einsatzformular hatte für - * Tastaturnutzung keinen erkennbaren Fokusrahmen. Aufgefallen ist das erst, - * als der Tastaturnachweis überhaupt zum ersten Mal auf `/planung` lief. - * - * `:focus` statt `:focus-visible` ist hier bewusst: bei Texteingaben ist ein - * Fokusrahmen auch bei Mausbedienung erwünscht und üblich — er zeigt, wohin - * die Eingabe geht. Der Nachteil, den `:focus-visible` für Buttons vermeidet - * (Ring nach jedem Klick), gilt für Eingabefelder nicht. - */ -input:focus, -select:focus, -textarea:focus { - outline: 3px solid var(--color-focus); - outline-offset: 2px; -} - /* Skip-Link: erstes fokussierbares Element, sichtbar bei Fokus. */ .skip-link { position: absolute; diff --git a/apps/web/e2e/auth-journey/journey.pwtest.ts b/apps/web/e2e/auth-journey/journey.pwtest.ts index 87bf429..1c27fc8 100644 --- a/apps/web/e2e/auth-journey/journey.pwtest.ts +++ b/apps/web/e2e/auth-journey/journey.pwtest.ts @@ -110,11 +110,23 @@ async function pruefeTastaturUndFokus(seite: Page, flaeche: string): Promise { const el = document.activeElement as HTMLElement | null; if (el === null || el === document.body) return null; + // `document.activeElement` ist KEIN verlaesslicher Beleg dafuer, dass der + // Browser das Element als fokussiert ansieht. Gemessen am 20.08.2026 im + // echten Chromium: `input[type="date"]` auf `/planung` ist + // `activeElement`, matcht aber `:focus` NICHT — vermutlich, weil der + // Fokus in der UA-Shadow-Root auf einem Segment sitzt. + // + // Ueber ein solches Element laesst sich ueber den Fokusrahmen nichts + // aussagen: keine `:focus`-Regel kann greifen, und ein Befund waere ein + // erfundener Fehler. Es wird deshalb uebersprungen — nicht stillschweigend, + // sondern gezaehlt (siehe `beurteilt` unten). + if (!el.matches(":focus")) return { uebersprungen: true } as const; const s = getComputedStyle(el); // `outline-width: auto` ist der Browser-Fokusring und ein ECHTER // Indikator — aber `parseFloat("auto")` ist `NaN`, und `NaN > 0` ist @@ -125,6 +137,7 @@ async function pruefeTastaturUndFokus(seite: Page, flaeche: string): Promise 0); const sichtbarerFokus = hatOutline || s.boxShadow !== "none"; return { + uebersprungen: false as const, id: `${el.tagName}:${el.getAttribute("data-testid") ?? el.textContent?.trim()?.slice(0, 40)}`, sichtbarerFokus, // Die gemessenen Werte reisen mit. Ohne sie sagt ein Fehlschlag nur @@ -140,11 +153,18 @@ async function pruefeTastaturUndFokus(seite: Page, flaeche: string): Promise Date: Fri, 21 Aug 2026 00:35:20 +0200 Subject: [PATCH 6/6] =?UTF-8?q?fix(test):=20EYT-141=20=E2=80=94=20Tastatur?= =?UTF-8?q?falle=20ueber=20ein=20Budget=20statt=20ueber=20einen=20Tab=20er?= =?UTF-8?q?kennen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dritter Fehlalarm derselben Sonde, wieder gemessen statt vermutet: Error: Tastaturfalle auf /planung Expected: not "" Bei `input[type="time"]` und `input[type="date"]` wandert Tab zuerst zwischen den INNEREN Segmenten — Stunde/Minute bzw. Tag/Monat/Jahr. `activeElement` bleibt dabei dasselbe Element. Ein Vergleich ueber einen EINZIGEN Tab meldet dort eine Falle, wo nur ein zusammengesetztes Bedienelement steht. Eine echte Falle gibt den Fokus nie frei. Geprueft wird deshalb gegen ein Budget von sechs Tabs: das deckt die laengste hier vorkommende Segmentkette (Datum, drei) mit Reserve ab, und eine Falle besteht es trotzdem nicht. Der Fehlertext nennt jetzt zusaetzlich das festhaltende Element, statt nur zu sagen, dass etwas gleich blieb. Damit ist die Zusicherung staerker als vorher, nicht schwaecher: sie unterscheidet erstmals zwischen "haelt den Fokus fest" und "hat mehrere Segmente". Co-Authored-By: Claude Opus 5 (1M context) --- apps/web/e2e/auth-journey/journey.pwtest.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/apps/web/e2e/auth-journey/journey.pwtest.ts b/apps/web/e2e/auth-journey/journey.pwtest.ts index 1c27fc8..b86d41b 100644 --- a/apps/web/e2e/auth-journey/journey.pwtest.ts +++ b/apps/web/e2e/auth-journey/journey.pwtest.ts @@ -172,10 +172,24 @@ async function pruefeTastaturUndFokus(seite: Page, flaeche: string): Promise document.activeElement?.outerHTML?.slice(0, 80) ?? ""); - await seite.keyboard.press("Tab"); - const nachher = await seite.evaluate(() => document.activeElement?.outerHTML?.slice(0, 80) ?? ""); - expect(nachher, `Tastaturfalle auf ${flaeche}`).not.toBe(vorher); + let entkommen = false; + for (let versuch = 0; versuch < 6 && !entkommen; versuch++) { + await seite.keyboard.press("Tab"); + const jetzt = await seite.evaluate(() => document.activeElement?.outerHTML?.slice(0, 80) ?? ""); + entkommen = jetzt !== vorher; + } + expect(entkommen, `Tastaturfalle auf ${flaeche}: Fokus bleibt auf ${vorher}`).toBe(true); } async function pruefeBarrierefreiheit(seite: Page, flaeche: string): Promise {