widget_tester.dart 33.5 KB
Newer Older
Hixie's avatar
Hixie committed
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:async';
6

7
import 'package:flutter/cupertino.dart';
8
import 'package:flutter/gestures.dart';
9
import 'package:flutter/material.dart';
10
import 'package:flutter/rendering.dart';
11
import 'package:flutter/scheduler.dart';
12
import 'package:flutter/services.dart';
13
import 'package:flutter/widgets.dart';
14
import 'package:meta/meta.dart';
15
import 'package:test_api/test_api.dart' as test_package;
16

17
import 'all_elements.dart';
18
import 'binding.dart';
19
import 'controller.dart';
20
import 'event_simulation.dart';
21
import 'finders.dart';
22
import 'matchers.dart';
23
import 'test_async_utils.dart';
24
import 'test_compat.dart';
25
import 'test_text_input.dart';
26

27 28 29
/// Keep users from needing multiple imports to test semantics.
export 'package:flutter/rendering.dart' show SemanticsHandle;

30 31 32
/// Hide these imports so that they do not conflict with our own implementations in
/// test_compat.dart. This handles setting up a declarer when one is not defined, which
/// can happen when a test is executed via flutter_run.
33
export 'package:test_api/test_api.dart' hide
34 35 36 37 38 39
  test,
  group,
  setUpAll,
  tearDownAll,
  setUp,
  tearDown,
40 41 42
  expect, // we have our own wrapper below
  TypeMatcher, // matcher's TypeMatcher conflicts with the one in the Flutter framework
  isInstanceOf; // we have our own wrapper in matchers.dart
43

44
/// Signature for callback to [testWidgets] and [benchmarkWidgets].
45
typedef WidgetTesterCallback = Future<void> Function(WidgetTester widgetTester);
46

47 48 49 50 51
/// Runs the [callback] inside the Flutter test environment.
///
/// Use this function for testing custom [StatelessWidget]s and
/// [StatefulWidget]s.
///
52 53
/// The callback can be asynchronous (using `async`/`await` or
/// using explicit [Future]s).
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
///
/// There are two kinds of timeouts that can be specified. The `timeout`
/// argument specifies the backstop timeout implemented by the `test` package.
/// If set, it should be relatively large (minutes). It defaults to ten minutes
/// for tests run by `flutter test`, and is unlimited for tests run by `flutter
/// run`; specifically, it defaults to
/// [TestWidgetsFlutterBinding.defaultTestTimeout].
///
/// The `initialTimeout` argument specifies the timeout implemented by the
/// `flutter_test` package itself. If set, it may be relatively small (seconds),
/// as it is automatically increased for some expensive operations, and can also
/// be manually increased by calling
/// [AutomatedTestWidgetsFlutterBinding.addTime]. The effective maximum value of
/// this timeout (even after calling `addTime`) is the one specified by the
/// `timeout` argument.
///
/// In general, timeouts are race conditions and cause flakes, so best practice
/// is to avoid the use of timeouts in tests.
72
///
73
/// If the `semanticsEnabled` parameter is set to `true`,
74 75
/// [WidgetTester.ensureSemantics] will have been called before the tester is
/// passed to the `callback`, and that handle will automatically be disposed
76
/// after the callback is finished. It defaults to true.
77
///
78 79 80 81 82 83
/// This function uses the [test] function in the test package to
/// register the given callback as a test. The callback, when run,
/// will be given a new instance of [WidgetTester]. The [find] object
/// provides convenient widget [Finder]s for use with the
/// [WidgetTester].
///
84 85 86
/// See also:
///
///  * [AutomatedTestWidgetsFlutterBinding.addTime] to learn more about
87
///    timeout and how to manually increase timeouts.
88
///
89
/// ## Sample code
90
///
91
/// ```dart
92 93 94 95 96
/// testWidgets('MyWidget', (WidgetTester tester) async {
///   await tester.pumpWidget(new MyWidget());
///   await tester.tap(find.text('Save'));
///   expect(find.text('Success'), findsOneWidget);
/// });
97
/// ```
98
@isTest
99 100 101
void testWidgets(
  String description,
  WidgetTesterCallback callback, {
102
  bool skip = false,
103
  test_package.Timeout timeout,
104
  Duration initialTimeout,
105
  bool semanticsEnabled = true,
106
}) {
107
  final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
108
  final WidgetTester tester = WidgetTester._(binding);
109
  test(
110 111
    description,
    () {
112 113 114 115
      SemanticsHandle semanticsHandle;
      if (semanticsEnabled == true) {
        semanticsHandle = tester.ensureSemantics();
      }
116
      tester._recordNumberOfSemanticsHandles();
117 118
      test_package.addTearDown(binding.postTest);
      return binding.runTest(
119
        () async {
120
          debugResetSemanticsIdCounter();
121 122 123
          await callback(tester);
          semanticsHandle?.dispose();
        },
124 125
        tester._endOfTestVerifications,
        description: description ?? '',
126
        timeout: initialTimeout,
127 128 129
      );
    },
    skip: skip,
130
    timeout: timeout ?? binding.defaultTestTimeout,
131
  );
132 133 134 135 136 137 138 139 140 141 142 143 144
}

