widget_tester.dart 40.3 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Hixie's avatar
Hixie committed
2 3 4
// 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/foundation.dart';
9
import 'package:flutter/gestures.dart';
10
import 'package:flutter/material.dart';
11
import 'package:flutter/rendering.dart';
12
import 'package:flutter/scheduler.dart';
13
import 'package:flutter/services.dart';
14
import 'package:flutter/widgets.dart';
15
import 'package:meta/meta.dart';
16 17

// ignore: deprecated_member_use
18
import 'package:test_api/test_api.dart' as test_package;
19

20
import 'all_elements.dart';
21
import 'binding.dart';
22
import 'controller.dart';
23
import 'event_simulation.dart';
24
import 'finders.dart';
25
import 'matchers.dart';
26
import 'test_async_utils.dart';
27
import 'test_compat.dart';
28
import 'test_text_input.dart';
29

30 31 32
/// Keep users from needing multiple imports to test semantics.
export 'package:flutter/rendering.dart' show SemanticsHandle;

33
// ignore: deprecated_member_use
34 35 36
/// 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.
37
export 'package:test_api/test_api.dart' hide
38 39 40 41 42 43
  test,
  group,
  setUpAll,
  tearDownAll,
  setUp,
  tearDown,
44 45 46
  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
47

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

51 52 53 54 55
/// Runs the [callback] inside the Flutter test environment.
///
/// Use this function for testing custom [StatelessWidget]s and
/// [StatefulWidget]s.
///
56 57
/// The callback can be asynchronous (using `async`/`await` or
/// using explicit [Future]s).
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
///
/// 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.
76
///
77
/// If the `semanticsEnabled` parameter is set to `true`,
78 79
/// [WidgetTester.ensureSemantics] will have been called before the tester is
/// passed to the `callback`, and that handle will automatically be disposed
80
/// after the callback is finished. It defaults to true.
81
///
82 83 84 85 86 87
/// 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].
///
88 89 90 91
/// When the [variant] argument is set, [testWidgets] will run the test once for
/// each value of the [TestVariant.values]. If [variant] is not set, the test
/// will be run once using the base test environment.
///
92 93 94
/// If the [tags] are passed, they declare user-defined tags that are implemented by
/// the `test` package.
///
95 96 97
/// See also:
///
///  * [AutomatedTestWidgetsFlutterBinding.addTime] to learn more about
98
///    timeout and how to manually increase timeouts.
99
///
100
/// ## Sample code
101
///
102
/// ```dart
103 104 105 106 107
/// testWidgets('MyWidget', (WidgetTester tester) async {
///   await tester.pumpWidget(new MyWidget());
///   await tester.tap(find.text('Save'));
///   expect(find.text('Success'), findsOneWidget);
/// });
108
/// ```
109
@isTest
110 111 112
void testWidgets(
  String description,
  WidgetTesterCallback callback, {
113
  bool skip = false,
114
  test_package.Timeout timeout,
115
  Duration initialTimeout,
116
  bool semanticsEnabled = true,
117
  TestVariant<Object> variant = const DefaultTestVariant(),
118
  dynamic tags,
119
}) {
120 121
  assert(variant != null);
  assert(variant.values.isNotEmpty, 'There must be at least on value to test in the testing variant');
122
  final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized() as TestWidgetsFlutterBinding;
123
  final WidgetTester tester = WidgetTester._(binding);
124
  for (final dynamic value in variant.values) {
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
    final String variationDescription = variant.describeValue(value);
    final String combinedDescription = variationDescription.isNotEmpty ? '$description ($variationDescription)' : description;
    test(
      combinedDescription,
      () {
        tester._testDescription = combinedDescription;
        SemanticsHandle semanticsHandle;
        if (semanticsEnabled == true) {
          semanticsHandle = tester.ensureSemantics();
        }
        tester._recordNumberOfSemanticsHandles();
        test_package.addTearDown(binding.postTest);
        return binding.runTest(
          () async {
            debugResetSemanticsIdCounter();
            tester.resetTestTextInput();
            Object memento;
            try {
              memento = await variant.setUp(value);
              await callback(tester);
            } finally {
              await variant.tearDown(value, memento);
            }
            semanticsHandle?.dispose();
          },
          tester._endOfTestVerifications,
          description: combinedDescription ?? '',
          timeout: initialTimeout,
        );
      },
      skip: skip,
      timeout: timeout ?? binding.defaultTestTimeout,
157
      tags: tags,
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
    );
  }
}

