blob: 53714a8e97ee8d36a20a27d283d10a2bff042a86 [file] [log] [blame]
// 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;
}