/// Runs the [callback] inside the Flutter benchmark environment.
///
/// Use this function for benchmarking custom [StatelessWidget]s and
/// [StatefulWidget]s when you want to be able to use features from
/// [TestWidgetsFlutterBinding]. The callback, when run, will be given
/// a new instance of [WidgetTester]. The [find] object provides
/// convenient widget [Finder]s for use with the [WidgetTester].
///
/// The callback can be asynchronous (using `async`/`await` or using
/// explicit [Future]s). If it is, then [benchmarkWidgets] will return
/// a [Future] that completes when the callback's does. Otherwise, it
145 146 147 148
/// will return a Future that is always complete.
///
/// If the callback is asynchronous, make sure you `await` the call
/// to [benchmarkWidgets], otherwise it won't run!
149
///
150 151 152 153 154
/// If the `semanticsEnabled` parameter is set to `true`,
/// [WidgetTester.ensureSemantics] will have been called before the tester is
/// passed to the `callback`, and that handle will automatically be disposed
/// after the callback is finished.
///
155 156 157 158
/// Benchmarks must not be run in checked mode, because the performance is not
/// representative. To avoid this, this function will print a big message if it
/// is run in checked mode. Unit tests of this method pass `mayRunWithAsserts`,
/// but it should not be used for actual benchmarking.
159 160 161 162
///
/// Example:
///
///     main() async {
163
///       assert(false); // fail in checked mode
164 165
///       await benchmarkWidgets((WidgetTester tester) async {
///         await tester.pumpWidget(new MyWidget());
166 167
///         final Stopwatch timer = new Stopwatch()..start();
///         for (int index = 0; index < 10000; index += 1) {
168 169
///           await tester.tap(find.text('Tap me'));
///           await tester.pump();
170 171
///         }
///         timer.stop();
172
///         debugPrint('Time taken: ${timer.elapsedMilliseconds}ms');
173 174 175
///       });
///       exit(0);
///     }
176 177 178
Future<void> benchmarkWidgets(
  WidgetTesterCallback callback, {
  bool mayRunWithAsserts = false,
179
  bool semanticsEnabled = false,
180
}) {
181
  assert(() {
182 183 184
    if (mayRunWithAsserts)
      return true;

185 186 187 188 189 190 191 192
    print('┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓');
    print('┇ ⚠ THIS BENCHMARK IS BEING RUN WITH ASSERTS ENABLED ⚠  ┇');
    print('┡╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┦');
    print('│                                                       │');
    print('│  Numbers obtained from a benchmark while asserts are  │');
    print('│  enabled will not accurately reflect the performance  │');
    print('│  that will be experienced by end users using release  ╎');
    print('│  builds. Benchmarks should be run using this command  ┆');
193
    print('│  line:  flutter run --release benchmark.dart          ┊');
194 195 196
    print('│                                                        ');
    print('└─────────────────────────────────────────────────╌┄┈  🐢');
    return true;
197
  }());
198
  final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
199
  assert(binding is! AutomatedTestWidgetsFlutterBinding);
200
  final WidgetTester tester = WidgetTester._(binding);
201 202 203 204
  SemanticsHandle semanticsHandle;
  if (semanticsEnabled == true) {
    semanticsHandle = tester.ensureSemantics();
  }
205
  tester._recordNumberOfSemanticsHandles();
206
  return binding.runTest(
207 208 209 210
    () async {
      await callback(tester);
      semanticsHandle?.dispose();
    },
211
    tester._endOfTestVerifications,
212
  ) ?? Future<void>.value();
213
}
214

