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

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com