1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
// 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;
}