215 216 217 218 219
/// Assert that `actual` matches `matcher`.
///
/// See [test_package.expect] for details. This is a variant of that function
/// that additionally verifies that there are no asynchronous APIs
/// that have not yet resolved.
220 221 222 223
///
/// See also:
///
///  * [expectLater] for use with asynchronous matchers.
224 225 226
void expect(
  dynamic actual,
  dynamic matcher, {
227
  String reason,
Ian Hickson's avatar
Ian Hickson committed
228
  dynamic skip, // true or a String
229 230
}) {
  TestAsyncUtils.guardSync();
Ian Hickson's avatar
Ian Hickson committed
231
  test_package.expect(actual, matcher, reason: reason, skip: skip);
232 233 234 235 236 237 238 239 240 241 242
}

/// Assert that `actual` matches `matcher`.
///
/// See [test_package.expect] for details. This variant will _not_ check that
/// there are no outstanding asynchronous API requests. As such, it can be
/// called from, e.g., callbacks that are run during build or layout, or in the
/// completion handlers of futures that execute in response to user input.
///
/// Generally, it is better to use [expect], which does include checks to ensure
/// that asynchronous APIs are not being called.
243 244 245
void expectSync(
  dynamic actual,
  dynamic matcher, {
246 247
  String reason,
}) {
248
  test_package.expect(actual, matcher, reason: reason);
249 250
}

251 252 253 254 255 256 257 258
/// Just like [expect], but returns a [Future] that completes when the matcher
/// has finished matching.
///
/// See [test_package.expectLater] for details.
///
/// If the matcher fails asynchronously, that failure is piped to the returned
/// future where it can be handled by user code. If it is not handled by user
/// code, the test will fail.
259 260 261
Future<void> expectLater(
  dynamic actual,
  dynamic matcher, {
262 263 264
  String reason,
  dynamic skip, // true or a String
}) {
265 266 267
  // We can't wrap the delegate in a guard, or we'll hit async barriers in
  // [TestWidgetsFlutterBinding] while we're waiting for the matcher to complete
  TestAsyncUtils.guardSync();
268 269
  return test_package.expectLater(actual, matcher, reason: reason, skip: skip)
           .then<void>((dynamic value) => null);
270 271
}

272
/// Class that programmatically interacts with widgets and the test environment.
273 274 275 276
///
/// For convenience, instances of this class (such as the one provided by
/// `testWidget`) can be used as the `vsync` for `AnimationController` objects.
class WidgetTester extends WidgetController implements HitTestDispatcher, TickerProvider {
277 278 279 280
  WidgetTester._(TestWidgetsFlutterBinding binding) : super(binding) {
    if (binding is LiveTestWidgetsFlutterBinding)
      binding.deviceEventDispatcher = this;
  }
281

282 283 284
  /// The binding instance used by the testing framework.
  @override
  TestWidgetsFlutterBinding get binding => super.binding;
285 286 287

