widget_tester.dart 30.6 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/widgets.dart';
13
import 'package:meta/meta.dart';
14
import 'package:test_api/test_api.dart' as test_package;
15

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

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

28 29 30
/// 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.
31
export 'package:test_api/test_api.dart' hide
32 33 34 35 36 37
  test,
  group,
  setUpAll,
  tearDownAll,
  setUp,
  tearDown,
38 39 40
  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
41

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

45 46 47 48 49
/// Runs the [callback] inside the Flutter test environment.
///
/// Use this function for testing custom [StatelessWidget]s and
/// [StatefulWidget]s.
///
50 51
/// The callback can be asynchronous (using `async`/`await` or
/// using explicit [Future]s).
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
///
/// 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.
70
///
71
/// If the `semanticsEnabled` parameter is set to `true`,
72 73
/// [WidgetTester.ensureSemantics] will have been called before the tester is
/// passed to the `callback`, and that handle will automatically be disposed
74
/// after the callback is finished. It defaults to true.
75
///
76 77 78 79 80 81
/// 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].
///
82 83 84
/// See also:
///
///  * [AutomatedTestWidgetsFlutterBinding.addTime] to learn more about
85
///    timeout and how to manually increase timeouts.
86
///
87
/// ## Sample code
88
///
89
/// ```dart
90 91 92 93 94
/// testWidgets('MyWidget', (WidgetTester tester) async {
///   await tester.pumpWidget(new MyWidget());
///   await tester.tap(find.text('Save'));
///   expect(find.text('Success'), findsOneWidget);
/// });
95
/// ```
96
@isTest
97 98 99
void testWidgets(
  String description,
  WidgetTesterCallback callback, {
100
  bool skip = false,
101
  test_package.Timeout timeout,
102
  Duration initialTimeout,
103
  bool semanticsEnabled = true,
104
}) {
105
  final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
106
  final WidgetTester tester = WidgetTester._(binding);
107
  test(
108 109
    description,
    () {
110 111 112 113
      SemanticsHandle semanticsHandle;
      if (semanticsEnabled == true) {
        semanticsHandle = tester.ensureSemantics();
      }
114
      tester._recordNumberOfSemanticsHandles();
115 116
      test_package.addTearDown(binding.postTest);
      return binding.runTest(
117
        () async {
118
          debugResetSemanticsIdCounter();
119 120 121
          await callback(tester);
          semanticsHandle?.dispose();
        },
122 123
        tester._endOfTestVerifications,
        description: description ?? '',
124
        timeout: initialTimeout,
125 126 127
      );
    },
    skip: skip,
128
    timeout: timeout ?? binding.defaultTestTimeout,
129
  );
130 131 132 133 134 135 136 137 138 139 140 141 142
}

/// 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
143 144 145 146
/// 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!
147
///
148 149 150 151 152
/// 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.
///
153 154 155 156
/// 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.
157 158 159 160
///
/// Example:
///
///     main() async {
161
///       assert(false); // fail in checked mode
162 163
///       await benchmarkWidgets((WidgetTester tester) async {
///         await tester.pumpWidget(new MyWidget());
164 165
///         final Stopwatch timer = new Stopwatch()..start();
///         for (int index = 0; index < 10000; index += 1) {
166 167
///           await tester.tap(find.text('Tap me'));
///           await tester.pump();
168 169
///         }
///         timer.stop();
170
///         debugPrint('Time taken: ${timer.elapsedMilliseconds}ms');
171 172 173
///       });
///       exit(0);
///     }
174 175 176
Future<void> benchmarkWidgets(
  WidgetTesterCallback callback, {
  bool mayRunWithAsserts = false,
177
  bool semanticsEnabled = false,
178
}) {
179
  assert(() {
180 181 182
    if (mayRunWithAsserts)
      return true;

183 184 185 186 187 188 189 190
    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  ┆');
191
    print('│  line:  flutter run --release benchmark.dart          ┊');
192 193 194
    print('│                                                        ');
    print('└─────────────────────────────────────────────────╌┄┈  🐢');
    return true;
195
  }());
196
  final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
197
  assert(binding is! AutomatedTestWidgetsFlutterBinding);
198
  final WidgetTester tester = WidgetTester._(binding);
199 200 201 202
  SemanticsHandle semanticsHandle;
  if (semanticsEnabled == true) {
    semanticsHandle = tester.ensureSemantics();
  }
203
  tester._recordNumberOfSemanticsHandles();
204
  return binding.runTest(
205 206 207 208
    () async {
      await callback(tester);
      semanticsHandle?.dispose();
    },
209
    tester._endOfTestVerifications,
210
  ) ?? Future<void>.value();
211
}
212