/// An abstract base class for describing test environment variants.
///
/// These serve as elements of the `variants` argument to [testWidgets].
///
/// Use care when adding more testing variants: it multiplies the number of
/// tests which run. This can drastically increase the time it takes to run all
/// the tests.
abstract class TestVariant<T> {
  /// A const constructor so that subclasses can be const.
  const TestVariant();

  /// Returns an iterable of the variations that this test dimension represents.
  ///
  /// The variations returned should be unique so that the same variation isn't
  /// needlessly run twice.
  Iterable<T> get values;

  /// Returns the string that will be used to both add to the test description, and
  /// be printed when a test fails for this variation.
  String describeValue(T value);

  /// A function that will be called before each value is tested, with the
  /// value that will be tested.
  ///
  /// This function should preserve any state needed to restore the testing
  /// environment back to its base state when [tearDown] is called in the
  /// `Object` that is returned. The returned object will then be passed to
  /// [tearDown] as a `memento` when the test is complete.
  Future<Object> setUp(T value);

  /// A function that is guaranteed to be called after a value is tested, even
  /// if it throws an exception.
  ///
  /// Calling this function must return the testing environment back to the base
  /// state it was in before [setUp] was called. The [memento] is the object
  /// returned from [setUp] when it was called.
  Future<void> tearDown(T value, covariant Object memento);
}

/// The [TestVariant] that represents the "default" test that is run if no
/// `variants` iterable is specified for [testWidgets].
///
/// This variant can be added into a list of other test variants to provide
/// a "control" test where nothing is changed from the base test environment.
class DefaultTestVariant extends TestVariant<void> {
  /// A const constructor for a [DefaultTestVariant].
  const DefaultTestVariant();

  @override
  Iterable<void> get values => const <void>[null];

  @override
  String describeValue(void value) => '';

  @override
  Future<void> setUp(void value) async {}

  @override
  Future<void> tearDown(void value, void memento) async {}
}

/// A [TestVariant] that runs tests with [debugDefaultTargetPlatformOverride]
/// set to different values of [TargetPlatform].
class TargetPlatformVariant extends TestVariant<TargetPlatform> {
  /// Creates a [TargetPlatformVariant] that tests the given [values].
  const TargetPlatformVariant(this.values);

  /// Creates a [TargetPlatformVariant] that tests all values from
  /// the [TargetPlatform] enum.
  TargetPlatformVariant.all() : values = TargetPlatform.values.toSet();

233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
  /// Creates a [TargetPlatformVariant] that includes platforms that are
  /// considered desktop platforms.
  TargetPlatformVariant.desktop() : values = <TargetPlatform>{
    TargetPlatform.linux,
    TargetPlatform.macOS,
    TargetPlatform.windows,
  };

  /// Creates a [TargetPlatformVariant] that includes platforms that are
  /// considered mobile platforms.
  TargetPlatformVariant.mobile() : values = <TargetPlatform>{
    TargetPlatform.android,
    TargetPlatform.iOS,
    TargetPlatform.fuchsia,
  };

249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
  /// Creates a [TargetPlatformVariant] that tests only the given value of
  /// [TargetPlatform].
  TargetPlatformVariant.only(TargetPlatform platform) : values = <TargetPlatform>{platform};

  @override
  final Set<TargetPlatform> values;

  @override
  String describeValue(TargetPlatform value) => value.toString();

