Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for basic test lifecycle callbacks #1721

Merged
merged 25 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e9b8e04
start playing around
bartekpacia Sep 19, 2023
f238d82
test out tearDown()
bartekpacia Sep 20, 2023
9f63eeb
create full mini example
bartekpacia Sep 21, 2023
877bdae
implement `patrolSetUp()` and `patrolTearDown()`
bartekpacia Sep 21, 2023
3ded673
create `callbacks_test.dart`
bartekpacia Sep 21, 2023
2ea7141
add simple guide for working on test bundling feature to dev_docs/GUIDE
bartekpacia Sep 21, 2023
9137fc1
callbacks_test: add top-level tests (to test out more complex scenario)
bartekpacia Sep 21, 2023
055a82e
common.dart: answer my own questions about possible problems
bartekpacia Sep 21, 2023
a2fd8f2
replace most of `PatrolBinding.ensureInitialized()` with `PatrolBindi…
bartekpacia Sep 21, 2023
561439b
test crashing on purpose
bartekpacia Sep 21, 2023
1359386
PatrolBinding: improve and standarize logging
bartekpacia Sep 21, 2023
596c1b2
delete playground (test/callbacks_test.dart)
bartekpacia Sep 22, 2023
fa4bc76
clean up redundant and outdated comments
bartekpacia Sep 22, 2023
b8ca920
Add support for ios 11 and 12
zltnDC Sep 19, 2023
256cf5c
Fix swift-format issues
zltnDC Sep 20, 2023
0d0da06
Log patrolServer.start() failed
zltnDC Sep 20, 2023
3ea92f5
Fix clang-format
zltnDC Sep 20, 2023
9e6de0c
Fix clang format
zltnDC Sep 20, 2023
f957daa
Merge pull request #1733 from leancodepl/feature/add-support-for-iOS1…
bartekpacia Oct 2, 2023
fee7277
patrol: bump version to 2.4.0-dev.1
bartekpacia Oct 2, 2023
1bc126b
Merge pull request #1764 from leancodepl/release_new_dev_versions
bartekpacia Oct 2, 2023
889da14
Merge branch 'develop' into lifecycle_callbacks
bartekpacia Oct 2, 2023
44dc28b
remove usages of Invoker that slept in
bartekpacia Oct 2, 2023
b9628a3
Merge branch 'develop' into lifecycle_callbacks
bartekpacia Oct 13, 2023
f87bdf5
PatrolBinding: fix bug in detecting patrol_test_explorer in setUp()
bartekpacia Oct 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading