diff --git a/dev/bots/test.dart b/dev/bots/test.dart index 99a68db1de2..737c54aa8f0 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -75,7 +75,7 @@ Future main() async { await _runFlutterTest(p.join(flutterRoot, 'packages', 'flutter'), options: coverageFlags, ); - await _runAllDartTests(p.join(flutterRoot, 'packages', 'flutter_driver')); + await _runFlutterTest(p.join(flutterRoot, 'packages', 'flutter_driver')); await _runFlutterTest(p.join(flutterRoot, 'packages', 'flutter_test')); await _runFlutterTest(p.join(flutterRoot, 'packages', 'flutter_markdown')); await _runAllDartTests(p.join(flutterRoot, 'packages', 'flutter_tools'), diff --git a/packages/flutter_driver/lib/src/driver.dart b/packages/flutter_driver/lib/src/driver.dart index fd2b6bf1140..8540bdc551c 100644 --- a/packages/flutter_driver/lib/src/driver.dart +++ b/packages/flutter_driver/lib/src/driver.dart @@ -339,6 +339,15 @@ class FlutterDriver { return null; } + /// Waits until there are no more transient callbacks in the queue. + /// + /// Use this method when you need to wait for the moment when the application + /// becomes "stable", for example, prior to taking a [screenshot]. + Future waitUntilNoTransientCallbacks({Duration timeout}) async { + await _sendCommand(new WaitUntilNoTransientCallbacks(timeout: timeout)); + return null; + } + /// Tell the driver to perform a scrolling action. /// /// A scrolling action begins with a "pointer down" event, which commonly maps diff --git a/packages/flutter_driver/lib/src/extension.dart b/packages/flutter_driver/lib/src/extension.dart index cda9841d8a1..2331ee7be2d 100644 --- a/packages/flutter_driver/lib/src/extension.dart +++ b/packages/flutter_driver/lib/src/extension.dart @@ -4,6 +4,8 @@ import 'dart:async'; +import 'package:meta/meta.dart'; + import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart' show RendererBinding; @@ -27,7 +29,7 @@ class _DriverBinding extends WidgetsFlutterBinding { // TODO(ianh): refactor so @override void initServiceExtensions() { super.initServiceExtensions(); - _FlutterDriverExtension extension = new _FlutterDriverExtension._(); + FlutterDriverExtension extension = new FlutterDriverExtension(); registerServiceExtension( name: _extensionMethodName, callback: extension.call @@ -57,10 +59,11 @@ typedef Command CommandDeserializerCallback(Map params); /// Runs the finder and returns the [Element] found, or `null`. typedef Finder FinderConstructor(SerializableFinder finder); -class _FlutterDriverExtension { +@visibleForTesting +class FlutterDriverExtension { static final Logger _log = new Logger('FlutterDriverExtension'); - _FlutterDriverExtension._() { + FlutterDriverExtension() { _commandHandlers.addAll({ 'get_health': _getHealth, 'get_render_tree': _getRenderTree, @@ -72,6 +75,7 @@ class _FlutterDriverExtension { 'setInputText': _setInputText, 'submitInputText': _submitInputText, 'waitFor': _waitFor, + 'waitUntilNoTransientCallbacks': _waitUntilNoTransientCallbacks, }); _commandDeserializers.addAll({ @@ -85,6 +89,7 @@ class _FlutterDriverExtension { 'setInputText': (Map params) => new SetInputText.deserialize(params), 'submitInputText': (Map params) => new SubmitInputText.deserialize(params), 'waitFor': (Map params) => new WaitFor.deserialize(params), + 'waitUntilNoTransientCallbacks': (Map params) => new WaitUntilNoTransientCallbacks.deserialize(params), }); _finders.addAll({ @@ -123,7 +128,7 @@ class _FlutterDriverExtension { throw 'Extension $_extensionMethod does not support command $commandKind'; Command command = commandDeserializer(params); Result response = await commandHandler(command).timeout(command.timeout); - return _makeResponse(response.toJson()); + return _makeResponse(response?.toJson()); } on TimeoutException catch (error, stackTrace) { String msg = 'Timeout while executing $commandKind: $error\n$stackTrace'; _log.error(msg); @@ -221,6 +226,11 @@ class _FlutterDriverExtension { return null; } + Future _waitUntilNoTransientCallbacks(Command command) async { + if (SchedulerBinding.instance.transientCallbackCount != 0) + await _waitUntilFrame(() => SchedulerBinding.instance.transientCallbackCount == 0); + } + Future _scroll(Command command) async { Scroll scrollCommand = command; Finder target = await _waitForElement(_createFinder(scrollCommand.finder)); diff --git a/packages/flutter_driver/lib/src/find.dart b/packages/flutter_driver/lib/src/find.dart index 82a992f4e34..1cf502740b1 100644 --- a/packages/flutter_driver/lib/src/find.dart +++ b/packages/flutter_driver/lib/src/find.dart @@ -62,6 +62,18 @@ class WaitFor extends CommandWithTarget { WaitFor.deserialize(Map json) : super.deserialize(json); } +/// Waits until there are no more transient callbacks in the queue. +class WaitUntilNoTransientCallbacks extends Command { + @override + final String kind = 'waitUntilNoTransientCallbacks'; + + WaitUntilNoTransientCallbacks({Duration timeout}) : super(timeout: timeout); + + /// Deserializes the command from JSON generated by [serialize]. + WaitUntilNoTransientCallbacks.deserialize(Map json) + : super.deserialize(json); +} + /// The result of a [WaitFor] command. class WaitForResult extends Result { /// Deserializes the result from JSON. diff --git a/packages/flutter_driver/pubspec.yaml b/packages/flutter_driver/pubspec.yaml index 8231212b1c3..6de24f5d629 100644 --- a/packages/flutter_driver/pubspec.yaml +++ b/packages/flutter_driver/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: file: 2.3.0 json_rpc_2: '^2.0.0' matcher: '>=0.12.0 <1.0.0' + meta: ^1.0.4 path: '^1.4.0' web_socket_channel: '^1.0.0' vm_service_client: '0.2.2+4' diff --git a/packages/flutter_driver/test/all.dart b/packages/flutter_driver/test/all.dart deleted file mode 100644 index 1639ae4f451..00000000000 --- a/packages/flutter_driver/test/all.dart +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2016 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 'flutter_driver_test.dart' as flutter_driver_test; -import 'src/retry_test.dart' as retry_test; -import 'src/timeline_summary_test.dart' as timeline_summary_test; -import 'src/timeline_test.dart' as timeline_test; - -void main() { - flutter_driver_test.main(); - retry_test.main(); - timeline_summary_test.main(); - timeline_test.main(); -} diff --git a/packages/flutter_driver/test/flutter_driver_test.dart b/packages/flutter_driver/test/flutter_driver_test.dart index 0e3bdf9e48d..f4470949710 100644 --- a/packages/flutter_driver/test/flutter_driver_test.dart +++ b/packages/flutter_driver/test/flutter_driver_test.dart @@ -9,7 +9,7 @@ import 'package:flutter_driver/src/error.dart'; import 'package:flutter_driver/src/health.dart'; import 'package:flutter_driver/src/timeline.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as rpc; -import 'package:mockito/mockito.dart'; +import 'package:mockito/mockito_no_mirrors.dart'; import 'package:test/test.dart'; import 'package:vm_service_client/vm_service_client.dart'; @@ -199,6 +199,19 @@ void main() { }); }); + group('waitUntilNoTransientCallbacks', () { + test('sends the waitUntilNoTransientCallbacks command', () async { + when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) { + expect(i.positionalArguments[1], { + 'command': 'waitUntilNoTransientCallbacks', + 'timeout': '1000', + }); + return makeMockResponse({}); + }); + await driver.waitUntilNoTransientCallbacks(timeout: const Duration(seconds: 1)); + }); + }); + group('traceAction', () { test('traces action', () async { bool actionCalled = false; diff --git a/packages/flutter_driver/test/src/extension_test.dart b/packages/flutter_driver/test/src/extension_test.dart new file mode 100644 index 00000000000..f3410336770 --- /dev/null +++ b/packages/flutter_driver/test/src/extension_test.dart @@ -0,0 +1,61 @@ +// Copyright 2016 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 'package:flutter/scheduler.dart'; +import 'package:flutter_driver/src/extension.dart'; +import 'package:flutter_driver/src/find.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('waitUntilNoTransientCallbacks', () { + FlutterDriverExtension extension; + Map result; + + setUp(() { + result = null; + extension = new FlutterDriverExtension(); + }); + + testWidgets('returns immediately when transient callback queue is empty', (WidgetTester tester) async { + extension.call(new WaitUntilNoTransientCallbacks().serialize()) + .then(expectAsync1((Map r) { + result = r; + })); + + await tester.idle(); + expect( + result, + { + 'isError': false, + 'response': null, + }, + ); + }); + + testWidgets('waits until no transient callbacks', (WidgetTester tester) async { + SchedulerBinding.instance.scheduleFrameCallback((_) { + // Intentionally blank. We only care about existence of a callback. + }); + + extension.call(new WaitUntilNoTransientCallbacks().serialize()) + .then(expectAsync1((Map r) { + result = r; + })); + + // Nothing should happen until the next frame. + await tester.idle(); + expect(result, isNull); + + // NOW we should receive the result. + await tester.pump(); + expect( + result, + { + 'isError': false, + 'response': null, + }, + ); + }); + }); +} diff --git a/packages/flutter_tools/test/drive_test.dart b/packages/flutter_tools/test/drive_test.dart index baa8fc95f77..7c7bad52643 100644 --- a/packages/flutter_tools/test/drive_test.dart +++ b/packages/flutter_tools/test/drive_test.dart @@ -11,7 +11,7 @@ import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/commands/drive.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/ios/simulators.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mockito/mockito_no_mirrors.dart'; import 'package:test/test.dart'; import 'src/common.dart'; diff --git a/packages/flutter_tools/test/install_test.dart b/packages/flutter_tools/test/install_test.dart index d4f66be921d..9596c4c60c9 100644 --- a/packages/flutter_tools/test/install_test.dart +++ b/packages/flutter_tools/test/install_test.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. import 'package:flutter_tools/src/commands/install.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mockito/mockito_no_mirrors.dart'; import 'package:test/test.dart'; import 'src/common.dart'; diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart index ff8961c665a..964253aabd9 100644 --- a/packages/flutter_tools/test/src/context.dart +++ b/packages/flutter_tools/test/src/context.dart @@ -20,7 +20,7 @@ import 'package:flutter_tools/src/ios/simulators.dart'; import 'package:flutter_tools/src/run_hot.dart'; import 'package:flutter_tools/src/usage.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mockito/mockito_no_mirrors.dart'; import 'package:process/process.dart'; import 'package:test/test.dart'; diff --git a/packages/flutter_tools/test/src/ios/devices_test.dart b/packages/flutter_tools/test/src/ios/devices_test.dart index f92e9f98878..ede769cbbe1 100644 --- a/packages/flutter_tools/test/src/ios/devices_test.dart +++ b/packages/flutter_tools/test/src/ios/devices_test.dart @@ -7,7 +7,7 @@ import 'package:file/file.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/ios/devices.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mockito/mockito_no_mirrors.dart'; import 'package:process/process.dart'; import 'package:test/test.dart'; diff --git a/packages/flutter_tools/test/src/ios/ios_workflow_test.dart b/packages/flutter_tools/test/src/ios/ios_workflow_test.dart index 404472d2955..5967afbf1f0 100644 --- a/packages/flutter_tools/test/src/ios/ios_workflow_test.dart +++ b/packages/flutter_tools/test/src/ios/ios_workflow_test.dart @@ -6,7 +6,7 @@ import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/ios/ios_workflow.dart'; import 'package:flutter_tools/src/ios/mac.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mockito/mockito_no_mirrors.dart'; import 'package:test/test.dart'; import '../context.dart'; diff --git a/packages/flutter_tools/test/src/ios/simulators_test.dart b/packages/flutter_tools/test/src/ios/simulators_test.dart index 0b26ec4f48b..9611f0f9cac 100644 --- a/packages/flutter_tools/test/src/ios/simulators_test.dart +++ b/packages/flutter_tools/test/src/ios/simulators_test.dart @@ -4,7 +4,7 @@ import 'package:file/file.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/ios/simulators.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mockito/mockito_no_mirrors.dart'; import 'package:process/process.dart'; import 'package:test/test.dart'; diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart index 706022b605b..13c817d3dae 100644 --- a/packages/flutter_tools/test/src/mocks.dart +++ b/packages/flutter_tools/test/src/mocks.dart @@ -12,7 +12,7 @@ import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/simulators.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mockito/mockito_no_mirrors.dart'; import 'package:test/test.dart'; class MockApplicationPackageStore extends ApplicationPackageStore { diff --git a/packages/flutter_tools/test/stop_test.dart b/packages/flutter_tools/test/stop_test.dart index 69d9250c25e..0401237f5ca 100644 --- a/packages/flutter_tools/test/stop_test.dart +++ b/packages/flutter_tools/test/stop_test.dart @@ -5,7 +5,7 @@ import 'dart:async'; import 'package:flutter_tools/src/commands/stop.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mockito/mockito_no_mirrors.dart'; import 'package:test/test.dart'; import 'src/common.dart';