  /// Renders the UI from the given [widget].
  ///
288 289 290 291
  /// Calls [runApp] with the given widget, then triggers a frame and flushes
  /// microtasks, by calling [pump] with the same `duration` (if any). The
  /// supplied [EnginePhase] is the final phase reached during the pump pass; if
  /// not supplied, the whole pass is executed.
292 293 294 295
  ///
  /// Subsequent calls to this is different from [pump] in that it forces a full
  /// rebuild of the tree, even if [widget] is the same as the previous call.
  /// [pump] will only rebuild the widgets that have changed.
296
  ///
297 298 299 300 301 302 303 304 305 306 307 308 309
  /// This method should not be used as the first parameter to an [expect] or
  /// [expectLater] call to test that a widget throws an exception. Instead, use
  /// [TestWidgetsFlutterBinding.takeException].
  ///
  /// {@tool sample}
  /// ```dart
  /// testWidgets('MyWidget asserts invalid bounds', (WidgetTester tester) async {
  ///   await tester.pumpWidget(MyWidget(-1));
  ///   expect(tester.takeException(), isAssertionError); // or isNull, as appropriate.
  /// });
  /// ```
  /// {@end-tool}
  ///
310 311
  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
  /// this method works when the test is run with `flutter run`.
312 313
  Future<void> pumpWidget(
    Widget widget, [
314
    Duration duration,
315
    EnginePhase phase = EnginePhase.sendSemanticsUpdate,
316
  ]) {
317
    return TestAsyncUtils.guard<void>(() {
318 319
      binding.attachRootWidget(widget);
      binding.scheduleFrame();
320 321
      return binding.pump(duration, phase);
    });
322 323
  }

324 325 326 327 328
  /// Triggers a frame after `duration` amount of time.
  ///
  /// This makes the framework act as if the application had janked (missed
  /// frames) for `duration` amount of time, and then received a v-sync signal
  /// to paint the application.
329
  ///
330 331
  /// This is a convenience function that just calls
  /// [TestWidgetsFlutterBinding.pump].
332 333 334
  ///
  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
  /// this method works when the test is run with `flutter run`.
335
  @override
336
  Future<void> pump([
337
    Duration duration,
338
    EnginePhase phase = EnginePhase.sendSemanticsUpdate,
339
  ]) {
340
    return TestAsyncUtils.guard<void>(() => binding.pump(duration, phase));
341
  }
342

343 344 345 346 347 348 349 350 351 352
  /// Triggers a frame after `duration` amount of time, return as soon as the frame is drawn.
  ///
  /// This enables driving an artificially high CPU load by rendering frames in
  /// a tight loop. It must be used with the frame policy set to
  /// [LiveTestWidgetsFlutterBindingFramePolicy.benchmark].
  ///
  /// Similarly to [pump], this doesn't actually wait for `duration`, just
  /// advances the clock.
  Future<void> pumpBenchmark(Duration duration) async {
    assert(() {
353 354 355
      final TestWidgetsFlutterBinding widgetsBinding = binding;
      return widgetsBinding is LiveTestWidgetsFlutterBinding &&
              widgetsBinding.framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark;
356 357 358 359 360
    }());

    dynamic caughtException;
    void handleError(dynamic error, StackTrace stackTrace) => caughtException ??= error;

361
    await Future<void>.microtask(() { binding.handleBeginFrame(duration); }).catchError(handleError);
362
    await idle();
363
    await Future<void>.microtask(() { binding.handleDrawFrame(); }).catchError(handleError);
364 365 366 367 368 369 370
    await idle();

    if (caughtException != null) {
      throw caughtException;
    }
  }

371
  /// Repeatedly calls [pump] with the given `duration` until there are no
372 373 374
  /// longer any frames scheduled. This will call [pump] at least once, even if
  /// no frames are scheduled when the function is called, to flush any pending
  /// microtasks which may themselves schedule a frame.
375 376 377
  ///
  /// This essentially waits for all animations to have completed.
  ///
378 379 380 381 382 383 384
  /// If it takes longer that the given `timeout` to settle, then the test will
  /// fail (this method will throw an exception). In particular, this means that
  /// if there is an infinite animation in progress (for example, if there is an
  /// indeterminate progress indicator spinning), this method will throw.
  ///
  /// The default timeout is ten minutes, which is longer than most reasonable
  /// finite animations would last.
385 386 387 388 389 390 391 392 393 394
  ///
  /// If the function returns, it returns the number of pumps that it performed.
  ///
  /// In general, it is better practice to figure out exactly why each frame is
  /// needed, and then to [pump] exactly as many frames as necessary. This will
  /// help catch regressions where, for instance, an animation is being started
  /// one frame later than it should.
  ///
  /// Alternatively, one can check that the return value from this function
  /// matches the expected number of pumps.
395
  Future<int> pumpAndSettle([
396 397 398 399
    Duration duration = const Duration(milliseconds: 100),
    EnginePhase phase = EnginePhase.sendSemanticsUpdate,
    Duration timeout = const Duration(minutes: 10),
  ]) {
400
    assert(duration != null);
401
    assert(duration > Duration.zero);
402
    assert(timeout != null);
403
    assert(timeout > Duration.zero);
404 405 406 407 408 409 410 411 412 413 414
    assert(() {
      final WidgetsBinding binding = this.binding;
      if (binding is LiveTestWidgetsFlutterBinding &&
          binding.framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark) {
        throw 'When using LiveTestWidgetsFlutterBindingFramePolicy.benchmark, '
              'hasScheduledFrame is never set to true. This means that pumpAndSettle() '
              'cannot be used, because it has no way to know if the application has '
              'stopped registering new frames.';
      }
      return true;
    }());
415
    int count = 0;
416
    return TestAsyncUtils.guard<void>(() async {
417
      final DateTime endTime = binding.clock.fromNowBy(timeout);
418
      do {
419
        if (binding.clock.now().isAfter(endTime))
420
          throw FlutterError('pumpAndSettle timed out');
421 422
        await binding.pump(duration, phase);
        count += 1;
423
      } while (binding.hasScheduledFrame);
424
    }).then<int>((_) => count);
425 426
  }

427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
  /// Runs a [callback] that performs real asynchronous work.
  ///
  /// This is intended for callers who need to call asynchronous methods where
  /// the methods spawn isolates or OS threads and thus cannot be executed
  /// synchronously by calling [pump].
  ///
  /// If callers were to run these types of asynchronous tasks directly in
  /// their test methods, they run the possibility of encountering deadlocks.
  ///
  /// If [callback] completes successfully, this will return the future
  /// returned by [callback].
  ///
  /// If [callback] completes with an error, the error will be caught by the
  /// Flutter framework and made available via [takeException], and this method
  /// will return a future that completes will `null`.
  ///
  /// Re-entrant calls to this method are not allowed; callers of this method
  /// are required to wait for the returned future to complete before calling
  /// this method again. Attempts to do otherwise will result in a
  /// [TestFailure] error being thrown.
447 448
  Future<T> runAsync<T>(
    Future<T> callback(), {
449
    Duration additionalTime = const Duration(milliseconds: 1000),
450
  }) => binding.runAsync<T>(callback, additionalTime: additionalTime);
451

452
  /// Whether there are any any transient callbacks scheduled.
453 454
  ///
  /// This essentially checks whether all animations have completed.
455 456 457 458 459 460 461 462 463 464 465
  ///
  /// See also:
  ///
  ///  * [pumpAndSettle], which essentially calls [pump] until there are no
  ///    scheduled frames.
  ///  * [SchedulerBinding.transientCallbackCount], which is the value on which
  ///    this is based.
  ///  * [SchedulerBinding.hasScheduledFrame], which is true whenever a frame is
  ///    pending. [SchedulerBinding.hasScheduledFrame] is made true when a
  ///    widget calls [State.setState], even if there are no transient callbacks
  ///    scheduled. This is what [pumpAndSettle] uses.
466 467
  bool get hasRunningAnimations => binding.transientCallbackCount > 0;

468
  @override
469
  HitTestResult hitTestOnBinding(Offset location) {
470 471 472 473
    location = binding.localToGlobal(location);
    return super.hitTestOnBinding(location);
  }

474
  @override
475 476
  Future<void> sendEventToBinding(PointerEvent event, HitTestResult result) {
    return TestAsyncUtils.guard<void>(() async {
477 478 479 480
      binding.dispatchEvent(event, result, source: TestBindingEventSource.test);
    });
  }

481 482 483 484 485 486
  /// Handler for device events caught by the binding in live test mode.
  @override
  void dispatchEvent(PointerEvent event, HitTestResult result) {
    if (event is PointerDownEvent) {
      final RenderObject innerTarget = result.path.firstWhere(
        (HitTestEntry candidate) => candidate.target is RenderObject,
487 488 489 490 491 492 493 494 495 496 497 498
      ).target;
      final Element innerTargetElement = collectAllElementsFrom(
        binding.renderViewElement,
        skipOffstage: true,
      ).lastWhere(
        (Element element) => element.renderObject == innerTarget,
        orElse: () => null,
      );
      if (innerTargetElement == null) {
        debugPrint('No widgets found at ${binding.globalToLocal(event.position)}.');
        return;
      }
499 500 501 502 503 504 505 506 507 508
      final List<Element> candidates = <Element>[];
      innerTargetElement.visitAncestorElements((Element element) {
        candidates.add(element);
        return true;
      });
      assert(candidates.isNotEmpty);
      String descendantText;
      int numberOfWithTexts = 0;
      int numberOfTypes = 0;
      int totalNumber = 0;
509
      debugPrint('Some possible finders for the widgets at ${binding.globalToLocal(event.position)}:');
510
      for (Element element in candidates) {
511
        if (totalNumber > 13) // an arbitrary number of finders that feels useful without being overwhelming
512
          break;
513 514 515 516 517 518 519 520 521 522
        totalNumber += 1; // optimistically assume we'll be able to describe it

        if (element.widget is Tooltip) {
          final Tooltip widget = element.widget;
          final Iterable<Element> matches = find.byTooltip(widget.message).evaluate();
          if (matches.length == 1) {
            debugPrint('  find.byTooltip(\'${widget.message}\')');
            continue;
          }
        }
523 524 525 526 527 528 529

        if (element.widget is Text) {
          assert(descendantText == null);
          final Text widget = element.widget;
          final Iterable<Element> matches = find.text(widget.data).evaluate();
          descendantText = widget.data;
          if (matches.length == 1) {
530
            debugPrint('  find.text(\'${widget.data}\')');
531 532 533 534 535 536 537
            continue;
          }
        }

        if (element.widget.key is ValueKey<dynamic>) {
          final ValueKey<dynamic> key = element.widget.key;
          String keyLabel;
538 539 540
          if (key is ValueKey<int> ||
              key is ValueKey<double> ||
              key is ValueKey<bool>) {
541 542
            keyLabel = 'const ${element.widget.key.runtimeType}(${key.value})';
          } else if (key is ValueKey<String>) {
543
            keyLabel = 'const Key(\'${key.value}\')';
544 545 546 547
          }
          if (keyLabel != null) {
            final Iterable<Element> matches = find.byKey(key).evaluate();
            if (matches.length == 1) {
548
              debugPrint('  find.byKey($keyLabel)');
549 550 551 552 553 554 555 556 557
              continue;
            }
          }
        }

        if (!_isPrivate(element.widget.runtimeType)) {
          if (numberOfTypes < 5) {
            final Iterable<Element> matches = find.byType(element.widget.runtimeType).evaluate();
            if (matches.length == 1) {
558
              debugPrint('  find.byType(${element.widget.runtimeType})');
559 560 561 562 563 564 565 566
              numberOfTypes += 1;
              continue;
            }
          }

          if (descendantText != null && numberOfWithTexts < 5) {
            final Iterable<Element> matches = find.widgetWithText(element.widget.runtimeType, descendantText).evaluate();
            if (matches.length == 1) {
567
              debugPrint('  find.widgetWithText(${element.widget.runtimeType}, \'$descendantText\')');
568 569 570 571 572 573 574 575 576
              numberOfWithTexts += 1;
              continue;
            }
          }
        }

        if (!_isPrivate(element.runtimeType)) {
          final Iterable<Element> matches = find.byElementType(element.runtimeType).evaluate();
          if (matches.length == 1) {
577
            debugPrint('  find.byElementType(${element.runtimeType})');
578 579 580 581 582 583 584
            continue;
          }
        }

        totalNumber -= 1; // if we got here, we didn't actually find something to say about it
      }
      if (totalNumber == 0)
585
        debugPrint('  <could not come up with any unique finders>');
586 587 588 589
    }
  }

