mirror of
https://github.com/flutter/flutter
synced 2024-10-12 19:23:02 +00:00
320 lines
10 KiB
Dart
320 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';
|
|
|
|
/// A callback to use with [integrationDriver].
|
|
///
|
|
/// The callback receives the name of screenshot passed to `binding.takeScreenshot(<name>)` and
|
|
/// a PNG byte buffer.
|
|
///
|
|
/// The callback returns `true` if the test passes or `false` otherwise.
|
|
///
|
|
/// You can use this callback to store the bytes locally in a file or upload them to a service
|
|
/// that compares the image against a gold or baseline version.
|
|
///
|
|
/// Since the function is executed on the host driving the test, you can access any environment
|
|
/// variable from it.
|
|
typedef ScreenshotCallback = Future<bool> Function(String name, List<int> image);
|
|
|
|
/// 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();
|
|
}
|
|
}
|
|
|
|
/// Representing a failure includes the method name and the failure details.
|
|
class Failure {
|
|
/// Constructor requiring all fields during initialization.
|
|
Failure(this.methodName, this.details);
|
|
|
|
/// The name of the test method which failed.
|
|
final String methodName;
|
|
|
|
/// The details of the failure such as stack trace.
|
|
final String? 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) {
|
|
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 {
|
|
/// Acknowledgement 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);
|
|
|
|
/// Takes a screenshot of the application.
|
|
/// Returns the data that is sent back to the host.
|
|
Future<Map<String, dynamic>> takeScreenshot(String screenshot);
|
|
|
|
/// Android only. Converts the Flutter surface to an image view.
|
|
Future<void> convertFlutterSurfaceToImage();
|
|
|
|
/// 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 successfully.
|
|
///
|
|
/// Completes when the tests have finished. The boolean value will be true if
|
|
/// all tests have passed, and false otherwise.
|
|
Completer<bool> get allTestsPassed;
|
|
}
|