[e2e] add support to report extra information (#2873)

* add extra info report
diff --git a/packages/e2e/CHANGELOG.md b/packages/e2e/CHANGELOG.md
index ad2f8d3..255d7ce 100644
--- a/packages/e2e/CHANGELOG.md
+++ b/packages/e2e/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.6.1
+
+* Added `data` in the reported json.
+
 ## 0.6.0
 
 * **Breaking change** `E2EPlugin` exports a `Future` for `testResults`.
diff --git a/packages/e2e/lib/common.dart b/packages/e2e/lib/common.dart
index 39efcc8..4325b65 100644
--- a/packages/e2e/lib/common.dart
+++ b/packages/e2e/lib/common.dart
@@ -11,13 +11,17 @@
 
   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()
+  Response.allTestsPassed({this.data})
       : this._allTestsPassed = true,
         this._failureDetails = null;
 
   /// Constructor for failure response.
-  Response.someTestsFailed(this._failureDetails) : this._allTestsPassed = false;
+  Response.someTestsFailed(this._failureDetails, {this.data})
+      : this._allTestsPassed = false;
 
   /// Whether the test ran successfully or not.
   bool get allTestsPassed => _allTestsPassed;
@@ -33,16 +37,19 @@
   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) {
-    Map<String, dynamic> responseJson = json.decode(source);
-    if (responseJson['result'] == 'true') {
-      return Response.allTestsPassed();
+    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']));
+        _failureDetailsFromJson(responseJson['failureDetails']),
+        data: responseJson['data'],
+      );
     }
   }
 
diff --git a/packages/e2e/lib/e2e.dart b/packages/e2e/lib/e2e.dart
index ec1615e..a9bf830 100644
--- a/packages/e2e/lib/e2e.dart
+++ b/packages/e2e/lib/e2e.dart
@@ -78,33 +78,47 @@
 
   static Map<String, String> _results = <String, String>{};
 