  bool _isPrivate(Type type) {
590
    // used above so that we don't suggest matchers for private types
591 592 593
    return '_'.matchAsPrefix(type.toString()) != null;
  }

594 595
  /// Returns the exception most recently caught by the Flutter framework.
  ///
596
  /// See [TestWidgetsFlutterBinding.takeException] for details.
597
  dynamic takeException() {
598
    return binding.takeException();
599 600
  }

601 602
  /// Acts as if the application went idle.
  ///
603 604
  /// Runs all remaining microtasks, including those scheduled as a result of
  /// running them, until there are no more microtasks scheduled.
605
  ///
606 607
  /// Does not run timers. May result in an infinite loop or run out of memory
  /// if microtasks continue to recursively schedule new microtasks.
608 609
  Future<void> idle() {
    return TestAsyncUtils.guard<void>(() => binding.idle());
610
  }
611 612 613 614 615

  Set<Ticker> _tickers;

  @override
  Ticker createTicker(TickerCallback onTick) {
616
    _tickers ??= <_TestTicker>{};
617
    final _TestTicker result = _TestTicker(onTick, _removeTicker);
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638
    _tickers.add(result);
    return result;
  }

  void _removeTicker(_TestTicker ticker) {
    assert(_tickers != null);
    assert(_tickers.contains(ticker));
    _tickers.remove(ticker);
  }

