blob: 4b67f77356615176f0f86673cf811f764317c81d [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_gallery/gallery/demos.dart';
import 'package:flutter_gallery/gallery/app.dart' show GalleryApp;
// Reports success or failure to the native code.
const MethodChannel _kTestChannel = const MethodChannel('io.flutter.demo.gallery/TestLifecycleListener');
// We don't want to wait for animations to complete before tapping the
// back button in the demos with these titles.
const List<String> _kUnsynchronizedDemoTitles = const <String>[
'Progress indicators',
'Activity Indicator',
'Video',
];
// These demos can't be backed out of by tapping a button whose
// tooltip is 'Back'.
const List<String> _kSkippedDemoTitles = const <String>[
'Pull to refresh',
'Progress indicators',
'Activity Indicator',
'Video',
];
Future<Null> main() async {
try {
// Verify that _kUnsynchronizedDemos and _kSkippedDemos identify
// demos that actually exist.
final List<String> allDemoTitles = kAllGalleryDemos.map((GalleryDemo demo) => demo.title).toList();
if (!new Set<String>.from(allDemoTitles).containsAll(_kUnsynchronizedDemoTitles))
fail('Unrecognized demo titles in _kUnsynchronizedDemosTitles: $_kUnsynchronizedDemoTitles');
if (!new Set<String>.from(allDemoTitles).containsAll(_kSkippedDemoTitles))
fail('Unrecognized demo names in _kSkippedDemoTitles: $_kSkippedDemoTitles');
runApp(const GalleryApp(testMode: true));
final _LiveWidgetController controller = new _LiveWidgetController();
for (GalleryDemoCategory category in kAllGalleryDemoCategories) {
await controller.tap(find.text(category.name));
for (GalleryDemo demo in kGalleryCategoryToDemos[category]) {
final Finder demoItem = find.text(demo.title);
await controller.scrollIntoView(demoItem, alignment: 0.5);
if (_kSkippedDemoTitles.contains(demo.title)) {
print('> skipped $demo');
continue;
}
for (int i = 0; i < 2; i += 1) {
await controller.tap(demoItem); // Launch the demo
controller.frameSync = !_kUnsynchronizedDemoTitles.contains(demo.title);
await controller.tap(find.byTooltip('Back'));
controller.frameSync = true;
}
print('Success');
}
await controller.tap(find.byTooltip('Back'));
}
_kTestChannel.invokeMethod('success');
} catch (error) {
_kTestChannel.invokeMethod('failure');
}
}
class _LiveWidgetController {
final WidgetController _controller = new WidgetController(WidgetsBinding.instance);
/// 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].
Future<Null> _waitUntilFrame(bool condition(), [Completer<Null> completer]) {
completer ??= new Completer<Null>();
if (!condition()) {
SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) {
_waitUntilFrame(condition, completer);
});
} else {
completer.complete();
}
return completer.future;
}
/// Runs `finder` repeatedly until it finds one or more [Element]s.
Future<Finder> _waitForElement(Finder finder) async {
if (frameSync)
await _waitUntilFrame(() => SchedulerBinding.instance.transientCallbackCount == 0);
await _waitUntilFrame(() => finder.precache());
if (frameSync)
await _waitUntilFrame(() => SchedulerBinding.instance.transientCallbackCount == 0);
return finder;
}
Future<Null> tap(Finder finder) async {
await _controller.tap(await _waitForElement(finder));
}
Future<Null> scrollIntoView(Finder finder, {double alignment}) async {
final Finder target = await _waitForElement(finder);
await Scrollable.ensureVisible(target.evaluate().single, duration: const Duration(milliseconds: 100), alignment: alignment);
}
}