  @override
  Future<TargetPlatform> setUp(TargetPlatform value) async {
    final TargetPlatform previousTargetPlatform = debugDefaultTargetPlatformOverride;
    debugDefaultTargetPlatformOverride = value;
    return previousTargetPlatform;
  }

  @override
  Future<void> tearDown(TargetPlatform value, TargetPlatform memento) async {
    debugDefaultTargetPlatformOverride = memento;
  }
270 271 272 273 274 275 276 277 278 279 280 281 282
}

/// 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
283 284 285 286
/// 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!
287
///
288 289 290 291 292
/// 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.
///
293 294 295 296
/// 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.
297 298 299 300
///
/// Example:
///
///     main() async {
301
///       assert(false); // fail in checked mode
302 303
///       await benchmarkWidgets((WidgetTester tester) async {
///         await tester.pumpWidget(new MyWidget());
304 305
///         final Stopwatch timer = new Stopwatch()..start();
///         for (int index = 0; index < 10000; index += 1) {
306 307
///           await tester.tap(find.text('Tap me'));
///           await tester.pump();
308 309
///         }
///         timer.stop();
310
///         debugPrint('Time taken: ${timer.elapsedMilliseconds}ms');
311 312 313
///       });
///       exit(0);
///     }
314 315 316
Future<void> benchmarkWidgets(
  WidgetTesterCallback callback, {
  bool mayRunWithAsserts = false,
317
  bool semanticsEnabled = false,
318
}) {
319
  assert(() {
320 321 322
    if (mayRunWithAsserts)
      return true;

323 324 325 326 327 328 329 330
    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  ┆');
331
    print('│  line:  flutter run --release benchmark.dart          ┊');
332 333 334
    print('│                                                        ');
    print('└─────────────────────────────────────────────────╌┄┈  🐢');
    return true;
335
  }());
336
  final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized() as TestWidgetsFlutterBinding;
337
  assert(binding is! AutomatedTestWidgetsFlutterBinding);
338
  final WidgetTester tester = WidgetTester._(binding);
339 340 341 342
  SemanticsHandle semanticsHandle;
  if (semanticsEnabled == true) {
    semanticsHandle = tester.ensureSemantics();
  }
343
  tester._recordNumberOfSemanticsHandles();
344
  return binding.runTest(
345 346 347 348
    () async {
      await callback(tester);
      semanticsHandle?.dispose();
    },
349
    tester._endOfTestVerifications,
350
  ) ?? Future<void>.value();
351
}
352

353 354 355 356 357
/// 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.
358 359 360 361
///
/// See also:
///
///  * [expectLater] for use with asynchronous matchers.
362 363 364
void expect(
  dynamic actual,
  dynamic matcher, {
365
  String reason,
Ian Hickson's avatar
Ian Hickson committed
366
  dynamic skip, // true or a String
367 368
}) {
  TestAsyncUtils.guardSync();
Ian Hickson's avatar
Ian Hickson committed
369
  test_package.expect(actual, matcher, reason: reason, skip: skip);
370 371 372 373 374 375 376 377 378 379 380
}

/// 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.
381 382 383
void expectSync(
  dynamic actual,
  dynamic matcher, {
384 385
  String reason,
}) {
386
  test_package.expect(actual, matcher, reason: reason);
387 388
}

389 390 391 392 393 394 395 396
/// 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.
397 398 399
Future<void> expectLater(
  dynamic actual,
  dynamic matcher, {
400 401 402
  String reason,
  dynamic skip, // true or a String
}) {
403 404 405
  // 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();
406 407
  return test_package.expectLater(actual, matcher, reason: reason, skip: skip)
           .then<void>((dynamic value) => null);
408 409
}

410
/// Class that programmatically interacts with widgets and the test environment.
411 412 413 414
///
/// 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 {
415 416 417 418
  WidgetTester._(TestWidgetsFlutterBinding binding) : super(binding) {
    if (binding is LiveTestWidgetsFlutterBinding)
      binding.deviceEventDispatcher = this;
  }
419