  /// Throws an exception if any tickers created by the [WidgetTester] are still
  /// active when the method is called.
  ///
  /// An argument can be specified to provide a string that will be used in the
  /// error message. It should be an adverbial phrase describing the current
  /// situation, such as "at the end of the test".
  void verifyTickersWereDisposed([ String when = 'when none should have been' ]) {
    assert(when != null);
    if (_tickers != null) {
      for (Ticker ticker in _tickers) {
        if (ticker.isActive) {
639
          throw FlutterError(
640 641 642 643 644 645 646 647 648 649 650 651 652
            'A Ticker was active $when.\n'
            'All Tickers must be disposed. Tickers used by AnimationControllers '
            'should be disposed by calling dispose() on the AnimationController itself. '
            'Otherwise, the ticker will leak.\n'
            'The offending ticker was: ${ticker.toString(debugIncludeStack: true)}'
          );
        }
      }
    }
  }

  void _endOfTestVerifications() {
    verifyTickersWereDisposed('at the end of the test');
653 654 655 656
    _verifySemanticsHandlesWereDisposed();
  }

  void _verifySemanticsHandlesWereDisposed() {
657 658
    assert(_lastRecordedSemanticsHandles != null);
    if (binding.pipelineOwner.debugOutstandingSemanticsHandles > _lastRecordedSemanticsHandles) {
659
      throw FlutterError(
660 661 662 663 664 665 666
        'A SemanticsHandle was active at the end of the test.\n'
        'All SemanticsHandle instances must be disposed by calling dispose() on '
        'the SemanticsHandle. If your test uses SemanticsTester, it is '
        'sufficient to call dispose() on SemanticsTester. Otherwise, the '
        'existing handle will leak into another test and alter its behavior.'
      );
    }
667 668 669 670 671 672 673
    _lastRecordedSemanticsHandles = null;
  }

