add ability to match child semantics data (#22001)

diff --git a/examples/flutter_gallery/test/demo/material/chip_demo_test.dart b/examples/flutter_gallery/test/demo/material/chip_demo_test.dart
index ef0b6d1..cfc9c65 100644
--- a/examples/flutter_gallery/test/demo/material/chip_demo_test.dart
+++ b/examples/flutter_gallery/test/demo/material/chip_demo_test.dart
@@ -14,7 +14,7 @@
       home: ChipDemo(),
     ));
 
-    expect(tester.getSemanticsData(find.byIcon(Icons.vignette)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byIcon(Icons.vignette)), matchesSemantics(
       isButton: true,
       hasEnabledState: true,
       isEnabled: true,
@@ -22,7 +22,7 @@
       label: 'Update border shape',
     ));
 
-    expect(tester.getSemanticsData(find.byIcon(Icons.refresh)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byIcon(Icons.refresh)), matchesSemantics(
       isButton: true,
       hasEnabledState: true,
       isEnabled: true,
diff --git a/packages/flutter/test/cupertino/slider_test.dart b/packages/flutter/test/cupertino/slider_test.dart
index 5c8e1ca..eae21e7 100644
--- a/packages/flutter/test/cupertino/slider_test.dart
+++ b/packages/flutter/test/cupertino/slider_test.dart
@@ -330,7 +330,7 @@
       ),
     ));
 
-    expect(tester.getSemanticsData(find.byType(CupertinoSlider)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byType(CupertinoSlider)), matchesSemantics(
       hasIncreaseAction: true,
       hasDecreaseAction: true,
       value: '50%',
@@ -348,7 +348,7 @@
       ),
     ));
 
-    expect(tester.getSemanticsData(find.byType(CupertinoSlider)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byType(CupertinoSlider)), matchesSemantics(
       hasIncreaseAction: true,
       hasDecreaseAction: true,
       value: '60%',
diff --git a/packages/flutter/test/material/back_button_test.dart b/packages/flutter/test/material/back_button_test.dart
index 3f50b76..dab5b65 100644
--- a/packages/flutter/test/material/back_button_test.dart
+++ b/packages/flutter/test/material/back_button_test.dart
@@ -82,7 +82,7 @@
 
     await tester.pumpAndSettle();
 
-    expect(tester.getSemanticsData(find.byType(BackButton)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byType(BackButton)), matchesSemantics(
       label: 'Back',
       isButton: true,
       hasEnabledState: true,
diff --git a/packages/flutter/test/material/checkbox_test.dart b/packages/flutter/test/material/checkbox_test.dart
index 3d3a64e..eba2aee 100644
--- a/packages/flutter/test/material/checkbox_test.dart
+++ b/packages/flutter/test/material/checkbox_test.dart
@@ -66,7 +66,7 @@
       ),
     ));
 
-    expect(tester.getSemanticsData(find.byType(Checkbox)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics(
       hasCheckedState: true,
       hasEnabledState: true,
       isEnabled: true,
@@ -80,7 +80,7 @@
       ),
     ));
 
-    expect(tester.getSemanticsData(find.byType(Checkbox)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics(
       hasCheckedState: true,
       hasEnabledState: true,
       isChecked: true,
@@ -95,7 +95,7 @@
       ),
     ));
 
-    expect(tester.getSemanticsData(find.byType(Checkbox)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics(
       hasCheckedState: true,
       hasEnabledState: true,
     ));
@@ -107,7 +107,7 @@
       ),
     ));
 
-    expect(tester.getSemanticsData(find.byType(Checkbox)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics(
       hasCheckedState: true,
       hasEnabledState: true,
       isChecked: true,
@@ -129,7 +129,7 @@
       ),
     ));
 