420 421 422 423
  /// The description string of the test currently being run.
  String get testDescription => _testDescription;
  String _testDescription = '';

424 425
  /// The binding instance used by the testing framework.
  @override
426
  TestWidgetsFlutterBinding get binding => super.binding as TestWidgetsFlutterBinding;
427 428 429

  /// Renders the UI from the given [widget].
  ///
430 431 432 433
  /// 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.
434 435 436 437
  ///
  /// 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.
438
  ///
439 440 441 442
  /// 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].
  ///
443
  /// {@tool snippet}
444 445 446 447 448 449 450 451
  /// ```dart
  /// testWidgets('MyWidget asserts invalid bounds', (WidgetTester tester) async {
  ///   await tester.pumpWidget(MyWidget(-1));
  ///   expect(tester.takeException(), isAssertionError); // or isNull, as appropriate.
  /// });
  /// ```
  /// {@end-tool}
  ///
452 453
  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
  /// this method works when the test is run with `flutter run`.
454 455
  Future<void> pumpWidget(
    Widget widget, [
456
    Duration duration,
457
    EnginePhase phase = EnginePhase.sendSemanticsUpdate,
458
  ]) {
459
    return TestAsyncUtils.guard<void>(() {
460 461
      binding.attachRootWidget(widget);
      binding.scheduleFrame();
462 463
      return binding.pump(duration, phase);
    });
464 465
  }

466 467 468
  /// Triggers a frame after `duration` amount of time.
  ///
  /// This makes the framework act as if the application had janked (missed
469
  /// frames) for `duration` amount of time, and then received a "Vsync" signal
470
  /// to paint the application.
471
  ///
472 473
  /// This is a convenience function that just calls
  /// [TestWidgetsFlutterBinding.pump].
474 475 476
  ///
  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
  /// this method works when the test is run with `flutter run`.
477
  @override
478
  Future<void> pump([
479
    Duration duration,
480
    EnginePhase phase = EnginePhase.sendSemanticsUpdate,
481
  ]) {
482
    return TestAsyncUtils.guard<void>(() => binding.pump(duration, phase));
483
  }
484

485 486 487 488 489 490 491 492 493 494
  /// 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(() {
495 496 497
      final TestWidgetsFlutterBinding widgetsBinding = binding;
      return widgetsBinding is LiveTestWidgetsFlutterBinding &&
              widgetsBinding.framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark;
498 499 500 501 502
    }());

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

503
    await Future<void>.microtask(() { binding.handleBeginFrame(duration); }).catchError(handleError);
504
    await idle();
505
    await Future<void>.microtask(() { binding.handleDrawFrame(); }).catchError(handleError);
506 507 508 509 510 511 512
    await idle();

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

513
  /// Repeatedly calls [pump] with the given `duration` until there are no
514 515 516
  /// 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.
517 518 519
  ///
  /// This essentially waits for all animations to have completed.
  ///
520 521 522 523 524 525 526
  /// 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.
527 528 529 530 531 532 533 534 535 536
  ///
  /// 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.
537
  Future<int> pumpAndSettle([
538 539 540 541
    Duration duration = const Duration(milliseconds: 100),
    EnginePhase phase = EnginePhase.sendSemanticsUpdate,
    Duration timeout = const Duration(minutes: 10),
  ]) {
542
    assert(duration != null);
543
    assert(duration > Duration.zero);
544
    assert(timeout != null);
545
    assert(timeout > Duration.zero);
546 547 548 549 550 551 552 553 554 555 556
    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;
    }());
557
    int count = 0;
558
    return TestAsyncUtils.guard<void>(() async {
559
      final DateTime endTime = binding.clock.fromNowBy(timeout);
560
      do {
561
        if (binding.clock.now().isAfter(endTime))
562
          throw FlutterError('pumpAndSettle timed out');
563 564
        await binding.pump(duration, phase);
        count += 1;
565
      } while (binding.hasScheduledFrame);
566
    }).then<int>((_) => count);
567 568
  }

