Skip to content

Commit

Permalink
Merge pull request #1721 from leancodepl/lifecycle_callbacks
Browse files Browse the repository at this point in the history
Add support for basic test lifecycle callbacks
  • Loading branch information
bartekpacia authored Oct 13, 2023
2 parents 555609d + f87bdf5 commit dd2ddc0
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 37 deletions.
17 changes: 17 additions & 0 deletions dev_docs/GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Working on the test bundling feature

`adb logcat` is your friend. Spice it up with `-v color`. If you need something
more powerful, check out [`purr`](https://github.com/google/purr).

### Find out when a test starts

Search for `TestRunner: started`.

```
09-21 12:24:09.223 23387 23406 I TestRunner: started: runDartTest[callbacks_test testA](pl.leancode.patrol.example.MainActivityTest)
```

### Find out when a test ends

Search for `TestRunner: finished`.
4 changes: 4 additions & 0 deletions packages/patrol/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.4.0-dev.3

- Add support for iOS 11 and 12 (#1733)

## 2.3.1

- Add support for iOS 11 and 12 (#1733)
Expand Down
57 changes: 57 additions & 0 deletions packages/patrol/example/integration_test/callbacks_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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() {
patrolSetUp(() async {
await Future<void>.delayed(Duration(seconds: 1));
_print('setting up before $currentTest');
});

patrolTearDown(() async {
await Future<void>.delayed(Duration(seconds: 1));
_print('tearing down after $currentTest');
});

patrolTest('testFirst', nativeAutomation: true, _body);

group('groupA', () {
patrolSetUp(() async {
if (currentTest == 'callbacks_test groupA testB') {
throw Exception('PATROL_DEBUG: Crashing testB on purpose!');
}
_print('setting up before $currentTest');
});

patrolTearDown(() async {
_print('tearing down after $currentTest');
});

patrolTest('testA', nativeAutomation: true, _body);
patrolTest('testB', nativeAutomation: true, _body);
patrolTest('testC', nativeAutomation: true, _body);
});

patrolTest('testLast', nativeAutomation: true, _body);
}

Future<void> _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));
}
29 changes: 18 additions & 11 deletions packages/patrol/lib/src/binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class PatrolBinding extends IntegrationTestWidgetsFlutterBinding {
///
/// You most likely don't want to call it yourself.
PatrolBinding() {
logger('created');
final oldTestExceptionReporter = reportTestException;
reportTestException = (details, testDescription) {
final currentDartTest = _currentDartTest;
Expand All @@ -52,7 +53,13 @@ class PatrolBinding extends IntegrationTestWidgetsFlutterBinding {
return;
}

if (global_state.currentTestIndividualName == 'patrol_test_explorer') {
// Ignore the fake test.
return;
}

_currentDartTest = global_state.currentTestFullName;
logger('setUp(): called with current Dart test = "$_currentDartTest"');
});

tearDown(() async {
Expand All @@ -64,24 +71,24 @@ class PatrolBinding extends IntegrationTestWidgetsFlutterBinding {
final testName = global_state.currentTestIndividualName;
final isTestExplorer = testName == 'patrol_test_explorer';
if (isTestExplorer) {
// Ignore the fake test.
return;
} else {
logger(
'tearDown(): count: ${_testResults.length}, results: $_testResults',
);
}

final nameOfRequestedTest = await patrolAppService.testExecutionRequested;
logger('tearDown(): called with current Dart test = "$_currentDartTest"');
logger('tearDown(): there are ${_testResults.length} test results:');
_testResults.forEach((dartTestName, result) {
logger('tearDown(): test "$dartTestName": "$result"');
});

if (nameOfRequestedTest == _currentDartTest) {
final requestedDartTest = await patrolAppService.testExecutionRequested;
if (requestedDartTest == _currentDartTest) {
logger(
'finished test $_currentDartTest. Will report its status back to the native side',
'tearDown(): finished test "$_currentDartTest". Will report its status back to the native side',
);

final passed = global_state.isCurrentTestPassing;
logger(
'tearDown(): test "$testName" in group "$_currentDartTest", passed: $passed',
);
logger('tearDown(): test "$_currentDartTest", passed: $passed');
await patrolAppService.markDartTestAsCompleted(
dartFileName: _currentDartTest!,
passed: passed,
Expand All @@ -91,7 +98,7 @@ class PatrolBinding extends IntegrationTestWidgetsFlutterBinding {
);
} else {
logger(
'finished test $_currentDartTest, but it was not requested, so its status will not be reported back to the native side',
'tearDown(): finished test "$_currentDartTest", but it was not requested, so its status will not be reported back to the native side',
);
}
});
Expand Down
57 changes: 35 additions & 22 deletions packages/patrol/lib/src/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,34 @@ import 'custom_finders/patrol_integration_tester.dart';
/// Signature for callback to [patrolTest].
typedef PatrolTesterCallback = Future<void> Function(PatrolIntegrationTester $);

/// A modification of [setUp] that works with Patrol's native automation.
void patrolSetUp(Future<void> Function() body) {
setUp(() async {
final currentTest = global_state.currentTestFullName;

final requestedToExecute = await PatrolBinding.instance.patrolAppService
.waitForExecutionRequest(currentTest);

if (requestedToExecute) {
await body();
}
});
}

/// A modification of [tearDown] that works with Patrol's native automation.
void patrolTearDown(Future<void> Function() body) {
tearDown(() async {
final currentTest = global_state.currentTestFullName;

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].
Expand Down Expand Up @@ -58,8 +86,6 @@ void patrolTest(
}) {
NativeAutomator? automator;

PatrolBinding? patrolBinding;

if (!nativeAutomation) {
debugPrint('''
╔════════════════════════════════════════════════════════════════════════════════════╗
Expand All @@ -76,8 +102,8 @@ void patrolTest(
case BindingType.patrol:
automator = NativeAutomator(config: nativeAutomatorConfig);

patrolBinding = PatrolBinding.ensureInitialized();
patrolBinding.framePolicy = framePolicy;
// PatrolBinding is initialized in the generated test bundle file.
PatrolBinding.instance.framePolicy = framePolicy;
break;
case BindingType.integrationTest:
IntegrationTestWidgetsFlutterBinding.ensureInitialized().framePolicy =
Expand All @@ -97,28 +123,16 @@ void patrolTest(
variant: variant,
tags: tags,
(widgetTester) async {
if (patrolBinding != null && !constants.hotRestartEnabled) {
if (!constants.hotRestartEnabled) {
// If Patrol's native automation feature is enabled, then this test will
// be executed only if the native side requested it to be executed.
// Otherwise, it returns early.
//
// The assumption here is that this test doesn't have any extra parent
// groups. Every Dart test suite has an implict, unnamed, top-level
// group. An additional group is present in the bundled_test.dart, and
// its name is equal to the path to the Dart test file in the
// integration_test directory.
//
// In other words, the developer cannot use `group()` in the tests.
//
// Example: if this function is called from the Dart test file named
// "example_test.dart", and that file is located in the
// "integration_test/examples" directory, we assume that the name of the
// immediate parent group is "examples.example_test".

final requestedToExecute = await patrolBinding.patrolAppService

final isRequestedToExecute = await PatrolBinding
.instance.patrolAppService
.waitForExecutionRequest(global_state.currentTestFullName);

if (!requestedToExecute) {
if (!isRequestedToExecute) {
return;
}
}
Expand Down Expand Up @@ -202,7 +216,6 @@ DartGroupEntry createDartTestGroup(
);
} else if (entry is Test) {
if (entry.name == 'patrol_test_explorer') {
// throw StateError('Expected group, got test: ${entry.name}');
// Ignore the bogus test that is used to discover the test structure.
continue;
}
Expand Down
19 changes: 19 additions & 0 deletions packages/patrol/lib/src/extensions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:meta/meta.dart';
// ignore: implementation_imports
import 'package:test_api/src/backend/invoker.dart';

/// Provides convenience methods for [Invoker].
@internal
extension InvokerX on Invoker {
/// Returns the full name of the current test (names of all ancestor groups +
/// name of the current test).
String fullCurrentTestName() {
final parentGroupName = liveTest.groups.last.name;
final testName = liveTest.individualName;

return '$parentGroupName $testName';
}

/// Returns the name of the current test only. No group prefixes.
String get currentTestName => liveTest.individualName;
}
5 changes: 2 additions & 3 deletions packages/patrol/lib/src/native/patrol_app_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
// TODO: Use a logger instead of print

import 'dart:async';
import 'dart:io';
import 'dart:io' as io;

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:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as shelf_io;

Expand All @@ -30,7 +29,7 @@ Future<void> runAppService(PatrolAppService service) async {

final server = await shelf_io.serve(
pipeline,
InternetAddress.anyIPv4,
io.InternetAddress.anyIPv4,
_port,
poweredByHeader: null,
);
Expand Down
2 changes: 1 addition & 1 deletion packages/patrol/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: patrol
description: >
Powerful Flutter-native UI testing framework overcoming limitations of
existing Flutter testing tools. Ready for action!
version: 2.3.1
version: 2.4.0-dev.3
homepage: https://patrol.leancode.co
repository: https://github.com/leancodepl/patrol
issue_tracker: https://github.com/leancodepl/patrol/issues
Expand Down

0 comments on commit dd2ddc0

Please sign in to comment.