1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'package:fake_async/fake_async.dart';
6/// @docImport 'package:flutter/material.dart';
7/// @docImport 'package:matcher/matcher.dart';
8/// @docImport 'package:test_api/hooks.dart';
9library;
10
11import 'package:flutter/cupertino.dart';
12import 'package:flutter/foundation.dart';
13import 'package:flutter/gestures.dart';
14import 'package:flutter/material.dart' show Tooltip;
15import 'package:flutter/rendering.dart';
16import 'package:flutter/scheduler.dart';
17import 'package:flutter/services.dart';
18import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
19import 'package:matcher/expect.dart' as matcher_expect;
20import 'package:meta/meta.dart';
21import 'package:test_api/scaffolding.dart' as test_package;
22
23import 'binding.dart';
24import 'controller.dart';
25import 'finders.dart';
26import 'matchers.dart';
27import 'restoration.dart';
28import 'test_async_utils.dart';
29import 'test_compat.dart';
30import 'test_pointer.dart';
31import 'test_text_input.dart';
32import 'tree_traversal.dart';
33
34// Keep users from needing multiple imports to test semantics.
35export 'package:flutter/rendering.dart' show SemanticsHandle;
36// We re-export the matcher package minus some features that we reimplement.
37//
38// - expect is reimplemented below, to catch incorrect async usage.
39//
40// - isInstanceOf is reimplemented in matchers.dart because we don't want to
41// mark it as deprecated (ours is just a method, not a class).
42//
43export 'package:matcher/expect.dart' hide expect, isInstanceOf;
44// We re-export the test package minus some features that we reimplement.
45//
46// Specifically:
47//
48// - test, group, setUpAll, tearDownAll, setUp, tearDown, and expect would
49// conflict with our own implementations in test_compat.dart. This handles
50// setting up a declarer when one is not defined, which can happen when a
51// test is executed via `flutter run`.
52//
53// The test_api package has a deprecation warning to discourage direct use but
54// that doesn't apply here.
55export 'package:test_api/hooks.dart' show TestFailure;
56export 'package:test_api/scaffolding.dart'
57 show
58 OnPlatform,
59 Retry,
60 Skip,
61 Tags,
62 TestOn,
63 Timeout,
64 addTearDown,
65 markTestSkipped,
66 printOnFailure,
67 pumpEventQueue,
68 registerException,
69 spawnHybridCode,
70 spawnHybridUri;
71
72/// Signature for callback to [testWidgets] and [benchmarkWidgets].
73typedef WidgetTesterCallback = Future<void> Function(WidgetTester widgetTester);
74
75// Return the last element that satisfies `test`, or return null if not found.
76E? _lastWhereOrNull<E>(Iterable<E> list, bool Function(E) test) {
77 late E result;
78 bool foundMatching = false;
79 for (final E element in list) {
80 if (test(element)) {
81 result = element;
82 foundMatching = true;
83 }
84 }
85 if (foundMatching) {
86 return result;
87 }
88 return null;
89}
90
91// Examples can assume:
92// typedef MyWidget = Placeholder;
93
94/// Runs the [callback] inside the Flutter test environment.
95///
96/// Use this function for testing custom [StatelessWidget]s and
97/// [StatefulWidget]s.
98///
99/// The callback can be asynchronous (using `async`/`await` or
100/// using explicit [Future]s).
101///
102/// The `timeout` argument specifies the backstop timeout implemented by the
103/// `test` package. If set, it should be relatively large (minutes). It defaults
104/// to ten minutes for tests run by `flutter test`, and is unlimited for tests
105/// run by `flutter run`; specifically, it defaults to
106/// [TestWidgetsFlutterBinding.defaultTestTimeout].
107///
108/// If the `semanticsEnabled` parameter is set to `true`,
109/// [WidgetTester.ensureSemantics] will have been called before the tester is
110/// passed to the `callback`, and that handle will automatically be disposed
111/// after the callback is finished. It defaults to true.
112///
113/// This function uses the [test] function in the test package to
114/// register the given callback as a test. The callback, when run,
115/// will be given a new instance of [WidgetTester]. The [find] object
116/// provides convenient widget [Finder]s for use with the
117/// [WidgetTester].
118///
119/// When the [variant] argument is set, [testWidgets] will run the test once for
120/// each value of the [TestVariant.values]. If [variant] is not set, the test
121/// will be run once using the base test environment.
122///
123/// If the [tags] are passed, they declare user-defined tags that are implemented by
124/// the `test` package.
125///
126/// The argument [experimentalLeakTesting] is experimental and is not recommended
127/// for use outside of the Flutter framework.
128/// When [experimentalLeakTesting] is set, it is used to leak track objects created
129/// during test execution.
130/// Otherwise [LeakTesting.settings] is used.
131/// Adjust [LeakTesting.settings] in `flutter_test_config.dart`
132/// (see https://flutter.dev/to/flutter-test-docs)
133/// for the entire package or folder, or in the test's main for a test file
134/// (don't use [setUp] or [setUpAll]).
135/// To turn off leak tracking just for one test, set [experimentalLeakTesting] to
136/// `LeakTrackingForTests.ignore()`.
137///
138/// ## Sample code
139///
140/// ```dart
141/// testWidgets('MyWidget', (WidgetTester tester) async {
142/// await tester.pumpWidget(const MyWidget());
143/// await tester.tap(find.text('Save'));
144/// expect(find.text('Success'), findsOneWidget);
145/// });
146/// ```
147@isTest
148void testWidgets(
149 String description,
150 WidgetTesterCallback callback, {
151 bool? skip,
152 test_package.Timeout? timeout,
153 bool semanticsEnabled = true,
154 TestVariant<Object?> variant = const DefaultTestVariant(),
155 dynamic tags,
156 int? retry,
157 LeakTesting? experimentalLeakTesting,
158}) {
159 assert(
160 variant.values.isNotEmpty,
161 'There must be at least one value to test in the testing variant.',
162 );
163 final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
164 final WidgetTester tester = WidgetTester._(binding);
165 for (final dynamic value in variant.values) {
166 final String variationDescription = variant.describeValue(value);
167 // IDEs may make assumptions about the format of this suffix in order to
168 // support running tests directly from the editor (where they may have
169 // access to only the test name, provided by the analysis server).
170 // See https://github.com/flutter/flutter/issues/86659.
171 final String combinedDescription = variationDescription.isNotEmpty
172 ? '$description (variant: $variationDescription)'
173 : description;
174 test(
175 combinedDescription,
176 () {
177 tester._testDescription = combinedDescription;
178 SemanticsHandle? semanticsHandle;
179 tester._recordNumberOfSemanticsHandles();
180 if (semanticsEnabled) {
181 semanticsHandle = tester.ensureSemantics();
182 }
183 test_package.addTearDown(binding.postTest);
184 return binding.runTest(
185 () async {
186 debugResetSemanticsIdCounter();
187 Object? memento;
188 try {
189 memento = await variant.setUp(value);
190 binding.reset(); // TODO(ianh): the binding should just do this itself in _runTest
191 maybeSetupLeakTrackingForTest(experimentalLeakTesting, combinedDescription);
192 await callback(tester);
193 } finally {
194 await variant.tearDown(value, memento);
195 maybeTearDownLeakTrackingForTest();
196 }
197 semanticsHandle?.dispose();
198 },
199 tester._endOfTestVerifications,
200 description: combinedDescription,
201 );
202 },
203 skip: skip,
204 timeout: timeout ?? binding.defaultTestTimeout,
205 tags: tags,
206 retry: retry,
207 );
208 }
209}
210
211/// An abstract base class for describing test environment variants.
212///
213/// These serve as elements of the `variants` argument to [testWidgets].
214///
215/// Use care when adding more testing variants: it multiplies the number of
216/// tests which run. This can drastically increase the time it takes to run all
217/// the tests.
218abstract class TestVariant<T> {
219 /// A const constructor so that subclasses can be const.
220 const TestVariant();
221
222 /// Returns an iterable of the variations that this test dimension represents.
223 ///
224 /// The variations returned should be unique so that the same variation isn't
225 /// needlessly run twice.
226 Iterable<T> get values;
227
228 /// Returns the string that will be used to both add to the test description, and
229 /// be printed when a test fails for this variation.
230 String describeValue(T value);
231
232 /// A function that will be called before each value is tested, with the
233 /// value that will be tested.
234 ///
235 /// This function should preserve any state needed to restore the testing
236 /// environment back to its base state when [tearDown] is called in the
237 /// `Object` that is returned. The returned object will then be passed to
238 /// [tearDown] as a `memento` when the test is complete.
239 Future<Object?> setUp(T value);
240
241 /// A function that is guaranteed to be called after a value is tested, even
242 /// if it throws an exception.
243 ///
244 /// Calling this function must return the testing environment back to the base
245 /// state it was in before [setUp] was called. The [memento] is the object
246 /// returned from [setUp] when it was called.
247 Future<void> tearDown(T value, covariant Object? memento);
248}
249
250/// The [TestVariant] that represents the "default" test that is run if no
251/// `variants` iterable is specified for [testWidgets].
252///
253/// This variant can be added into a list of other test variants to provide
254/// a "control" test where nothing is changed from the base test environment.
255class DefaultTestVariant extends TestVariant<void> {
256 /// A const constructor for a [DefaultTestVariant].
257 const DefaultTestVariant();
258
259 @override
260 Iterable<void> get values => const <void>[null];
261
262 @override
263 String describeValue(void value) => '';
264
265 @override
266 Future<void> setUp(void value) async {}
267
268 @override
269 Future<void> tearDown(void value, void memento) async {}
270}
271
272/// A [TestVariant] that runs tests with [debugDefaultTargetPlatformOverride]
273/// set to different values of [TargetPlatform].
274class TargetPlatformVariant extends TestVariant<TargetPlatform> {
275 /// Creates a [TargetPlatformVariant] that tests the given [values].
276 const TargetPlatformVariant(this.values);
277
278 /// Creates a [TargetPlatformVariant] that tests all values from
279 /// the [TargetPlatform] enum. If [excluding] is provided, will test all platforms
280 /// except those in [excluding].
281 TargetPlatformVariant.all({Set<TargetPlatform> excluding = const <TargetPlatform>{}})
282 : values = TargetPlatform.values.toSet()..removeAll(excluding);
283
284 /// Creates a [TargetPlatformVariant] that includes platforms that are
285 /// considered desktop platforms.
286 TargetPlatformVariant.desktop()
287 : values = <TargetPlatform>{TargetPlatform.linux, TargetPlatform.macOS, TargetPlatform.windows};
288
289 /// Creates a [TargetPlatformVariant] that includes platforms that are
290 /// considered mobile platforms.
291 TargetPlatformVariant.mobile()
292 : values = <TargetPlatform>{TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.fuchsia};
293
294 /// Creates a [TargetPlatformVariant] that tests only the given value of
295 /// [TargetPlatform].
296 TargetPlatformVariant.only(TargetPlatform platform) : values = <TargetPlatform>{platform};
297
298 @override
299 final Set<TargetPlatform> values;
300
301 @override
302 String describeValue(TargetPlatform value) => value.toString();
303
304 @override
305 Future<TargetPlatform?> setUp(TargetPlatform value) async {
306 final TargetPlatform? previousTargetPlatform = debugDefaultTargetPlatformOverride;
307 debugDefaultTargetPlatformOverride = value;
308 return previousTargetPlatform;
309 }
310
311 @override
312 Future<void> tearDown(TargetPlatform value, TargetPlatform? memento) async {
313 debugDefaultTargetPlatformOverride = memento;
314 }
315}
316
317/// A [TestVariant] that runs separate tests with each of the given values.
318///
319/// To use this variant, define it before the test, and then access
320/// [currentValue] inside the test.
321///
322/// The values are typically enums, but they don't have to be. The `toString`
323/// for the given value will be used to describe the variant. Values will have
324/// their type name stripped from their `toString` output, so that enum values
325/// will only print the value, not the type.
326///
327/// {@tool snippet}
328/// This example shows how to set up the test to access the [currentValue]. In
329/// this example, two tests will be run, one with `value1`, and one with
330/// `value2`. The test with `value2` will fail. The names of the tests will be:
331///
332/// - `Test handling of TestScenario (value1)`
333/// - `Test handling of TestScenario (value2)`
334///
335/// ```dart
336/// enum TestScenario {
337/// value1,
338/// value2,
339/// value3,
340/// }
341///
342/// final ValueVariant<TestScenario> variants = ValueVariant<TestScenario>(
343/// <TestScenario>{TestScenario.value1, TestScenario.value2},
344/// );
345/// void main() {
346/// testWidgets('Test handling of TestScenario', (WidgetTester tester) async {
347/// expect(variants.currentValue, equals(TestScenario.value1));
348/// }, variant: variants);
349/// }
350/// ```
351/// {@end-tool}
352class ValueVariant<T> extends TestVariant<T> {
353 /// Creates a [ValueVariant] that tests the given [values].
354 ValueVariant(this.values);
355
356 /// Returns the value currently under test.
357 T? get currentValue => _currentValue;
358 T? _currentValue;
359
360 @override
361 final Set<T> values;
362
363 @override
364 String describeValue(T value) => value.toString().replaceFirst('$T.', '');
365
366 @override
367 Future<T> setUp(T value) async => _currentValue = value;
368
369 @override
370 Future<void> tearDown(T value, T memento) async {}
371}
372
373/// The warning message to show when a benchmark is performed with assert on.
374const String kDebugWarning = '''
375┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓
376┇ ⚠ THIS BENCHMARK IS BEING RUN IN DEBUG MODE ⚠ ┇
377┡╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┦
378│ │
379│ Numbers obtained from a benchmark while asserts are │
380│ enabled will not accurately reflect the performance │
381│ that will be experienced by end users using release ╎
382│ builds. Benchmarks should be run using this command ╎
383│ line: "flutter run --profile test.dart" or ┊
384│ or "flutter drive --profile -t test.dart". ┊
385│ ┊
386└─────────────────────────────────────────────────╌┄┈ 🐢
387''';
388
389/// Runs the [callback] inside the Flutter benchmark environment.
390///
391/// Use this function for benchmarking custom [StatelessWidget]s and
392/// [StatefulWidget]s when you want to be able to use features from
393/// [TestWidgetsFlutterBinding]. The callback, when run, will be given
394/// a new instance of [WidgetTester]. The [find] object provides
395/// convenient widget [Finder]s for use with the [WidgetTester].
396///
397/// The callback can be asynchronous (using `async`/`await` or using
398/// explicit [Future]s). If it is, then [benchmarkWidgets] will return
399/// a [Future] that completes when the callback's does. Otherwise, it
400/// will return a Future that is always complete.
401///
402/// If the callback is asynchronous, make sure you `await` the call
403/// to [benchmarkWidgets], otherwise it won't run!
404///
405/// If the `semanticsEnabled` parameter is set to `true`,
406/// [WidgetTester.ensureSemantics] will have been called before the tester is
407/// passed to the `callback`, and that handle will automatically be disposed
408/// after the callback is finished.
409///
410/// Benchmarks must not be run in debug mode, because the performance is not
411/// representative. To avoid this, this function will print a big message if it
412/// is run in debug mode. Unit tests of this method pass `mayRunWithAsserts`,
413/// but it should not be used for actual benchmarking.
414///
415/// Example:
416///
417/// main() async {
418/// assert(false); // fail in debug mode
419/// await benchmarkWidgets((WidgetTester tester) async {
420/// await tester.pumpWidget(MyWidget());
421/// final Stopwatch timer = Stopwatch()..start();
422/// for (int index = 0; index < 10000; index += 1) {
423/// await tester.tap(find.text('Tap me'));
424/// await tester.pump();
425/// }
426/// timer.stop();
427/// debugPrint('Time taken: ${timer.elapsedMilliseconds}ms');
428/// });
429/// exit(0);
430/// }
431Future<void> benchmarkWidgets(
432 WidgetTesterCallback callback, {
433 bool mayRunWithAsserts = false,
434 bool semanticsEnabled = false,
435}) {
436 assert(() {
437 if (mayRunWithAsserts) {
438 return true;
439 }
440 debugPrint(kDebugWarning);
441 return true;
442 }());
443 final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
444 assert(binding is! AutomatedTestWidgetsFlutterBinding);
445 final WidgetTester tester = WidgetTester._(binding);
446 SemanticsHandle? semanticsHandle;
447 if (semanticsEnabled) {
448 semanticsHandle = tester.ensureSemantics();
449 }
450 tester._recordNumberOfSemanticsHandles();
451 return binding.runTest(() async {
452 await callback(tester);
453 semanticsHandle?.dispose();
454 }, tester._endOfTestVerifications);
455}
456
457/// Assert that `actual` matches `matcher`.
458///
459/// See [matcher_expect.expect] for details. This is a variant of that function
460/// that additionally verifies that there are no asynchronous APIs
461/// that have not yet resolved.
462///
463/// See also:
464///
465/// * [expectLater] for use with asynchronous matchers.
466void expect(
467 dynamic actual,
468 dynamic matcher, {
469 String? reason,
470 dynamic skip, // true or a String
471}) {
472 TestAsyncUtils.guardSync();
473 matcher_expect.expect(actual, matcher, reason: reason, skip: skip);
474}
475
476/// Assert that `actual` matches `matcher`.
477///
478/// See [matcher_expect.expect] for details. This variant will _not_ check that
479/// there are no outstanding asynchronous API requests. As such, it can be
480/// called from, e.g., callbacks that are run during build or layout, or in the
481/// completion handlers of futures that execute in response to user input.
482///
483/// Generally, it is better to use [expect], which does include checks to ensure
484/// that asynchronous APIs are not being called.
485void expectSync(dynamic actual, dynamic matcher, {String? reason}) {
486 matcher_expect.expect(actual, matcher, reason: reason);
487}
488
489/// Just like [expect], but returns a [Future] that completes when the matcher
490/// has finished matching.
491///
492/// See [matcher_expect.expectLater] for details.
493///
494/// If the matcher fails asynchronously, that failure is piped to the returned
495/// future where it can be handled by user code. If it is not handled by user
496/// code, the test will fail.
497Future<void> expectLater(
498 dynamic actual,
499 dynamic matcher, {
500 String? reason,
501 dynamic skip, // true or a String
502}) {
503 // We can't wrap the delegate in a guard, or we'll hit async barriers in
504 // [TestWidgetsFlutterBinding] while we're waiting for the matcher to complete
505 TestAsyncUtils.guardSync();
506 return matcher_expect
507 .expectLater(actual, matcher, reason: reason, skip: skip)
508 .then<void>((dynamic value) => null);
509}
510
511/// Class that programmatically interacts with widgets and the test environment.
512///
513/// Typically, a test uses [pumpWidget] to load a widget tree (in a manner very
514/// similar to how [runApp] works in a Flutter application). Then, methods such
515/// as [tap], [drag], [enterText], [fling], [longPress], etc, can be used to
516/// interact with the application. The application runs in a [FakeAsync] zone,
517/// which allows time to be stepped forward deliberately; this is done using the
518/// [pump] method.
519///
520/// The [expect] function can then be used to examine the state of the
521/// application, typically using [Finder]s such as those in the [find]
522/// namespace, and [Matcher]s such as [findsOneWidget].
523///
524/// ```dart
525/// testWidgets('MyWidget', (WidgetTester tester) async {
526/// await tester.pumpWidget(const MyWidget());
527/// await tester.tap(find.text('Save'));
528/// await tester.pump(); // allow the application to handle
529/// await tester.pump(const Duration(seconds: 1)); // skip past the animation
530/// expect(find.text('Success'), findsOneWidget);
531/// });
532/// ```
533///
534/// For convenience, instances of this class (such as the one provided by
535/// `testWidgets`) can be used as the `vsync` for `AnimationController` objects.
536///
537/// When the binding is [LiveTestWidgetsFlutterBinding], events from
538/// [LiveTestWidgetsFlutterBinding.deviceEventDispatcher] will be handled in
539/// [dispatchEvent]. Thus, using `flutter run` to run a test lets one tap on
540/// the screen to generate [Finder]s relevant to the test.
541class WidgetTester extends WidgetController implements HitTestDispatcher, TickerProvider {
542 WidgetTester._(super.binding) {
543 if (binding is LiveTestWidgetsFlutterBinding) {
544 (binding as LiveTestWidgetsFlutterBinding).deviceEventDispatcher = this;
545 }
546 }
547
548 /// The description string of the test currently being run.
549 String get testDescription => _testDescription;
550 String _testDescription = '';
551
552 /// The binding instance used by the testing framework.
553 @override
554 TestWidgetsFlutterBinding get binding => super.binding as TestWidgetsFlutterBinding;
555
556 /// Renders the UI from the given [widget].
557 ///
558 /// Calls [runApp] with the given widget, then triggers a frame and flushes
559 /// microtasks, by calling [pump] with the same `duration` (if any). The
560 /// supplied [EnginePhase] is the final phase reached during the pump pass; if
561 /// not supplied, the whole pass is executed.
562 ///
563 /// Subsequent calls to this is different from [pump] in that it forces a full
564 /// rebuild of the tree, even if [widget] is the same as the previous call.
565 /// [pump] will only rebuild the widgets that have changed.
566 ///
567 /// This method should not be used as the first parameter to an [expect] or
568 /// [expectLater] call to test that a widget throws an exception. Instead, use
569 /// [TestWidgetsFlutterBinding.takeException].
570 ///
571 /// {@tool snippet}
572 /// ```dart
573 /// testWidgets('MyWidget asserts invalid bounds', (WidgetTester tester) async {
574 /// await tester.pumpWidget(const MyWidget());
575 /// expect(tester.takeException(), isAssertionError); // or isNull, as appropriate.
576 /// });
577 /// ```
578 /// {@end-tool}
579 ///
580 /// By default, the provided `widget` is rendered into [WidgetTester.view],
581 /// whose properties tests can modify to simulate different scenarios (e.g.
582 /// running on a large/small screen). Tests that want to control the
583 /// [FlutterView] into which content is rendered can set `wrapWithView` to
584 /// false and use [View] widgets in the provided `widget` tree to specify the
585 /// desired [FlutterView]s.
586 ///
587 /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
588 /// this method works when the test is run with `flutter run`.
589 Future<void> pumpWidget(
590 Widget widget, {
591 Duration? duration,
592 EnginePhase phase = EnginePhase.sendSemanticsUpdate,
593 bool wrapWithView = true,
594 }) {
595 return TestAsyncUtils.guard<void>(() {
596 binding.attachRootWidget(wrapWithView ? binding.wrapWithDefaultView(widget) : widget);
597 binding.scheduleFrame();
598 return binding.pump(duration, phase);
599 });
600 }
601
602 @override
603 Future<List<Duration>> handlePointerEventRecord(Iterable<PointerEventRecord> records) {
604 assert(records.isNotEmpty);
605 return TestAsyncUtils.guard<List<Duration>>(() async {
606 final List<Duration> handleTimeStampDiff = <Duration>[];
607 DateTime? startTime;
608 for (final PointerEventRecord record in records) {
609 final DateTime now = binding.clock.now();
610 startTime ??= now;
611 // So that the first event is promised to receive a zero timeDiff
612 final Duration timeDiff = record.timeDelay - now.difference(startTime);
613 if (timeDiff.isNegative) {
614 // Flush all past events
615 handleTimeStampDiff.add(-timeDiff);
616 for (final PointerEvent event in record.events) {
617 binding.handlePointerEventForSource(event, source: TestBindingEventSource.test);
618 }
619 } else {
620 await binding.pump();
621 await binding.delayed(timeDiff);
622 handleTimeStampDiff.add(binding.clock.now().difference(startTime) - record.timeDelay);
623 for (final PointerEvent event in record.events) {
624 binding.handlePointerEventForSource(event, source: TestBindingEventSource.test);
625 }
626 }
627 }
628 await binding.pump();
629 // This makes sure that a gesture is completed, with no more pointers
630 // active.
631 return handleTimeStampDiff;
632 });
633 }
634
635 /// Triggers a frame after `duration` amount of time.
636 ///
637 /// This makes the framework act as if the application had janked (missed
638 /// frames) for `duration` amount of time, and then received a "Vsync" signal
639 /// to paint the application.
640 ///
641 /// For a [FakeAsync] environment (typically in `flutter test`), this advances
642 /// time and timeout counting; for a live environment this delays `duration`
643 /// time.
644 ///
645 /// This is a convenience function that just calls
646 /// [TestWidgetsFlutterBinding.pump].
647 ///
648 /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
649 /// this method works when the test is run with `flutter run`.
650 @override
651 Future<void> pump([Duration? duration, EnginePhase phase = EnginePhase.sendSemanticsUpdate]) {
652 return TestAsyncUtils.guard<void>(() => binding.pump(duration, phase));
653 }
654
655 /// Triggers a frame after `duration` amount of time, return as soon as the frame is drawn.
656 ///
657 /// This enables driving an artificially high CPU load by rendering frames in
658 /// a tight loop. It must be used with the frame policy set to
659 /// [LiveTestWidgetsFlutterBindingFramePolicy.benchmark].
660 ///
661 /// Similarly to [pump], this doesn't actually wait for `duration`, just
662 /// advances the clock.
663 Future<void> pumpBenchmark(Duration duration) async {
664 assert(() {
665 final TestWidgetsFlutterBinding widgetsBinding = binding;
666 return widgetsBinding is LiveTestWidgetsFlutterBinding &&
667 widgetsBinding.framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark;
668 }());
669
670 dynamic caughtException;
671 StackTrace? stackTrace;
672 void handleError(dynamic error, StackTrace trace) {
673 caughtException ??= error;
674 stackTrace ??= trace;
675 }
676
677 await Future<void>.microtask(() {
678 binding.handleBeginFrame(duration);
679 }).catchError(handleError);
680 await idle();
681 await Future<void>.microtask(() {
682 binding.handleDrawFrame();
683 }).catchError(handleError);
684 await idle();
685
686 if (caughtException != null) {
687 Error.throwWithStackTrace(caughtException as Object, stackTrace!);
688 }
689 }
690
691 @override
692 Future<int> pumpAndSettle([
693 Duration duration = const Duration(milliseconds: 100),
694 EnginePhase phase = EnginePhase.sendSemanticsUpdate,
695 Duration timeout = const Duration(minutes: 10),
696 ]) {
697 assert(duration > Duration.zero);
698 assert(timeout > Duration.zero);
699 assert(() {
700 final WidgetsBinding binding = this.binding;
701 if (binding is LiveTestWidgetsFlutterBinding &&
702 binding.framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark) {
703 matcher_expect.fail(
704 'When using LiveTestWidgetsFlutterBindingFramePolicy.benchmark, '
705 'hasScheduledFrame is never set to true. This means that pumpAndSettle() '
706 'cannot be used, because it has no way to know if the application has '
707 'stopped registering new frames.',
708 );
709 }
710 return true;
711 }());
712 return TestAsyncUtils.guard<int>(() async {
713 final DateTime endTime = binding.clock.fromNowBy(timeout);
714 int count = 0;
715 do {
716 if (binding.clock.now().isAfter(endTime)) {
717 throw FlutterError('pumpAndSettle timed out');
718 }
719 await binding.pump(duration, phase);
720 count += 1;
721 } while (binding.hasScheduledFrame);
722 return count;
723 });
724 }
725
726 /// Repeatedly pump frames that render the `target` widget with a fixed time
727 /// `interval` as many as `maxDuration` allows.
728 ///
729 /// The `maxDuration` argument is required. The `interval` argument defaults to
730 /// 16.683 milliseconds (59.94 FPS).
731 Future<void> pumpFrames(
732 Widget target,
733 Duration maxDuration, [
734 Duration interval = const Duration(milliseconds: 16, microseconds: 683),
735 ]) {
736 // The interval following the last frame doesn't have to be within the fullDuration.
737 Duration elapsed = Duration.zero;
738 return TestAsyncUtils.guard<void>(() async {
739 binding.attachRootWidget(binding.wrapWithDefaultView(target));
740 binding.scheduleFrame();
741 while (elapsed < maxDuration) {
742 await binding.pump(interval);
743 elapsed += interval;
744 }
745 });
746 }
747
748 /// Simulates restoring the state of the widget tree after the application
749 /// is restarted.
750 ///
751 /// The method grabs the current serialized restoration data from the
752 /// [RestorationManager], takes down the widget tree to destroy all in-memory
753 /// state, and then restores the widget tree from the serialized restoration
754 /// data.
755 Future<void> restartAndRestore() async {
756 assert(
757 binding.restorationManager.debugRootBucketAccessed,
758 'The current widget tree did not inject the root bucket of the RestorationManager and '
759 'therefore no restoration data has been collected to restore from. Did you forget to wrap '
760 'your widget tree in a RootRestorationScope?',
761 );
762 return TestAsyncUtils.guard<void>(() async {
763 final RootWidget widget = binding.rootElement!.widget as RootWidget;
764 final TestRestorationData restorationData = binding.restorationManager.restorationData;
765 runApp(Container(key: UniqueKey()));
766 await pump();
767 binding.restorationManager.restoreFrom(restorationData);
768 binding.attachToBuildOwner(widget);
769 binding.scheduleFrame();
770 return binding.pump();
771 });
772 }
773
774 /// Retrieves the current restoration data from the [RestorationManager].
775 ///
776 /// The returned [TestRestorationData] describes the current state of the
777 /// widget tree under test and can be provided to [restoreFrom] to restore
778 /// the widget tree to the state described by this data.
779 Future<TestRestorationData> getRestorationData() async {
780 assert(
781 binding.restorationManager.debugRootBucketAccessed,
782 'The current widget tree did not inject the root bucket of the RestorationManager and '
783 'therefore no restoration data has been collected. Did you forget to wrap your widget tree '
784 'in a RootRestorationScope?',
785 );
786 return binding.restorationManager.restorationData;
787 }
788
789 /// Restores the widget tree under test to the state described by the
790 /// provided [TestRestorationData].
791 ///
792 /// The data provided to this method is usually obtained from
793 /// [getRestorationData].
794 Future<void> restoreFrom(TestRestorationData data) {
795 binding.restorationManager.restoreFrom(data);
796 return pump();
797 }
798
799 /// Runs a [callback] that performs real asynchronous work.
800 ///
801 /// This is intended for callers who need to call asynchronous methods where
802 /// the methods spawn isolates or OS threads and thus cannot be executed
803 /// synchronously by calling [pump].
804 ///
805 /// If callers were to run these types of asynchronous tasks directly in
806 /// their test methods, they run the possibility of encountering deadlocks.
807 ///
808 /// If [callback] completes successfully, this will return the future
809 /// returned by [callback].
810 ///
811 /// If [callback] completes with an error, the error will be caught by the
812 /// Flutter framework and made available via [takeException], and this method
813 /// will return a future that completes with `null`.
814 ///
815 /// Re-entrant calls to this method are not allowed; callers of this method
816 /// are required to wait for the returned future to complete before calling
817 /// this method again. Attempts to do otherwise will result in a
818 /// [TestFailure] error being thrown.
819 ///
820 /// If your widget test hangs and you are using [runAsync], chances are your
821 /// code depends on the result of a task that did not complete. Fake async
822 /// environment is unable to resolve a future that was created in [runAsync].
823 /// If you observe such behavior or flakiness, you have a number of options:
824 ///
825 /// * Consider restructuring your code so you do not need [runAsync]. This is
826 /// the optimal solution as widget tests are designed to run in fake async
827 /// environment.
828 ///
829 /// * Expose a [Future] in your application code that signals the readiness of
830 /// your widget tree, then await that future inside [callback].
831 Future<T?> runAsync<T>(
832 Future<T> Function() callback, {
833 @Deprecated(
834 'This is no longer supported and has no effect. '
835 'This feature was deprecated after v3.12.0-1.1.pre.',
836 )
837 Duration additionalTime = const Duration(milliseconds: 1000),
838 }) => binding.runAsync<T?>(callback);
839
840 /// Whether there are any transient callbacks scheduled.
841 ///
842 /// This essentially checks whether all animations have completed.
843 ///
844 /// See also:
845 ///
846 /// * [pumpAndSettle], which essentially calls [pump] until there are no
847 /// scheduled frames.
848 /// * [SchedulerBinding.transientCallbackCount], which is the value on which
849 /// this is based.
850 /// * [SchedulerBinding.hasScheduledFrame], which is true whenever a frame is
851 /// pending. [SchedulerBinding.hasScheduledFrame] is made true when a
852 /// widget calls [State.setState], even if there are no transient callbacks
853 /// scheduled. This is what [pumpAndSettle] uses.
854 bool get hasRunningAnimations => binding.transientCallbackCount > 0;
855
856 @override
857 HitTestResult hitTestOnBinding(Offset location, {int? viewId}) {
858 viewId ??= view.viewId;
859 final RenderView renderView = binding.renderViews.firstWhere(
860 (RenderView r) => r.flutterView.viewId == viewId,
861 );
862 location = binding.localToGlobal(location, renderView);
863 return super.hitTestOnBinding(location, viewId: viewId);
864 }
865
866 @override
867 Future<void> sendEventToBinding(PointerEvent event) {
868 return TestAsyncUtils.guard<void>(() async {
869 binding.handlePointerEventForSource(event, source: TestBindingEventSource.test);
870 });
871 }
872
873 /// Handler for device events caught by the binding in live test mode.
874 ///
875 /// [PointerDownEvent]s received here will only print a diagnostic message
876 /// showing possible [Finder]s that can be used to interact with the widget at
877 /// the location of [result].
878 @override
879 void dispatchEvent(PointerEvent event, HitTestResult result) {
880 if (event is PointerDownEvent) {
881 final RenderObject innerTarget = result.path
882 .map((HitTestEntry candidate) => candidate.target)
883 .whereType<RenderObject>()
884 .first;
885 final Element? innerTargetElement = binding.renderViews.contains(innerTarget)
886 ? null
887 : _lastWhereOrNull(
888 collectAllElementsFrom(binding.rootElement!, skipOffstage: true),
889 (Element element) => element.renderObject == innerTarget,
890 );
891 if (innerTargetElement == null) {
892 printToConsole('No widgets found at ${event.position}.');
893 return;
894 }
895 final List<Element> candidates = <Element>[];
896 innerTargetElement.visitAncestorElements((Element element) {
897 candidates.add(element);
898 return true;
899 });
900 assert(candidates.isNotEmpty);
901 String? descendantText;
902 int numberOfWithTexts = 0;
903 int numberOfTypes = 0;
904 int totalNumber = 0;
905 printToConsole('Some possible finders for the widgets at ${event.position}:');
906 for (final Element element in candidates) {
907 if (totalNumber > 13) {
908 break;
909 }
910 totalNumber += 1; // optimistically assume we'll be able to describe it
911
912 final Widget widget = element.widget;
913 if (widget is Tooltip) {
914 final String message = widget.message ?? widget.richMessage!.toPlainText();
915 final Iterable<Element> matches = find.byTooltip(message).evaluate();
916 if (matches.length == 1) {
917 printToConsole(" find.byTooltip('$message')");
918 continue;
919 }
920 }
921
922 if (widget is Text) {
923 assert(descendantText == null);
924 assert(widget.data != null || widget.textSpan != null);
925 final String text = widget.data ?? widget.textSpan!.toPlainText();
926 final Iterable<Element> matches = find.text(text).evaluate();
927 descendantText = widget.data;
928 if (matches.length == 1) {
929 printToConsole(" find.text('$text')");
930 continue;
931 }
932 }
933
934 final Key? key = widget.key;
935 if (key is ValueKey<dynamic>) {
936 final String? keyLabel = switch (key.value) {
937 int() || double() || bool() => 'const ${key.runtimeType}(${key.value})',
938 final String value => "const Key('$value')",
939 _ => null,
940 };
941 if (keyLabel != null) {
942 final Iterable<Element> matches = find.byKey(key).evaluate();
943 if (matches.length == 1) {
944 printToConsole(' find.byKey($keyLabel)');
945 continue;
946 }
947 }
948 }
949
950 if (!_isPrivate(widget.runtimeType)) {
951 if (numberOfTypes < 5) {
952 final Iterable<Element> matches = find.byType(widget.runtimeType).evaluate();
953 if (matches.length == 1) {
954 printToConsole(' find.byType(${widget.runtimeType})');
955 numberOfTypes += 1;
956 continue;
957 }
958 }
959
960 if (descendantText != null && numberOfWithTexts < 5) {
961 final Iterable<Element> matches = find
962 .widgetWithText(widget.runtimeType, descendantText)
963 .evaluate();
964 if (matches.length == 1) {
965 printToConsole(" find.widgetWithText(${widget.runtimeType}, '$descendantText')");
966 numberOfWithTexts += 1;
967 continue;
968 }
969 }
970 }
971
972 if (!_isPrivate(element.runtimeType)) {
973 final Iterable<Element> matches = find.byElementType(element.runtimeType).evaluate();
974 if (matches.length == 1) {
975 printToConsole(' find.byElementType(${element.runtimeType})');
976 continue;
977 }
978 }
979
980 totalNumber -= 1; // if we got here, we didn't actually find something to say about it
981 }
982 if (totalNumber == 0) {
983 printToConsole(' <could not come up with any unique finders>');
984 }
985 }
986 }
987
988 bool _isPrivate(Type type) {
989 // used above so that we don't suggest matchers for private types
990 return '_'.matchAsPrefix(type.toString()) != null;
991 }
992
993 /// Returns the exception most recently caught by the Flutter framework.
994 ///
995 /// See [TestWidgetsFlutterBinding.takeException] for details.
996 dynamic takeException() {
997 return binding.takeException();
998 }
999
1000 /// {@macro flutter.flutter_test.TakeAccessibilityAnnouncements}
1001 ///
1002 /// See [TestWidgetsFlutterBinding.takeAnnouncements] for details.
1003 List<CapturedAccessibilityAnnouncement> takeAnnouncements() {
1004 return binding.takeAnnouncements();
1005 }
1006
1007 /// Acts as if the application went idle.
1008 ///
1009 /// Runs all remaining microtasks, including those scheduled as a result of
1010 /// running them, until there are no more microtasks scheduled. Then, runs any
1011 /// previously scheduled timers with zero time, and completes the returned future.
1012 ///
1013 /// May result in an infinite loop or run out of memory if microtasks continue
1014 /// to recursively schedule new microtasks. Will not run any timers scheduled
1015 /// after this method was invoked, even if they are zero-time timers.
1016 Future<void> idle() {
1017 return TestAsyncUtils.guard<void>(() => binding.idle());
1018 }
1019
1020 Set<Ticker>? _tickers;
1021
1022 @override
1023 Ticker createTicker(TickerCallback onTick) {
1024 _tickers ??= <_TestTicker>{};
1025 final _TestTicker result = _TestTicker(onTick, _removeTicker);
1026 _tickers!.add(result);
1027 return result;
1028 }
1029
1030 void _removeTicker(_TestTicker ticker) {
1031 assert(_tickers != null);
1032 assert(_tickers!.contains(ticker));
1033 _tickers!.remove(ticker);
1034 }
1035
1036 /// Throws an exception if any tickers created by the [WidgetTester] are still
1037 /// active when the method is called.
1038 ///
1039 /// An argument can be specified to provide a string that will be used in the
1040 /// error message. It should be an adverbial phrase describing the current
1041 /// situation, such as "at the end of the test".
1042 void verifyTickersWereDisposed([String when = 'when none should have been']) {
1043 if (_tickers != null) {
1044 for (final Ticker ticker in _tickers!) {
1045 if (ticker.isActive) {
1046 throw FlutterError.fromParts(<DiagnosticsNode>[
1047 ErrorSummary('A Ticker was active $when.'),
1048 ErrorDescription('All Tickers must be disposed.'),
1049 ErrorHint(
1050 'Tickers used by AnimationControllers '
1051 'should be disposed by calling dispose() on the AnimationController itself. '
1052 'Otherwise, the ticker will leak.',
1053 ),
1054 ticker.describeForError('The offending ticker was'),
1055 ]);
1056 }
1057 }
1058 }
1059 }
1060
1061 void _endOfTestVerifications() {
1062 verifyTickersWereDisposed('at the end of the test');
1063 _verifySemanticsHandlesWereDisposed();
1064 }
1065
1066 void _verifySemanticsHandlesWereDisposed() {
1067 assert(_lastRecordedSemanticsHandles != null);
1068 // TODO(goderbauer): Fix known leak in web engine when running integration tests and remove this "correction", https://github.com/flutter/flutter/issues/121640.
1069 final int knownWebEngineLeakForLiveTestsCorrection =
1070 kIsWeb && binding is LiveTestWidgetsFlutterBinding ? 1 : 0;
1071
1072 if (_currentSemanticsHandles - knownWebEngineLeakForLiveTestsCorrection >
1073 _lastRecordedSemanticsHandles!) {
1074 throw FlutterError.fromParts(<DiagnosticsNode>[
1075 ErrorSummary('A SemanticsHandle was active at the end of the test.'),
1076 ErrorDescription(
1077 'All SemanticsHandle instances must be disposed by calling dispose() on '
1078 'the SemanticsHandle.',
1079 ),
1080 ]);
1081 }
1082 _lastRecordedSemanticsHandles = null;
1083 }
1084
1085 int? _lastRecordedSemanticsHandles;
1086
1087 // TODO(goderbauer): Only use binding.debugOutstandingSemanticsHandles when deprecated binding.pipelineOwner is removed.
1088 int get _currentSemanticsHandles =>
1089 binding.debugOutstandingSemanticsHandles +
1090 binding.pipelineOwner.debugOutstandingSemanticsHandles;
1091
1092 void _recordNumberOfSemanticsHandles() {
1093 _lastRecordedSemanticsHandles = _currentSemanticsHandles;
1094 }
1095
1096 /// Returns the TestTextInput singleton.
1097 ///
1098 /// Typical app tests will not need to use this value. To add text to widgets
1099 /// like [TextField] or [TextFormField], call [enterText].
1100 ///
1101 /// Some of the properties and methods on this value are only valid if the
1102 /// binding's [TestWidgetsFlutterBinding.registerTestTextInput] flag is set to
1103 /// true as a test is starting (meaning that the keyboard is to be simulated
1104 /// by the test framework). If those members are accessed when using a binding
1105 /// that sets this flag to false, they will throw.
1106 TestTextInput get testTextInput => binding.testTextInput;
1107
1108 /// Give the text input widget specified by [finder] the focus, as if the
1109 /// onscreen keyboard had appeared.
1110 ///
1111 /// Implies a call to [pump].
1112 ///
1113 /// The widget specified by [finder] must be an [EditableText] or have
1114 /// an [EditableText] descendant. For example `find.byType(TextField)`
1115 /// or `find.byType(TextFormField)`, or `find.byType(EditableText)`.
1116 ///
1117 /// Tests that just need to add text to widgets like [TextField]
1118 /// or [TextFormField] only need to call [enterText].
1119 Future<void> showKeyboard(FinderBase<Element> finder) async {
1120 bool skipOffstage = true;
1121 if (finder is Finder) {
1122 skipOffstage = finder.skipOffstage;
1123 }
1124 return TestAsyncUtils.guard<void>(() async {
1125 final EditableTextState editable = state<EditableTextState>(
1126 find.descendant(
1127 of: finder,
1128 matching: find.byType(EditableText, skipOffstage: skipOffstage),
1129 matchRoot: true,
1130 ),
1131 );
1132 // Setting focusedEditable causes the binding to call requestKeyboard()
1133 // on the EditableTextState, which itself eventually calls TextInput.attach
1134 // to establish the connection.
1135 binding.focusedEditable = editable;
1136 await pump();
1137 });
1138 }
1139
1140 /// Give the text input widget specified by [finder] the focus and replace its
1141 /// content with [text], as if it had been provided by the onscreen keyboard.
1142 ///
1143 /// The widget specified by [finder] must be an [EditableText] or have
1144 /// an [EditableText] descendant. For example `find.byType(TextField)`
1145 /// or `find.byType(TextFormField)`, or `find.byType(EditableText)`.
1146 ///
1147 /// When the returned future completes, the text input widget's text will be
1148 /// exactly `text`, and the caret will be placed at the end of `text`.
1149 ///
1150 /// To just give [finder] the focus without entering any text,
1151 /// see [showKeyboard].
1152 ///
1153 /// To enter text into other widgets (e.g. a custom widget that maintains a
1154 /// TextInputConnection the way that a [EditableText] does), first ensure that
1155 /// that widget has an open connection (e.g. by using [tap] to focus it),
1156 /// then call `testTextInput.enterText` directly (see
1157 /// [TestTextInput.enterText]).
1158 Future<void> enterText(FinderBase<Element> finder, String text) async {
1159 return TestAsyncUtils.guard<void>(() async {
1160 await showKeyboard(finder);
1161 testTextInput.enterText(text);
1162 await idle();
1163 });
1164 }
1165
1166 /// Makes an effort to dismiss the current page with a Material [Scaffold] or
1167 /// a [CupertinoPageScaffold].
1168 ///
1169 /// Will throw an error if there is no back button in the page.
1170 Future<void> pageBack() async {
1171 return TestAsyncUtils.guard<void>(() async {
1172 Finder backButton = find.byTooltip('Back');
1173 if (backButton.evaluate().isEmpty) {
1174 backButton = find.byType(CupertinoNavigationBarBackButton);
1175 }
1176
1177 expectSync(backButton, findsOneWidget, reason: 'One back button expected on screen');
1178
1179 await tap(backButton);
1180 });
1181 }
1182
1183 @override
1184 void printToConsole(String message) {
1185 binding.debugPrintOverride(message);
1186 }
1187}
1188
1189typedef _TickerDisposeCallback = void Function(_TestTicker ticker);
1190
1191class _TestTicker extends Ticker {
1192 _TestTicker(super.onTick, this._onDispose);
1193
1194 final _TickerDisposeCallback _onDispose;
1195
1196 @override
1197 void dispose() {
1198 _onDispose(this);
1199 super.dispose();
1200 }
1201}
1202