569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
  /// Repeatedly pump frames that render the `target` widget with a fixed time
  /// `interval` as many as `maxDuration` allows.
  ///
  /// The `maxDuration` argument is required. The `interval` argument defaults to
  /// 16.683 milliseconds (59.94 FPS).
  Future<void> pumpFrames(
    Widget target,
    Duration maxDuration, [
    Duration interval = const Duration(milliseconds: 16, microseconds: 683),
  ]) {
    assert(maxDuration != null);
    // The interval following the last frame doesn't have to be within the fullDuration.
    Duration elapsed = Duration.zero;
    return TestAsyncUtils.guard<void>(() async {
      binding.attachRootWidget(target);
      binding.scheduleFrame();
      while (elapsed < maxDuration) {
        await binding.pump(interval);
        elapsed += interval;
      }
    });
  }

592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611
  /// 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.
612 613
  Future<T> runAsync<T>(
    Future<T> callback(), {
614
    Duration additionalTime = const Duration(milliseconds: 1000),
615
  }) => binding.runAsync<T>(callback, additionalTime: additionalTime);
616

617
  /// Whether there are any any transient callbacks scheduled.
618 619
  ///
  /// This essentially checks whether all animations have completed.
620 621 622 623 624 625 626 627 628 629 630
  ///
  /// 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.
631 632
  bool get hasRunningAnimations => binding.transientCallbackCount > 0;

633
  @override
634
  HitTestResult hitTestOnBinding(Offset location) {
635 636 637 638
    location = binding.localToGlobal(location);
    return super.hitTestOnBinding(location);
  }

639
  @override
640 641
  Future<void> sendEventToBinding(PointerEvent event, HitTestResult result) {
    return TestAsyncUtils.guard<void>(() async {
642 643 644 645
      binding.dispatchEvent(event, result, source: TestBindingEventSource.test);
    });
  }

646 647 648 649
  /// Handler for device events caught by the binding in live test mode.
  @override
  void dispatchEvent(PointerEvent event, HitTestResult result) {
    if (event is PointerDownEvent) {
650 651 652 653
      final RenderObject innerTarget = result.path
        .map((HitTestEntry candidate) => candidate.target)
        .whereType<RenderObject>()
        .first;
654 655 656 657 658 659 660 661 662 663 664
      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;
      }
665 666 667 668 669 670 671 672 673 674
      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;
675
      debugPrint('Some possible finders for the widgets at ${binding.globalToLocal(event.position)}:');
676
      for (final Element element in candidates) {
677
        if (totalNumber > 13) // an arbitrary number of finders that feels useful without being overwhelming
678
          break;
679 680
        totalNumber += 1; // optimistically assume we'll be able to describe it

681 682
        final Widget widget = element.widget;
        if (widget is Tooltip) {
683 684
          final Iterable<Element> matches = find.byTooltip(widget.message).evaluate();
          if (matches.length == 1) {
685
            debugPrint("  find.byTooltip('${widget.message}')");
686 687 688
            continue;
          }
        }
689

690
        if (widget is Text) {
691 692 693 694
          assert(descendantText == null);
          final Iterable<Element> matches = find.text(widget.data).evaluate();
          descendantText = widget.data;
          if (matches.length == 1) {
695
            debugPrint("  find.text('${widget.data}')");
696 697 698 699
            continue;
          }
        }

700 701
        final Key key = widget.key;
        if (key is ValueKey<dynamic>) {
702
          String keyLabel;
703 704 705
          if (key is ValueKey<int> ||
              key is ValueKey<double> ||
              key is ValueKey<bool>) {
706
            keyLabel = 'const ${key.runtimeType}(${key.value})';
707
          } else if (key is ValueKey<String>) {
708
            keyLabel = "const Key('${key.value}')";
709 710 711 712
          }
          if (keyLabel != null) {
            final Iterable<Element> matches = find.byKey(key).evaluate();
            if (matches.length == 1) {
713
              debugPrint('  find.byKey($keyLabel)');
714 715 716 717 718
              continue;
            }
          }
        }

719
        if (!_isPrivate(widget.runtimeType)) {
720
          if (numberOfTypes < 5) {
721
            final Iterable<Element> matches = find.byType(widget.runtimeType).evaluate();
722
            if (matches.length == 1) {
723
              debugPrint('  find.byType(${widget.runtimeType})');
724 725 726 727 728 729
              numberOfTypes += 1;
              continue;
            }
          }

          if (descendantText != null && numberOfWithTexts < 5) {
730
            final Iterable<Element> matches = find.widgetWithText(widget.runtimeType, descendantText).evaluate();
731
            if (matches.length == 1) {
732
              debugPrint("  find.widgetWithText(${widget.runtimeType}, '$descendantText')");
733 734 735 736 737 738 739 740 741
              numberOfWithTexts += 1;
              continue;
            }
          }
        }

        if (!_isPrivate(element.runtimeType)) {
          final Iterable<Element> matches = find.byElementType(element.runtimeType).evaluate();
          if (matches.length == 1) {
742
            debugPrint('  find.byElementType(${element.runtimeType})');
743 744 745 746 747 748 749
            continue;
          }
        }

        totalNumber -= 1; // if we got here, we didn't actually find something to say about it
      }
      if (totalNumber == 0)
750
        debugPrint('  <could not come up with any unique finders>');
751 752 753 754
    }
  }

  bool _isPrivate(Type type) {
755
    // used above so that we don't suggest matchers for private types
756 757 758
    return '_'.matchAsPrefix(type.toString()) != null;
  }

