• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 The Chromium 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// ATTENTION!
6//
7// This file is not named "*_test.dart", and as such will not run when you run
8// "flutter test". It is only intended to be run as part of the
9// flutter_gallery_instrumentation_test devicelab test.
10
11import 'dart:async';
12
13import 'package:flutter/cupertino.dart';
14import 'package:flutter/material.dart';
15import 'package:flutter/scheduler.dart';
16import 'package:flutter/services.dart';
17import 'package:flutter/widgets.dart';
18import 'package:flutter/gestures.dart' show kPrimaryButton;
19import 'package:flutter_test/flutter_test.dart';
20
21import 'package:flutter_gallery/gallery/demos.dart';
22import 'package:flutter_gallery/gallery/app.dart' show GalleryApp;
23
24// Reports success or failure to the native code.
25const MethodChannel _kTestChannel = MethodChannel('io.flutter.demo.gallery/TestLifecycleListener');
26
27// We don't want to wait for animations to complete before tapping the
28// back button in the demos with these titles.
29const List<String> _kUnsynchronizedDemoTitles = <String>[
30  'Progress indicators',
31  'Activity Indicator',
32  'Video',
33];
34
35// These demos can't be backed out of by tapping a button whose
36// tooltip is 'Back'.
37const List<String> _kSkippedDemoTitles = <String>[
38  'Progress indicators',
39  'Activity Indicator',
40  'Video',
41];
42
43// There are 3 places where the Gallery demos are traversed.
44// 1- In widget tests such as examples/flutter_gallery/test/smoke_test.dart
45// 2- In driver tests such as examples/flutter_gallery/test_driver/transitions_perf_test.dart
46// 3- In on-device instrumentation tests such as examples/flutter_gallery/test/live_smoketest.dart
47//
48// If you change navigation behavior in the Gallery or in the framework, make
49// sure all 3 are covered.
50
51Future<void> main() async {
52  try {
53    // Verify that _kUnsynchronizedDemos and _kSkippedDemos identify
54    // demos that actually exist.
55    final List<String> allDemoTitles = kAllGalleryDemos.map((GalleryDemo demo) => demo.title).toList();
56    if (!Set<String>.from(allDemoTitles).containsAll(_kUnsynchronizedDemoTitles))
57      fail('Unrecognized demo titles in _kUnsynchronizedDemosTitles: $_kUnsynchronizedDemoTitles');
58    if (!Set<String>.from(allDemoTitles).containsAll(_kSkippedDemoTitles))
59      fail('Unrecognized demo names in _kSkippedDemoTitles: $_kSkippedDemoTitles');
60
61    print('Starting app...');
62    runApp(const GalleryApp(testMode: true));
63    final _LiveWidgetController controller = _LiveWidgetController(WidgetsBinding.instance);
64    for (GalleryDemoCategory category in kAllGalleryDemoCategories) {
65      print('Tapping "${category.name}" section...');
66      await controller.tap(find.text(category.name));
67      for (GalleryDemo demo in kGalleryCategoryToDemos[category]) {
68        final Finder demoItem = find.text(demo.title);
69        print('Scrolling to "${demo.title}"...');
70        await controller.scrollIntoView(demoItem, alignment: 0.5);
71        if (_kSkippedDemoTitles.contains(demo.title))
72          continue;
73        for (int i = 0; i < 2; i += 1) {
74          print('Tapping "${demo.title}"...');
75          await controller.tap(demoItem); // Launch the demo
76          controller.frameSync = !_kUnsynchronizedDemoTitles.contains(demo.title);
77          print('Going back to demo list...');
78          await controller.tap(backFinder);
79          controller.frameSync = true;
80        }
81      }
82      print('Going back to home screen...');
83      await controller.tap(find.byTooltip('Back'));
84    }
85    print('Finished successfully!');
86    _kTestChannel.invokeMethod<void>('success');
87  } catch (error, stack) {
88    print('Caught error: $error\n$stack');
89    _kTestChannel.invokeMethod<void>('failure');
90  }
91}
92
93final Finder backFinder = find.byElementPredicate(
94  (Element element) {
95    final Widget widget = element.widget;
96    if (widget is Tooltip)
97      return widget.message == 'Back';
98    if (widget is CupertinoNavigationBarBackButton)
99      return true;
100    return false;
101  },
102  description: 'Material or Cupertino back button',
103);
104
105class _LiveWidgetController extends LiveWidgetController {
106  _LiveWidgetController(WidgetsBinding binding) : super(binding);
107
108  /// With [frameSync] enabled, Flutter Driver will wait to perform an action
109  /// until there are no pending frames in the app under test.
110  bool frameSync = true;
111
112  /// Waits until at the end of a frame the provided [condition] is [true].
113  Future<void> _waitUntilFrame(bool condition(), [Completer<void> completer]) {
114    completer ??= Completer<void>();
115    if (!condition()) {
116      SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) {
117        _waitUntilFrame(condition, completer);
118      });
119    } else {
120      completer.complete();
121    }
122    return completer.future;
123  }
124
125  /// Runs `finder` repeatedly until it finds one or more [Element]s.
126  Future<Finder> _waitForElement(Finder finder) async {
127    if (frameSync)
128      await _waitUntilFrame(() => binding.transientCallbackCount == 0);
129    await _waitUntilFrame(() => finder.precache());
130    if (frameSync)
131      await _waitUntilFrame(() => binding.transientCallbackCount == 0);
132    return finder;
133  }
134
135  @override
136  Future<void> tap(Finder finder, { int pointer, int buttons = kPrimaryButton }) async {
137    await super.tap(await _waitForElement(finder), pointer: pointer, buttons: buttons);
138  }
139
140  Future<void> scrollIntoView(Finder finder, {double alignment}) async {
141    final Finder target = await _waitForElement(finder);
142    await Scrollable.ensureVisible(target.evaluate().single, duration: const Duration(milliseconds: 100), alignment: alignment);
143  }
144}
145