  int _lastRecordedSemanticsHandles;

  void _recordNumberOfSemanticsHandles() {
    _lastRecordedSemanticsHandles = binding.pipelineOwner.debugOutstandingSemanticsHandles;
674
  }
675 676 677 678

  /// Returns the TestTextInput singleton.
  ///
  /// Typical app tests will not need to use this value. To add text to widgets
679
  /// like [TextField] or [TextFormField], call [enterText].
680 681
  TestTextInput get testTextInput => binding.testTextInput;

682
  /// Give the text input widget specified by [finder] the focus, as if the
683 684
  /// onscreen keyboard had appeared.
  ///
685 686
  /// Implies a call to [pump].
  ///
687 688
  /// The widget specified by [finder] must be an [EditableText] or have
  /// an [EditableText] descendant. For example `find.byType(TextField)`
689
  /// or `find.byType(TextFormField)`, or `find.byType(EditableText)`.
690 691
  ///
  /// Tests that just need to add text to widgets like [TextField]
692
  /// or [TextFormField] only need to call [enterText].
693 694
  Future<void> showKeyboard(Finder finder) async {
    return TestAsyncUtils.guard<void>(() async {
695
      final EditableTextState editable = state<EditableTextState>(
696 697 698 699 700 701 702 703
        find.descendant(
          of: finder,
          matching: find.byType(EditableText),
          matchRoot: true,
        ),
      );
      binding.focusedEditable = editable;
      await pump();
704
    });
705 706
  }

707
  /// Give the text input widget specified by [finder] the focus and
708
  /// enter [text] as if it been provided by the onscreen keyboard.
709 710 711
  ///
  /// The widget specified by [finder] must be an [EditableText] or have
  /// an [EditableText] descendant. For example `find.byType(TextField)`
712
  /// or `find.byType(TextFormField)`, or `find.byType(EditableText)`.
713 714 715
  ///
  /// To just give [finder] the focus without entering any text,
  /// see [showKeyboard].
716 717
  Future<void> enterText(Finder finder, String text) async {
    return TestAsyncUtils.guard<void>(() async {
718 719 720 721
      await showKeyboard(finder);
      testTextInput.enterText(text);
      await idle();
    });
722
  }
723

724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791
  /// Simulates sending physical key down and up events through the system channel.
  ///
  /// This only simulates key events coming from a physical keyboard, not from a
  /// soft keyboard.
  ///
  /// Specify `platform` as one of the platforms allowed in
  /// [Platform.operatingSystem] to make the event appear to be from that type
  /// of system. Defaults to "android". Must not be null. Some platforms (e.g.
  /// Windows, iOS) are not yet supported.
  ///
  /// Keys that are down when the test completes are cleared after each test.
  ///
  /// This method sends both the key down and the key up events, to simulate a
  /// key press. To simulate individual down and/or up events, see
  /// [sendKeyDownEvent] and [sendKeyUpEvent].
  ///
  /// See also:
  ///
  ///  - [sendKeyDownEvent] to simulate only a key down event.
  ///  - [sendKeyUpEvent] to simulate only a key up event.
  Future<void> sendKeyEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
    assert(platform != null);
    await simulateKeyDownEvent(key, platform: platform);
    // Internally wrapped in async guard.
    return simulateKeyUpEvent(key, platform: platform);
  }

  /// Simulates sending a physical key down event through the system channel.
  ///
  /// This only simulates key down events coming from a physical keyboard, not
  /// from a soft keyboard.
  ///
  /// Specify `platform` as one of the platforms allowed in
  /// [Platform.operatingSystem] to make the event appear to be from that type
  /// of system. Defaults to "android". Must not be null. Some platforms (e.g.
  /// Windows, iOS) are not yet supported.
  ///
  /// Keys that are down when the test completes are cleared after each test.
  ///
  /// See also:
  ///
  ///  - [sendKeyUpEvent] to simulate the corresponding key up event.
  ///  - [sendKeyEvent] to simulate both the key up and key down in the same call.
  Future<void> sendKeyDownEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
    assert(platform != null);
    // Internally wrapped in async guard.
    return simulateKeyDownEvent(key, platform: platform);
  }

  /// Simulates sending a physical key up event through the system channel.
  ///
  /// This only simulates key up events coming from a physical keyboard,
  /// not from a soft keyboard.
  ///
  /// Specify `platform` as one of the platforms allowed in
  /// [Platform.operatingSystem] to make the event appear to be from that type
  /// of system. Defaults to "android". May not be null.
  ///
  /// See also:
  ///
  ///  - [sendKeyDownEvent] to simulate the corresponding key down event.
  ///  - [sendKeyEvent] to simulate both the key up and key down in the same call.
  Future<void> sendKeyUpEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
    assert(platform != null);
    // Internally wrapped in async guard.
    return simulateKeyUpEvent(key, platform: platform);
  }

