diff --git a/packages/patrol/example/integration_test/internal/callbacks_all_test.dart b/packages/patrol/example/integration_test/internal/callbacks_all_test.dart new file mode 100644 index 000000000..0995a81fe --- /dev/null +++ b/packages/patrol/example/integration_test/internal/callbacks_all_test.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:patrol/src/extensions.dart'; +// ignore: depend_on_referenced_packages +import 'package:test_api/src/backend/invoker.dart'; + +import '../common.dart'; + +String get currentTest => Invoker.current!.fullCurrentTestName(); + +void _print(String text) => print('PATROL_DEBUG: $text'); + +void main() { + patrolSetUpAll(() async { + await Future.delayed(Duration(seconds: 1)); + _print('setting up all before $currentTest'); + }); + + patrolTest('testA', nativeAutomation: true, _body); + patrolTest('testB', nativeAutomation: true, _body); + patrolTest('testC', nativeAutomation: true, _body); +} + +Future _body(PatrolTester $) async { + final testName = Invoker.current!.fullCurrentTestName(); + _print('test body: name=$testName'); + + await createApp($); + + await $(FloatingActionButton).tap(); + expect($(#counterText).text, '1'); + + await $(#textField).enterText(testName); + + await $.pumpAndSettle(duration: Duration(seconds: 2)); +} diff --git a/packages/patrol/example/test/callbacks_test.dart b/packages/patrol/example/test/callbacks_test.dart new file mode 100644 index 000000000..fb7f325d8 --- /dev/null +++ b/packages/patrol/example/test/callbacks_test.dart @@ -0,0 +1,45 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:patrol/src/extensions.dart'; +// ignore: depend_on_referenced_packages +import 'package:test_api/src/backend/invoker.dart'; + +const String requestedTest = 'testA'; + +String get currentTest => Invoker.current!.fullCurrentTestName(); + +void main() { + group('alpha', () { + setUpAll(() { + final groupName = Invoker.current!.liveTest.groups.last.name; + final individualName = Invoker.current!.liveTest.individualName; + print( + 'setUpAll: parentGroupName=$groupName, individualName=$individualName', + ); + }); + + setUp(() { + final groupName = Invoker.current!.liveTest.groups.last.name; + final individualName = Invoker.current!.liveTest.individualName; + print( + 'setUpAll: parentGroupName=$groupName, individualName=$individualName', + ); + }); + + patrolTest('testA', _body); + patrolTest('testB', _body); + patrolTest('testC', _body); + }); +} + +Future _body() async => print(Invoker.current!.fullCurrentTestName()); + +void patrolTest(String name, Future Function() body) { + test(name, () async { + final currentTest = Invoker.current!.fullCurrentTestName(); + + if (currentTest == requestedTest) { + print('Requested test $currentTest, will execute it'); + await body(); + } + }); +} diff --git a/packages/patrol/lib/src/common.dart b/packages/patrol/lib/src/common.dart index b6b479e00..7c81a0f2c 100644 --- a/packages/patrol/lib/src/common.dart +++ b/packages/patrol/lib/src/common.dart @@ -48,6 +48,25 @@ void patrolTearDown(Future Function() body) { }); } +/// A modification of [setUpAll] that works with Patrol's native automation. +void patrolSetUpAll(Future Function() body) { + setUpAll(() async { + final currentTest = global_state.currentTestFullName; + + final parentGroups = global_state.currentGroupFullName; + + final patrolAppService = PatrolBinding.instance.patrolAppService; + final currentSetUpAllIndex = patrolAppService.setUpAllCount += 1; + + final requestedToExecute = await PatrolBinding.instance.patrolAppService + .waitForExecutionRequest(currentTest); + + if (requestedToExecute) { + await body(); + } + }); +} + /// Like [testWidgets], but with support for Patrol custom finders. /// /// To customize the Patrol-specific configuration, set [config]. diff --git a/packages/patrol/lib/src/global_state.dart b/packages/patrol/lib/src/global_state.dart index 318574d3e..4abf8d264 100644 --- a/packages/patrol/lib/src/global_state.dart +++ b/packages/patrol/lib/src/global_state.dart @@ -24,6 +24,10 @@ String get currentTestFullName { return nameCandidate; } +String get currentGroupFullName { + return Invoker.current!.liveTest.groups.last.name; +} + /// Returns the individual name of the current test. Omits all ancestor groups. String get currentTestIndividualName { return Invoker.current!.liveTest.individualName; diff --git a/packages/patrol/lib/src/native/patrol_app_service.dart b/packages/patrol/lib/src/native/patrol_app_service.dart index f67806ff7..d0a3544c8 100644 --- a/packages/patrol/lib/src/native/patrol_app_service.dart +++ b/packages/patrol/lib/src/native/patrol_app_service.dart @@ -10,6 +10,8 @@ import 'package:patrol/src/native/contracts/contracts.dart'; import 'package:patrol/src/native/contracts/patrol_app_service_server.dart'; import 'package:shelf/shelf.dart' as shelf; import 'package:shelf/shelf_io.dart' as shelf_io; +// ignore: implementation_imports +import 'package:test_api/src/backend/live_test.dart'; const _port = 8082; const _idleTimeout = Duration(hours: 2); @@ -55,6 +57,13 @@ class PatrolAppService extends PatrolAppServiceServer { /// bundled Dart test file. final DartGroupEntry topLevelDartTestGroup; + /// The number of all setUpAll callbacks. + /// + /// setUpAlls, unlike setUps, aren't executed in the [LiveTest] context. + /// Because of this, we can't depend on the [LiveTest]'s name, so we identify + /// them by indexes instead. + int setUpAllCount = 0; + /// A completer that completes with the name of the Dart test file that was /// requested to execute by the native side. final _testExecutionRequested = Completer(); @@ -136,6 +145,8 @@ class PatrolAppService extends PatrolAppServiceServer { return ListDartTestsResponse(group: topLevelDartTestGroup); } + + @override Future runDartTest(RunDartTestRequest request) async { assert(_testExecutionCompleted.isCompleted == false);