Testing Elements in FlutterWidgetTest that are not Rendered on Screen but Exist Offstage, Similar to IndexedStack
Introduction
In my personal development, I added a new feature to the Flutter package LazyLoadIndexedStack
and released a new version.
While revising the tests for this, I wanted to verify if the expected elements existed within the children of IndexedStack
. Normally, for elements in IndexedStack
children other than the specified index, they are not rendered on the screen, and a WidgetTest like expect(find.text('expected text'), findsOneWidget);
would not pass.
This article will detail how to write tests for cases where the elements are not rendered on screen but their existence still needs to be verified.
Testing Method
In conclusion, pass skipOffstage: false
as an argument to the Finder
method.
expect(find.text('expected text', skipOffstage: false), findsOneWidget);
Let’s delve deeper.
Regarding the skipOffstage
argument, the source code of Finder
comments:
If the
skipOffstage
argument is true (the default), then this skips nodes that are [Offstage] or that are from inactive [Route]s.
ref: https://github.com/flutter/flutter/blob/d0482116e77994122a7e8596f4a6b7078f08e190/packages/flutter_test/lib/src/finders.dart#L80C1-L81C68(My translation) If the
skipOffstage
argument is true (the default), this skips nodes that are [Offstage] or from inactive [Route]s.
I wasn’t familiar with Offstage
, so I looked it up.
A widget that lays the child out as if it was in the tree, but without painting anything, without making the child available for hit testing, and without taking any room in the parent.
Offstage children are still active: they can receive focus and have keyboard input directed to them.
Animations continue to run in offstage children, and therefore use battery and CPU time, regardless of whether the animations end up being visible.
Offstage can be used to measure the dimensions of a widget without bringing it on screen (yet). To hide a widget from view while it is not needed, prefer removing the widget from the tree entirely rather than keeping it alive in an Offstage subtree.
In summary, widgets that are not rendered on screen but are in a waiting state are stored in Offstage
, and probably for performance enhancement, they are skipped during testing.
So, by passing the flag skipOffstage: false
as an argument, elements inside Offstage
can also be verified in tests.
Before I noticed this argument, I was writing tests like the following. I found the target IndexedStack
and directly checked its children. This method is explicit and not bad in itself, but it has a lot of description and poor visibility.
final indexedStack = find.byType(IndexedStack);
expect(indexedStack, findsOneWidget);
final IndexedStack indexedStackWidget = tester.widget(indexedStack) as IndexedStack;
final children = indexedStackWidget.children;
expect(find.text('element1'), findsOneWidget);
bool hasElement2 = children.any((Widget widget) {
return widget is Center and widget.child is Text and (widget.child as Text).data == 'element2';
});
expect(hasElement2, isTrue);
Conclusion
I still have much to learn about Flutter widgets.