From 6d3ab7049b4245e8b864a7fb0d98ff1669112b95 Mon Sep 17 00:00:00 2001 From: karolinaoparczyk Date: Mon, 7 Oct 2024 12:56:53 +0200 Subject: [PATCH 1/7] Create OverflowScreen --- dev/e2e_app/lib/main.dart | 9 +++++++++ dev/e2e_app/lib/overflow_screen.dart | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 dev/e2e_app/lib/overflow_screen.dart 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..d98ccb8f5 --- /dev/null +++ b/dev/e2e_app/lib/overflow_screen.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +class OverflowScreen extends StatelessWidget { + const OverflowScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Overflow'), + ), + body: Row( + children: const [ + SizedBox( + width: 500, + child: Text('This container is too wide for the row'), + ), + Icon(Icons.star, size: 50), + ], + ), + ); + } +} From 7c82d8d4d39bc1bf07e97a3536f4383b1d3c82ce Mon Sep 17 00:00:00 2001 From: karolinaoparczyk Date: Mon, 7 Oct 2024 14:38:40 +0200 Subject: [PATCH 2/7] Print all flutter exceptions in report --- .../integration_test/overflow_test.dart | 9 +++++++++ dev/e2e_app/lib/overflow_screen.dart | 14 ++++++++++++++ packages/patrol/lib/src/binding.dart | 16 ++++++++-------- .../lib/src/native/patrol_app_service.dart | 19 +++++++++++++++++++ 4 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 dev/e2e_app/integration_test/overflow_test.dart 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..2e4f345b0 --- /dev/null +++ b/dev/e2e_app/integration_test/overflow_test.dart @@ -0,0 +1,9 @@ +import 'common.dart'; + +void main() { + patrol('opens the overflow screen', ($) async { + await createApp($); + + await $('Open overflow screen').scrollTo().tap(); + }); +} diff --git a/dev/e2e_app/lib/overflow_screen.dart b/dev/e2e_app/lib/overflow_screen.dart index d98ccb8f5..a7fd1d6e7 100644 --- a/dev/e2e_app/lib/overflow_screen.dart +++ b/dev/e2e_app/lib/overflow_screen.dart @@ -1,10 +1,24 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; class OverflowScreen extends StatelessWidget { const OverflowScreen({super.key}); @override Widget build(BuildContext context) { + Future throwStackOverflowException() async { + await Future.delayed(const Duration(milliseconds: 100)); + throw StackOverflowError(); + } + + Future throwPlatformException() async { + await Future.delayed(const Duration(milliseconds: 150)); + throw PlatformException(code: 'code'); + } + + throwStackOverflowException(); + throwPlatformException(); + return Scaffold( appBar: AppBar( title: const Text('Overflow'), diff --git a/packages/patrol/lib/src/binding.dart b/packages/patrol/lib/src/binding.dart index 47407ab1e..ff3faa2ad 100644 --- a/packages/patrol/lib/src/binding.dart +++ b/packages/patrol/lib/src/binding.dart @@ -49,8 +49,7 @@ class PatrolBinding extends LiveTestWidgetsFlutterBinding { /// 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) { + if (_currentDartTest case final testName?) { assert(!constants.hotRestartEnabled); // On iOS in release mode, diagnostics are compacted or truncated. // We use the exceptionAsString() and stack to get the information @@ -58,7 +57,8 @@ class PatrolBinding extends LiveTestWidgetsFlutterBinding { final detailsAsString = (kReleaseMode && io.Platform.isIOS) ? '${details.exceptionAsString()} \n ${details.stack}' : details.toString(); - _testResults[currentDartTest] = Failure( + + testResults[testName] = Failure( testDescription, detailsAsString, ); @@ -89,7 +89,7 @@ class PatrolBinding extends LiveTestWidgetsFlutterBinding { return; } else { logger( - 'tearDown(): count: ${_testResults.length}, results: $_testResults', + 'tearDown(): count: ${testResults.length}, results: $testResults', ); } @@ -127,8 +127,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 +178,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 +235,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..cf45a16fc 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,23 @@ class PatrolAppService extends PatrolAppServiceServer { print('PatrolAppService.runDartTest(${request.name}) called'); _testExecutionRequested.complete(request.name); + final patrolBinding = + PatrolBinding.ensureInitialized(const NativeAutomatorConfig()); + + FlutterError.onError = (details) { + final previousDetails = + switch (patrolBinding.testResults[request.name] as Failure?) { + Failure(:final details?) => FlutterErrorDetails(exception: details), + _ => null, + }; + + patrolBinding.testResults[request.name] = Failure( + request.name, + '$details\n$previousDetails', + ); + }; final testExecutionResult = await testExecutionCompleted; + return RunDartTestResponse( result: testExecutionResult.passed ? RunDartTestResponseResult.success From edd8aaf86f3a9d3e0bb0d788cc3cfb539c278fd2 Mon Sep 17 00:00:00 2001 From: karolinaoparczyk Date: Tue, 8 Oct 2024 14:49:56 +0200 Subject: [PATCH 3/7] Rename failing test --- .../{overflow_test.dart => overflow_test_failures.dart} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dev/e2e_app/integration_test/{overflow_test.dart => overflow_test_failures.dart} (100%) diff --git a/dev/e2e_app/integration_test/overflow_test.dart b/dev/e2e_app/integration_test/overflow_test_failures.dart similarity index 100% rename from dev/e2e_app/integration_test/overflow_test.dart rename to dev/e2e_app/integration_test/overflow_test_failures.dart From fdd2322b716df0a09f52c6b7895978e7812c2de3 Mon Sep 17 00:00:00 2001 From: karolinaoparczyk Date: Tue, 8 Oct 2024 15:06:27 +0200 Subject: [PATCH 4/7] Restore onError --- packages/patrol/lib/src/native/patrol_app_service.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/patrol/lib/src/native/patrol_app_service.dart b/packages/patrol/lib/src/native/patrol_app_service.dart index cf45a16fc..451d5c3ad 100644 --- a/packages/patrol/lib/src/native/patrol_app_service.dart +++ b/packages/patrol/lib/src/native/patrol_app_service.dart @@ -156,6 +156,7 @@ class PatrolAppService extends PatrolAppServiceServer { final patrolBinding = PatrolBinding.ensureInitialized(const NativeAutomatorConfig()); + final previousOnError = FlutterError.onError; FlutterError.onError = (details) { final previousDetails = switch (patrolBinding.testResults[request.name] as Failure?) { @@ -169,6 +170,7 @@ class PatrolAppService extends PatrolAppServiceServer { ); }; final testExecutionResult = await testExecutionCompleted; + FlutterError.onError = previousOnError; return RunDartTestResponse( result: testExecutionResult.passed From 70ffff4ceb555de0bca6420579ebc268abc0397c Mon Sep 17 00:00:00 2001 From: karolinaoparczyk Date: Tue, 8 Oct 2024 15:49:03 +0200 Subject: [PATCH 5/7] Rename test and comment exceptions on screen --- ..._test_failures.dart => overflow_test.dart} | 0 dev/e2e_app/lib/overflow_screen.dart | 36 ++++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) rename dev/e2e_app/integration_test/{overflow_test_failures.dart => overflow_test.dart} (100%) diff --git a/dev/e2e_app/integration_test/overflow_test_failures.dart b/dev/e2e_app/integration_test/overflow_test.dart similarity index 100% rename from dev/e2e_app/integration_test/overflow_test_failures.dart rename to dev/e2e_app/integration_test/overflow_test.dart diff --git a/dev/e2e_app/lib/overflow_screen.dart b/dev/e2e_app/lib/overflow_screen.dart index a7fd1d6e7..e36de8c2d 100644 --- a/dev/e2e_app/lib/overflow_screen.dart +++ b/dev/e2e_app/lib/overflow_screen.dart @@ -1,23 +1,23 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; class OverflowScreen extends StatelessWidget { const OverflowScreen({super.key}); @override Widget build(BuildContext context) { - Future throwStackOverflowException() async { - await Future.delayed(const Duration(milliseconds: 100)); - throw StackOverflowError(); - } - - Future throwPlatformException() async { - await Future.delayed(const Duration(milliseconds: 150)); - throw PlatformException(code: 'code'); - } - - throwStackOverflowException(); - throwPlatformException(); + // 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: 150)); + // throw PlatformException(code: 'code'); + // } + // + // throwStackOverflowException(); + // throwPlatformException(); return Scaffold( appBar: AppBar( @@ -25,11 +25,13 @@ class OverflowScreen extends StatelessWidget { ), body: Row( children: const [ - SizedBox( - width: 500, - child: Text('This container is too wide for the row'), - ), + // 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), ], ), ); From 5a57113653632748cc7dbfc23668e88bc6f29da0 Mon Sep 17 00:00:00 2001 From: karolinaoparczyk Date: Tue, 8 Oct 2024 17:01:16 +0200 Subject: [PATCH 6/7] Upgrade patrol --- packages/patrol/CHANGELOG.md | 4 ++++ packages/patrol/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) 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/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 From b233a2441cf49fc26b4ea5595bff16feaf4121f8 Mon Sep 17 00:00:00 2001 From: karolinaoparczyk Date: Thu, 10 Oct 2024 18:15:55 +0200 Subject: [PATCH 7/7] Fix onError when using patrol finders --- .../integration_test/overflow_test.dart | 6 +++++ dev/e2e_app/lib/overflow_screen.dart | 10 +++++++- packages/patrol/lib/src/binding.dart | 23 ------------------- .../lib/src/native/patrol_app_service.dart | 11 ++++++--- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/dev/e2e_app/integration_test/overflow_test.dart b/dev/e2e_app/integration_test/overflow_test.dart index 2e4f345b0..36108aee0 100644 --- a/dev/e2e_app/integration_test/overflow_test.dart +++ b/dev/e2e_app/integration_test/overflow_test.dart @@ -1,3 +1,6 @@ +// Uncomment to test `Multiple exceptions were thrown` issue +// import 'package:flutter/widgets.dart'; + import 'common.dart'; void main() { @@ -5,5 +8,8 @@ void main() { 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/overflow_screen.dart b/dev/e2e_app/lib/overflow_screen.dart index e36de8c2d..51c838621 100644 --- a/dev/e2e_app/lib/overflow_screen.dart +++ b/dev/e2e_app/lib/overflow_screen.dart @@ -1,4 +1,6 @@ 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}); @@ -12,12 +14,18 @@ class OverflowScreen extends StatelessWidget { // } // // Future throwPlatformException() async { - // await Future.delayed(const Duration(milliseconds: 150)); + // 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( diff --git a/packages/patrol/lib/src/binding.dart b/packages/patrol/lib/src/binding.dart index ff3faa2ad..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) { - if (_currentDartTest case final testName?) { - 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[testName] = Failure( - testDescription, - detailsAsString, - ); - } - oldTestExceptionReporter(details, testDescription); - }; - setUp(() { if (constants.hotRestartEnabled) { return; diff --git a/packages/patrol/lib/src/native/patrol_app_service.dart b/packages/patrol/lib/src/native/patrol_app_service.dart index 451d5c3ad..acf513b24 100644 --- a/packages/patrol/lib/src/native/patrol_app_service.dart +++ b/packages/patrol/lib/src/native/patrol_app_service.dart @@ -158,17 +158,22 @@ class PatrolAppService extends PatrolAppServiceServer { final previousOnError = FlutterError.onError; FlutterError.onError = (details) { - final previousDetails = - switch (patrolBinding.testResults[request.name] as Failure?) { + 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, - '$details\n$previousDetails', + '$detailsAsString\n$previousDetails', ); + + previousOnError?.call(details); }; + final testExecutionResult = await testExecutionCompleted; FlutterError.onError = previousOnError;