live_smoketest.dart 5.76 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6 7 8 9 10
// ATTENTION!
//
// This file is not named "*_test.dart", and as such will not run when you run
// "flutter test". It is only intended to be run as part of the
// flutter_gallery_instrumentation_test devicelab test.

11 12
import 'dart:async';

13
import 'package:flutter/cupertino.dart';
14
import 'package:flutter/gestures.dart' show kPrimaryButton;
15 16
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
17
import 'package:flutter/services.dart';
18
import 'package:flutter_gallery/gallery/app.dart' show GalleryApp;
19 20
import 'package:flutter_gallery/gallery/demos.dart';
import 'package:flutter_test/flutter_test.dart';
21

22
// Reports success or failure to the native code.
23
const MethodChannel _kTestChannel = MethodChannel('io.flutter.demo.gallery/TestLifecycleListener');
24

25 26
// We don't want to wait for animations to complete before tapping the
// back button in the demos with these titles.
27
const List<String> _kUnsynchronizedDemoTitles = <String>[
28 29 30 31 32 33 34
  'Progress indicators',
  'Activity Indicator',
  'Video',
];

// These demos can't be backed out of by tapping a button whose
// tooltip is 'Back'.
35
const List<String> _kSkippedDemoTitles = <String>[
36 37 38
  'Progress indicators',
  'Activity Indicator',
  'Video',
39 40
];

41
// There are 3 places where the Gallery demos are traversed.
42 43 44
// 1- In widget tests such as dev/integration_tests/flutter_gallery/test/smoke_test.dart
// 2- In driver tests such as dev/integration_tests/flutter_gallery/test_driver/transitions_perf_test.dart
// 3- In on-device instrumentation tests such as dev/integration_tests/flutter_gallery/test/live_smoketest.dart
45 46 47 48
//
// If you change navigation behavior in the Gallery or in the framework, make
// sure all 3 are covered.

49
Future<void> main() async {
50
  try {
51 52
    // Verify that _kUnsynchronizedDemos and _kSkippedDemos identify
    // demos that actually exist.
53
    final List<String> allDemoTitles = kAllGalleryDemos.map((GalleryDemo demo) => demo.title).toList();
54
    if (!Set<String>.from(allDemoTitles).containsAll(_kUnsynchronizedDemoTitles)) {
55
      fail('Unrecognized demo titles in _kUnsynchronizedDemosTitles: $_kUnsynchronizedDemoTitles');
56 57
    }
    if (!Set<String>.from(allDemoTitles).containsAll(_kSkippedDemoTitles)) {
58
      fail('Unrecognized demo names in _kSkippedDemoTitles: $_kSkippedDemoTitles');
59
    }
60

61
    print('Starting app...');
62
    runApp(const GalleryApp(testMode: true));
63
    final _LiveWidgetController controller = _LiveWidgetController(WidgetsBinding.instance);
64
    for (final GalleryDemoCategory category in kAllGalleryDemoCategories) {
65
      print('Tapping "${category.name}" section...');
66
      await controller.tap(find.text(category.name));
67
      for (final 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
        }
74
        for (int i = 0; i < 2; i += 1) {
75
          print('Tapping "${demo.title}"...');
76 77
          await controller.tap(demoItem); // Launch the demo
          controller.frameSync = !_kUnsynchronizedDemoTitles.contains(demo.title);
78
          print('Going back to demo list...');
79
          await controller.tap(backFinder);
80 81
          controller.frameSync = true;
        }
82
      }
83
      print('Going back to home screen...');
84
      await controller.tap(find.byTooltip('Back'));
85
    }
86
    print('Finished successfully!');
87
    _kTestChannel.invokeMethod<void>('success');
88 89
  } catch (error, stack) {
    print('Caught error: $error\n$stack');
90
    _kTestChannel.invokeMethod<void>('failure');
91 92 93
  }
}

94 95 96
final Finder backFinder = find.byElementPredicate(
  (Element element) {
    final Widget widget = element.widget;
97
    if (widget is Tooltip) {
98
      return widget.message == 'Back';
99 100
    }
    if (widget is CupertinoNavigationBarBackButton) {
101
      return true;
102
    }
103 104 105 106 107
    return false;
  },
  description: 'Material or Cupertino back button',
);

108
class _LiveWidgetController extends LiveWidgetController {
109
  _LiveWidgetController(super.binding);
110 111 112 113 114 115

  /// With [frameSync] enabled, Flutter Driver will wait to perform an action
  /// until there are no pending frames in the app under test.
  bool frameSync = true;

  /// Waits until at the end of a frame the provided [condition] is [true].
116
  Future<void> _waitUntilFrame(bool Function() condition, [Completer<void>? completer]) {
117
    completer ??= Completer<void>();
118
    if (!condition()) {
119
      SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) {
120 121 122 123 124 125 126 127 128
        _waitUntilFrame(condition, completer);
      });
    } else {
      completer.complete();
    }
    return completer.future;
  }

  /// Runs `finder` repeatedly until it finds one or more [Element]s.
129
  Future<FinderBase<Element>> _waitForElement(FinderBase<Element> finder) async {
130
    if (frameSync) {
131
      await _waitUntilFrame(() => binding.transientCallbackCount == 0);
132
    }
133
    await _waitUntilFrame(() => finder.tryEvaluate());
134
    if (frameSync) {
135
      await _waitUntilFrame(() => binding.transientCallbackCount == 0);
136
    }
137 138 139
    return finder;
  }

140
  @override
141
  Future<void> tap(FinderBase<Element> finder, { int? pointer, int buttons = kPrimaryButton, bool warnIfMissed = true }) async {
142
    await super.tap(await _waitForElement(finder), pointer: pointer, buttons: buttons, warnIfMissed: warnIfMissed);
143 144
  }

145 146
  Future<void> scrollIntoView(FinderBase<Element> finder, {required double alignment}) async {
    final FinderBase<Element> target = await _waitForElement(finder);
147 148 149
    await Scrollable.ensureVisible(target.evaluate().single, duration: const Duration(milliseconds: 100), alignment: alignment);
  }
}