Skip to content

Commit b4a2349

Browse files
fix description for semantics finders (flutter#181214)
Fixes flutter#181196 This PR fixes the description for the bySemanticsLabel/Identifier matchers, which was missing. Note: The framework uses `byElementPredicate()` in a bunch of places in tests, but not all use the `description` field. Should we add descriptions to those? Example: ```dart import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('bySemanticsLabel', (tester) async { await tester.pumpWidget(const MaterialApp(home: Text('hello'))); await tester.pumpAndSettle(); expect(find.bySemanticsLabel('goodbye'), findsOneWidget); }); testWidgets('bySemanticsLabel regex', (tester) async { await tester.pumpWidget(const MaterialApp(home: Text('hello'))); await tester.pumpAndSettle(); expect(find.bySemanticsLabel(RegExp('^foo')), findsOneWidget); }); testWidgets('bySemanticsIdentifier', (tester) async { await tester.pumpWidget(const MaterialApp(home: Text('hello'))); await tester.pumpAndSettle(); expect(find.bySemanticsIdentifier('goodbye'), findsOneWidget); }); testWidgets('bySemanticsIdentifier regex', (tester) async { await tester.pumpWidget(const MaterialApp(home: Text('hello'))); await tester.pumpAndSettle(); expect(find.bySemanticsIdentifier(RegExp('^foo')), findsOneWidget); }); } ``` now gives: ```dart ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ The following TestFailure was thrown running a test: Expected: exactly one matching candidate Actual: _ElementPredicateWidgetFinder:<Found 0 widgets with a semantics label named "goodbye": []> Which: means none were found but one was expected When the exception was thrown, this was the stack: #4 main.<anonymous closure> (file:///Users/navaronbracke/Desktop/matchers_test/test/widget_test.dart:8:5) <asynchronous suspension> #5 testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:192:15) <asynchronous suspension> #6 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1692:5) <asynchronous suspension> <asynchronous suspension> (elided one frame from package:stack_trace) This was caught by the test expectation on the following line: file:///Users/navaronbracke/Desktop/matchers_test/test/widget_test.dart line 8 The test description was: bySemanticsLabel ════════════════════════════════════════════════════════════════════════════════════════════════════ ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ The following TestFailure was thrown running a test: Expected: exactly one matching candidate Actual: _ElementPredicateWidgetFinder:<Found 0 widgets with a semantics label matching the pattern "^foo": []> Which: means none were found but one was expected When the exception was thrown, this was the stack: #4 main.<anonymous closure> (file:///Users/navaronbracke/Desktop/matchers_test/test/widget_test.dart:14:5) <asynchronous suspension> #5 testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:192:15) <asynchronous suspension> #6 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1692:5) <asynchronous suspension> <asynchronous suspension> (elided one frame from package:stack_trace) This was caught by the test expectation on the following line: file:///Users/navaronbracke/Desktop/matchers_test/test/widget_test.dart line 14 The test description was: bySemanticsLabel regex ════════════════════════════════════════════════════════════════════════════════════════════════════ ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ The following TestFailure was thrown running a test: Expected: exactly one matching candidate Actual: _ElementPredicateWidgetFinder:<Found 0 widgets with a semantics identifier named "goodbye": []> Which: means none were found but one was expected When the exception was thrown, this was the stack: #4 main.<anonymous closure> (file:///Users/navaronbracke/Desktop/matchers_test/test/widget_test.dart:20:5) <asynchronous suspension> #5 testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:192:15) <asynchronous suspension> #6 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1692:5) <asynchronous suspension> <asynchronous suspension> (elided one frame from package:stack_trace) This was caught by the test expectation on the following line: file:///Users/navaronbracke/Desktop/matchers_test/test/widget_test.dart line 20 The test description was: bySemanticsIdentifier ════════════════════════════════════════════════════════════════════════════════════════════════════ ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ The following TestFailure was thrown running a test: Expected: exactly one matching candidate Actual: _ElementPredicateWidgetFinder:<Found 0 widgets with a semantics identifier matching the pattern "^foo": []> Which: means none were found but one was expected When the exception was thrown, this was the stack: #4 main.<anonymous closure> (file:///Users/navaronbracke/Desktop/matchers_test/test/widget_test.dart:26:5) <asynchronous suspension> #5 testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:192:15) <asynchronous suspension> #6 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1692:5) <asynchronous suspension> <asynchronous suspension> (elided one frame from package:stack_trace) This was caught by the test expectation on the following line: file:///Users/navaronbracke/Desktop/matchers_test/test/widget_test.dart line 26 The test description was: bySemanticsIdentifier regex ════════════════════════════════════════════════════════════════════════════════════════════════════ ``` *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
1 parent d947e1a commit b4a2349

2 files changed

Lines changed: 118 additions & 12 deletions

File tree

packages/flutter_test/lib/src/finders.dart

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -567,8 +567,15 @@ class CommonFinders {
567567
/// If the `skipOffstage` argument is true (the default), then this skips
568568
/// nodes that are [Offstage] or that are from inactive [Route]s.
569569
Finder bySemanticsLabel(Pattern label, {bool skipOffstage = true}) {
570+
final String description = switch (label) {
571+
final RegExp regExp => 'a semantics label matching the pattern "${regExp.pattern}"',
572+
final String labelString => 'a semantics label named "$labelString"',
573+
_ => 'a semantics label matching "$label"',
574+
};
575+
570576
return _bySemanticsProperty(
571577
label,
578+
description,
572579
(SemanticsNode? semantics) => semantics?.label,
573580
skipOffstage: skipOffstage,
574581
);
@@ -591,15 +598,23 @@ class CommonFinders {
591598
/// If the `skipOffstage` argument is true (the default), then this skips
592599
/// nodes that are [Offstage] or that are from inactive [Route]s.
593600
Finder bySemanticsIdentifier(Pattern identifier, {bool skipOffstage = true}) {
601+
final String description = switch (identifier) {
602+
final RegExp regExp => 'a semantics identifier matching the pattern "${regExp.pattern}"',
603+
final String id => 'a semantics identifier named "$id"',
604+
_ => 'a semantics identifier matching "$identifier"',
605+
};
606+
594607
return _bySemanticsProperty(
595608
identifier,
609+
description,
596610
(SemanticsNode? semantics) => semantics?.identifier,
597611
skipOffstage: skipOffstage,
598612
);
599613
}
600614

601615
Finder _bySemanticsProperty(
602616
Pattern pattern,
617+
String description,
603618
String? Function(SemanticsNode?) propertyGetter, {
604619
bool skipOffstage = true,
605620
}) {
@@ -610,18 +625,23 @@ class CommonFinders {
610625
'this finder, and call dispose on its return value after.',
611626
);
612627
}
613-
return byElementPredicate((Element element) {
614-
// Multiple elements can have the same renderObject - we want the "owner"
615-
// of the renderObject, i.e. the RenderObjectElement.
616-
if (element is! RenderObjectElement) {
617-
return false;
618-
}
619-
final String? propertyValue = propertyGetter(element.renderObject.debugSemantics);
620-
if (propertyValue == null) {
621-
return false;
622-
}
623-
return pattern is RegExp ? pattern.hasMatch(propertyValue) : pattern == propertyValue;
624-
}, skipOffstage: skipOffstage);
628+
629+
return byElementPredicate(
630+
(Element element) {
631+
// Multiple elements can have the same renderObject - we want the "owner"
632+
// of the renderObject, i.e. the RenderObjectElement.
633+
if (element is! RenderObjectElement) {
634+
return false;
635+
}
636+
final String? propertyValue = propertyGetter(element.renderObject.debugSemantics);
637+
if (propertyValue == null) {
638+
return false;
639+
}
640+
return pattern is RegExp ? pattern.hasMatch(propertyValue) : pattern == propertyValue;
641+
},
642+
description: description,
643+
skipOffstage: skipOffstage,
644+
);
625645
}
626646
}
627647

packages/flutter_test/test/finders_test.dart

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,92 @@ void main() {
359359
expect(find.bySemanticsIdentifier(RegExp(r'^item-')), findsNWidgets(2));
360360
semanticsHandle.dispose();
361361
});
362+
363+
testWidgets(
364+
'bySemanticsIdentifier contains given semantics identifier string in the error message',
365+
(WidgetTester tester) async {
366+
await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
367+
368+
late TestFailure failure;
369+
try {
370+
expect(find.bySemanticsIdentifier('custom-identifier'), findsOneWidget);
371+
} on TestFailure catch (e) {
372+
failure = e;
373+
}
374+
375+
expect(failure, isNotNull);
376+
expect(
377+
failure.message,
378+
contains(
379+
'Actual: _ElementPredicateWidgetFinder:<Found 0 widgets with a semantics identifier named "custom-identifier"',
380+
),
381+
);
382+
},
383+
);
384+
385+
testWidgets(
386+
'bySemanticsIdentifier contains given semantics identifier RegExp in the error message',
387+
(WidgetTester tester) async {
388+
await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
389+
390+
late TestFailure failure;
391+
try {
392+
expect(find.bySemanticsIdentifier(RegExp(r'^item-')), findsOneWidget);
393+
} on TestFailure catch (e) {
394+
failure = e;
395+
}
396+
397+
expect(failure, isNotNull);
398+
expect(
399+
failure.message,
400+
contains(
401+
'Actual: _ElementPredicateWidgetFinder:<Found 0 widgets with a semantics identifier matching the pattern "^item-"',
402+
),
403+
);
404+
},
405+
);
406+
407+
testWidgets('bySemanticsLabel contains given label string in the error message', (
408+
WidgetTester tester,
409+
) async {
410+
await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
411+
412+
late TestFailure failure;
413+
try {
414+
expect(find.bySemanticsLabel('label'), findsOneWidget);
415+
} on TestFailure catch (e) {
416+
failure = e;
417+
}
418+
419+
expect(failure, isNotNull);
420+
expect(
421+
failure.message,
422+
contains(
423+
'Actual: _ElementPredicateWidgetFinder:<Found 0 widgets with a semantics label named "label"',
424+
),
425+
);
426+
});
427+
428+
testWidgets('bySemanticsLabel contains given label RegExp in the error message', (
429+
WidgetTester tester,
430+
) async {
431+
await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
432+
433+
late TestFailure failure;
434+
try {
435+
expect(find.bySemanticsLabel(RegExp(r'^item-')), findsOneWidget);
436+
} on TestFailure catch (e) {
437+
failure = e;
438+
}
439+
440+
expect(failure, isNotNull);
441+
expect(
442+
failure.message,
443+
contains(
444+
'Actual: _ElementPredicateWidgetFinder:<Found 0 widgets with a semantics label matching the pattern "^item-"',
445+
),
446+
);
447+
});
362448
});
363449

364450
group('byTooltip', () {

0 commit comments

Comments
 (0)