expectNoA11yViolations function

Future<void> expectNoA11yViolations(
  1. WidgetTester tester, {
  2. Finder? finder,
  3. List<A11yRule> rules = const [ContrastRule(), TapTargetSizeRule()],
  4. List<A11ySemanticsRule> semanticsRules = const [SemanticLabelRule(), FocusOrderRule()],
  5. A11ySeverity minimumSeverity = A11ySeverity.warning,
  6. A11yReport? baseline,
})

Fails the current test if A11yReport.generate finds any violation at or above minimumSeverity below finder (the whole app, if omitted).

Runs headless: no real device or screen reader needed. Semantics-based rules (on by default via semanticsRules) need an active SemanticsHandle to see anything — testWidgets provides one automatically unless the test opted out with semanticsEnabled: false.

Pass baseline — e.g. loaded from a checked-in JSON report via A11yReport.fromJson(jsonDecode(await File(...).readAsString())) — to only fail on violations not already present in it, via A11yReport.newViolationsComparedTo. That's the "fail the PR build on new violations" workflow: known, accepted issues don't block every unrelated change.

Implementation

Future<void> expectNoA11yViolations(
  WidgetTester tester, {
  Finder? finder,
  List<A11yRule> rules = const [ContrastRule(), TapTargetSizeRule()],
  List<A11ySemanticsRule> semanticsRules = const [
    SemanticLabelRule(),
    FocusOrderRule(),
  ],
  A11ySeverity minimumSeverity = A11ySeverity.warning,
  A11yReport? baseline,
}) async {
  final context = finder != null
      ? tester.element(finder)
      : tester.binding.rootElement!;

  final report = A11yReport.generate(
    context,
    rules: rules,
    semanticsRules: semanticsRules,
  );

  var relevant = [
    for (final v in report.violations)
      if (_severityRank(v.severity) >= _severityRank(minimumSeverity)) v,
  ];

  if (baseline != null) {
    relevant = A11yReport(
      violations: relevant,
      generatedAt: report.generatedAt,
    ).newViolationsComparedTo(baseline);
  }

  if (relevant.isEmpty) return;

  final buffer = StringBuffer(
    'flutter_a11y_lens found ${relevant.length} violation(s):\n',
  );
  for (final v in relevant) {
    buffer.writeln(
      '  [${v.severity.name}] ${v.ruleId} on ${v.widgetType ?? 'unknown widget'}: '
      '${v.message}',
    );
    if (v.suggestedFix != null) {
      buffer.writeln('    fix: ${v.suggestedFix}');
    }
  }
  fail(buffer.toString());
}