critical priority medium complexity infrastructure pending testing specialist Tier 1

Acceptance Criteria

simulateScreenReaderSession(WidgetTester, Widget) returns a ScreenReaderSession object containing an ordered list of spoken utterances for the full widget tree
Focus traversal order matches the documented reading order of SemanticsNode children (depth-first, left-to-right, top-to-bottom) for both VoiceOver and TalkBack conventions
Each utterance entry contains: node path, label text, hint text, semantic role, available actions, and traversal index
Gaps in semantic coverage are detected and reported: interactive elements with no label, groups with no group label, images with no description
simulateFocusTraversal() returns the ordered sequence of focusable nodes and asserts no unreachable interactive nodes exist
simulateAnnouncement(String) verifies that a live-region announcement with the given text was emitted during a widget state change
The harness correctly handles merged semantics nodes (SemanticsFlag.isMergingSemanticsOfDescendants)
All simulation methods are compatible with flutter_test WidgetTester and do not require a physical device
API is documented with dartdoc comments including usage examples for at least 3 common scenarios

Technical Requirements

frameworks
Flutter
flutter_test
flutter/semantics
apis
SemanticsBinding
SemanticsNode
SemanticsData
SemanticsFlag
SemanticsAction
WidgetTester.pumpAndSettle
TestSemantics
data models
ScreenReaderSession
SemanticUtterance
SemanticCoverageGap
FocusTraversalResult
performance requirements
simulateScreenReaderSession() completes within 200ms for widget trees up to 200 nodes
No memory leaks — all SemanticsHandle references must be disposed after each simulation
security requirements
Simulation layer must not retain any user PII from semantic labels between test runs
Test utilities must be in a separate test/ or test_utils/ package and never compiled into production builds

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use SemanticsBinding.instance.ensureSemantics() inside the harness setUp to force semantics on in the test environment — Flutter disables semantics by default in tests. Walk the semantic tree via SemanticsNode.visitChildren recursively, collecting nodes into a flat ordered list. To determine VoiceOver vs TalkBack traversal differences, check platform at test invocation and apply the appropriate sort comparator (VoiceOver: geometry-based reading order; TalkBack: child insertion order). For gap detection, flag any node where SemanticsNode.hasFlag(SemanticsFlag.isTextField) || hasAction(SemanticsAction.tap) but SemanticsNode.label.isEmpty.

Store the simulation result as an immutable value object to allow assertion helpers like expectUtteranceContains(session, 'Submit'). Avoid directly calling the private _semanticsOwner — use the public SemanticsBinding API. This task builds directly on the harness scaffolding from task-001; confirm that task's SemanticsHandle lifecycle management is reused here.

Testing Requirements

Unit tests using flutter_test: verify traversal order matches expected depth-first sequence for a hand-crafted widget tree; verify gap detection identifies an unlabelled IconButton; verify merged semantics are collapsed into a single utterance. Integration tests: run simulateScreenReaderSession() against the Activity Registration wizard and assert all form fields produce a label utterance. Edge-case tests: empty widget tree returns empty session; widget tree with only non-interactive decorative nodes returns zero focusable nodes. Target coverage: 90% line coverage on the simulation engine classes.

Component
Accessibility Test Harness
infrastructure medium
Epic Risks (3)
high impact high prob technical

Flutter's build pipeline and SemanticsService.announce() operate asynchronously. Announcements triggered too early (before the semantic tree settles) may be swallowed silently on both platforms, causing acceptance criteria around the 500ms window to fail intermittently in CI and on device, which would block pilot launch.

Mitigation & Contingency

Mitigation: Implement the LiveRegionAnnouncer with a post-frame callback delay and an internal timing guard. Write integration tests using WidgetTester.pump() sequences that verify announcement delivery across multiple frame boundaries. Validate on physical devices at each sprint boundary, not only in CI.

Contingency: If consistent announcement timing cannot be achieved within Flutter's semantic pipeline, switch to a platform channel approach that calls native UIAccessibility.post (iOS) and AccessibilityManager.announce (Android) directly, bypassing Flutter's intermediary.

high impact medium prob technical

Flutter does not natively emit a focus-gain event to Dart code when VoiceOver or TalkBack moves focus to a specific widget. If the intercept mechanism for the SensitiveFieldWarningDialog relies on an unsupported or undocumented hook in the semantics tree, it may miss focus events for some field types or in some navigation contexts, leaving sensitive data unprotected.

Mitigation & Contingency

Mitigation: Prototype the focus-intercept mechanism during the first sprint of this epic, before building the dialog UI. Evaluate Flutter's SemanticsBinding.instance callbacks and custom SemanticsActions as intercept points. Document the chosen mechanism with platform compatibility notes.

Contingency: If no reliable focus-intercept is available, implement an alternative where sensitive fields show a static 'Screen reader active — tap to reveal' overlay instead of an OS dialog, which is less seamless but achieves equivalent privacy protection without relying on an unreliable event hook.

medium impact low prob dependency

The AccessibilityTestHarness depends on internal flutter_test semantic tree APIs that can change between Flutter minor versions. If the project upgrades Flutter during this epic, the harness may break silently, causing CI accessibility tests to pass while actually skipping assertions.

Mitigation & Contingency

Mitigation: Pin the Flutter SDK version in pubspec.yaml for the duration of this epic. Document which flutter_test APIs are used and their stability tier. Add a canary test that explicitly fails if the semantic tree API surface changes.

Contingency: If a forced Flutter upgrade breaks the harness, prioritise patching the harness as a blocking task before any other epic work continues, using the canary test failure as the trigger.