diff --git a/packages/flutter_driver/lib/src/driver.dart b/packages/flutter_driver/lib/src/driver.dart index 433268ddd99..df18dcc6baa 100644 --- a/packages/flutter_driver/lib/src/driver.dart +++ b/packages/flutter_driver/lib/src/driver.dart @@ -4,20 +4,23 @@ import 'dart:async'; import 'dart:convert'; +import 'package:file/file.dart' as f; import 'dart:io'; import 'package:json_rpc_2/json_rpc_2.dart' as rpc; import 'package:meta/meta.dart'; +import 'package:path/path.dart' as p; import 'package:vm_service_client/vm_service_client.dart'; import 'package:web_socket_channel/io.dart'; +import 'common.dart'; import 'error.dart'; import 'find.dart'; +import 'frame_sync.dart'; import 'gesture.dart'; import 'health.dart'; import 'input.dart'; import 'message.dart'; import 'render_tree.dart'; -import 'frame_sync.dart'; import 'timeline.dart'; /// Timeline stream identifier. @@ -94,13 +97,19 @@ class FlutterDriver { /// Creates a driver that uses a connection provided by the given /// [_serviceClient], [_peer] and [_appIsolate]. @visibleForTesting - FlutterDriver.connectedTo(this._serviceClient, this._peer, this._appIsolate); + FlutterDriver.connectedTo(this._serviceClient, this._peer, this._appIsolate, + { bool printCommunication: false, bool logCommunicationToFile: true }) + : _printCommunication = printCommunication, + _logCommunicationToFile = logCommunicationToFile, + _driverId = _nextDriverId++; static const String _kFlutterExtensionMethod = 'ext.flutter.driver'; static const String _kSetVMTimelineFlagsMethod = '_setVMTimelineFlags'; static const String _kGetVMTimelineMethod = '_getVMTimeline'; static const Duration _kRpcGraceTime = const Duration(seconds: 2); + static int _nextDriverId = 0; + /// Connects to a Flutter application. /// /// Resumes the application if it is currently paused (e.g. at a breakpoint). @@ -108,7 +117,15 @@ class FlutterDriver { /// [dartVmServiceUrl] is the URL to Dart observatory (a.k.a. VM service). If /// not specified, the URL specified by the `VM_SERVICE_URL` environment /// variable is used, or 'http://localhost:8183'. - static Future connect({String dartVmServiceUrl}) async { + /// + /// [printCommunication] determines whether the command communication between + /// the test and the app should be printed to stdout. + /// + /// [logCommunicationToFile] determines whether the command communication + /// between the test and the app should be logged to `flutter_driver_commands.log`. + static Future connect({ String dartVmServiceUrl, + bool printCommunication: false, + bool logCommunicationToFile: true }) async { dartVmServiceUrl ??= Platform.environment['VM_SERVICE_URL']; dartVmServiceUrl ??= 'http://localhost:8183'; @@ -137,7 +154,11 @@ class FlutterDriver { isolate = await vm.isolates.first.loadRunnable(); } - FlutterDriver driver = new FlutterDriver.connectedTo(client, connection.peer, isolate); + FlutterDriver driver = new FlutterDriver.connectedTo( + client, connection.peer, isolate, + printCommunication: printCommunication, + logCommunicationToFile: logCommunicationToFile + ); // Attempts to resume the isolate, but does not crash if it fails because // the isolate is already resumed. There could be a race with other tools, @@ -220,19 +241,28 @@ class FlutterDriver { return driver; } + /// The unique ID of this driver instance. + final int _driverId; /// Client connected to the Dart VM running the Flutter application final VMServiceClient _serviceClient; /// JSON-RPC client useful for sending raw JSON requests. final rpc.Peer _peer; /// The main isolate hosting the Flutter application final VMIsolateRef _appIsolate; + /// Whether to print communication between host and app to `stdout`. + final bool _printCommunication; + /// Whether to log communication between host and app to `flutter_driver_commands.log`. + final bool _logCommunicationToFile; Future> _sendCommand(Command command) async { Map response; try { - response = await _appIsolate - .invokeExtension(_kFlutterExtensionMethod, command.serialize()) + Map serialized = command.serialize(); + _logCommunication('>>> $serialized'); + response = await _appIsolate + .invokeExtension(_kFlutterExtensionMethod, serialized) .timeout(command.timeout + _kRpcGraceTime); + _logCommunication('<<< $response'); } on TimeoutException catch (error, stackTrace) { throw new DriverError( 'Failed to fulfill ${command.runtimeType}: Flutter application not responding', @@ -251,6 +281,16 @@ class FlutterDriver { return response['response']; } + void _logCommunication(String message) { + if (_printCommunication) + _log.info(message); + if (_logCommunicationToFile) { + f.File file = fs.file(p.join(testOutputsDirectory, 'flutter_driver_commands_${_driverId}.log')); + file.createSync(recursive: true); // no-op if file exists + file.writeAsStringSync('${new DateTime.now()} $message\n', mode: f.FileMode.APPEND, flush: true); + } + } + /// Checks the status of the Flutter Driver extension. Future checkHealth() async { return Health.fromJson(await _sendCommand(new GetHealth()));