759 760
  /// Returns the exception most recently caught by the Flutter framework.
  ///
761
  /// See [TestWidgetsFlutterBinding.takeException] for details.
762
  dynamic takeException() {
763
    return binding.takeException();
764 765
  }

766 767
  /// Acts as if the application went idle.
  ///
768 769
  /// Runs all remaining microtasks, including those scheduled as a result of
  /// running them, until there are no more microtasks scheduled.
770
  ///
771 772
  /// Does not run timers. May result in an infinite loop or run out of memory
  /// if microtasks continue to recursively schedule new microtasks.
773 774
  Future<void> idle() {
    return TestAsyncUtils.guard<void>(() => binding.idle());
775
  }
776 777 778 779 780

  Set<Ticker> _tickers;

  @override
  Ticker createTicker(TickerCallback onTick) {
781
    _tickers ??= <_TestTicker>{};
782
    final _TestTicker result = _TestTicker(onTick, _removeTicker);
783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
    _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) {
802
      for (final Ticker ticker in _tickers) {
803
        if (ticker.isActive) {
804 805 806 807 808 809 810 811 812 813
          throw FlutterError.fromParts(<DiagnosticsNode>[
            ErrorSummary('A Ticker was active $when.'),
            ErrorDescription('All Tickers must be disposed.'),
            ErrorHint(
              'Tickers used by AnimationControllers '
              'should be disposed by calling dispose() on the AnimationController itself. '
              'Otherwise, the ticker will leak.'
            ),
            ticker.describeForError('The offending ticker was')
          ]);
814 815 816 817 818 819 820
        }
      }
    }
  }

  void _endOfTestVerifications() {
    verifyTickersWereDisposed('at the end of the test');
821 822 823 824
    _verifySemanticsHandlesWereDisposed();
  }

  void _verifySemanticsHandlesWereDisposed() {
825 826
    assert(_lastRecordedSemanticsHandles != null);
    if (binding.pipelineOwner.debugOutstandingSemanticsHandles > _lastRecordedSemanticsHandles) {
827 828 829 830 831 832 833 834 835 836 837 838
      throw FlutterError.fromParts(<DiagnosticsNode>[
        ErrorSummary('A SemanticsHandle was active at the end of the test.'),
        ErrorDescription(
          'All SemanticsHandle instances must be disposed by calling dispose() on '
          'the SemanticsHandle.'
        ),
        ErrorHint(
          '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.'
        )
      ]);
839
    }
840 841 842 843 844 845 846
    _lastRecordedSemanticsHandles = null;
  }

  int _lastRecordedSemanticsHandles;

  void _recordNumberOfSemanticsHandles() {
    _lastRecordedSemanticsHandles = binding.pipelineOwner.debugOutstandingSemanticsHandles;
847
  }