213 214 215 216 217
/// 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.
218 219 220 221
///
/// See also:
///
///  * [expectLater] for use with asynchronous matchers.
222 223 224
void expect(
  dynamic actual,
  dynamic matcher, {
225
  String reason,
Ian Hickson's avatar
Ian Hickson committed
226
  dynamic skip, // true or a String
227 228
}) {
  TestAsyncUtils.guardSync();
Ian Hickson's avatar
Ian Hickson committed
229
  test_package.expect(actual, matcher, reason: reason, skip: skip);
230 231 232 233 234 235 236 237 238 239 240
}

/// 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.
241 242 243
void expectSync(
  dynamic actual,
  dynamic matcher, {
244 245
  String reason,
}) {
246
  test_package.expect(actual, matcher, reason: reason);
247 248
}

249 250 251 252 253 254 255 256
/// 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.
257 258 259
Future<void> expectLater(
  dynamic actual,
  dynamic matcher, {
260 261 262
  String reason,
  dynamic skip, // true or a String
}) {
263 264 265
  // 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();
266 267
  return test_package.expectLater(actual, matcher, reason: reason, skip: skip)
           .then<void>((dynamic value) => null);
268 269
}

270
/// Class that programmatically interacts with widgets and the test environment.
271 272 273 274
///
/// 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 {
275 276 277 278
  WidgetTester._(TestWidgetsFlutterBinding binding) : super(binding) {
    if (binding is LiveTestWidgetsFlutterBinding)
      binding.deviceEventDispatcher = this;
  }
279

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

  /// Renders the UI from the given [widget].
  ///
286 287 288 289
  /// 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.
290 291 292 293
  ///
  /// 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.
294
  ///
295 296 297 298 299 300 301 302 303 304 305 306 307
  /// 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}
  ///
308 309
  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
  /// this method works when the test is run with `flutter run`.
310 311
  Future<void> pumpWidget(
    Widget widget, [
312
    Duration duration,
313
    EnginePhase phase = EnginePhase.sendSemanticsUpdate,
314
  ]) {
315
    return TestAsyncUtils.guard<void>(() {
316 317
      binding.attachRootWidget(widget);
      binding.scheduleFrame();
318 319
      return binding.pump(duration, phase);
    });
320 321
  }

322 323 324 325 326
  /// 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.
327
  ///
328 329
  /// This is a convenience function that just calls
  /// [TestWidgetsFlutterBinding.pump].
330 331 332
  ///
  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
  /// this method works when the test is run with `flutter run`.
333
  @override
334
  Future<void> pump([
335
    Duration duration,
336
    EnginePhase phase = EnginePhase.sendSemanticsUpdate,
337
  ]) {
338
    return TestAsyncUtils.guard<void>(() => binding.pump(duration, phase));
339
  }
340

341 342 343 344 345 346 347 348 349 350
  /// 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(() {
351 352 353
      final TestWidgetsFlutterBinding widgetsBinding = binding;
      return widgetsBinding is LiveTestWidgetsFlutterBinding &&
              widgetsBinding.framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark;
354 355 356 357 358
    }());

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

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

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

369
  /// Repeatedly calls [pump] with the given `duration` until there are no
370 371 372
  /// 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.
373 374 375
  ///
  /// This essentially waits for all animations to have completed.
  ///
376 377 378 379 380 381 382
  /// 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.
383 384 385 386 387 388 389 390 391 392
  ///
  /// 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.
