mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

* [integration_test] Reland add a `run` method for proper reporting of test results * Changes to path resolving from previous PR
326 lines
10 KiB
Dart
326 lines
10 KiB
Dart
// Copyright 2014 The Flutter 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 {
|
|
/// Constructor to use for positive response.
|
|
Response.allTestsPassed({this.data})
|
|
: _allTestsPassed = true,
|
|
_failureDetails = null;
|
|
|
|
/// Constructor for failure response.
|
|
Response.someTestsFailed(this._failureDetails, {this.data})
|
|
: _allTestsPassed = false;
|
|
|
|
/// Constructor for failure response.
|
|
Response.toolException({String ex})
|
|
: _allTestsPassed = false,
|
|
_failureDetails = <Failure>[Failure('ToolException', ex)];
|
|
|
|
/// Constructor for web driver commands response.
|
|
Response.webDriverCommand({this.data})
|
|
: _allTestsPassed = false,
|
|
_failureDetails = null;
|
|
|
|
final List<Failure> _failureDetails;
|
|
|
|
final bool _allTestsPassed;
|
|
|
|
/// The extra information to be added along side the test result.
|
|
Map<String, dynamic> data;
|
|
|
|
/// 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) as Map<String, dynamic>;
|
|
if (responseJson['result'] as String == 'true') {
|
|
return Response.allTestsPassed(data: responseJson['data'] as Map<String, dynamic>);
|
|
} else {
|
|
return Response.someTestsFailed(
|
|
_failureDetailsFromJson(responseJson['failureDetails'] as List<dynamic>),
|
|
data: responseJson['data'] as Map<String, dynamic>,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Method for formatting the test failures' details.
|
|
String formatFailures(List<Failure> failureDetails) {
|
|
if (failureDetails.isEmpty) {
|
|
return '';
|
|
}
|
|
|
|
final StringBuffer sb = StringBuffer();
|
|
int failureCount = 1;
|
|
for (final Failure failure in failureDetails) {
|
|
sb.writeln('Failure in method: ${failure.methodName}');
|
|
sb.writeln(failure.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 = <String>[];
|
|
if (_failureDetails == null || _failureDetails.isEmpty) {
|
|
return list;
|
|
}
|
|
|
|
for (final Failure failure in _failureDetails) {
|
|
list.add(failure.toJson());
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
/// Creates a [Failure] list using a json response.
|
|
static List<Failure> _failureDetailsFromJson(List<dynamic> list) {
|
|
return list.map((dynamic s) {
|
|
return Failure.fromJsonString(s as String);
|
|
}).toList();
|
|
}
|
|
}
|
|
|
|
/// Represents the result of running a test.
|
|
class TestResult {
|
|
TestResult._(this.methodName);
|
|
|
|
/// The name of the test method which failed.
|
|
final String methodName;
|
|
}
|
|
|
|
/// Represents successful execution of a test.
|
|
class Success extends TestResult {
|
|
/// Constructor requiring all fields during initialization.
|
|
Success(String methodName) : super._(methodName);
|
|
}
|
|
|
|
/// Represents a test failure.
|
|
class Failure extends TestResult {
|
|
/// Constructor requiring all fields during initialization.
|
|
///
|
|
/// If [error] is passed, [errors] will be ignored.
|
|
Failure(String methodName, String details, {
|
|
Object error,
|
|
List<AsyncError> errors,
|
|
}) :
|
|
errors = error != null
|
|
? <AsyncError>[AsyncError(error, StackTrace.fromString(details))]
|
|
: errors ?? <AsyncError>[],
|
|
super._(methodName);
|
|
|
|
/// Errors that were thrown during the test.
|
|
final List<AsyncError> errors;
|
|
|
|
/// The first error that was thrown during the test.
|
|
Object get error => errors.isEmpty ? null : errors.first.error;
|
|
|
|
/// The details of the first failure such as stack trace.
|
|
String get details => errors.isEmpty ? null : errors.first.stackTrace.toString();
|
|
|
|
/// Serializes the object to JSON.
|
|
String toJson() {
|
|
return json.encode(<String, String>{
|
|
'methodName': methodName,
|
|
'error': error.toString(),
|
|
'details': details,
|
|
});
|
|
}
|
|
|
|
@override
|
|
String toString() => toJson();
|
|
|
|
/// Decode a JSON string to create a Failure object.
|
|
static Failure fromJsonString(String jsonString) {
|
|
final Map<String, dynamic> failure = json.decode(jsonString) as Map<String, dynamic>;
|
|
return Failure(failure['methodName'] as String, failure['details'] as String);
|
|
}
|
|
}
|
|
|
|
/// 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 {
|
|
/// 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;
|
|
|
|
final bool _isSuccess;
|
|
final bool _isPending;
|
|
|
|
// /// 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 {
|
|
/// Constructor for [WebDriverCommandType.noop] command.
|
|
WebDriverCommand.noop()
|
|
: type = WebDriverCommandType.noop,
|
|
values = <String, dynamic>{};
|
|
|
|
/// Constructor for [WebDriverCommandType.noop] screenshot.
|
|
WebDriverCommand.screenshot(String screenshotName)
|
|
: type = WebDriverCommandType.screenshot,
|
|
values = <String, dynamic>{'screenshot_name': screenshotName};
|
|
|
|
/// 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;
|
|
|
|
/// Util method for converting [WebDriverCommandType] to a map entry.
|
|
///
|
|
/// Used for converting messages to json format.
|
|
static Map<String, dynamic> typeToMap(WebDriverCommandType type) => <String, dynamic>{
|
|
'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;
|
|
}
|