848 849 850 851

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

Dan Field's avatar
Dan Field committed
855 856 857 858 859 860 861 862 863 864 865
  /// Ensures that [testTextInput] is registered and [TestTextInput.log] is
  /// reset.
  ///
  /// This is called by the testing framework before test runs, so that if a
  /// previous test has set its own handler on [SystemChannels.textInput], the
  /// [testTextInput] regains control and the log is fresh for the new test.
  /// It should not typically need to be called by tests.
  void resetTestTextInput() {
    testTextInput.resetAndRegister();
  }

866
  /// Give the text input widget specified by [finder] the focus, as if the
867 868
  /// onscreen keyboard had appeared.
  ///
869 870
  /// Implies a call to [pump].
  ///
871 872
  /// The widget specified by [finder] must be an [EditableText] or have
  /// an [EditableText] descendant. For example `find.byType(TextField)`
873
  /// or `find.byType(TextFormField)`, or `find.byType(EditableText)`.
874 875
  ///
  /// Tests that just need to add text to widgets like [TextField]
876
  /// or [TextFormField] only need to call [enterText].
877 878
  Future<void> showKeyboard(Finder finder) async {
    return TestAsyncUtils.guard<void>(() async {
879
      final EditableTextState editable = state<EditableTextState>(
880 881 882 883 884 885 886 887
        find.descendant(
          of: finder,
          matching: find.byType(EditableText),
          matchRoot: true,
        ),
      );
      binding.focusedEditable = editable;
      await pump();
888
    });
889 890
  }

891
  /// Give the text input widget specified by [finder] the focus and
892
  /// enter [text] as if it been provided by the onscreen keyboard.
893 894 895
  ///
  /// The widget specified by [finder] must be an [EditableText] or have
  /// an [EditableText] descendant. For example `find.byType(TextField)`
896
  /// or `find.byType(TextFormField)`, or `find.byType(EditableText)`.
897 898 899
  ///
  /// To just give [finder] the focus without entering any text,
  /// see [showKeyboard].
900 901
  Future<void> enterText(Finder finder, String text) async {
    return TestAsyncUtils.guard<void>(() async {
902 903 904 905
      await showKeyboard(finder);
      testTextInput.enterText(text);
      await idle();
    });
906
  }
907

908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975
  /// 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);
  }

976 977 978 979 980
  /// 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 {
981
    return TestAsyncUtils.guard<void>(() async {
982 983
      Finder backButton = find.byTooltip('Back');
      if (backButton.evaluate().isEmpty) {
984
        backButton = find.byType(CupertinoNavigationBarBackButton);
985
      }
986

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

989 990
      await tap(backButton);
    });
991
  }
992

993
  /// Attempts to find the [SemanticsNode] of first result from `finder`.
994
  ///
995
  /// If the object identified by the finder doesn't own it's semantic node,
996 997
  /// 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
998 999 1000 1001
  /// 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.
1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015
  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) {
1016
      renderObject = renderObject?.parent as RenderObject;
1017 1018 1019 1020 1021 1022 1023
      result = renderObject?.debugSemantics;
    }
    if (result == null)
      throw StateError('No Semantics data found.');
    return result;
  }

1024 1025 1026 1027 1028 1029
  /// 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();
  }
1030 1031 1032 1033 1034 1035

  /// 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));
1036 1037
}

1038
typedef _TickerDisposeCallback = void Function(_TestTicker ticker);
1039 1040 1041 1042

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

1043
  final _TickerDisposeCallback _onDispose;
1044 1045 1046 1047 1048 1049 1050

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