-    expect(tester.getSemanticsData(find.byType(Checkbox)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics(
       label: 'foo',
       textDirection: TextDirection.ltr,
       hasCheckedState: true,
diff --git a/packages/flutter/test/material/dropdown_test.dart b/packages/flutter/test/material/dropdown_test.dart
index 950629d..c061135 100644
--- a/packages/flutter/test/material/dropdown_test.dart
+++ b/packages/flutter/test/material/dropdown_test.dart
@@ -692,7 +692,7 @@
     ));
 
     // By default the hint contributes the label.
-    expect(tester.getSemanticsData(find.byKey(key)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byKey(key)), matchesSemantics(
       isButton: true,
       label: 'test',
       hasTapAction: true,
@@ -707,7 +707,7 @@
     ));
 
     // Displays label of select item and is no longer tappable.
-    expect(tester.getSemanticsData(find.byKey(key)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byKey(key)), matchesSemantics(
       isButton: true,
       label: 'three',
       hasTapAction: true,
diff --git a/packages/flutter/test/material/expand_icon_test.dart b/packages/flutter/test/material/expand_icon_test.dart
index 2b537eb..57e0a2a 100644
--- a/packages/flutter/test/material/expand_icon_test.dart
+++ b/packages/flutter/test/material/expand_icon_test.dart
@@ -95,7 +95,7 @@
         )
     ));
 
-    expect(tester.getSemanticsData(find.byType(ExpandIcon)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byType(ExpandIcon)), matchesSemantics(
       hasTapAction: true,
       hasEnabledState: true,
       isEnabled: true,
@@ -110,7 +110,7 @@
       )
     ));
 
-    expect(tester.getSemanticsData(find.byType(ExpandIcon)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byType(ExpandIcon)), matchesSemantics(
       hasTapAction: true,
       hasEnabledState: true,
       isEnabled: true,
diff --git a/packages/flutter/test/material/expansion_panel_test.dart b/packages/flutter/test/material/expansion_panel_test.dart
index eb8725d..bf915a7 100644
--- a/packages/flutter/test/material/expansion_panel_test.dart
+++ b/packages/flutter/test/material/expansion_panel_test.dart
@@ -382,7 +382,7 @@
       ),
     );
 
-    expect(tester.getSemanticsData(find.byKey(expandedKey)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byKey(expandedKey)), matchesSemantics(
       label: 'Expanded',
       isButton: true,
       hasEnabledState: true,
@@ -391,7 +391,7 @@
       onTapHint: localizations.expandedIconTapHint,
     ));
 
-    expect(tester.getSemanticsData(find.byKey(collapsedKey)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byKey(collapsedKey)), matchesSemantics(
       label: 'Collapsed',
       isButton: true,
       hasEnabledState: true,
diff --git a/packages/flutter/test/material/reorderable_list_test.dart b/packages/flutter/test/material/reorderable_list_test.dart
index bab13e2..7acdedc 100644
--- a/packages/flutter/test/material/reorderable_list_test.dart
+++ b/packages/flutter/test/material/reorderable_list_test.dart
@@ -462,10 +462,10 @@
           ));
 
           // Get the switch tile's semantics:
-          final SemanticsData semanticsData = tester.getSemanticsData(find.byKey(const Key('Switch tile')));
+          final SemanticsNode semanticsNode = tester.getSemantics(find.byKey(const Key('Switch tile')));
 
           // Check for properties of both SwitchTile semantics and the ReorderableListView custom semantics actions.