393
  Future<int> pumpAndSettle([
394 395 396 397
    Duration duration = const Duration(milliseconds: 100),
    EnginePhase phase = EnginePhase.sendSemanticsUpdate,
    Duration timeout = const Duration(minutes: 10),
  ]) {
398
    assert(duration != null);
399
    assert(duration > Duration.zero);
400
    assert(timeout != null);
401
    assert(timeout > Duration.zero);
402 403 404 405 406 407 408 409 410 411 412
    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;
    }());
413
    int count = 0;
414
    return TestAsyncUtils.guard<void>(() async {
415
      final DateTime endTime = binding.clock.fromNowBy(timeout);
416
      do {
417
        if (binding.clock.now().isAfter(endTime))
418
          throw FlutterError('pumpAndSettle timed out');
419 420
        await binding.pump(duration, phase);
        count += 1;
421
      } while (binding.hasScheduledFrame);
422
    }).then<int>((_) => count);
423 424
  }

425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
  /// 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.
445 446
  Future<T> runAsync<T>(
    Future<T> callback(), {
447
    Duration additionalTime = const Duration(milliseconds: 1000),
448
  }) => binding.runAsync<T>(callback, additionalTime: additionalTime);
449

450
  /// Whether there are any any transient callbacks scheduled.
451 452
  ///
  /// This essentially checks whether all animations have completed.
453 454 455 456 457 458 459 460 461 462 463
  ///
  /// 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.
464 465
  bool get hasRunningAnimations => binding.transientCallbackCount > 0;

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

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

479 480 481 482 483 484
  /// 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,
485 486 487 488 489 490 491 492 493 494 495 496
      ).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;
      }
497 498 499 500 501 502 503 504 505 506
      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;
507
      debugPrint('Some possible finders for the widgets at ${binding.globalToLocal(event.position)}:');
508
      for (Element element in candidates) {
509
        if (totalNumber > 13) // an arbitrary number of finders that feels useful without being overwhelming
510
          break;
511 512 513 514 515 516 517 518 519 520
        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;
          }
        }
521 522 523 524 525 526 527

        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) {
528
            debugPrint('  find.text(\'${widget.data}\')');
529 530 531 532 533 534 535
            continue;
          }
        }

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

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

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

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

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

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

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

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

  Set<Ticker> _tickers;

  @override
  Ticker createTicker(TickerCallback onTick) {
614
    _tickers ??= <_TestTicker>{};
615
    final _TestTicker result = _TestTicker(onTick, _removeTicker);
616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
    _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) {
637
          throw FlutterError(
638 639 640 641 642 643 644 645 646 647 648 649 650
            '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');
651 652 653 654
    _verifySemanticsHandlesWereDisposed();
  }

  void _verifySemanticsHandlesWereDisposed() {
655 656
    assert(_lastRecordedSemanticsHandles != null);
    if (binding.pipelineOwner.debugOutstandingSemanticsHandles > _lastRecordedSemanticsHandles) {
657
      throw FlutterError(
658 659 660 661 662 663 664
        '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.'
      );
    }
665 666 667 668 669 670 671
    _lastRecordedSemanticsHandles = null;
  }

  int _lastRecordedSemanticsHandles;

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

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

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

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

  /// 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 {
727
    return TestAsyncUtils.guard<void>(() async {
728 729
      Finder backButton = find.byTooltip('Back');
      if (backButton.evaluate().isEmpty) {
730
        backButton = find.byType(CupertinoNavigationBarBackButton);
731
      }
732

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

735 736
      await tap(backButton);
    });
737
  }
738

739
  /// Attempts to find the [SemanticsNode] of first result from `finder`.
740
  ///
741
  /// If the object identified by the finder doesn't own it's semantic node,
742 743
  /// 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
744 745 746 747
  /// 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.
748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769
  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;
  }

770 771 772 773 774 775
  /// 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();
  }
776 777 778 779 780 781

  /// 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));
782 783
}

784
typedef _TickerDisposeCallback = void Function(_TestTicker ticker);
785 786 787 788

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

789
  final _TickerDisposeCallback _onDispose;
790 791 792 793 794 795 796

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