diff --git a/dev/e2e_app/integration_test/overflow_test.dart b/dev/e2e_app/integration_test/overflow_test.dart new file mode 100644 index 000000000..36108aee0 --- /dev/null +++ b/dev/e2e_app/integration_test/overflow_test.dart @@ -0,0 +1,15 @@ +// Uncomment to test `Multiple exceptions were thrown` issue +// import 'package:flutter/widgets.dart'; + +import 'common.dart'; + +void main() { + patrol('opens the overflow screen', ($) async { + await createApp($); + + await $('Open overflow screen').scrollTo().tap(); + + // Uncomment to test `Multiple exceptions were thrown` issue + // return $(ValueKey('key')).scrollTo().tap(); + }); +} diff --git a/dev/e2e_app/lib/main.dart b/dev/e2e_app/lib/main.dart index ad7356c0d..a9df2a520 100644 --- a/dev/e2e_app/lib/main.dart +++ b/dev/e2e_app/lib/main.dart @@ -3,6 +3,7 @@ import 'package:e2e_app/applink_screen.dart'; import 'package:e2e_app/loading_screen.dart'; import 'package:e2e_app/location_screen.dart'; import 'package:e2e_app/notifications_screen.dart'; +import 'package:e2e_app/overflow_screen.dart'; import 'package:e2e_app/overlay_screen.dart'; import 'package:e2e_app/permissions_screen.dart'; import 'package:e2e_app/scrolling_screen.dart'; @@ -264,6 +265,14 @@ class _ExampleHomePageState extends State { ), child: const Text('Open permissions screen'), ), + TextButton( + onPressed: () async => Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const OverflowScreen(), + ), + ), + child: const Text('Open overflow screen'), + ), Text('EXAMPLE_KEY: ${const String.fromEnvironment('EXAMPLE_KEY')}'), ], ), diff --git a/dev/e2e_app/lib/overflow_screen.dart b/dev/e2e_app/lib/overflow_screen.dart new file mode 100644 index 000000000..51c838621 --- /dev/null +++ b/dev/e2e_app/lib/overflow_screen.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +// Uncomment to test `Multiple exceptions were thrown` issue +// import 'package:flutter/services.dart'; + +class OverflowScreen extends StatelessWidget { + const OverflowScreen({super.key}); + + @override + Widget build(BuildContext context) { + // Uncomment to test `Multiple exceptions were thrown` issue + // Future throwStackOverflowException() async { + // await Future.delayed(const Duration(milliseconds: 100)); + // throw StackOverflowError(); + // } + // + // Future throwPlatformException() async { + // await Future.delayed(const Duration(milliseconds: 110)); + // throw PlatformException(code: 'code'); + // } + // + // Future throwFormatException() async { + // await Future.delayed(const Duration(milliseconds: 120)); + // throw FormatException(); + // } + // + // throwStackOverflowException(); + // throwPlatformException(); + // throwFormatException(); + + return Scaffold( + appBar: AppBar( + title: const Text('Overflow'), + ), + body: Row( + children: const [ + // Uncomment to test `Multiple exceptions were thrown` issue + // SizedBox( + // width: 500, + // child: Text('This container is too wide for the row'), + // ), + Icon(Icons.star, size: 50), + Icon(Icons.abc, size: 50), + ], + ), + ); + } +} diff --git a/packages/patrol/CHANGELOG.md b/packages/patrol/CHANGELOG.md index 852bbeced..78372df75 100644 --- a/packages/patrol/CHANGELOG.md +++ b/packages/patrol/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.12.0 + +- Make `PatrolBinding`'s `testResults` public. (#2362) + ## 3.11.1 - Replace whitespace in test case name in `PatrolJUnitRunner.java`. (#2361) diff --git a/packages/patrol/lib/src/binding.dart b/packages/patrol/lib/src/binding.dart index 47407ab1e..bf2b532f8 100644 --- a/packages/patrol/lib/src/binding.dart +++ b/packages/patrol/lib/src/binding.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:developer'; -import 'dart:io' as io; import 'dart:isolate'; import 'package:flutter/foundation.dart'; @@ -44,28 +43,6 @@ class PatrolBinding extends LiveTestWidgetsFlutterBinding { /// You most likely don't want to call it yourself. PatrolBinding(NativeAutomatorConfig config) : _serviceExtensions = DevtoolsServiceExtensions(config) { - final oldTestExceptionReporter = reportTestException; - - /// Wraps the default test exception reporter to report the test results to - /// the native side of Patrol. - reportTestException = (details, testDescription) { - final currentDartTest = _currentDartTest; - if (currentDartTest != null) { - assert(!constants.hotRestartEnabled); - // On iOS in release mode, diagnostics are compacted or truncated. - // We use the exceptionAsString() and stack to get the information - // about the exception. See [DiagnosticLevel]. - final detailsAsString = (kReleaseMode && io.Platform.isIOS) - ? '${details.exceptionAsString()} \n ${details.stack}' - : details.toString(); - _testResults[currentDartTest] = Failure( - testDescription, - detailsAsString, - ); - } - oldTestExceptionReporter(details, testDescription); - }; - setUp(() { if (constants.hotRestartEnabled) { return; @@ -89,7 +66,7 @@ class PatrolBinding extends LiveTestWidgetsFlutterBinding { return; } else { logger( - 'tearDown(): count: ${_testResults.length}, results: $_testResults', + 'tearDown(): count: ${testResults.length}, results: $testResults', ); } @@ -127,8 +104,8 @@ class PatrolBinding extends LiveTestWidgetsFlutterBinding { await patrolAppService.markDartTestAsCompleted( dartFileName: _currentDartTest!, passed: passed, - details: _testResults[_currentDartTest!] is Failure - ? (_testResults[_currentDartTest!] as Failure?)?.details + details: testResults[_currentDartTest!] is Failure + ? (testResults[_currentDartTest!] as Failure?)?.details : null, ); } else { @@ -178,7 +155,7 @@ class PatrolBinding extends LiveTestWidgetsFlutterBinding { /// Keys are the test descriptions, and values are either [_success] or a /// [Failure]. - final Map _testResults = {}; + final Map testResults = {}; final DevtoolsServiceExtensions _serviceExtensions; @@ -235,7 +212,7 @@ class PatrolBinding extends LiveTestWidgetsFlutterBinding { invariantTester, description: description, ); - _testResults[description] ??= _success; + testResults[description] ??= _success; } @override diff --git a/packages/patrol/lib/src/native/patrol_app_service.dart b/packages/patrol/lib/src/native/patrol_app_service.dart index 5749ae871..acf513b24 100644 --- a/packages/patrol/lib/src/native/patrol_app_service.dart +++ b/packages/patrol/lib/src/native/patrol_app_service.dart @@ -5,9 +5,12 @@ import 'dart:async'; import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:patrol/src/binding.dart'; import 'package:patrol/src/common.dart'; import 'package:patrol/src/native/contracts/contracts.dart'; import 'package:patrol/src/native/contracts/patrol_app_service_server.dart'; +import 'package:patrol/src/native/native.dart'; import 'package:shelf/shelf.dart' as shelf; import 'package:shelf/shelf_io.dart' as shelf_io; @@ -150,7 +153,30 @@ class PatrolAppService extends PatrolAppServiceServer { print('PatrolAppService.runDartTest(${request.name}) called'); _testExecutionRequested.complete(request.name); + final patrolBinding = + PatrolBinding.ensureInitialized(const NativeAutomatorConfig()); + + final previousOnError = FlutterError.onError; + FlutterError.onError = (details) { + final previousDetails = switch (patrolBinding.testResults[request.name]) { + Failure(:final details?) => FlutterErrorDetails(exception: details), + _ => null, + }; + final detailsAsString = (kReleaseMode && Platform.isIOS) + ? '${details.exceptionAsString()} \n ${details.stack}' + : details.toString(); + + patrolBinding.testResults[request.name] = Failure( + request.name, + '$detailsAsString\n$previousDetails', + ); + + previousOnError?.call(details); + }; + final testExecutionResult = await testExecutionCompleted; + FlutterError.onError = previousOnError; + return RunDartTestResponse( result: testExecutionResult.passed ? RunDartTestResponseResult.success diff --git a/packages/patrol/pubspec.yaml b/packages/patrol/pubspec.yaml index edcffecf5..61889ecf9 100644 --- a/packages/patrol/pubspec.yaml +++ b/packages/patrol/pubspec.yaml @@ -2,7 +2,7 @@ name: patrol description: > Powerful Flutter-native UI testing framework overcoming limitations of existing Flutter testing tools. Ready for action! -version: 3.11.1 +version: 3.12.0 homepage: https://patrol.leancode.co repository: https://github.com/leancodepl/patrol/tree/master/packages/patrol issue_tracker: https://github.com/leancodepl/patrol/issues