Skip to content

test(EYT-141): Tastatur und sichtbaren Fokus auf /planung und /kosten wirklich fahren - #87

Merged
DYAI2025 merged 6 commits into
masterfrom
kb-focus
Aug 20, 2026
Merged

test(EYT-141): Tastatur und sichtbaren Fokus auf /planung und /kosten wirklich fahren#87
DYAI2025 merged 6 commits into
masterfrom
kb-focus

Conversation

@DYAI2025

@DYAI2025 DYAI2025 commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Kein Produktionscode, nur der fehlende Nachweis.

Der Befund

shell-smoke.spec.ts prüft den Tab-Zyklus — ausschliesslich auf /. In journey.pwtest.ts kam keyboard null Mal vor:

$ grep -c 'visibleFocus\|keyboard' apps/web/e2e/auth-journey/journey.pwtest.ts
0

/planung und /kosten, die beiden Flächen der Kernreise, hatten damit keinen einzigen Tastaturnachweis; nur die Startseite hatte einen. Das Akzeptanzkriterium nennt „Tastatur, sichtbarer Fokus" ausdrücklich für die tatsächlich implementierten Sprint-6-Flächen.

Das ist dieselbe Lücke wie beim verschachtelten <main> in PR #81 — nur an einer anderen Stelle: der Wächter existierte, hat die Fläche aber nie betreten.

Was jetzt läuft

pruefeTastaturUndFokus hängt an pruefeBarrierefreiheit und läuft damit auf beiden Flächen, mit echter GoTrue-Sitzung und echten Daten aus PostgreSQL.

Die Sichtbarkeitsprüfung (outline mit Breite > 0 oder box-shadow) ist aus shell-smoke.spec.ts wörtlich übernommen, nicht neu formuliert. Zwei Definitionen von „sichtbarer Fokus" wären zwei Wahrheiten, die auseinanderlaufen können — und die schwächere gäbe dann den Ausschlag.

Was bewusst NICHT zugesichert wird

Die DOM-Reihenfolge. shell-smoke vergleicht Tab- gegen DOM-Reihenfolge; auf einer statischen Seite trägt das. Hier stehen Formulare mit Zuständen, die während des Durchtabbens nachladen können — eine Reihenfolgezusicherung wäre flaky und würde als „Fokusfehler" gelesen, obwohl sie ein Timingartefakt ist.

Zugesichert ist, was hier wirklich trägt: jedes erreichte Element zeigt einen sichtbaren Fokus, und es gibt keine Tastaturfalle (ein weiterer Tab verlässt das zuletzt fokussierte Element).

Zwei Vorkehrungen gegen einen vakuösen Nachweis

  1. Die Zahl der fokussierbaren Elemente wird vorab gegen 0 geprüft. Ohne das liefe die Schleife auf einer leeren Fläche null Mal und wäre grün, ohne etwas gemessen zu haben.
  2. Elemente ohne Indikator werden benannt statt gezählt. „2 Elemente ohne Indikator" zwingt zur Suche; die Liste zeigt sofort, welches Bedienelement gemeint ist.

Gates lokal

tsc --noEmit   → 0
eslint         → 0
prettier       → 0

Die eigentliche Messung ist auth-journey in diesem PR.

🤖 Generated with Claude Code

Summary by Sourcery

Sichere die Tastaturbedienbarkeit und sichtbaren Fokusindikatoren auf den zentralen authentifizierten Flächen ab.

Bug Fixes:

  • Erweitere die Barrierefreiheitsprüfungen der angemeldeten Kernflächen /planung und /kosten um den tatsächlichen Nachweis von Tastaturbedienbarkeit, sichtbarem Fokus und dem Fehlen von Tastaturfallen.
  • Erkenne native Browser-Fokusringe mit outline-width: auto korrekt, damit die Fokusprüfung keine gültigen Indikatoren als Fehler meldet.

Enhancements:

  • Verbessere die Diagnose der Tastaturtests durch die Benennung betroffener Bedienelemente und Schutzprüfungen gegen vakuose oder nicht tatsächlich ausgeführte Messungen.

Tests:

  • Verknüpfe den Tastatur- und Fokusnachweis mit den Barrierefreiheitstests der authentifizierten Reise und prüfe alle erreichten interaktiven Elemente.

…laechen wirklich fahren

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) <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @DYAI2025, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@sourcery-ai

