How to wait until the Finder is visible for next code execution in Flutter integration test?

Viewed 888

Information:
I have created a sample Flutter unit test to test the login screen where I have email & password as input field and a login button.

Requirement:
Need to test false cases and for that, I have written code as per the below steps.

  1. Open main.dart
  2. Filled the email & password field
  3. onTap event is done on the login button. Over here API will be called and loader is displayed on the screen until API gets a success or failure response.
  4. Need to check if failure dialog is displayed with a message.

Issue/Query:
Now when the API is calling I want to wait when the loader is visible until the loader is gone. So, as of now I just put a manual delay to execute the next code but I want to make it dynamic. So, let me know how we can put dynamic delay based on the loader visible?

Code:

void main() {
  group('App Test', () {
    IntegrationTestWidgetsFlutterBinding.ensureInitialized();

    testWidgets('Login Fail Test', (WidgetTester tester) async {
      await app.main();
      await tester.pumpAndSettle();

      await tester.pump(new Duration(seconds: 2));

      final emailField = find.byType(TextFormField).first;
      final passwordField = find.byType(TextFormField).last;
      final loginButton = find.byType(RaisedButton).first;

      await tester.enterText(emailField, 'Test');
      await tester.pumpAndSettle();
      await tester.pump(new Duration(seconds: 1));

      await tester.enterText(passwordField, 'Test123');
      await tester.pumpAndSettle();
      await tester.pump(new Duration(seconds: 1));

      await tester.tap(loginButton);
      await tester.pumpAndSettle();
      await tester.pump(new Duration(seconds: 3));

     
      final dialog = find.byType(AlertDialog).first;
      await tester.element(dialog);
      await tester.pumpAndSettle();
      await tester.pump(new Duration(seconds: 1));

      final dialogButton = find.byType(FlatButton).first;
      await tester.tap(dialogButton);
      await tester.pumpAndSettle();
      await tester.pump(new Duration(seconds: 2));
    });
}
2 Answers

I have a file called utils.dart for functionality like this. In this case I use the following function which will basically poll until the finder is valid

// utils.dart

Future<void> pumpUntilFound(
  WidgetTester tester,
  Finder finder, {
  Duration timeout = const Duration(seconds: 10),
}) async {
  bool timerDone = false;
  final timer = Timer(timeout, () => timerDone = true);
  while (timerDone != true) {
    await tester.pump();

    final found = tester.any(finder);
    if (found) {
      timerDone = true;
    }
  }
  timer.cancel();
}

You can also make it throw an exception if it times out, but the error messages aren't helpful, so I usually follow it up with an expect

It would look like

// my_test.dart

final fab = find.byKey(const ValueKey('fab'));
await pumpUntilFound(widgetTester, fab);
expect(fab, findsOneWidget);

Try wrapping like this:

testWidgets('test',
    (WidgetTester tester) async {
    await tester.runAsync(() async {

      // test code here

    });
 });

If you use:

await tester.pumpAndSettle();

And then:

  final widget = find.byKey(Key('whatever'));

It will find dinamically

Related