-          expect(semanticsData, matchesSemanticsData(
+          expect(semanticsNode, matchesSemantics(
             hasToggledState: true,
             isToggled: true,
             isEnabled: true,
diff --git a/packages/flutter/test/material/snack_bar_test.dart b/packages/flutter/test/material/snack_bar_test.dart
index 992c132..1404263 100644
--- a/packages/flutter/test/material/snack_bar_test.dart
+++ b/packages/flutter/test/material/snack_bar_test.dart
@@ -555,7 +555,7 @@
     await tester.tap(find.text('X'));
     await tester.pumpAndSettle();
 
-    expect(tester.getSemanticsData(find.text('snack')), matchesSemanticsData(
+    expect(tester.getSemantics(find.text('snack')), matchesSemantics(
       isLiveRegion: true,
       hasDismissAction: true,
       hasScrollDownAction: true,
diff --git a/packages/flutter/test/material/user_accounts_drawer_header_test.dart b/packages/flutter/test/material/user_accounts_drawer_header_test.dart
index 25ea2bd..31fb510 100644
--- a/packages/flutter/test/material/user_accounts_drawer_header_test.dart
+++ b/packages/flutter/test/material/user_accounts_drawer_header_test.dart
@@ -378,17 +378,17 @@
     final SemanticsHandle handle = tester.ensureSemantics();
     await pumpTestWidget(tester);
 
-    expect(tester.getSemanticsData(find.text('B')), matchesSemanticsData(
+    expect(tester.getSemantics(find.text('B')), matchesSemantics(
       label: 'B',
       size: const Size(48.0, 48.0),
     ));
 
-    expect(tester.getSemanticsData(find.text('C')), matchesSemanticsData(
+    expect(tester.getSemantics(find.text('C')), matchesSemantics(
       label: 'C',
       size: const Size(48.0, 48.0),
     ));
 
-    expect(tester.getSemanticsData(find.text('D')), matchesSemanticsData(
+    expect(tester.getSemantics(find.text('D')), matchesSemantics(
       label: 'D',
       size: const Size(48.0, 48.0),
     ));
diff --git a/packages/flutter/test/widgets/image_icon_test.dart b/packages/flutter/test/widgets/image_icon_test.dart
index cf70e78..676c530 100644
--- a/packages/flutter/test/widgets/image_icon_test.dart
+++ b/packages/flutter/test/widgets/image_icon_test.dart
@@ -111,7 +111,7 @@
       ),
     );
 
-    expect(tester.getSemanticsData(find.byType(ImageIcon)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byType(ImageIcon)), matchesSemantics(
       label: 'test',
       textDirection: TextDirection.ltr,
     ));
diff --git a/packages/flutter/test/widgets/semantics_test.dart b/packages/flutter/test/widgets/semantics_test.dart
index c2214c7..6f27b26 100644
--- a/packages/flutter/test/widgets/semantics_test.dart
+++ b/packages/flutter/test/widgets/semantics_test.dart
@@ -643,7 +643,7 @@
       onTapHint: 'test',
     ));
 
-    expect(tester.getSemanticsData(find.byType(Semantics)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byType(Semantics)), matchesSemantics(
       hasTapAction: true,
       onTapHint: 'test'
     ));
@@ -654,7 +654,7 @@
       onLongPressHint: 'foo',
     ));
 
-    expect(tester.getSemanticsData(find.byType(Semantics)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byType(Semantics)), matchesSemantics(
       hasLongPressAction: true,
       onLongPressHint: 'foo'
     ));
@@ -671,7 +671,7 @@
       },
     ));
 
-    expect(tester.getSemanticsData(find.byType(Semantics)), matchesSemanticsData(
+    expect(tester.getSemantics(find.byType(Semantics)), matchesSemantics(
       customActions: <CustomSemanticsAction>[
         const CustomSemanticsAction(label: 'bar'),
         const CustomSemanticsAction(label: 'foo'),
diff --git a/packages/flutter_test/lib/src/matchers.dart b/packages/flutter_test/lib/src/matchers.dart
index c30759a..dda8377 100644
--- a/packages/flutter_test/lib/src/matchers.dart
+++ b/packages/flutter_test/lib/src/matchers.dart
@@ -325,13 +325,13 @@
   return _MatchesReferenceImage(image);
 }
 
-/// Asserts that a [SemanticsData] contains the specified information.
+/// Asserts that a [SemanticsNode] contains the specified information.
 ///
 /// If either the label, hint, value, textDirection, or rect fields are not
 /// provided, then they are not part of the comparison.  All of the boolean
 /// flag and action fields must match, and default to false.
 ///
-/// To retrieve the semantics data of a widget, use [tester.getSemanticsData]
+/// To retrieve the semantics data of a widget, use [tester.getSemantics]
 /// with a [Finder] that returns a single widget. Semantics must be enabled
 /// in order to use this method.
 ///
@@ -339,14 +339,178 @@
 ///
 /// ```dart
 /// final SemanticsHandle handle = tester.ensureSemantics();
-/// final SemanticsData data = tester.getSemanticsData(find.text('hello'));
-/// expect(data, matchesSemanticsData(label: 'hello'));
+/// expect(tester.getSemantics(find.text('hello')), matchesSemanticsNode(label: 'hello'));
 /// handle.dispose();
 /// ```
 ///
 /// See also:
 ///
-///   * [WidgetTester.getSemanticsData], the tester method which retrieves data.
+///   * [WidgetTester.getSemantics], the tester method which retrieves semantics.
+Matcher matchesSemantics({
+  String label,
+  String hint,
+  String value,
+  String increasedValue,
+  String decreasedValue,
+  TextDirection textDirection,
+  Rect rect,
+  Size size,
+  // Flags //
+  bool hasCheckedState = false,
+  bool isChecked = false,
+  bool isSelected = false,
+  bool isButton = false,
+  bool isFocused = false,
+  bool isTextField = false,
+  bool hasEnabledState = false,
+  bool isEnabled = false,
+  bool isInMutuallyExclusiveGroup = false,
+  bool isHeader = false,
+  bool isObscured = false,
+  bool namesRoute = false,
+  bool scopesRoute = false,
+  bool isHidden = false,
+  bool isImage = false,
+  bool isLiveRegion = false,
+  bool hasToggledState = false,
+  bool isToggled = false,
+  bool hasImplicitScrolling = false,
+  // Actions //
+  bool hasTapAction = false,
+  bool hasLongPressAction = false,
+  bool hasScrollLeftAction = false,
+  bool hasScrollRightAction = false,
+  bool hasScrollUpAction = false,
+  bool hasScrollDownAction = false,
+  bool hasIncreaseAction = false,
+  bool hasDecreaseAction = false,
+  bool hasShowOnScreenAction = false,
+  bool hasMoveCursorForwardByCharacterAction = false,
+  bool hasMoveCursorBackwardByCharacterAction = false,
+  bool hasMoveCursorForwardByWordAction = false,
+  bool hasMoveCursorBackwardByWordAction = false,
+  bool hasSetSelectionAction = false,
+  bool hasCopyAction = false,
+  bool hasCutAction = false,
+  bool hasPasteAction = false,
+  bool hasDidGainAccessibilityFocusAction = false,
+  bool hasDidLoseAccessibilityFocusAction = false,
+  bool hasDismissAction = false,
+  // Custom actions and overrides
+  String onTapHint,
+  String onLongPressHint,
+  List<CustomSemanticsAction> customActions,
+  List<Matcher> children,
+}) {
+  final List<SemanticsFlag> flags = <SemanticsFlag>[];
+  if (hasCheckedState)
+    flags.add(SemanticsFlag.hasCheckedState);
+  if (isChecked)
+    flags.add(SemanticsFlag.isChecked);
+  if (isSelected)
+    flags.add(SemanticsFlag.isSelected);
+  if (isButton)
+    flags.add(SemanticsFlag.isButton);
+  if (isTextField)
+    flags.add(SemanticsFlag.isTextField);
+  if (isFocused)
+    flags.add(SemanticsFlag.isFocused);
+  if (hasEnabledState)
+    flags.add(SemanticsFlag.hasEnabledState);
+  if (isEnabled)
+    flags.add(SemanticsFlag.isEnabled);
+  if (isInMutuallyExclusiveGroup)
+    flags.add(SemanticsFlag.isInMutuallyExclusiveGroup);
+  if (isHeader)
+    flags.add(SemanticsFlag.isHeader);
+  if (isObscured)
+    flags.add(SemanticsFlag.isObscured);
+  if (namesRoute)
+    flags.add(SemanticsFlag.namesRoute);
+  if (scopesRoute)
+    flags.add(SemanticsFlag.scopesRoute);
+  if (isHidden)
+    flags.add(SemanticsFlag.isHidden);
+  if (isImage)
+    flags.add(SemanticsFlag.isImage);
+  if (isLiveRegion)
+    flags.add(SemanticsFlag.isLiveRegion);
+  if (hasToggledState)
+    flags.add(SemanticsFlag.hasToggledState);
+  if (isToggled)
+    flags.add(SemanticsFlag.isToggled);
+  if (hasImplicitScrolling)
+    flags.add(SemanticsFlag.hasImplicitScrolling);
+
+  final List<SemanticsAction> actions = <SemanticsAction>[];
+  if (hasTapAction)
+    actions.add(SemanticsAction.tap);
+  if (hasLongPressAction)
+    actions.add(SemanticsAction.longPress);
+  if (hasScrollLeftAction)
+    actions.add(SemanticsAction.scrollLeft);
+  if (hasScrollRightAction)
+    actions.add(SemanticsAction.scrollRight);
+  if (hasScrollUpAction)
+    actions.add(SemanticsAction.scrollUp);
+  if (hasScrollDownAction)
+    actions.add(SemanticsAction.scrollDown);
+  if (hasIncreaseAction)
+    actions.add(SemanticsAction.increase);
+  if (hasDecreaseAction)
+    actions.add(SemanticsAction.decrease);
+  if (hasShowOnScreenAction)
+    actions.add(SemanticsAction.showOnScreen);
+  if (hasMoveCursorForwardByCharacterAction)
+    actions.add(SemanticsAction.moveCursorForwardByCharacter);
+  if (hasMoveCursorBackwardByCharacterAction)
+    actions.add(SemanticsAction.moveCursorBackwardByCharacter);
+  if (hasSetSelectionAction)
+    actions.add(SemanticsAction.setSelection);
+  if (hasCopyAction)
+    actions.add(SemanticsAction.copy);
+  if (hasCutAction)
+    actions.add(SemanticsAction.cut);
+  if (hasPasteAction)
+    actions.add(SemanticsAction.paste);
+  if (hasDidGainAccessibilityFocusAction)
+    actions.add(SemanticsAction.didGainAccessibilityFocus);
+  if (hasDidLoseAccessibilityFocusAction)
+    actions.add(SemanticsAction.didLoseAccessibilityFocus);
+  if (customActions != null && customActions.isNotEmpty)
+    actions.add(SemanticsAction.customAction);
+  if (hasDismissAction)
+    actions.add(SemanticsAction.dismiss);
+  if (hasMoveCursorForwardByWordAction)
+    actions.add(SemanticsAction.moveCursorForwardByWord);
+  if (hasMoveCursorBackwardByWordAction)
+    actions.add(SemanticsAction.moveCursorBackwardByWord);
+  SemanticsHintOverrides hintOverrides;
+  if (onTapHint != null || onLongPressHint != null)
+    hintOverrides = SemanticsHintOverrides(
+      onTapHint: onTapHint,
+      onLongPressHint: onLongPressHint,
+    );
+
+  return _MatchesSemanticsData(
+    label: label,
+    hint: hint,
+    value: value,
+    increasedValue: increasedValue,
+    decreasedValue: decreasedValue,
+    actions: actions,
+    flags: flags,
+    textDirection: textDirection,
+    rect: rect,
+    size: size,
+    customActions: customActions,
+    hintOverrides: hintOverrides,
+    children: children,
+  );
+}
+
+/// DEPRECATED: use [matchesSemantics] instead.
+@Deprecated('use matchesSemantics instead')
 Matcher matchesSemanticsData({
   String label,
   String hint,
@@ -402,7 +566,7 @@
   String onLongPressHint,
   List<CustomSemanticsAction> customActions,
 }) {
-  final List<SemanticsFlag> flags = <SemanticsFlag>[];
+    final List<SemanticsFlag> flags = <SemanticsFlag>[];
   if (hasCheckedState)
     flags.add(SemanticsFlag.hasCheckedState);
   if (isChecked)
@@ -1686,6 +1850,7 @@
     this.size,
     this.customActions,
     this.hintOverrides,
+    this.children,
   });
 
   final String label;
@@ -1700,43 +1865,51 @@
   final TextDirection textDirection;
   final Rect rect;
   final Size size;
+  final List<Matcher> children;
 
   @override
   Description describe(Description description) {
     description.add('has semantics');
     if (label != null)
-      description.add('with label: $label ');
+      description.add(' with label: $label');
     if (value != null)
-      description.add('with value: $value ');
+      description.add(' with value: $value');
     if (hint != null)
-      description.add('with hint: $hint ');
+      description.add(' with hint: $hint');
     if (increasedValue != null)
-      description.add('with increasedValue: $increasedValue');
+      description.add(' with increasedValue: $increasedValue ');
     if (decreasedValue != null)
-      description.add('with decreasedValue: $decreasedValue');
+      description.add(' with decreasedValue: $decreasedValue ');
     if (actions != null)
-      description.add('with actions:').addDescriptionOf(actions);
+      description.add(' with actions: ').addDescriptionOf(actions);
     if (flags != null)
-      description.add('with flags:').addDescriptionOf(flags);
+      description.add(' with flags: ').addDescriptionOf(flags);
     if (textDirection != null)
-      description.add('with textDirection: $textDirection ');
+      description.add(' with textDirection: $textDirection ');
     if (rect != null)
-      description.add('with rect: $rect');
+      description.add(' with rect: $rect');
     if (size != null)
-      description.add('with size: $size');
+      description.add(' with size: $size');
     if (customActions != null)
-      description.add('with custom actions: $customActions');
+      description.add(' with custom actions: $customActions');
     if (hintOverrides != null)
-      description.add('with custom hints: $hintOverrides');
+      description.add(' with custom hints: $hintOverrides');
+    if (children != null) {
+      description.add(' with children:\n');
+      for (_MatchesSemanticsData child in children)
+        child.describe(description);
+    }
     return description;
   }
 
 
   @override
-  bool matches(covariant SemanticsData data, Map<dynamic, dynamic> matchState) {
-    if (data == null)
+  bool matches(dynamic node, Map<dynamic, dynamic> matchState) {
+    // TODO(jonahwilliams): remove dynamic once we have removed getSemanticsData.
+    if (node == null)
       return failWithDescription(matchState, 'No SemanticsData provided. '
-        'Maybe you forgot to enabled semantics?');
+        'Maybe you forgot to enable semantics?');
+    final SemanticsData data = node is SemanticsNode ? node.getSemanticsData() : node;
     if (label != null && label != data.label)
       return failWithDescription(matchState, 'label was: ${data.label}');
     if (hint != null && hint != data.hint)
@@ -1800,7 +1973,16 @@
         return failWithDescription(matchState, 'flags were: $flagSummary');
       }
     }
-    return true;
+    bool allMatched = true;
+    if (children != null) {
+      int i = 0;
+      node.visitChildren((SemanticsNode child) {
+        allMatched = children[i].matches(child, matchState) && allMatched;
+        i += 1;
+        return allMatched;
+      });
+    }
+    return allMatched;
   }
 
   bool failWithDescription(Map<dynamic, dynamic> matchState, String description) {
diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart
index bcdee59..bac856f 100644
--- a/packages/flutter_test/lib/src/widget_tester.dart
+++ b/packages/flutter_test/lib/src/widget_tester.dart
@@ -617,15 +617,39 @@
     });
   }
 
-  /// Attempts to find the [SemanticsData] of first result from `finder`.
+  /// Attempts to find the [SemanticsNode] of first result from `finder`.
   ///
   /// If the object identified by the finder doesn't own it's semantic node,
-  /// this will return the semantics data of the first ancestor with semantics
-  /// data. The ancestor's semantic data will include the child's as well as
+  /// this will return the semantics data of the first ancestor with semantics.
+  /// The ancestor's semantic data will include the child's as well as
   /// other nodes that have been merged together.
   ///
   /// Will throw a [StateError] if the finder returns more than one element or
   /// if no semantics are found or are not enabled.
+  SemanticsNode getSemantics(Finder finder) {
+    if (binding.pipelineOwner.semanticsOwner == null)
+      throw StateError('Semantics are not enabled.');
+    final Iterable<Element> candidates = finder.evaluate();
+    if (candidates.isEmpty) {
+      throw StateError('Finder returned no matching elements.');
+    }
+    if (candidates.length > 1) {
+      throw StateError('Finder returned more than one element.');
+    }
+    final Element element = candidates.single;
+    RenderObject renderObject = element.findRenderObject();
+    SemanticsNode result = renderObject.debugSemantics;
+    while (renderObject != null && result == null) {
+      renderObject = renderObject?.parent;
+      result = renderObject?.debugSemantics;
+    }
+    if (result == null)
+      throw StateError('No Semantics data found.');
+    return result;
+  }
+
+  /// DEPRECATED: use [getSemantics] instead.
+  @Deprecated('use getSemantics instead')
   SemanticsData getSemanticsData(Finder finder) {
     if (binding.pipelineOwner.semanticsOwner == null)
       throw StateError('Semantics are not enabled.');
diff --git a/packages/flutter_test/test/matchers_test.dart b/packages/flutter_test/test/matchers_test.dart
index 166ef1c..334247f 100644
--- a/packages/flutter_test/test/matchers_test.dart
+++ b/packages/flutter_test/test/matchers_test.dart
@@ -409,8 +409,8 @@
         },
       ));
 
-      expect(tester.getSemanticsData(find.byKey(key)),
-        matchesSemanticsData(
+      expect(tester.getSemantics(find.byKey(key)),
+        matchesSemantics(
           label: 'foo',
           hint: 'bar',
           value: 'baz',
@@ -432,8 +432,8 @@
       );
 
       // Doesn't match custom actions
-      expect(tester.getSemanticsData(find.byKey(key)),
-        isNot(matchesSemanticsData(
+      expect(tester.getSemantics(find.byKey(key)),
+        isNot(matchesSemantics(
           label: 'foo',
           hint: 'bar',
           value: 'baz',
@@ -453,8 +453,8 @@
       );
 
       // Doesn't match wrong hints
-      expect(tester.getSemanticsData(find.byKey(key)),
-        isNot(matchesSemanticsData(
+      expect(tester.getSemantics(find.byKey(key)),
+        isNot(matchesSemantics(
           label: 'foo',
           hint: 'bar',
           value: 'baz',
@@ -500,8 +500,10 @@
         scrollExtentMin: null,
         customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)],
       );
+      final _FakeSemanticsNode node = _FakeSemanticsNode();
+      node.data = data;
 
-      expect(data, matchesSemanticsData(
+      expect(node, matchesSemantics(
          rect: Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
          size: const Size(10.0, 10.0),
          /* Flags */
@@ -548,6 +550,35 @@
          customActions: <CustomSemanticsAction>[action],
       ));
     });
+
+    testWidgets('Can match child semantics', (WidgetTester tester) async {
+      final SemanticsHandle handle = tester.ensureSemantics();
+      const Key key = Key('a');
+      await tester.pumpWidget(Semantics(
+        key: key,
+        label: 'Foo',
+        container: true,
+        explicitChildNodes: true,
+        textDirection: TextDirection.ltr,
+        child: Semantics(
+          label: 'Bar',
+          textDirection: TextDirection.ltr,
+        ),
+      ));
+      final SemanticsNode node = tester.getSemantics(find.byKey(key));
+
+      expect(node, matchesSemantics(
+        label: 'Foo',
+        textDirection: TextDirection.ltr,
+        children: <Matcher>[
+          matchesSemantics(
+            label: 'Bar',
+            textDirection: TextDirection.ltr,
+          ),
+        ],
+      ));
+      handle.dispose();
+    });
   });
 }
 
@@ -592,3 +623,9 @@
     return Future<void>.value();
   }
 }
+
+class _FakeSemanticsNode extends SemanticsNode {
+  SemanticsData data;
+  @override
+  SemanticsData getSemanticsData() => data;
+}
\ No newline at end of file
diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart
index 9249398..e37ef47 100644
--- a/packages/flutter_test/test/widget_tester_test.dart
+++ b/packages/flutter_test/test/widget_tester_test.dart
@@ -540,7 +540,7 @@
         ),
       );
 
-      expect(() => tester.getSemanticsData(find.text('hello')),
+      expect(() => tester.getSemantics(find.text('hello')),
         throwsA(isInstanceOf<StateError>()));
     });
 
@@ -560,7 +560,7 @@
         ),
       );
 
-      expect(() => tester.getSemanticsData(find.text('hello')),
+      expect(() => tester.getSemantics(find.text('hello')),
           throwsA(isInstanceOf<StateError>()));
       semanticsHandle.dispose();
     });
@@ -581,7 +581,8 @@
         ),
       );
 
-      final SemanticsData semantics = tester.getSemanticsData(find.text('hello'));
+      final SemanticsNode node = tester.getSemantics(find.text('hello'));
+      final SemanticsData semantics = node.getSemanticsData();
       expect(semantics.label, 'hello');
       expect(semantics.hasAction(SemanticsAction.tap), true);
       expect(semantics.hasFlag(SemanticsFlag.isButton), true);
@@ -609,7 +610,8 @@
         ),
       );
 
-      final SemanticsData semantics = tester.getSemanticsData(find.byKey(key));
+      final SemanticsNode node = tester.getSemantics(find.byKey(key));
+      final SemanticsData semantics = node.getSemanticsData();
       expect(semantics.label, 'A\nB\nC');
       semanticsHandle.dispose();
     });