How to check widget is visible when testing

Viewed 1794

In my case, when I using ExpansionPanelList, I need to check widget inside panel is visible.

If not visible, tester.tap(find.text('More Info')) will throw exception:

Warning: A call to tap() with finder "exactly one widget with text "More Info" (ignoring offstage widgets): Text("More Info", dependencies: [MediaQuery, DefaultTextStyle])" derived an Offset (Offset(400.0, 641.8)) that would not hit test on the specified widget. Maybe the widget is actually off-screen, or another widget is obscuring it, or the widget cannot receive pointer events. Indeed, Offset(400.0, 641.8) is outside the bounds of the root of the render tree, Size(800.0, 600.0). The finder corresponds to this RenderBox: RenderParagraph#1b6b1 relayoutBoundary=up27 The hit test result at that offset is: HitTestResult(HitTestEntry#b18dd(RenderView#408c3), HitTestEntry#a2393()) #0 WidgetController._getElementPoint (package:flutter_test/src/controller.dart:953:25) #1 WidgetController.getCenter (package:flutter_test/src/controller.dart:836:12) #2 WidgetController.tap (package:flutter_test/src/controller.dart:271:18) #3 main. (file:///Users/tommy/Repos/surveyapp/survey/test/widget_test.dart:99:18)

How do I check this widget is visible in testing?

3 Answers

Use hitTestable(), which produces a finder which only matches if the widget described by your finder is truly visible on the screen. For example:

  expect(find.text('More Info').hitTestable(), findsOneWidget);

In your case, you may need to wait until the ExpansionPanelList expands or the list scrolls into view. You may need to use pumpSettle() or ensureVisible() first.

RednerObject has a property called _needsPaint and its getter debugNeedsPaint, when this object is visible it should be true.

final targetWidget = find.text('More Info').evaluate().first;
expect(targetWidget.renderObject!.debugNeedsPaint, isTrue);

But when ExpansionPanelList close again, the paint state is still showing it has been paint.


Another work around is that you can ignore the warning and check the side affect.

For example, when tap the hidden widget, it should pop up some view with text Hello World:

await tester.tap(find.text('More Info'), warnIfMissed: false);
expect(find.text('Hello World'), findsOneWidget);

An excellent way to deal with this would be testing the UI in a unit-testing fashion. In Android, we have Roboelectric — which is super useful. In Flutter the widget testing is baked to the platform and comes out of the box. A common widget test might look somewhat like this:

testWidgets('great widget test', (tester) async {
await tester.pumpWidget(new MyWidget());
final finder = find...
expect(finder, findsOneWidget);
}

You pump your widget; then you find if the widget contains the text/image/icon/button and confirm if it appears correctly. Apply widget changes One important note to mention is that sometimes you change your widget depending on a user action. For instance: remove a todo field when the user taps the delete button.

await tester.tap(find.byType(Checkbox));
// Rebuild the Widget after the state has changed
await tester.pump();

This is valid only if you change the state of the widget and you need to rebuild it. It will not help you find out if the button callback was invoked.

Finder options

You can find the target widget by a bunch of ways:

type finder.byType

key finder.byKey

text finder.textfinder.widgetWithText

icon finder.byIcon

create a duplicate widget in your test finder.byWidget

regex finder.bySemanticsLabel

And so on… finder.ancestorfinder.descendant

A great way to learn more about how to find widgets is to take a look at the source code.

Related