792 793 794 795 796
  /// Makes an effort to dismiss the current page with a Material [Scaffold] or
  /// a [CupertinoPageScaffold].
  ///
  /// Will throw an error if there is no back button in the page.
  Future<void> pageBack() async {
797
    return TestAsyncUtils.guard<void>(() async {
798 799
      Finder backButton = find.byTooltip('Back');
      if (backButton.evaluate().isEmpty) {
800
        backButton = find.byType(CupertinoNavigationBarBackButton);
801
      }
802

803
      expectSync(backButton, findsOneWidget, reason: 'One back button expected on screen');
804

805 806
      await tap(backButton);
    });
807
  }
808

809
  /// Attempts to find the [SemanticsNode] of first result from `finder`.
810
  ///
811
  /// If the object identified by the finder doesn't own it's semantic node,
812 813
  /// 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
814 815 816 817
  /// 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.
818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839
  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;
  }

840 841 842 843 844 845
  /// Enable semantics in a test by creating a [SemanticsHandle].
  ///
  /// The handle must be disposed at the end of the test.
  SemanticsHandle ensureSemantics() {
    return binding.pipelineOwner.ensureSemantics();
  }
846 847 848 849 850 851

  /// Given a widget `W` specified by [finder] and a [Scrollable] widget `S` in
  /// its ancestry tree, this scrolls `S` so as to make `W` visible.
  ///
  /// Shorthand for `Scrollable.ensureVisible(tester.element(finder))`
  Future<void> ensureVisible(Finder finder) => Scrollable.ensureVisible(element(finder));
852 853
}

854
typedef _TickerDisposeCallback = void Function(_TestTicker ticker);
855 856 857 858

class _TestTicker extends Ticker {
  _TestTicker(TickerCallback onTick, this._onDispose) : super(onTick);

859
  final _TickerDisposeCallback _onDispose;
860 861 862 863 864 865 866

  @override
  void dispose() {
    if (_onDispose != null)
      _onDispose(this);
    super.dispose();
  }
867
}