simulatedAccessibilityTraversal method
- @Deprecated('Use startNode instead. ' 'This method was originally created before semantics finders were available. ' 'Semantics finders avoid edge cases where some nodes are not discoverable by widget finders and should be preferred for semantics testing. ' 'This feature was deprecated after v3.15.0-15.2.pre.') FinderBase<
Element> ? start, - @Deprecated('Use endNode instead. ' 'This method was originally created before semantics finders were available. ' 'Semantics finders avoid edge cases where some nodes are not discoverable by widget finders and should be preferred for semantics testing. ' 'This feature was deprecated after v3.15.0-15.2.pre.') FinderBase<
Element> ? end, - FinderBase<
SemanticsNode> ? startNode, - FinderBase<
SemanticsNode> ? endNode, - FlutterView? view,
Simulates a traversal of the currently visible semantics tree as if by assistive technologies.
Starts at the node for startNode
. If startNode
is not provided, then
the traversal begins with the first accessible node in the tree. If
startNode
finds zero elements or more than one element, a StateError
will be thrown.
Ends at the node for endNode
, inclusive. If endNode
is not provided,
then the traversal ends with the last accessible node in the currently
available tree. If endNode
finds zero elements or more than one element,
a StateError will be thrown.
If provided, the nodes for endNode
and startNode
must be part of the
same semantics tree, i.e. they must be part of the same view.
If neither startNode
or endNode
is provided, view
can be provided to
specify the semantics tree to traverse. If view
is left unspecified,
WidgetTester.view is traversed by default.
Since the order is simulated, edge cases that differ between platforms (such as how the last visible item in a scrollable list is handled) may be inconsistent with platform behavior, but are expected to be sufficient for testing order, availability to assistive technologies, and interactions.
Sample Code
testWidgets('MyWidget', (WidgetTester tester) async {
await tester.pumpWidget(const MyWidget());
expect(
tester.semantics.simulatedAccessibilityTraversal(),
containsAllInOrder(<Matcher>[
containsSemantics(label: 'My Widget'),
containsSemantics(label: 'is awesome!', isChecked: true),
]),
);
});
See also:
- containsSemantics and matchesSemantics, which can be used to match against a single node in the traversal.
- containsAllInOrder, which can be given an Iterable<Matcher> to fuzzy match the order allowing extra nodes before after and between matching parts of the traversal.
- orderedEquals, which can be given an Iterable<Matcher> to exactly match the order of the traversal.
Implementation
Iterable<SemanticsNode> simulatedAccessibilityTraversal({
@Deprecated(
'Use startNode instead. '
'This method was originally created before semantics finders were available. '
'Semantics finders avoid edge cases where some nodes are not discoverable by widget finders and should be preferred for semantics testing. '
'This feature was deprecated after v3.15.0-15.2.pre.'
)
finders.FinderBase<Element>? start,
@Deprecated(
'Use endNode instead. '
'This method was originally created before semantics finders were available. '
'Semantics finders avoid edge cases where some nodes are not discoverable by widget finders and should be preferred for semantics testing. '
'This feature was deprecated after v3.15.0-15.2.pre.'
)
finders.FinderBase<Element>? end,
finders.FinderBase<SemanticsNode>? startNode,
finders.FinderBase<SemanticsNode>? endNode,
FlutterView? view,
}) {
TestAsyncUtils.guardSync();
assert(
start == null || startNode == null,
'Cannot provide both start and startNode. Prefer startNode as start is deprecated.',
);
assert(
end == null || endNode == null,
'Cannot provide both end and endNode. Prefer endNode as end is deprecated.',
);
FlutterView? startView;
if (start != null) {
startView = _controller.viewOf(start);
if (view != null && startView != view) {
throw StateError(
'The start node is not part of the provided view.\n'
'Finder: ${start.toString(describeSelf: true)}\n'
'View of start node: $startView\n'
'Specified view: $view'
);
}
} else if (startNode != null) {
final SemanticsOwner owner = startNode.evaluate().single.owner!;
final RenderView renderView = _controller.binding.renderViews.firstWhere(
(RenderView render) => render.owner!.semanticsOwner == owner,
);
startView = renderView.flutterView;
if (view != null && startView != view) {
throw StateError(
'The start node is not part of the provided view.\n'
'Finder: ${startNode.toString(describeSelf: true)}\n'
'View of start node: $startView\n'
'Specified view: $view'
);
}
}
FlutterView? endView;
if (end != null) {
endView = _controller.viewOf(end);
if (view != null && endView != view) {
throw StateError(
'The end node is not part of the provided view.\n'
'Finder: ${end.toString(describeSelf: true)}\n'
'View of end node: $endView\n'
'Specified view: $view'
);
}
} else if (endNode != null) {
final SemanticsOwner owner = endNode.evaluate().single.owner!;
final RenderView renderView = _controller.binding.renderViews.firstWhere(
(RenderView render) => render.owner!.semanticsOwner == owner,
);
endView = renderView.flutterView;
if (view != null && endView != view) {
throw StateError(
'The end node is not part of the provided view.\n'
'Finder: ${endNode.toString(describeSelf: true)}\n'
'View of end node: $endView\n'
'Specified view: $view'
);
}
}
if (endView != null && startView != null && endView != startView) {
throw StateError(
'The start and end node are in different views.\n'
'Start finder: ${start!.toString(describeSelf: true)}\n'
'End finder: ${end!.toString(describeSelf: true)}\n'
'View of start node: $startView\n'
'View of end node: $endView'
);
}
final FlutterView actualView = view ?? startView ?? endView ?? _controller.view;
final RenderView renderView = _controller.binding.renderViews.firstWhere((RenderView r) => r.flutterView == actualView);
final List<SemanticsNode> traversal = <SemanticsNode>[];
_accessibilityTraversal(
renderView.owner!.semanticsOwner!.rootSemanticsNode!,
traversal,
);
// Setting the range
SemanticsNode? node;
String? errorString;
int startIndex;
if (start != null) {
node = find(start);
startIndex = traversal.indexOf(node);
errorString = start.toString(describeSelf: true);
} else if (startNode != null) {
node = startNode.evaluate().single;
startIndex = traversal.indexOf(node);
errorString = startNode.toString(describeSelf: true);
} else {
startIndex = 0;
}
if (startIndex == -1) {
throw StateError(
'The expected starting node was not found.\n'
'Finder: $errorString\n\n'
'Expected Start Node: $node\n\n'
'Traversal: [\n ${traversal.join('\n ')}\n]');
}
int? endIndex;
if (end != null) {
node = find(end);
endIndex = traversal.indexOf(node);
errorString = end.toString(describeSelf: true);
} else if (endNode != null) {
node = endNode.evaluate().single;
endIndex = traversal.indexOf(node);
errorString = endNode.toString(describeSelf: true);
}
if (endIndex == -1) {
throw StateError(
'The expected ending node was not found.\n'
'Finder: $errorString\n\n'
'Expected End Node: $node\n\n'
'Traversal: [\n ${traversal.join('\n ')}\n]');
}
endIndex ??= traversal.length - 1;
return traversal.getRange(startIndex, endIndex + 1);
}