Commit bfef36f7 authored by Yegor's avatar Yegor Committed by GitHub

add waitUntilNoTransientCallbacks to driver API (#8475)

parent c7695b4a
...@@ -75,7 +75,7 @@ Future<Null> main() async { ...@@ -75,7 +75,7 @@ Future<Null> main() async {
await _runFlutterTest(p.join(flutterRoot, 'packages', 'flutter'), await _runFlutterTest(p.join(flutterRoot, 'packages', 'flutter'),
options: coverageFlags, 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_test'));
await _runFlutterTest(p.join(flutterRoot, 'packages', 'flutter_markdown')); await _runFlutterTest(p.join(flutterRoot, 'packages', 'flutter_markdown'));
await _runAllDartTests(p.join(flutterRoot, 'packages', 'flutter_tools'), await _runAllDartTests(p.join(flutterRoot, 'packages', 'flutter_tools'),
......
...@@ -339,6 +339,15 @@ class FlutterDriver { ...@@ -339,6 +339,15 @@ class FlutterDriver {
return null; 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<Null> waitUntilNoTransientCallbacks({Duration timeout}) async {
await _sendCommand(new WaitUntilNoTransientCallbacks(timeout: timeout));
return null;
}
/// Tell the driver to perform a scrolling action. /// Tell the driver to perform a scrolling action.
/// ///
/// A scrolling action begins with a "pointer down" event, which commonly maps /// A scrolling action begins with a "pointer down" event, which commonly maps
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
import 'dart:async'; import 'dart:async';
import 'package:meta/meta.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart' show RendererBinding; import 'package:flutter/rendering.dart' show RendererBinding;
...@@ -27,7 +29,7 @@ class _DriverBinding extends WidgetsFlutterBinding { // TODO(ianh): refactor so ...@@ -27,7 +29,7 @@ class _DriverBinding extends WidgetsFlutterBinding { // TODO(ianh): refactor so
@override @override
void initServiceExtensions() { void initServiceExtensions() {
super.initServiceExtensions(); super.initServiceExtensions();
_FlutterDriverExtension extension = new _FlutterDriverExtension._(); FlutterDriverExtension extension = new FlutterDriverExtension();
registerServiceExtension( registerServiceExtension(
name: _extensionMethodName, name: _extensionMethodName,
callback: extension.call callback: extension.call
...@@ -57,10 +59,11 @@ typedef Command CommandDeserializerCallback(Map<String, String> params); ...@@ -57,10 +59,11 @@ typedef Command CommandDeserializerCallback(Map<String, String> params);
/// Runs the finder and returns the [Element] found, or `null`. /// Runs the finder and returns the [Element] found, or `null`.
typedef Finder FinderConstructor(SerializableFinder finder); typedef Finder FinderConstructor(SerializableFinder finder);
class _FlutterDriverExtension { @visibleForTesting
class FlutterDriverExtension {
static final Logger _log = new Logger('FlutterDriverExtension'); static final Logger _log = new Logger('FlutterDriverExtension');
_FlutterDriverExtension._() { FlutterDriverExtension() {
_commandHandlers.addAll(<String, CommandHandlerCallback>{ _commandHandlers.addAll(<String, CommandHandlerCallback>{
'get_health': _getHealth, 'get_health': _getHealth,
'get_render_tree': _getRenderTree, 'get_render_tree': _getRenderTree,
...@@ -72,6 +75,7 @@ class _FlutterDriverExtension { ...@@ -72,6 +75,7 @@ class _FlutterDriverExtension {
'setInputText': _setInputText, 'setInputText': _setInputText,
'submitInputText': _submitInputText, 'submitInputText': _submitInputText,
'waitFor': _waitFor, 'waitFor': _waitFor,
'waitUntilNoTransientCallbacks': _waitUntilNoTransientCallbacks,
}); });
_commandDeserializers.addAll(<String, CommandDeserializerCallback>{ _commandDeserializers.addAll(<String, CommandDeserializerCallback>{
...@@ -85,6 +89,7 @@ class _FlutterDriverExtension { ...@@ -85,6 +89,7 @@ class _FlutterDriverExtension {
'setInputText': (Map<String, String> params) => new SetInputText.deserialize(params), 'setInputText': (Map<String, String> params) => new SetInputText.deserialize(params),
'submitInputText': (Map<String, String> params) => new SubmitInputText.deserialize(params), 'submitInputText': (Map<String, String> params) => new SubmitInputText.deserialize(params),
'waitFor': (Map<String, String> params) => new WaitFor.deserialize(params), 'waitFor': (Map<String, String> params) => new WaitFor.deserialize(params),
'waitUntilNoTransientCallbacks': (Map<String, String> params) => new WaitUntilNoTransientCallbacks.deserialize(params),
}); });
_finders.addAll(<String, FinderConstructor>{ _finders.addAll(<String, FinderConstructor>{
...@@ -123,7 +128,7 @@ class _FlutterDriverExtension { ...@@ -123,7 +128,7 @@ class _FlutterDriverExtension {
throw 'Extension $_extensionMethod does not support command $commandKind'; throw 'Extension $_extensionMethod does not support command $commandKind';
Command command = commandDeserializer(params); Command command = commandDeserializer(params);
Result response = await commandHandler(command).timeout(command.timeout); Result response = await commandHandler(command).timeout(command.timeout);
return _makeResponse(response.toJson()); return _makeResponse(response?.toJson());
} on TimeoutException catch (error, stackTrace) { } on TimeoutException catch (error, stackTrace) {
String msg = 'Timeout while executing $commandKind: $error\n$stackTrace'; String msg = 'Timeout while executing $commandKind: $error\n$stackTrace';
_log.error(msg); _log.error(msg);
...@@ -221,6 +226,11 @@ class _FlutterDriverExtension { ...@@ -221,6 +226,11 @@ class _FlutterDriverExtension {
return null; return null;
} }
Future<Null> _waitUntilNoTransientCallbacks(Command command) async {
if (SchedulerBinding.instance.transientCallbackCount != 0)
await _waitUntilFrame(() => SchedulerBinding.instance.transientCallbackCount == 0);
}
Future<ScrollResult> _scroll(Command command) async { Future<ScrollResult> _scroll(Command command) async {
Scroll scrollCommand = command; Scroll scrollCommand = command;
Finder target = await _waitForElement(_createFinder(scrollCommand.finder)); Finder target = await _waitForElement(_createFinder(scrollCommand.finder));
......
...@@ -62,6 +62,18 @@ class WaitFor extends CommandWithTarget { ...@@ -62,6 +62,18 @@ class WaitFor extends CommandWithTarget {
WaitFor.deserialize(Map<String, String> json) : super.deserialize(json); WaitFor.deserialize(Map<String, String> 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<String, String> json)
: super.deserialize(json);
}
/// The result of a [WaitFor] command. /// The result of a [WaitFor] command.
class WaitForResult extends Result { class WaitForResult extends Result {
/// Deserializes the result from JSON. /// Deserializes the result from JSON.
......
...@@ -11,6 +11,7 @@ dependencies: ...@@ -11,6 +11,7 @@ dependencies:
file: 2.3.0 file: 2.3.0
json_rpc_2: '^2.0.0' json_rpc_2: '^2.0.0'
matcher: '>=0.12.0 <1.0.0' matcher: '>=0.12.0 <1.0.0'
meta: ^1.0.4
path: '^1.4.0' path: '^1.4.0'
web_socket_channel: '^1.0.0' web_socket_channel: '^1.0.0'
vm_service_client: '0.2.2+4' vm_service_client: '0.2.2+4'
......
// 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();
}
...@@ -9,7 +9,7 @@ import 'package:flutter_driver/src/error.dart'; ...@@ -9,7 +9,7 @@ import 'package:flutter_driver/src/error.dart';
import 'package:flutter_driver/src/health.dart'; import 'package:flutter_driver/src/health.dart';
import 'package:flutter_driver/src/timeline.dart'; import 'package:flutter_driver/src/timeline.dart';
import 'package:json_rpc_2/json_rpc_2.dart' as rpc; 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:test/test.dart';
import 'package:vm_service_client/vm_service_client.dart'; import 'package:vm_service_client/vm_service_client.dart';
...@@ -199,6 +199,19 @@ void main() { ...@@ -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], <String, dynamic>{
'command': 'waitUntilNoTransientCallbacks',
'timeout': '1000',
});
return makeMockResponse(<String, dynamic>{});
});
await driver.waitUntilNoTransientCallbacks(timeout: const Duration(seconds: 1));
});
});
group('traceAction', () { group('traceAction', () {
test('traces action', () async { test('traces action', () async {
bool actionCalled = false; bool actionCalled = false;
......
// 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<String, dynamic> 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<Null>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
await tester.idle();
expect(
result,
<String, dynamic>{
'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<Null>(expectAsync1((Map<String, dynamic> 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,
<String, dynamic>{
'isError': false,
'response': null,
},
);
});
});
}
...@@ -11,7 +11,7 @@ import 'package:flutter_tools/src/base/platform.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/commands/drive.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/ios/simulators.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 'package:test/test.dart';
import 'src/common.dart'; import 'src/common.dart';
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter_tools/src/commands/install.dart'; 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 'package:test/test.dart';
import 'src/common.dart'; import 'src/common.dart';
......
...@@ -20,7 +20,7 @@ import 'package:flutter_tools/src/ios/simulators.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/run_hot.dart';
import 'package:flutter_tools/src/usage.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:process/process.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
......
...@@ -7,7 +7,7 @@ import 'package:file/file.dart'; ...@@ -7,7 +7,7 @@ import 'package:file/file.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/ios/devices.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:process/process.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
......
...@@ -6,7 +6,7 @@ import 'package:flutter_tools/src/doctor.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/ios_workflow.dart';
import 'package:flutter_tools/src/ios/mac.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 'package:test/test.dart';
import '../context.dart'; import '../context.dart';
......
...@@ -4,7 +4,7 @@ import 'package:file/file.dart'; ...@@ -4,7 +4,7 @@ import 'package:file/file.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/ios/simulators.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:process/process.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
......
...@@ -12,7 +12,7 @@ import 'package:flutter_tools/src/device.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/devices.dart';
import 'package:flutter_tools/src/ios/simulators.dart'; import 'package:flutter_tools/src/ios/simulators.dart';
import 'package:flutter_tools/src/runner/flutter_command.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'; import 'package:test/test.dart';
class MockApplicationPackageStore extends ApplicationPackageStore { class MockApplicationPackageStore extends ApplicationPackageStore {
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_tools/src/commands/stop.dart'; 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 'package:test/test.dart';
import 'src/common.dart'; import 'src/common.dart';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment