| // Copyright 2019 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 'dart:convert'; |
| |
| /// Classes shared between `integration_test.dart` and `flutter drive` based |
| /// adoptor (ex: `integration_test_driver.dart`). |
| |
| /// An object sent from integration_test back to the Flutter Driver in response to |
| /// `request_data` command. |
| class Response { |
| final List<Failure> _failureDetails; |
| |
| final bool _allTestsPassed; |
| |
| /// The extra information to be added along side the test result. |
| Map<String, dynamic> data; |
| |
| /// Constructor to use for positive response. |
| Response.allTestsPassed({this.data}) |
| : this._allTestsPassed = true, |
| this._failureDetails = null; |
| |
| /// Constructor for failure response. |
| Response.someTestsFailed(this._failureDetails, {this.data}) |
| : this._allTestsPassed = false; |
| |
| /// Constructor for failure response. |
| Response.toolException({String ex}) |
| : this._allTestsPassed = false, |
| this._failureDetails = [Failure('ToolException', ex)]; |
| |
| /// Constructor for web driver commands response. |
| Response.webDriverCommand({this.data}) |
| : this._allTestsPassed = false, |
| this._failureDetails = null; |
| |
| /// Whether the test ran successfully or not. |
| bool get allTestsPassed => _allTestsPassed; |
| |
| /// If the result are failures get the formatted details. |
| String get formattedFailureDetails => |
| _allTestsPassed ? '' : formatFailures(_failureDetails); |
| |
| /// Failure details as a list. |
| List<Failure> get failureDetails => _failureDetails; |
| |
| /// Serializes this message to a JSON map. |
| String toJson() => json.encode(<String, dynamic>{ |
| 'result': allTestsPassed.toString(), |
| 'failureDetails': _failureDetailsAsString(), |
| if (data != null) 'data': data |
| }); |
| |
| /// Deserializes the result from JSON. |
| static Response fromJson(String source) { |
| final Map<String, dynamic> responseJson = json.decode(source); |
| if (responseJson['result'] as String == 'true') { |
| return Response.allTestsPassed(data: responseJson['data']); |
| } else { |
| return Response.someTestsFailed( |
| _failureDetailsFromJson(responseJson['failureDetails']), |
| data: responseJson['data'], |
| ); |
| } |
| } |
| |
| /// Method for formatting the test failures' details. |
| String formatFailures(List<Failure> failureDetails) { |
| if (failureDetails.isEmpty) { |
| return ''; |
| } |
| |
| StringBuffer sb = StringBuffer(); |
| int failureCount = 1; |
| failureDetails.forEach((Failure f) { |
| sb.writeln('Failure in method: ${f.methodName}'); |
| sb.writeln('${f.details}'); |
| sb.writeln('end of failure ${failureCount.toString()}\n\n'); |
| failureCount++; |
| }); |
| return sb.toString(); |
| } |
| |
| /// Create a list of Strings from [_failureDetails]. |
| List<String> _failureDetailsAsString() { |
| final List<String> list = List<String>(); |
| if (_failureDetails == null || _failureDetails.isEmpty) { |
| return list; |
| } |
| |
| _failureDetails.forEach((Failure f) { |
| list.add(f.toJson()); |
| }); |
| |
| return list; |
| } |
| |
| /// Creates a [Failure] list using a json response. |
| static List<Failure> _failureDetailsFromJson(List<dynamic> list) { |
| final List<Failure> failureList = List<Failure>(); |
| list.forEach((s) { |
| final String failure = s as String; |
| failureList.add(Failure.fromJsonString(failure)); |
| }); |
| return failureList; |
| } |
| } |
| |
| /// Representing a failure includes the method name and the failure details. |
| class Failure { |
| /// The name of the test method which failed. |
| final String methodName; |
| |
| /// The details of the failure such as stack trace. |
| final String details; |
| |
| /// Constructor requiring all fields during initialization. |
| Failure(this.methodName, this.details); |
| |
| /// Serializes the object to JSON. |
| String toJson() { |
| return json.encode(<String, String>{ |
| 'methodName': methodName, |
| 'details': details, |
| }); |
| } |
| |
| @override |
| String toString() => toJson(); |
| |
| /// Decode a JSON string to create a Failure object. |
| static Failure fromJsonString(String jsonString) { |
| Map<String, dynamic> failure = json.decode(jsonString); |
| return Failure(failure['methodName'], failure['details']); |
| } |
| } |
| |
| /// Message used to communicate between app side tests and driver tests. |
| /// |
| /// Not all `integration_tests` use this message. They are only used when app |
| /// side tests are sending [WebDriverCommand]s to the driver side. |
| /// |
| /// These messages are used for the handshake since they carry information on |
| /// the driver side test such as: status pending or tests failed. |
| class DriverTestMessage { |
| final bool _isSuccess; |
| final bool _isPending; |
| |
| /// When tests are failed on the driver side. |
| DriverTestMessage.error() |
| : _isSuccess = false, |
| _isPending = false; |
| |
| /// When driver side is waiting on [WebDriverCommand]s to be sent from the |
| /// app side. |
| DriverTestMessage.pending() |
| : _isSuccess = false, |
| _isPending = true; |
| |
| /// When driver side successfully completed executing the [WebDriverCommand]. |
| DriverTestMessage.complete() |
| : _isSuccess = true, |
| _isPending = false; |
| |
| // /// Status of this message. |
| // /// |
| // /// The status will be use to notify `integration_test` of driver side's |
| // /// state. |
| // String get status => _status; |
| |
| /// Has the command completed successfully by the driver. |
| bool get isSuccess => _isSuccess; |
| |
| /// Is the driver waiting for a command. |
| bool get isPending => _isPending; |
| |
| /// Depending on the values of [isPending] and [isSuccess], returns a string |
| /// to represent the [DriverTestMessage]. |
| /// |
| /// Used as an alternative method to converting the object to json since |
| /// [RequestData] is only accepting string as `message`. |
| @override |
| String toString() { |
| if (isPending) { |
| return 'pending'; |
| } else if (isSuccess) { |
| return 'complete'; |
| } else { |
| return 'error'; |
| } |
| } |
| |
| /// Return a DriverTestMessage depending on `status`. |
| static DriverTestMessage fromString(String status) { |
| switch (status) { |
| case 'error': |
| return DriverTestMessage.error(); |
| case 'pending': |
| return DriverTestMessage.pending(); |
| case 'complete': |
| return DriverTestMessage.complete(); |
| default: |
| throw StateError('This type of status does not exist: $status'); |
| } |
| } |
| } |
| |
| /// Types of different WebDriver commands that can be used in web integration |
| /// tests. |
| /// |
| /// These commands are either commands that WebDriver can execute or used |
| /// for the communication between `integration_test` and the driver test. |
| enum WebDriverCommandType { |
| /// Acknowlegement for the previously sent message. |
| ack, |
| |
| /// No further WebDriver commands is requested by the app-side tests. |
| noop, |
| |
| /// Asking WebDriver to take a screenshot of the Web page. |
| screenshot, |
| } |
| |
| /// Command for WebDriver to execute. |
| /// |
| /// Only works on Web when tests are run via `flutter driver` command. |
| /// |
| /// See: https://www.w3.org/TR/webdriver/ |
| class WebDriverCommand { |
| /// Type of the [WebDriverCommand]. |
| /// |
| /// Currently the only command that triggers a WebDriver API is `screenshot`. |
| /// |
| /// There are also `ack` and `noop` commands defined to manage the handshake |
| /// during the communication. |
| final WebDriverCommandType type; |
| |
| /// Used for adding extra values to the commands such as file name for |
| /// `screenshot`. |
| final Map<String, dynamic> values; |
| |
| /// Constructor for [WebDriverCommandType.noop] command. |
| WebDriverCommand.noop() |
| : this.type = WebDriverCommandType.noop, |
| this.values = Map(); |
| |
| /// Constructor for [WebDriverCommandType.noop] screenshot. |
| WebDriverCommand.screenshot(String screenshot_name) |
| : this.type = WebDriverCommandType.screenshot, |
| this.values = {'screenshot_name': screenshot_name}; |
| |
| /// Util method for converting [WebDriverCommandType] to a map entry. |
| /// |
| /// Used for converting messages to json format. |
| static Map<String, dynamic> typeToMap(WebDriverCommandType type) => { |
| 'web_driver_command': '${type}', |
| }; |
| } |
| |
| /// Template methods each class that responses the driver side inputs must |
| /// implement. |
| /// |
| /// Depending on the platform the communication between `integration_tests` and |
| /// the `driver_tests` can be different. |
| /// |
| /// For the web implementation [WebCallbackManager]. |
| /// For the io implementation [IOCallbackManager]. |
| abstract class CallbackManager { |
| /// The callback function to response the driver side input. |
| Future<Map<String, dynamic>> callback( |
| Map<String, String> params, IntegrationTestResults testRunner); |
| |
| /// Request to take a screenshot of the application. |
| Future<void> takeScreenshot(String screenshot); |
| |
| /// Cleanup and completers or locks used during the communication. |
| void cleanup(); |
| } |
| |
| /// Interface that surfaces test results of integration tests. |
| /// |
| /// Implemented by [IntegrationTestWidgetsFlutterBinding]s. |
| /// |
| /// Any class which needs to access the test results but do not want to create |
| /// a cyclic dependency [IntegrationTestWidgetsFlutterBinding]s can use this |
| /// interface. Example [CallbackManager]. |
| abstract class IntegrationTestResults { |
| /// Stores failure details. |
| /// |
| /// Failed test method's names used as key. |
| List<Failure> get failureMethodsDetails; |
| |
| /// The extra data for the reported result. |
| Map<String, dynamic> get reportData; |
| |
| /// Whether all the test methods completed succesfully. |
| Completer<bool> get allTestsPassed; |
| } |