[e2e] Adding failure details for driver tests (#2593)
* Adding failure details for driver tests
* formate fix
* addressing reviewer comments
* addressing the reviewer comments. part 2
* fix error in response_data
* addressing reviewer comments
* fixing formatting
* addressin reviewer comments. Carrying the repeated driver test code to e2e package
* chaning the failure's details type from map to list
* format changes
* add documentation to public members
* formatting
* changing namespace name
diff --git a/packages/e2e/CHANGELOG.md b/packages/e2e/CHANGELOG.md
index 72528d6..7996912 100644
--- a/packages/e2e/CHANGELOG.md
+++ b/packages/e2e/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 0.4.0
+
+* **Breaking change** Driver request_data call's response has changed to
+ encapsulate the failure details.
+* Details for failure cases are added: failed method name, stack trace.
+
## 0.3.0+1
* Replace deprecated `getFlutterEngine` call on Android.
diff --git a/packages/e2e/README.md b/packages/e2e/README.md
index 7d571a5..823b1b8 100644
--- a/packages/e2e/README.md
+++ b/packages/e2e/README.md
@@ -43,16 +43,11 @@
```dart
import 'dart:async';
-import 'dart:io';
-import 'package:flutter_driver/flutter_driver.dart';
-Future<void> main() async {
- final FlutterDriver driver = await FlutterDriver.connect();
- final String result =
- await driver.requestData(null, timeout: const Duration(minutes: 1));
- await driver.close();
- exit(result == 'pass' ? 0 : 1);
-}
+import 'package:e2e/e2e_driver.dart' as e2e;
+
+Future<void> main() async => e2e.main();
+
```
To run a example app test with Flutter driver:
@@ -69,7 +64,7 @@
flutter drive --driver=test_driver/<package_name>_test.dart test/<package_name>_e2e.dart
```
-You can run tests on web on release mode.
+You can run tests on web in release or profile mode.
First you need to make sure you have downloaded the driver for the browser.
diff --git a/packages/e2e/example/test_driver/example_e2e_test.dart b/packages/e2e/example/test_driver/example_e2e_test.dart
index 6147d44..983c386 100644
--- a/packages/e2e/example/test_driver/example_e2e_test.dart
+++ b/packages/e2e/example/test_driver/example_e2e_test.dart
@@ -1,12 +1,5 @@
import 'dart:async';
-import 'dart:io';
-import 'package:flutter_driver/flutter_driver.dart';
+import 'package:e2e/e2e_driver.dart' as e2e;
-Future<void> main() async {
- final FlutterDriver driver = await FlutterDriver.connect();
- final String result =
- await driver.requestData(null, timeout: const Duration(minutes: 1));
- await driver.close();
- exit(result == 'pass' ? 0 : 1);
-}
+Future<void> main() async => e2e.main();
diff --git a/packages/e2e/example/test_driver/failure_test.dart b/packages/e2e/example/test_driver/failure_test.dart
index 9177309..a828df6 100644
--- a/packages/e2e/example/test_driver/failure_test.dart
+++ b/packages/e2e/example/test_driver/failure_test.dart
@@ -1,17 +1,19 @@
import 'dart:async';
+import 'package:e2e/common.dart' as common;
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
Future<void> main() async {
test('fails gracefully', () async {
final FlutterDriver driver = await FlutterDriver.connect();
- final String result =
+ final String jsonResult =
await driver.requestData(null, timeout: const Duration(minutes: 1));
+ common.Response response = common.Response.fromJson(jsonResult);
await driver.close();
expect(
- result,
- 'fail',
+ response.allTestsPassed,
+ false,
);
});
}
diff --git a/packages/e2e/lib/common.dart b/packages/e2e/lib/common.dart
new file mode 100644
index 0000000..39efcc8
--- /dev/null
+++ b/packages/e2e/lib/common.dart
@@ -0,0 +1,116 @@
+// 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:convert';
+
+/// An object sent from e2e back to the Flutter Driver in response to
+/// `request_data` command.
+class Response {
+ final List<Failure> _failureDetails;
+
+ final bool _allTestsPassed;
+
+ /// Constructor to use for positive response.
+ Response.allTestsPassed()
+ : this._allTestsPassed = true,
+ this._failureDetails = null;
+
+ /// Constructor for failure response.
+ Response.someTestsFailed(this._failureDetails) : this._allTestsPassed = false;
+
+ /// 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(),
+ });
+
+ /// Deserializes the result from JSON.
+ static Response fromJson(String source) {
+ Map<String, dynamic> responseJson = json.decode(source);
+ if (responseJson['result'] == 'true') {
+ return Response.allTestsPassed();
+ } else {
+ return Response.someTestsFailed(
+ _failureDetailsFromJson(responseJson['failureDetails']));
+ }
+ }
+
+ /// Method for formating 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.toString());
+ });
+
+ 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.
+ @override
+ String toString() {
+ return json.encode(<String, String>{
+ 'methodName': methodName,
+ 'details': details,
+ });
+ }
+
+ /// 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']);
+ }
+}
diff --git a/packages/e2e/lib/e2e.dart b/packages/e2e/lib/e2e.dart
index eef594a..799b77e 100644
--- a/packages/e2e/lib/e2e.dart
+++ b/packages/e2e/lib/e2e.dart
@@ -3,11 +3,13 @@
// found in the LICENSE file.
import 'dart:async';
+
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
+import 'common.dart';
import '_extension_io.dart' if (dart.library.html) '_extension_web.dart';
/// A subclass of [LiveTestWidgetsFlutterBinding] that reports tests results
@@ -36,6 +38,11 @@
final Completer<bool> _allTestsPassed = Completer<bool>();
+ /// Stores failure details.
+ ///
+ /// Failed test method's names used as key.
+ final List<Failure> _failureMethodsDetails = List<Failure>();
+
/// Similar to [WidgetsFlutterBinding.ensureInitialized].
///
/// Returns an instance of the [E2EWidgetsFlutterBinding], creating and
@@ -63,7 +70,9 @@
case 'request_data':
final bool allTestsPassed = await _allTestsPassed.future;
response = <String, String>{
- 'message': allTestsPassed ? 'pass' : 'fail',
+ 'message': allTestsPassed
+ ? Response.allTestsPassed().toJson()
+ : Response.someTestsFailed(_failureMethodsDetails).toJson(),
};
break;
case 'get_health':
@@ -94,6 +103,7 @@
reportTestException =
(FlutterErrorDetails details, String testDescription) {
_results[description] = 'failed';
+ _failureMethodsDetails.add(Failure(testDescription, details.toString()));
if (!_allTestsPassed.isCompleted) _allTestsPassed.complete(false);
valueBeforeTest(details, testDescription);
};
diff --git a/packages/e2e/lib/e2e_driver.dart b/packages/e2e/lib/e2e_driver.dart
new file mode 100644
index 0000000..2e43c5a
--- /dev/null
+++ b/packages/e2e/lib/e2e_driver.dart
@@ -0,0 +1,21 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'package:e2e/common.dart' as e2e;
+import 'package:flutter_driver/flutter_driver.dart';
+
+Future<void> main() async {
+ final FlutterDriver driver = await FlutterDriver.connect();
+ final String jsonResult =
+ await driver.requestData(null, timeout: const Duration(minutes: 1));
+ final e2e.Response response = e2e.Response.fromJson(jsonResult);
+ await driver.close();
+
+ if (response.allTestsPassed) {
+ print('All tests passed.');
+ exit(0);
+ } else {
+ print('Failure Details:\n${response.formattedFailureDetails}');
+ exit(1);
+ }
+}
diff --git a/packages/e2e/pubspec.yaml b/packages/e2e/pubspec.yaml
index a087bce..1af3ab9 100644
--- a/packages/e2e/pubspec.yaml
+++ b/packages/e2e/pubspec.yaml
@@ -1,6 +1,6 @@
name: e2e
description: Runs tests that use the flutter_test API as integration tests.
-version: 0.3.0+1
+version: 0.4.0
homepage: https://github.com/flutter/plugins/tree/master/packages/e2e
environment:
@@ -10,6 +10,8 @@
dependencies:
flutter:
sdk: flutter
+ flutter_driver:
+ sdk: flutter
flutter_test:
sdk: flutter