+  /// The extra data for the reported result.
+  ///
+  /// The values in `reportData` must be json-serializable objects or `null`.
+  /// If it's `null`, no extra data is attached to the result.
+  ///
+  /// The default value is `null`.
+  Map<String, dynamic> reportData;
+
+  /// the callback function to response the driver side input.
+  @visibleForTesting
+  Future<Map<String, dynamic>> callback(Map<String, String> params) async {
+    final String command = params['command'];
+    Map<String, String> response;
+    switch (command) {
+      case 'request_data':
+        final bool allTestsPassed = await _allTestsPassed.future;
+        response = <String, String>{
+          'message': allTestsPassed
+              ? Response.allTestsPassed(data: reportData).toJson()
+              : Response.someTestsFailed(
+                  _failureMethodsDetails,
+                  data: reportData,
+                ).toJson(),
+        };
+        break;
+      case 'get_health':
+        response = <String, String>{'status': 'ok'};
+        break;
+      default:
+        throw UnimplementedError('$command is not implemented');
+    }
+    return <String, dynamic>{
+      'isError': false,
+      'response': response,
+    };
+  }
+
   // Emulates the Flutter driver extension, returning 'pass' or 'fail'.
   @override
   void initServiceExtensions() {
     super.initServiceExtensions();
-    Future<Map<String, dynamic>> callback(Map<String, String> params) async {
-      final String command = params['command'];
-      Map<String, String> response;
-      switch (command) {
-        case 'request_data':
-          final bool allTestsPassed = await _allTestsPassed.future;
-          response = <String, String>{
-            'message': allTestsPassed
-                ? Response.allTestsPassed().toJson()
-                : Response.someTestsFailed(_failureMethodsDetails).toJson(),
-          };
-          break;
-        case 'get_health':
-          response = <String, String>{'status': 'ok'};
-          break;
-        default:
-          throw UnimplementedError('$command is not implemented');
-      }
-      return <String, dynamic>{
-        'isError': false,
-        'response': response,
-      };
-    }
 
     if (kIsWeb) {
       registerWebServiceExtension(callback);
diff --git a/packages/e2e/pubspec.yaml b/packages/e2e/pubspec.yaml
index 25eeb3c..70e57d0 100644
--- a/packages/e2e/pubspec.yaml
+++ b/packages/e2e/pubspec.yaml
@@ -1,10 +1,10 @@
 name: e2e
 description: Runs tests that use the flutter_test API as integration tests.
-version: 0.6.0
+version: 0.6.1
 homepage: https://github.com/flutter/plugins/tree/master/packages/e2e
 
 environment:
-  sdk: ">=2.1.0 <3.0.0"
+  sdk: ">=2.2.2 <3.0.0"
   flutter: ">=1.12.13+hotfix.5 <2.0.0"
 
 dependencies:
diff --git a/packages/e2e/test/binding_test.dart b/packages/e2e/test/binding_test.dart
new file mode 100644
index 0000000..8c97e16
--- /dev/null
+++ b/packages/e2e/test/binding_test.dart
@@ -0,0 +1,41 @@
+import 'package:flutter/material.dart';
+
+import 'package:e2e/e2e.dart';
+import 'package:e2e/common.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() async {
+  Future<Map<String, dynamic>> request;
+
+  group('Test E2E binding', () {
+    final WidgetsBinding binding = E2EWidgetsFlutterBinding.ensureInitialized();
+    assert(binding is E2EWidgetsFlutterBinding);
+    final E2EWidgetsFlutterBinding e2ebinding =
+        binding as E2EWidgetsFlutterBinding;
+
+    setUp(() {
+      request = e2ebinding.callback(<String, String>{
+        'command': 'request_data',
+      });
+    });
+
+    testWidgets('Run E2E app', (WidgetTester tester) async {
+      runApp(MaterialApp(
+        home: Text('Test'),
+      ));
+      expect(tester.binding, e2ebinding);
+      e2ebinding.reportData = <String, dynamic>{'answer': 42};
+    });
+  });
+
+  tearDownAll(() async {
+    // This part is outside the group so that `request` has been compeleted as
+    // part of the `tearDownAll` registerred in the group during
+    // `E2EWidgetsFlutterBinding` initialization.
+    final Map<String, dynamic> response =
+        (await request)['response'] as Map<String, dynamic>;
+    final String message = response['message'] as String;
+    Response result = Response.fromJson(message);
+    assert(result.data['answer'] == 42);
+  });
+}
diff --git a/packages/e2e/test/response_serialization_test.dart b/packages/e2e/test/response_serialization_test.dart
new file mode 100644
index 0000000..4d823e2
--- /dev/null
+++ b/packages/e2e/test/response_serialization_test.dart
@@ -0,0 +1,47 @@
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:e2e/common.dart';
+
+void main() {
+  test('Serialize and deserialize Failure', () {
+    Failure fail = Failure('what a name', 'no detail');
+    Failure restored = Failure.fromJsonString(fail.toString());
+    expect(restored.methodName, fail.methodName);
+    expect(restored.details, fail.details);
+  });
+
+  test('Serialize and deserialize Response', () {
+    Response response, restored;
+    String jsonString;
+
+    response = Response.allTestsPassed();
+    jsonString = response.toJson();
+    expect(jsonString, '{"result":"true","failureDetails":[]}');
+    restored = Response.fromJson(jsonString);
+    expect(restored.allTestsPassed, response.allTestsPassed);
+    expect(restored.data, null);
+    expect(restored.formattedFailureDetails, '');
+
+    final Failure fail = Failure('what a name', 'no detail');
+    final Failure fail2 = Failure('what a name2', 'no detail2');
+    response = Response.someTestsFailed([fail, fail2]);
+    jsonString = response.toJson();
+    restored = Response.fromJson(jsonString);
+    expect(restored.allTestsPassed, response.allTestsPassed);
+    expect(restored.data, null);
+    expect(restored.formattedFailureDetails, response.formattedFailureDetails);
+
+    Map<String, dynamic> data = <String, dynamic>{'aaa': 'bbb'};
+    response = Response.allTestsPassed(data: data);
+    jsonString = response.toJson();
+    restored = Response.fromJson(jsonString);
+    expect(restored.data.keys, ['aaa']);
+    expect(restored.data.values, ['bbb']);
+
+    response = Response.someTestsFailed([fail, fail2], data: data);
+    jsonString = response.toJson();
+    restored = Response.fromJson(jsonString);
+    expect(restored.data.keys, ['aaa']);
+    expect(restored.data.values, ['bbb']);
+  });
+}