sourcery-ai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a focused Playwright-based keyboard accessibility check to the authenticated journey tests and wires it into the existing accessibility helper, ensuring /planung and /kosten have non‑vacuous coverage for visible focus and keyboard traps using the same definition as shell-smoke.

Sequence diagram for the updated accessibility checks with pruefeTastaturUndFokus

sequenceDiagram
  participant TestRunner
  participant pruefeBarrierefreiheit
  participant pruefeTastaturUndFokus
  participant Page
  participant DOM

  TestRunner->>pruefeBarrierefreiheit: pruefeBarrierefreiheit(seite, flaeche)
  pruefeBarrierefreiheit->>Page: setViewportSize
  pruefeBarrierefreiheit->>Page: goto
  pruefeBarrierefreiheit-->>TestRunner: viewport restored

  pruefeBarrierefreiheit->>pruefeTastaturUndFokus: pruefeTastaturUndFokus(seite, flaeche)

  pruefeTastaturUndFokus->>Page: evaluate(querySelectorAll.length)
  Page->>DOM: querySelectorAll(focusable)
  DOM-->>Page: fokussierbare
  Page-->>pruefeTastaturUndFokus: fokussierbare
  pruefeTastaturUndFokus->>TestRunner: expect(fokussierbare).toBeGreaterThan(0)

  loop Tab cycle over fokussierbare
    pruefeTastaturUndFokus->>Page: keyboard.press("Tab")
    pruefeTastaturUndFokus->>Page: evaluate(activeElement, getComputedStyle)
    Page->>DOM: document.activeElement
    DOM-->>Page: activeElement
    Page-->>pruefeTastaturUndFokus: { id, sichtbarerFokus }
    alt halt ohne sichtbarerFokus
      pruefeTastaturUndFokus->>pruefeTastaturUndFokus: ohneIndikator.push(id)
    end
  end

  pruefeTastaturUndFokus->>TestRunner: expect(ohneIndikator).toEqual([])

  pruefeTastaturUndFokus->>Page: evaluate(activeElement.outerHTML.slice)
  Page-->>pruefeTastaturUndFokus: vorher
  pruefeTastaturUndFokus->>Page: keyboard.press("Tab")
  pruefeTastaturUndFokus->>Page: evaluate(activeElement.outerHTML.slice)
  Page-->>pruefeTastaturUndFokus: nachher
  pruefeTastaturUndFokus->>TestRunner: expect(nachher).not.toBe(vorher)

  pruefeBarrierefreiheit->>Page: AxeBuilder(...).analyze()
  Page-->>pruefeBarrierefreiheit: ergebnis
  pruefeBarrierefreiheit-->>TestRunner: completes accessibility checks
Loading

File-Level Changes

Change Details Files
Introduce pruefeTastaturUndFokus helper to assert keyboard operability and visible focus on authenticated pages.
  • Define a Playwright helper that counts focusable elements via querySelectorAll and fails when none are present.
  • Simulate Tab key presses to traverse the focusable elements and inspect document.activeElement in the browser context.
  • Determine visible focus using the same outline/box-shadow rule as shell-smoke and collect identifiers for elements without indicators.
  • Assert that the list of elements without visible focus is empty, with a descriptive failure message naming offending controls.
  • Add a post-loop check to ensure an additional Tab changes the active element, guarding against keyboard traps.
apps/web/e2e/auth-journey/journey.pwtest.ts
Integrate the new keyboard and focus assertions into the existing pruefeBarrierefreiheit flow for authenticated journey pages.
  • Invoke pruefeTastaturUndFokus from pruefeBarrierefreiheit after viewport-based Axe runs.
  • Ensure the new checks run with real authenticated sessions and data, alongside existing WCAG Axe checks.
apps/web/e2e/auth-journey/journey.pwtest.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

BenPerro and others added 5 commits August 20, 2026 23:59
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
…rsachen

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) <noreply@anthropic.com>
…kussiert nennt

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) <noreply@anthropic.com>
… Tab erkennen

Dritter Fehlalarm derselben Sonde, wieder gemessen statt vermutet:

    Error: Tastaturfalle auf /planung
    Expected: not "<input data-testid=\"feld-beginn\" type=\"time\" …>"

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) <noreply@anthropic.com>
@DYAI2025
DYAI2025 merged commit ec5349c into master Aug 20, 2026
23 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants