From 9e8e5fafa08e93c11230695137de48d985ba83dd Mon Sep 17 00:00:00 2001 From: pdenert Date: Thu, 25 Jul 2024 13:02:41 +0200 Subject: [PATCH 01/10] Add native skip on android --- .../patrol/e2e_app/MainActivityTest.java | 19 ++++- .../integration_test/android_app_test.dart | 30 +++++++ .../pl/leancode/patrol/PatrolJUnitRunner.java | 13 +-- .../pl/leancode/patrol/contracts/Contracts.kt | 3 +- .../Classes/AutomatorServer/Contracts.swift | 1 + .../PatrolAppServiceClient.swift | 4 - .../patrol/example/MainActivityTest.java | 21 +++-- packages/patrol/lib/src/common.dart | 51 ++++++------ .../lib/src/native/contracts/contracts.dart | 3 + .../lib/src/native/contracts/contracts.g.dart | 2 + packages/patrol/test/internals_test.dart | 81 ++++++++++++++++++- .../lib/api/contracts.dart | 3 + .../lib/api/contracts.g.dart | 2 + schema.dart | 1 + 14 files changed, 188 insertions(+), 46 deletions(-) diff --git a/dev/e2e_app/android/app/src/androidTest/java/pl/leancode/patrol/e2e_app/MainActivityTest.java b/dev/e2e_app/android/app/src/androidTest/java/pl/leancode/patrol/e2e_app/MainActivityTest.java index c59e67de8..74a0b0329 100644 --- a/dev/e2e_app/android/app/src/androidTest/java/pl/leancode/patrol/e2e_app/MainActivityTest.java +++ b/dev/e2e_app/android/app/src/androidTest/java/pl/leancode/patrol/e2e_app/MainActivityTest.java @@ -1,30 +1,41 @@ package pl.leancode.patrol.e2e_app; +import static org.junit.Assume.*; + +import java.util.Collection; + import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import pl.leancode.patrol.PatrolJUnitRunner; + @RunWith(Parameterized.class) public class MainActivityTest { - @Parameters(name = "{0}") - public static Object[] testCases() { + @Parameters + public static Collection testCases() { PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation(); instrumentation.setUp(MainActivity.class); instrumentation.waitForPatrolAppService(); return instrumentation.listDartTests(); } - public MainActivityTest(String dartTestName) { + public MainActivityTest(String dartTestName, Boolean skip) { this.dartTestName = dartTestName; + this.skip = skip; } - private final String dartTestName; + private String dartTestName; + + private Boolean skip; @Test public void runDartTest() { + assumeFalse(skip); + PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation(); instrumentation.runDartTest(dartTestName); } diff --git a/dev/e2e_app/integration_test/android_app_test.dart b/dev/e2e_app/integration_test/android_app_test.dart index 26ec80c88..7b9474516 100644 --- a/dev/e2e_app/integration_test/android_app_test.dart +++ b/dev/e2e_app/integration_test/android_app_test.dart @@ -23,4 +23,34 @@ void main() { await $.native.pressBack(); }); + + patrol('taps around 2', skip: true, ($) async { + await createApp($); + + await $.native.pressHome(); + await $.native.pressDoubleRecentApps(); + + await $.native.openNotifications(); + + await $.native.enableWifi(); + await $.native.disableWifi(); + await $.native.enableWifi(); + + await $.native.enableCellular(); + await $.native.disableCellular(); + await $.native.enableCellular(); + + await $.native.enableDarkMode(); + await $.native.disableDarkMode(); + await $.native.enableDarkMode(); + + await $.native.pressBack(); + }); + + patrol('taps around 3', ($) async { + await createApp($); + + await $.native.pressHome(); + await $.native.pressDoubleRecentApps(); + }); } diff --git a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/PatrolJUnitRunner.java b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/PatrolJUnitRunner.java index 03c969a7e..6914f33fd 100644 --- a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/PatrolJUnitRunner.java +++ b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/PatrolJUnitRunner.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.concurrent.ExecutionException; @@ -102,19 +103,19 @@ public void waitForPatrolAppService() { Logger.INSTANCE.i(TAG + "PatrolAppService is ready to report Dart tests"); } - public Object[] listDartTests() { + public Collection listDartTests() { final String TAG = "PatrolJUnitRunner.listDartTests(): "; try { final DartGroupEntry dartTestGroup = patrolAppServiceClient.listDartTests(); List dartTestCases = ContractsExtensionsKt.listTestsFlat(dartTestGroup, ""); - List dartTestCaseNamesList = new ArrayList<>(); + List dartTestCaseNamesList = new ArrayList<>(); for (DartGroupEntry dartTestCase : dartTestCases) { - dartTestCaseNamesList.add(dartTestCase.getName()); + Object[] dartTestCaseName = {dartTestCase.getName(), dartTestCase.getSkip()}; + dartTestCaseNamesList.add(dartTestCaseName); } - Object[] dartTestCaseNames = dartTestCaseNamesList.toArray(); - Logger.INSTANCE.i(TAG + "Got Dart tests: " + Arrays.toString(dartTestCaseNames)); - return dartTestCaseNames; + Logger.INSTANCE.i(TAG + "Got Dart tests: " + Arrays.deepToString(dartTestCaseNamesList.toArray(new Object[0][]))); + return dartTestCaseNamesList; } catch (PatrolAppServiceClientException e) { Logger.INSTANCE.e(TAG + "Failed to list Dart tests: ", e); throw new RuntimeException(e); diff --git a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/Contracts.kt b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/Contracts.kt index 44ccd4925..2cf87d080 100644 --- a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/Contracts.kt +++ b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/Contracts.kt @@ -122,7 +122,8 @@ class Contracts { data class DartGroupEntry ( val name: String, val type: GroupEntryType, - val entries: List + val entries: List, + val skip: Boolean ) data class ListDartTestsResponse ( diff --git a/packages/patrol/darwin/Classes/AutomatorServer/Contracts.swift b/packages/patrol/darwin/Classes/AutomatorServer/Contracts.swift index a9d63cae1..81fc1567a 100644 --- a/packages/patrol/darwin/Classes/AutomatorServer/Contracts.swift +++ b/packages/patrol/darwin/Classes/AutomatorServer/Contracts.swift @@ -122,6 +122,7 @@ public struct DartGroupEntry: Codable { public var name: String public var type: GroupEntryType public var entries: [DartGroupEntry] + public var skip: Bool } public struct ListDartTestsResponse: Codable { diff --git a/packages/patrol/darwin/Classes/AutomatorServer/PatrolAppServiceClient.swift b/packages/patrol/darwin/Classes/AutomatorServer/PatrolAppServiceClient.swift index 19f8d7e4f..f48791431 100644 --- a/packages/patrol/darwin/Classes/AutomatorServer/PatrolAppServiceClient.swift +++ b/packages/patrol/darwin/Classes/AutomatorServer/PatrolAppServiceClient.swift @@ -69,10 +69,6 @@ class PatrolAppServiceClient { urlconfig.timeoutIntervalForRequest = timeout urlconfig.timeoutIntervalForResource = timeout - urlconfig.connectionProxyDictionary = [ - kCFNetworkProxiesHTTPEnable: false - ] - var request = URLRequest(url: url) request.httpMethod = "POST" request.httpBody = body diff --git a/packages/patrol/example/android/app/src/androidTest/java/pl/leancode/patrol/example/MainActivityTest.java b/packages/patrol/example/android/app/src/androidTest/java/pl/leancode/patrol/example/MainActivityTest.java index 675e46464..74a0b0329 100644 --- a/packages/patrol/example/android/app/src/androidTest/java/pl/leancode/patrol/example/MainActivityTest.java +++ b/packages/patrol/example/android/app/src/androidTest/java/pl/leancode/patrol/example/MainActivityTest.java @@ -1,30 +1,41 @@ -package pl.leancode.patrol.example; +package pl.leancode.patrol.e2e_app; + +import static org.junit.Assume.*; + +import java.util.Collection; import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import pl.leancode.patrol.PatrolJUnitRunner; + @RunWith(Parameterized.class) public class MainActivityTest { - @Parameters(name = "{0}") - public static Object[] testCases() { + @Parameters + public static Collection testCases() { PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation(); instrumentation.setUp(MainActivity.class); instrumentation.waitForPatrolAppService(); return instrumentation.listDartTests(); } - public MainActivityTest(String dartTestName) { + public MainActivityTest(String dartTestName, Boolean skip) { this.dartTestName = dartTestName; + this.skip = skip; } - private final String dartTestName; + private String dartTestName; + + private Boolean skip; @Test public void runDartTest() { + assumeFalse(skip); + PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation(); instrumentation.runDartTest(dartTestName); } diff --git a/packages/patrol/lib/src/common.dart b/packages/patrol/lib/src/common.dart index f119a60be..551e1b64c 100644 --- a/packages/patrol/lib/src/common.dart +++ b/packages/patrol/lib/src/common.dart @@ -174,6 +174,7 @@ DartGroupEntry createDartTestGroup( name: name, type: GroupEntryType.group, entries: [], + skip: parentGroup.metadata.skip, ); for (final entry in parentGroup.entries) { @@ -191,32 +192,34 @@ DartGroupEntry createDartTestGroup( name = deduplicateGroupEntryName(parentGroup.name, name); } - if (entry is Group) { - groupDTO.entries.add( - createDartTestGroup( - entry, - name: name, - level: level + 1, - maxTestCaseLength: maxTestCaseLength, - ), - ); - } else if (entry is Test) { - if (entry.name == 'patrol_test_explorer') { - // Ignore the bogus test that is used to discover the test structure. - continue; - } + switch (entry) { + case Test _: + if (entry.name == 'patrol_test_explorer') { + // Ignore the bogus test that is used to discover the test structure. + continue; + } - if (level < 1) { - throw StateError('Test is not allowed to be defined at level $level'); - } + if (level < 1) { + throw StateError('Test is not allowed to be defined at level $level'); + } - groupDTO.entries.add( - DartGroupEntry(name: name, type: GroupEntryType.test, entries: []), - ); - } else { - // This should really never happen, because Group and Test are the only - // subclasses of GroupEntry. - throw StateError('invalid state'); + groupDTO.entries.add( + DartGroupEntry( + name: name, + type: GroupEntryType.test, + entries: [], + skip: entry.metadata.skip, + ), + ); + case Group _: + groupDTO.entries.add( + createDartTestGroup( + entry, + name: name, + level: level + 1, + maxTestCaseLength: maxTestCaseLength, + ), + ); } } diff --git a/packages/patrol/lib/src/native/contracts/contracts.dart b/packages/patrol/lib/src/native/contracts/contracts.dart index 9a423d560..6142c8e25 100644 --- a/packages/patrol/lib/src/native/contracts/contracts.dart +++ b/packages/patrol/lib/src/native/contracts/contracts.dart @@ -223,6 +223,7 @@ class DartGroupEntry with EquatableMixin { required this.name, required this.type, required this.entries, + required this.skip, }); factory DartGroupEntry.fromJson(Map json) => @@ -231,6 +232,7 @@ class DartGroupEntry with EquatableMixin { final String name; final GroupEntryType type; final List entries; + final bool skip; Map toJson() => _$DartGroupEntryToJson(this); @@ -239,6 +241,7 @@ class DartGroupEntry with EquatableMixin { name, type, entries, + skip, ]; } diff --git a/packages/patrol/lib/src/native/contracts/contracts.g.dart b/packages/patrol/lib/src/native/contracts/contracts.g.dart index 00cfba2f9..e94d108d7 100644 --- a/packages/patrol/lib/src/native/contracts/contracts.g.dart +++ b/packages/patrol/lib/src/native/contracts/contracts.g.dart @@ -13,6 +13,7 @@ DartGroupEntry _$DartGroupEntryFromJson(Map json) => entries: (json['entries'] as List) .map((e) => DartGroupEntry.fromJson(e as Map)) .toList(), + skip: json['skip'] as bool, ); Map _$DartGroupEntryToJson(DartGroupEntry instance) => @@ -20,6 +21,7 @@ Map _$DartGroupEntryToJson(DartGroupEntry instance) => 'name': instance.name, 'type': _$GroupEntryTypeEnumMap[instance.type]!, 'entries': instance.entries, + 'skip': instance.skip, }; const _$GroupEntryTypeEnumMap = { diff --git a/packages/patrol/test/internals_test.dart b/packages/patrol/test/internals_test.dart index 1c00becef..cadda704f 100644 --- a/packages/patrol/test/internals_test.dart +++ b/packages/patrol/test/internals_test.dart @@ -62,14 +62,17 @@ void main() { DartGroupEntry( name: '', type: GroupEntryType.group, + skip: false, entries: [ DartGroupEntry( name: 'example_test', type: GroupEntryType.group, + skip: false, entries: [ DartGroupEntry( name: 'alpha', type: GroupEntryType.group, + skip: false, entries: [ _testEntry('first'), _testEntry('second'), @@ -78,6 +81,7 @@ void main() { DartGroupEntry( name: 'bravo', type: GroupEntryType.group, + skip: false, entries: [ _testEntry('first'), _testEntry('second'), @@ -88,6 +92,7 @@ void main() { DartGroupEntry( name: 'open_app_test', type: GroupEntryType.group, + skip: false, entries: [ _testEntry('open maps'), _testEntry('open browser'), @@ -131,15 +136,18 @@ void main() { DartGroupEntry( name: '', type: GroupEntryType.group, + skip: false, entries: [ DartGroupEntry( name: 'example_test', type: GroupEntryType.group, + skip: false, entries: [ _testEntry('alpha'), DartGroupEntry( name: 'bravo', type: GroupEntryType.group, + skip: false, entries: [ _testEntry('first'), _testEntry('second'), @@ -149,6 +157,7 @@ void main() { DartGroupEntry( name: 'delta', type: GroupEntryType.group, + skip: false, entries: [ _testEntry('first'), _testEntry('second'), @@ -189,10 +198,12 @@ void main() { DartGroupEntry( name: '', type: GroupEntryType.group, + skip: false, entries: [ DartGroupEntry( name: 'example_test', type: GroupEntryType.group, + skip: false, entries: [ _testEntry('alpha'), _testEntry('zielony'), @@ -221,10 +232,76 @@ void main() { expect(result, equals('first')); }); }); + + group('skip group of tests', () { + test('skip test', () { + // given + final topLevelGroup = Group.root([ + LocalTest('patrol_test_explorer', Metadata.empty, () {}), + Group( + 'example_test', + [ + _localTest('example_test alpha'), + ], + metadata: Metadata(skip: true), + ), + Group( + 'example2_test', + [ + _localTest('example2_test alpha'), + _localTest('example2_test bravo first'), + _localTest('example2_test bravo second'), + ], + ), + ]); + + // when + final dartTestGroup = createDartTestGroup( + topLevelGroup, + ); + + // then + expect( + dartTestGroup, + equals( + DartGroupEntry( + name: '', + type: GroupEntryType.group, + entries: [ + DartGroupEntry( + name: 'example_test', + type: GroupEntryType.group, + entries: [ + _testEntry('alpha'), + ], + skip: true, + ), + DartGroupEntry( + name: 'example2_test', + type: GroupEntryType.group, + entries: [ + _testEntry('alpha'), + _testEntry('bravo first'), + _testEntry('bravo second'), + ], + skip: false, + ), + ], + skip: false, + ), + ), + ); + }); + }); } LocalTest _localTest(String name) => LocalTest(name, Metadata.empty, () {}); -DartGroupEntry _testEntry(String name) { - return DartGroupEntry(name: name, type: GroupEntryType.test, entries: []); +DartGroupEntry _testEntry(String name, {bool skip = false}) { + return DartGroupEntry( + name: name, + type: GroupEntryType.test, + entries: [], + skip: skip, + ); } diff --git a/packages/patrol_devtools_extension/lib/api/contracts.dart b/packages/patrol_devtools_extension/lib/api/contracts.dart index 9a423d560..6142c8e25 100644 --- a/packages/patrol_devtools_extension/lib/api/contracts.dart +++ b/packages/patrol_devtools_extension/lib/api/contracts.dart @@ -223,6 +223,7 @@ class DartGroupEntry with EquatableMixin { required this.name, required this.type, required this.entries, + required this.skip, }); factory DartGroupEntry.fromJson(Map json) => @@ -231,6 +232,7 @@ class DartGroupEntry with EquatableMixin { final String name; final GroupEntryType type; final List entries; + final bool skip; Map toJson() => _$DartGroupEntryToJson(this); @@ -239,6 +241,7 @@ class DartGroupEntry with EquatableMixin { name, type, entries, + skip, ]; } diff --git a/packages/patrol_devtools_extension/lib/api/contracts.g.dart b/packages/patrol_devtools_extension/lib/api/contracts.g.dart index 00cfba2f9..e94d108d7 100644 --- a/packages/patrol_devtools_extension/lib/api/contracts.g.dart +++ b/packages/patrol_devtools_extension/lib/api/contracts.g.dart @@ -13,6 +13,7 @@ DartGroupEntry _$DartGroupEntryFromJson(Map json) => entries: (json['entries'] as List) .map((e) => DartGroupEntry.fromJson(e as Map)) .toList(), + skip: json['skip'] as bool, ); Map _$DartGroupEntryToJson(DartGroupEntry instance) => @@ -20,6 +21,7 @@ Map _$DartGroupEntryToJson(DartGroupEntry instance) => 'name': instance.name, 'type': _$GroupEntryTypeEnumMap[instance.type]!, 'entries': instance.entries, + 'skip': instance.skip, }; const _$GroupEntryTypeEnumMap = { diff --git a/schema.dart b/schema.dart index d1903b6d3..2e4f8305c 100644 --- a/schema.dart +++ b/schema.dart @@ -11,6 +11,7 @@ class DartGroupEntry { late String name; late GroupEntryType type; late List entries; + late bool skip; } enum GroupEntryType { group, test } From ab58846185dd0e98929df386ea9f99007255c80c Mon Sep 17 00:00:00 2001 From: pdenert Date: Fri, 26 Jul 2024 13:15:58 +0200 Subject: [PATCH 02/10] Add ios native skip --- .../integration_test/open_app_test.dart | 25 +++++++++++ .../Classes/ObjCPatrolAppServiceClient.swift | 4 +- .../Classes/PatrolIntegrationTestIosRunner.h | 42 ++++++++++++------- 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/dev/e2e_app/integration_test/open_app_test.dart b/dev/e2e_app/integration_test/open_app_test.dart index f01c19501..8a522b8cb 100644 --- a/dev/e2e_app/integration_test/open_app_test.dart +++ b/dev/e2e_app/integration_test/open_app_test.dart @@ -30,6 +30,31 @@ void main() { expect($(#counterText).text, '1'); }); + patrol('same open maps test that should be skipped', skip: true, ($) async { + final String mapsId; + if (io.Platform.isIOS) { + mapsId = 'com.apple.Maps'; + } else if (io.Platform.isAndroid) { + mapsId = 'com.google.android.apps.maps'; + } else { + throw UnsupportedError('Unsupported platform'); + } + + await createApp($); + await $.waitUntilVisible($(#counterText)); + + expect($(#counterText).text, '0'); + + await $(FloatingActionButton).tap(); + + await $.native.pressHome(); + await $.native.openApp(appId: mapsId); + await $.native.pressHome(); + await $.native.openApp(); + + expect($(#counterText).text, '1'); + }); + patrol('open browser', ($) async { final String browserId; if (io.Platform.isIOS) { diff --git a/packages/patrol/darwin/Classes/ObjCPatrolAppServiceClient.swift b/packages/patrol/darwin/Classes/ObjCPatrolAppServiceClient.swift index 706ff917e..477531327 100644 --- a/packages/patrol/darwin/Classes/ObjCPatrolAppServiceClient.swift +++ b/packages/patrol/darwin/Classes/ObjCPatrolAppServiceClient.swift @@ -48,7 +48,7 @@ NSLog("PatrolAppServiceClient: created, port: \(port)") } - @objc public func listDartTests(completion: @escaping ([String]?, Error?) -> Void) { + @objc public func listDartTests(completion: @escaping ([[String: Any]]?, Error?) -> Void) { NSLog("PatrolAppServiceClient.listDartTests()") client.listDartTests { result in @@ -56,7 +56,7 @@ case .success(let result): NSLog("PatrolAppServiceClient.listDartTests(): succeeded") let output = result.group.listTestsFlat(parentGroupName: "").map { - $0.name + ["name": $0.name, "skip": $0.skip] } completion(output, nil) case .failure(let error): diff --git a/packages/patrol/darwin/Classes/PatrolIntegrationTestIosRunner.h b/packages/patrol/darwin/Classes/PatrolIntegrationTestIosRunner.h index 49bb89773..ac42ed9b7 100644 --- a/packages/patrol/darwin/Classes/PatrolIntegrationTestIosRunner.h +++ b/packages/patrol/darwin/Classes/PatrolIntegrationTestIosRunner.h @@ -11,29 +11,33 @@ // so it is reported as a native XCTest run result. #define PATROL_INTEGRATION_TEST_IOS_RUNNER(__test_class) \ @interface __test_class : XCTestCase \ - @property(class, strong, nonatomic) NSString *selectedTest; \ + @property(class, strong, nonatomic) NSDictionary *selectedTest; \ @end \ \ @implementation __test_class \ \ - static NSString *_selectedTest = nil; \ + static NSDictionary *_selectedTest = nil; \ \ - +(NSString *)selectedTest { \ + +(NSDictionary *)selectedTest { \ return _selectedTest; \ } \ \ - +(void)setSelectedTest : (NSString *)newSelectedTest { \ + +(void)setSelectedTest : (NSDictionary *)newSelectedTest { \ if (newSelectedTest != _selectedTest) { \ _selectedTest = [newSelectedTest copy]; \ } \ } \ \ - +(BOOL)instancesRespondToSelector : (SEL)aSelector { \ - [self setSelectedTest:NSStringFromSelector(aSelector)]; \ + +(BOOL)instancesRespondToSelector : (SEL)aSelector { \ + NSString *name = NSStringFromSelector(aSelector); \ + BOOL skip = NO; \ + NSDictionary *testInfo = @{@"name": name, @"skip": @(skip)}; \ + [self setSelectedTest:testInfo]; \ + \ [self defaultTestSuite]; /* calls testInvocations */ \ BOOL result = [super instancesRespondToSelector:aSelector]; \ return true; \ - } \ + } \ \ +(NSArray *)testInvocations { \ /* Start native automation server */ \ @@ -55,7 +59,7 @@ [systemAlerts.buttons[@"Allow"] tap]; \ } \ \ - __block NSArray *dartTests = NULL; \ + __block NSArray *dartTests = NULL; \ if ([self selectedTest] != nil) { \ NSLog(@"selectedTest: %@", [self selectedTest]); \ dartTests = [NSArray arrayWithObject:[self selectedTest]]; \ @@ -66,7 +70,7 @@ while (!server.appReady) { \ [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ } \ - [appServiceClient listDartTestsWithCompletion:^(NSArray *_Nullable tests, NSError *_Nullable err) { \ + [appServiceClient listDartTestsWithCompletion:^(NSArray *_Nullable tests, NSError *_Nullable err) { \ if (err != NULL) { \ NSLog(@"listDartTests(): failed, err: %@", err); \ } \ @@ -92,15 +96,21 @@ * Step 2. Create invocations to the generated methods and return them \ */ \ \ - for (NSString * dartTest in dartTests) { \ + for (NSDictionary * dartTest in dartTests) { \ /* Step 1 - dynamically create test cases */ \ - \ + NSString * dartTestName = dartTest[@"name"]; \ + BOOL skip = [dartTest[@"skip"] boolValue]; \ + \ IMP implementation = imp_implementationWithBlock(^(id _self) { \ [[[XCUIApplication alloc] init] launch]; \ + if (skip) { \ + XCTSkip(@"Skip that test \"%@\"", dartTestName); \ + } \ + \ \ __block ObjCRunDartTestResponse *response = NULL; \ __block NSError *error; \ - [appServiceClient runDartTestWithName:dartTest \ + [appServiceClient runDartTestWithName:dartTestName \ completion:^(ObjCRunDartTestResponse *_Nullable r, NSError *_Nullable err) { \ NSString *status; \ if (err != NULL) { \ @@ -110,7 +120,7 @@ response = r; \ status = response.passed ? @"PASSED" : @"FAILED"; \ } \ - NSLog(@"runDartTest(\"%@\"): call finished, test result: %@", dartTest, status); \ + NSLog(@"runDartTest(\"%@\"): call finished, test result: %@", dartTestName, status); \ }]; \ \ /* Wait until Dart test finishes (either fails or passes) or crashes */ \ @@ -121,15 +131,15 @@ NSString *details = response ? response.details : @"(no details - app likely crashed)"; \ XCTAssertTrue(passed, @"%@", details); \ }); \ - SEL selector = NSSelectorFromString(dartTest); \ + SEL selector = NSSelectorFromString(dartTestName); \ class_addMethod(self, selector, implementation, "v@:"); \ \ - /* Step 2 – create invocations to the dynamically created methods */ \ + /* Step 2 – create invocations to the dynamically created methods */ \ NSMethodSignature *signature = [self instanceMethodSignatureForSelector:selector]; \ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; \ invocation.selector = selector; \ \ - NSLog(@"RunnerUITests.testInvocations(): selectorName = %@, signature: %@", dartTest, signature); \ + NSLog(@"RunnerUITests.testInvocations(): selectorName = %@, signature: %@", dartTestName, signature); \ \ [invocations addObject:invocation]; \ } \ From 992bdbea4a91165287cf3738f86ee346858c4207 Mon Sep 17 00:00:00 2001 From: pdenert Date: Fri, 26 Jul 2024 13:18:51 +0200 Subject: [PATCH 03/10] Align '\' in PatrolIntegrationTestIosRunner --- .../Classes/PatrolIntegrationTestIosRunner.h | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/patrol/darwin/Classes/PatrolIntegrationTestIosRunner.h b/packages/patrol/darwin/Classes/PatrolIntegrationTestIosRunner.h index ac42ed9b7..be2ab4350 100644 --- a/packages/patrol/darwin/Classes/PatrolIntegrationTestIosRunner.h +++ b/packages/patrol/darwin/Classes/PatrolIntegrationTestIosRunner.h @@ -11,33 +11,33 @@ // so it is reported as a native XCTest run result. #define PATROL_INTEGRATION_TEST_IOS_RUNNER(__test_class) \ @interface __test_class : XCTestCase \ - @property(class, strong, nonatomic) NSDictionary *selectedTest; \ + @property(class, strong, nonatomic) NSDictionary *selectedTest; \ @end \ \ @implementation __test_class \ \ - static NSDictionary *_selectedTest = nil; \ + static NSDictionary *_selectedTest = nil; \ \ - +(NSDictionary *)selectedTest { \ + +(NSDictionary *)selectedTest { \ return _selectedTest; \ } \ \ - +(void)setSelectedTest : (NSDictionary *)newSelectedTest { \ ++(void)setSelectedTest : (NSDictionary *)newSelectedTest { \ if (newSelectedTest != _selectedTest) { \ _selectedTest = [newSelectedTest copy]; \ } \ } \ \ - +(BOOL)instancesRespondToSelector : (SEL)aSelector { \ - NSString *name = NSStringFromSelector(aSelector); \ - BOOL skip = NO; \ - NSDictionary *testInfo = @{@"name": name, @"skip": @(skip)}; \ + +(BOOL)instancesRespondToSelector : (SEL)aSelector { \ + NSString *name = NSStringFromSelector(aSelector); \ + BOOL skip = NO; \ + NSDictionary *testInfo = @{@"name": name, @"skip": @(skip)}; \ [self setSelectedTest:testInfo]; \ \ [self defaultTestSuite]; /* calls testInvocations */ \ BOOL result = [super instancesRespondToSelector:aSelector]; \ return true; \ - } \ + } \ \ +(NSArray *)testInvocations { \ /* Start native automation server */ \ @@ -59,7 +59,7 @@ [systemAlerts.buttons[@"Allow"] tap]; \ } \ \ - __block NSArray *dartTests = NULL; \ + __block NSArray *dartTests = NULL; \ if ([self selectedTest] != nil) { \ NSLog(@"selectedTest: %@", [self selectedTest]); \ dartTests = [NSArray arrayWithObject:[self selectedTest]]; \ @@ -96,21 +96,20 @@ * Step 2. Create invocations to the generated methods and return them \ */ \ \ - for (NSDictionary * dartTest in dartTests) { \ + for (NSDictionary * dartTest in dartTests) { \ /* Step 1 - dynamically create test cases */ \ - NSString * dartTestName = dartTest[@"name"]; \ - BOOL skip = [dartTest[@"skip"] boolValue]; \ - \ + NSString * dartTestName = dartTest[@"name"]; \ + BOOL skip = [dartTest[@"skip"] boolValue]; \ + \ IMP implementation = imp_implementationWithBlock(^(id _self) { \ [[[XCUIApplication alloc] init] launch]; \ - if (skip) { \ - XCTSkip(@"Skip that test \"%@\"", dartTestName); \ - } \ - \ + if (skip) { \ + XCTSkip(@"Skip that test \"%@\"", dartTestName); \ + } \ \ __block ObjCRunDartTestResponse *response = NULL; \ __block NSError *error; \ - [appServiceClient runDartTestWithName:dartTestName \ + [appServiceClient runDartTestWithName:dartTestName \ completion:^(ObjCRunDartTestResponse *_Nullable r, NSError *_Nullable err) { \ NSString *status; \ if (err != NULL) { \ @@ -131,7 +130,7 @@ NSString *details = response ? response.details : @"(no details - app likely crashed)"; \ XCTAssertTrue(passed, @"%@", details); \ }); \ - SEL selector = NSSelectorFromString(dartTestName); \ + SEL selector = NSSelectorFromString(dartTestName); \ class_addMethod(self, selector, implementation, "v@:"); \ \ /* Step 2 – create invocations to the dynamically created methods */ \ @@ -139,7 +138,7 @@ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; \ invocation.selector = selector; \ \ - NSLog(@"RunnerUITests.testInvocations(): selectorName = %@, signature: %@", dartTestName, signature); \ + NSLog(@"RunnerUITests.testInvocations(): selectorName = %@, signature: %@", dartTestName, signature); \ \ [invocations addObject:invocation]; \ } \ From 6c74642bacd9720670949011d677a7e92284c5f7 Mon Sep 17 00:00:00 2001 From: pdenert Date: Fri, 26 Jul 2024 13:23:19 +0200 Subject: [PATCH 04/10] Update changelog --- packages/patrol/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/patrol/CHANGELOG.md b/packages/patrol/CHANGELOG.md index 9d7c666f2..c8749ba40 100644 --- a/packages/patrol/CHANGELOG.md +++ b/packages/patrol/CHANGELOG.md @@ -4,6 +4,7 @@ - Implement `enableAirplaneMode` and `disableAirplaneMode` methods for Android. (#2254) - Implement `enableLocation` and `disableLocation` methods for Android. (#2259) - Fix opening settings app with clean state on iOS. (#2275) +- Add native skip. (#2278) ## 3.9.0 From 88180295d7803a8d8680537fb90606e20bda1899 Mon Sep 17 00:00:00 2001 From: pdenert Date: Fri, 26 Jul 2024 14:23:59 +0200 Subject: [PATCH 05/10] Add native skip on macos --- .../macos/macos_app_test.dart | 25 +++++++++++++++++++ dev/e2e_app/macos/Podfile.lock | 2 +- .../PatrolIntegrationTestMacosRunner.h | 19 ++++++++------ 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/dev/e2e_app/integration_test/macos/macos_app_test.dart b/dev/e2e_app/integration_test/macos/macos_app_test.dart index 2bc1758c0..a86aa3d77 100644 --- a/dev/e2e_app/integration_test/macos/macos_app_test.dart +++ b/dev/e2e_app/integration_test/macos/macos_app_test.dart @@ -27,4 +27,29 @@ void main() { scrollDirection: AxisDirection.up, ); }); + + patrol('taps around test to skip', skip: true, ($) async { + await createApp($); + await $.waitUntilVisible($(#counterText)); + + expect($(#counterText).text, '0'); + + await $(FloatingActionButton).tap(); + + expect($(#counterText).text, '1'); + + await $(#textField).enterText('Hello, Flutter!'); + expect($('Hello, Flutter!'), findsOneWidget); + + await $('Open scrolling screen').scrollTo().tap(); + await $.waitUntilVisible($(#topText)); + + await $.scrollUntilVisible(finder: $(#bottomText)); + + await $.tap($(#backButton)); + await $.scrollUntilVisible( + finder: $(#counterText), + scrollDirection: AxisDirection.up, + ); + }); } diff --git a/dev/e2e_app/macos/Podfile.lock b/dev/e2e_app/macos/Podfile.lock index 2959bc617..365f7dbc6 100644 --- a/dev/e2e_app/macos/Podfile.lock +++ b/dev/e2e_app/macos/Podfile.lock @@ -47,7 +47,7 @@ SPEC CHECKSUMS: flutter_timezone: 6b906d1740654acb16e50b639835628fea851037 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 geolocator_apple: 72a78ae3f3e4ec0db62117bd93e34523f5011d58 - patrol: 3e21d514020dbee24b3e3383caac9e8e051292ac + patrol: 0564cee315ff6c86fb802b3647db05cc2d3d0624 PODFILE CHECKSUM: 4dcdd5fa8959bf7a21c4e3da36b083ff9ad52d38 diff --git a/packages/patrol/darwin/Classes/PatrolIntegrationTestMacosRunner.h b/packages/patrol/darwin/Classes/PatrolIntegrationTestMacosRunner.h index 6b69a138b..ae29197a7 100644 --- a/packages/patrol/darwin/Classes/PatrolIntegrationTestMacosRunner.h +++ b/packages/patrol/darwin/Classes/PatrolIntegrationTestMacosRunner.h @@ -43,8 +43,8 @@ \ NSLog(@"listDartTests"); \ \ - __block NSArray *dartTests = NULL; \ - [appServiceClient listDartTestsWithCompletion:^(NSArray *_Nullable tests, NSError *_Nullable err) { \ +__block NSArray *dartTests = NULL; \ + [appServiceClient listDartTestsWithCompletion:^(NSArray *_Nullable tests, NSError *_Nullable err) { \ if (err != NULL) { \ NSLog(@"listDartTests(): failed, err: %@", err); \ } \ @@ -71,15 +71,20 @@ * Step 2. Create invocations to the generated methods and return them \ */ \ \ - for (NSString * dartTest in dartTests) { \ + for (NSDictionary * dartTest in dartTests) { \ /* Step 1 - dynamically create test cases */ \ + NSString * dartTestName = dartTest[@"name"]; \ + BOOL skip = [dartTest[@"skip"] boolValue]; \ \ IMP implementation = imp_implementationWithBlock(^(id _self) { \ [[[XCUIApplication alloc] init] launch]; \ + if (skip) { \ + XCTSkip(@"Skip that test \"%@\"", dartTestName); \ + } \ \ __block ObjCRunDartTestResponse *response = NULL; \ __block NSError *error; \ - [appServiceClient runDartTestWithName:dartTest \ + [appServiceClient runDartTestWithName:dartTestName \ completion:^(ObjCRunDartTestResponse *_Nullable r, NSError *_Nullable err) { \ NSString *status; \ if (err != NULL) { \ @@ -89,7 +94,7 @@ response = r; \ status = response.passed ? @"PASSED" : @"FAILED"; \ } \ - NSLog(@"runDartTest(\"%@\"): call finished, test result: %@", dartTest, status); \ + NSLog(@"runDartTest(\"%@\"): call finished, test result: %@", dartTestName, status); \ }]; \ \ /* Wait until Dart test finishes (either fails or passes) or crashes */ \ @@ -100,7 +105,7 @@ NSString *details = response ? response.details : @"(no details - app likely crashed)"; \ XCTAssertTrue(passed, @"%@", details); \ }); \ - SEL selector = NSSelectorFromString(dartTest); \ + SEL selector = NSSelectorFromString(dartTestName); \ class_addMethod(self, selector, implementation, "v@:"); \ \ /* Step 2 – create invocations to the dynamically created methods */ \ @@ -108,7 +113,7 @@ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; \ invocation.selector = selector; \ \ - NSLog(@"RunnerUITests.testInvocations(): selectorName = %@, signature: %@", dartTest, signature); \ + NSLog(@"RunnerUITests.testInvocations(): selectorName = %@, signature: %@", dartTestName, signature); \ \ [invocations addObject:invocation]; \ } \ From 1f9577ddf8ad29687a8ad2bfb308c4e46a890bb2 Mon Sep 17 00:00:00 2001 From: pdenert Date: Mon, 29 Jul 2024 13:16:49 +0200 Subject: [PATCH 06/10] Revert changes in MainActivityTests --- .../patrol/e2e_app/MainActivityTest.java | 21 +++++-------------- .../patrol/example/MainActivityTest.java | 17 +++------------ 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/dev/e2e_app/android/app/src/androidTest/java/pl/leancode/patrol/e2e_app/MainActivityTest.java b/dev/e2e_app/android/app/src/androidTest/java/pl/leancode/patrol/e2e_app/MainActivityTest.java index 74a0b0329..7b2473027 100644 --- a/dev/e2e_app/android/app/src/androidTest/java/pl/leancode/patrol/e2e_app/MainActivityTest.java +++ b/dev/e2e_app/android/app/src/androidTest/java/pl/leancode/patrol/e2e_app/MainActivityTest.java @@ -1,42 +1,31 @@ package pl.leancode.patrol.e2e_app; -import static org.junit.Assume.*; - -import java.util.Collection; - import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import pl.leancode.patrol.PatrolJUnitRunner; - @RunWith(Parameterized.class) public class MainActivityTest { - @Parameters - public static Collection testCases() { + @Parameters(name = "{0}") + public static Object[] testCases() { PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation(); instrumentation.setUp(MainActivity.class); instrumentation.waitForPatrolAppService(); return instrumentation.listDartTests(); } - public MainActivityTest(String dartTestName, Boolean skip) { + public MainActivityTest(String dartTestName) { this.dartTestName = dartTestName; - this.skip = skip; } - private String dartTestName; - - private Boolean skip; + private final String dartTestName; @Test public void runDartTest() { - assumeFalse(skip); - PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation(); instrumentation.runDartTest(dartTestName); } -} +} \ No newline at end of file diff --git a/packages/patrol/example/android/app/src/androidTest/java/pl/leancode/patrol/example/MainActivityTest.java b/packages/patrol/example/android/app/src/androidTest/java/pl/leancode/patrol/example/MainActivityTest.java index 74a0b0329..43a972216 100644 --- a/packages/patrol/example/android/app/src/androidTest/java/pl/leancode/patrol/example/MainActivityTest.java +++ b/packages/patrol/example/android/app/src/androidTest/java/pl/leancode/patrol/example/MainActivityTest.java @@ -1,41 +1,30 @@ package pl.leancode.patrol.e2e_app; -import static org.junit.Assume.*; - -import java.util.Collection; - import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import pl.leancode.patrol.PatrolJUnitRunner; - @RunWith(Parameterized.class) public class MainActivityTest { - @Parameters - public static Collection testCases() { + @Parameters(name = "{0}") + public static Object[] testCases() { PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation(); instrumentation.setUp(MainActivity.class); instrumentation.waitForPatrolAppService(); return instrumentation.listDartTests(); } - public MainActivityTest(String dartTestName, Boolean skip) { + public MainActivityTest(String dartTestCaseNamesList) { this.dartTestName = dartTestName; - this.skip = skip; } private String dartTestName; - private Boolean skip; - @Test public void runDartTest() { - assumeFalse(skip); - PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation(); instrumentation.runDartTest(dartTestName); } From 92ded802483c1491974756387e4ef88bce96cc8b Mon Sep 17 00:00:00 2001 From: pdenert Date: Mon, 29 Jul 2024 13:23:55 +0200 Subject: [PATCH 07/10] Skip test in PatrolJUnitRunner --- .../pl/leancode/patrol/PatrolJUnitRunner.java | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/PatrolJUnitRunner.java b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/PatrolJUnitRunner.java index 6914f33fd..a9ced7b5d 100644 --- a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/PatrolJUnitRunner.java +++ b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/PatrolJUnitRunner.java @@ -5,6 +5,8 @@ package pl.leancode.patrol; +import static org.junit.Assume.*; + import android.app.Instrumentation; import android.content.Intent; import android.os.Bundle; @@ -16,8 +18,9 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import static pl.leancode.patrol.contracts.Contracts.DartGroupEntry; @@ -30,6 +33,7 @@ */ public class PatrolJUnitRunner extends AndroidJUnitRunner { public PatrolAppServiceClient patrolAppServiceClient; + private Map dartTestCaseSkipMap = new HashMap<>(); @Override protected boolean shouldWaitForActivitiesToComplete() { @@ -103,19 +107,20 @@ public void waitForPatrolAppService() { Logger.INSTANCE.i(TAG + "PatrolAppService is ready to report Dart tests"); } - public Collection listDartTests() { + public Object[] listDartTests() { final String TAG = "PatrolJUnitRunner.listDartTests(): "; try { final DartGroupEntry dartTestGroup = patrolAppServiceClient.listDartTests(); List dartTestCases = ContractsExtensionsKt.listTestsFlat(dartTestGroup, ""); - List dartTestCaseNamesList = new ArrayList<>(); + List dartTestCaseNamesList = new ArrayList<>(); for (DartGroupEntry dartTestCase : dartTestCases) { - Object[] dartTestCaseName = {dartTestCase.getName(), dartTestCase.getSkip()}; - dartTestCaseNamesList.add(dartTestCaseName); + dartTestCaseSkipMap.put(dartTestCase.getName(), dartTestCase.getSkip()); + dartTestCaseNamesList.add(dartTestCase.getName()); } - Logger.INSTANCE.i(TAG + "Got Dart tests: " + Arrays.deepToString(dartTestCaseNamesList.toArray(new Object[0][]))); - return dartTestCaseNamesList; + Object[] dartTestCaseNames = dartTestCaseNamesList.toArray(); + Logger.INSTANCE.i(TAG + "Got Dart tests: " + Arrays.toString(dartTestCaseNames)); + return dartTestCaseNames; } catch (PatrolAppServiceClientException e) { Logger.INSTANCE.e(TAG + "Failed to list Dart tests: ", e); throw new RuntimeException(e); @@ -128,6 +133,12 @@ public Collection listDartTests() { */ public RunDartTestResponse runDartTest(String name) { final String TAG = "PatrolJUnitRunner.runDartTest(" + name + "): "; + + final Boolean skip = dartTestCaseSkipMap.get(name); + if (skip) { + Logger.INSTANCE.i(TAG + "Test skipped"); + assumeFalse(skip); + } try { Logger.INSTANCE.i(TAG + "Requested execution"); From 01bcda3ae8693ad9b9ee55d21697316f9791d0df Mon Sep 17 00:00:00 2001 From: pdenert Date: Mon, 29 Jul 2024 15:41:46 +0200 Subject: [PATCH 08/10] Fix kotlin test --- .../test/kotlin/pl/leancode/patrol/ContractsExtensionsTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/patrol/android/src/test/kotlin/pl/leancode/patrol/ContractsExtensionsTest.kt b/packages/patrol/android/src/test/kotlin/pl/leancode/patrol/ContractsExtensionsTest.kt index c556b5d3e..ad4e6e4a5 100644 --- a/packages/patrol/android/src/test/kotlin/pl/leancode/patrol/ContractsExtensionsTest.kt +++ b/packages/patrol/android/src/test/kotlin/pl/leancode/patrol/ContractsExtensionsTest.kt @@ -9,11 +9,11 @@ fun dartTestGroup( name: String, entries: List, ): DartGroupEntry { - return DartGroupEntry(name, GroupEntryType.group, entries) + return DartGroupEntry(name, GroupEntryType.group, entries, false) } fun dartTestCase(name: String): DartGroupEntry { - return DartGroupEntry(name, GroupEntryType.test, listOf()) + return DartGroupEntry(name, GroupEntryType.test, listOf(), false) } class DartTestGroupExtensionsTest { From 3e31a2e72d45e9a375c260d5c46bd70d9b3c6592 Mon Sep 17 00:00:00 2001 From: pdenert Date: Mon, 29 Jul 2024 15:48:40 +0200 Subject: [PATCH 09/10] Format *.h fles --- .../Classes/PatrolIntegrationTestIosRunner.h | 276 +++++++++--------- .../PatrolIntegrationTestMacosRunner.h | 226 +++++++------- 2 files changed, 253 insertions(+), 249 deletions(-) diff --git a/packages/patrol/darwin/Classes/PatrolIntegrationTestIosRunner.h b/packages/patrol/darwin/Classes/PatrolIntegrationTestIosRunner.h index be2ab4350..b155e03dd 100644 --- a/packages/patrol/darwin/Classes/PatrolIntegrationTestIosRunner.h +++ b/packages/patrol/darwin/Classes/PatrolIntegrationTestIosRunner.h @@ -9,141 +9,143 @@ // For every Flutter dart test, dynamically generate an Objective-C method mirroring the test results // so it is reported as a native XCTest run result. -#define PATROL_INTEGRATION_TEST_IOS_RUNNER(__test_class) \ - @interface __test_class : XCTestCase \ - @property(class, strong, nonatomic) NSDictionary *selectedTest; \ - @end \ - \ - @implementation __test_class \ - \ - static NSDictionary *_selectedTest = nil; \ - \ - +(NSDictionary *)selectedTest { \ - return _selectedTest; \ - } \ - \ -+(void)setSelectedTest : (NSDictionary *)newSelectedTest { \ - if (newSelectedTest != _selectedTest) { \ - _selectedTest = [newSelectedTest copy]; \ - } \ - } \ - \ - +(BOOL)instancesRespondToSelector : (SEL)aSelector { \ - NSString *name = NSStringFromSelector(aSelector); \ - BOOL skip = NO; \ - NSDictionary *testInfo = @{@"name": name, @"skip": @(skip)}; \ - [self setSelectedTest:testInfo]; \ - \ - [self defaultTestSuite]; /* calls testInvocations */ \ - BOOL result = [super instancesRespondToSelector:aSelector]; \ - return true; \ - } \ - \ - +(NSArray *)testInvocations { \ - /* Start native automation server */ \ - PatrolServer *server = [[PatrolServer alloc] init]; \ - \ - NSError *_Nullable __autoreleasing *_Nullable err = NULL; \ - [server startAndReturnError:err]; \ - if (err != NULL) { \ - NSLog(@"patrolServer.start(): failed, err: %@", err); \ - } \ - \ - /* Create a client for PatrolAppService, which lets us list and run Dart tests */ \ - __block ObjCPatrolAppServiceClient *appServiceClient = [[ObjCPatrolAppServiceClient alloc] init]; \ - \ - /* Allow the Local Network permission required by Dart Observatory */ \ - XCUIApplication *springboard = [[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.springboard"]; \ - XCUIElementQuery *systemAlerts = springboard.alerts; \ - if (systemAlerts.buttons[@"Allow"].exists) { \ - [systemAlerts.buttons[@"Allow"] tap]; \ - } \ - \ - __block NSArray *dartTests = NULL; \ - if ([self selectedTest] != nil) { \ - NSLog(@"selectedTest: %@", [self selectedTest]); \ - dartTests = [NSArray arrayWithObject:[self selectedTest]]; \ - } else { \ - /* Run the app for the first time to gather Dart tests */ \ - [[[XCUIApplication alloc] init] launch]; \ - /* Spin the runloop waiting until the app reports that it is ready to report Dart tests */ \ - while (!server.appReady) { \ - [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ - } \ - [appServiceClient listDartTestsWithCompletion:^(NSArray *_Nullable tests, NSError *_Nullable err) { \ - if (err != NULL) { \ - NSLog(@"listDartTests(): failed, err: %@", err); \ - } \ - \ - dartTests = tests; \ - }]; \ - \ - /* Spin the runloop waiting until the app reports the Dart tests it contains */ \ - while (!dartTests) { \ - [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ - } \ - \ - NSLog(@"Got %lu Dart tests: %@", dartTests.count, dartTests); \ - } \ - \ - NSMutableArray *invocations = [[NSMutableArray alloc] init]; \ - \ - /** \ - * Once Dart tests are available, we: \ - * \ - * Step 1. Dynamically add test case methods that request execution of an individual Dart test file. \ - * \ - * Step 2. Create invocations to the generated methods and return them \ - */ \ - \ - for (NSDictionary * dartTest in dartTests) { \ - /* Step 1 - dynamically create test cases */ \ - NSString * dartTestName = dartTest[@"name"]; \ - BOOL skip = [dartTest[@"skip"] boolValue]; \ - \ - IMP implementation = imp_implementationWithBlock(^(id _self) { \ - [[[XCUIApplication alloc] init] launch]; \ - if (skip) { \ - XCTSkip(@"Skip that test \"%@\"", dartTestName); \ - } \ - \ - __block ObjCRunDartTestResponse *response = NULL; \ - __block NSError *error; \ - [appServiceClient runDartTestWithName:dartTestName \ - completion:^(ObjCRunDartTestResponse *_Nullable r, NSError *_Nullable err) { \ - NSString *status; \ - if (err != NULL) { \ - error = err; \ - status = @"CRASHED"; \ - } else { \ - response = r; \ - status = response.passed ? @"PASSED" : @"FAILED"; \ - } \ - NSLog(@"runDartTest(\"%@\"): call finished, test result: %@", dartTestName, status); \ - }]; \ - \ - /* Wait until Dart test finishes (either fails or passes) or crashes */ \ - while (!response && !error) { \ - [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ - } \ - BOOL passed = response ? response.passed : NO; \ - NSString *details = response ? response.details : @"(no details - app likely crashed)"; \ - XCTAssertTrue(passed, @"%@", details); \ - }); \ - SEL selector = NSSelectorFromString(dartTestName); \ - class_addMethod(self, selector, implementation, "v@:"); \ - \ - /* Step 2 – create invocations to the dynamically created methods */ \ - NSMethodSignature *signature = [self instanceMethodSignatureForSelector:selector]; \ - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; \ - invocation.selector = selector; \ - \ - NSLog(@"RunnerUITests.testInvocations(): selectorName = %@, signature: %@", dartTestName, signature); \ - \ - [invocations addObject:invocation]; \ - } \ - \ - return invocations; \ - } \ - \ +#define PATROL_INTEGRATION_TEST_IOS_RUNNER(__test_class) \ + @interface __test_class : XCTestCase \ + @property(class, strong, nonatomic) NSDictionary *selectedTest; \ + @end \ + \ + @implementation __test_class \ + \ + static NSDictionary *_selectedTest = nil; \ + \ + +(NSDictionary *)selectedTest { \ + return _selectedTest; \ + } \ + \ + +(void)setSelectedTest : (NSDictionary *)newSelectedTest { \ + if (newSelectedTest != _selectedTest) { \ + _selectedTest = [newSelectedTest copy]; \ + } \ + } \ + \ + +(BOOL)instancesRespondToSelector : (SEL)aSelector { \ + NSString *name = NSStringFromSelector(aSelector); \ + BOOL skip = NO; \ + NSDictionary *testInfo = @{@"name" : name, @"skip" : @(skip)}; \ + [self setSelectedTest:testInfo]; \ + \ + [self defaultTestSuite]; /* calls testInvocations */ \ + BOOL result = [super instancesRespondToSelector:aSelector]; \ + return true; \ + } \ + \ + +(NSArray *)testInvocations { \ + /* Start native automation server */ \ + PatrolServer *server = [[PatrolServer alloc] init]; \ + \ + NSError *_Nullable __autoreleasing *_Nullable err = NULL; \ + [server startAndReturnError:err]; \ + if (err != NULL) { \ + NSLog(@"patrolServer.start(): failed, err: %@", err); \ + } \ + \ + /* Create a client for PatrolAppService, which lets us list and run Dart tests */ \ + __block ObjCPatrolAppServiceClient *appServiceClient = [[ObjCPatrolAppServiceClient alloc] init]; \ + \ + /* Allow the Local Network permission required by Dart Observatory */ \ + XCUIApplication *springboard = [[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.springboard"]; \ + XCUIElementQuery *systemAlerts = springboard.alerts; \ + if (systemAlerts.buttons[@"Allow"].exists) { \ + [systemAlerts.buttons[@"Allow"] tap]; \ + } \ + \ + __block NSArray *dartTests = NULL; \ + if ([self selectedTest] != nil) { \ + NSLog(@"selectedTest: %@", [self selectedTest]); \ + dartTests = [NSArray arrayWithObject:[self selectedTest]]; \ + } else { \ + /* Run the app for the first time to gather Dart tests */ \ + [[[XCUIApplication alloc] init] launch]; \ + /* Spin the runloop waiting until the app reports that it is ready to report Dart tests */ \ + while (!server.appReady) { \ + [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ + } \ + [appServiceClient \ + listDartTestsWithCompletion:^(NSArray *_Nullable tests, NSError *_Nullable err) { \ + if (err != NULL) { \ + NSLog(@"listDartTests(): failed, err: %@", err); \ + } \ + \ + dartTests = tests; \ + }]; \ + \ + /* Spin the runloop waiting until the app reports the Dart tests it contains */ \ + while (!dartTests) { \ + [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ + } \ + \ + NSLog(@"Got %lu Dart tests: %@", dartTests.count, dartTests); \ + } \ + \ + NSMutableArray *invocations = [[NSMutableArray alloc] init]; \ + \ + /** \ + * Once Dart tests are available, we: \ + * \ + * Step 1. Dynamically add test case methods that request execution of an individual Dart test file. \ + * \ + * Step 2. Create invocations to the generated methods and return them \ + */ \ + \ + for (NSDictionary * dartTest in dartTests) { \ + /* Step 1 - dynamically create test cases */ \ + NSString *dartTestName = dartTest[@"name"]; \ + BOOL skip = [dartTest[@"skip"] boolValue]; \ + \ + IMP implementation = imp_implementationWithBlock(^(id _self) { \ + [[[XCUIApplication alloc] init] launch]; \ + if (skip) { \ + XCTSkip(@"Skip that test \"%@\"", dartTestName); \ + } \ + \ + __block ObjCRunDartTestResponse *response = NULL; \ + __block NSError *error; \ + [appServiceClient \ + runDartTestWithName:dartTestName \ + completion:^(ObjCRunDartTestResponse *_Nullable r, NSError *_Nullable err) { \ + NSString *status; \ + if (err != NULL) { \ + error = err; \ + status = @"CRASHED"; \ + } else { \ + response = r; \ + status = response.passed ? @"PASSED" : @"FAILED"; \ + } \ + NSLog(@"runDartTest(\"%@\"): call finished, test result: %@", dartTestName, status); \ + }]; \ + \ + /* Wait until Dart test finishes (either fails or passes) or crashes */ \ + while (!response && !error) { \ + [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ + } \ + BOOL passed = response ? response.passed : NO; \ + NSString *details = response ? response.details : @"(no details - app likely crashed)"; \ + XCTAssertTrue(passed, @"%@", details); \ + }); \ + SEL selector = NSSelectorFromString(dartTestName); \ + class_addMethod(self, selector, implementation, "v@:"); \ + \ + /* Step 2 – create invocations to the dynamically created methods */ \ + NSMethodSignature *signature = [self instanceMethodSignatureForSelector:selector]; \ + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; \ + invocation.selector = selector; \ + \ + NSLog(@"RunnerUITests.testInvocations(): selectorName = %@, signature: %@", dartTestName, signature); \ + \ + [invocations addObject:invocation]; \ + } \ + \ + return invocations; \ + } \ + \ @end\ diff --git a/packages/patrol/darwin/Classes/PatrolIntegrationTestMacosRunner.h b/packages/patrol/darwin/Classes/PatrolIntegrationTestMacosRunner.h index ae29197a7..ae0e5bff3 100644 --- a/packages/patrol/darwin/Classes/PatrolIntegrationTestMacosRunner.h +++ b/packages/patrol/darwin/Classes/PatrolIntegrationTestMacosRunner.h @@ -9,116 +9,118 @@ // For every Flutter dart test, dynamically generate an Objective-C method mirroring the test results // so it is reported as a native XCTest run result. -#define PATROL_INTEGRATION_TEST_MACOS_RUNNER(__test_class) \ - @interface __test_class : XCTestCase \ - @end \ - \ - @implementation __test_class \ - \ - +(NSArray *)testInvocations { \ - /* Start native automation gRPC server */ \ - PatrolServer *server = [[PatrolServer alloc] init]; \ - NSError *_Nullable __autoreleasing *_Nullable err = NULL; \ - [server startAndReturnError:err]; \ - if (err != NULL) { \ - NSLog(@"patrolServer.start(): failed, err: %@", err); \ - } \ - \ - NSLog(@"Create PatrolAppServiceClient"); \ - \ - /* Create a client for PatrolAppService, which lets us list and run Dart tests */ \ - __block ObjCPatrolAppServiceClient *appServiceClient = [[ObjCPatrolAppServiceClient alloc] init]; \ - \ - NSLog(@"Run the app for the first time"); \ - \ - /* Run the app for the first time to gather Dart tests */ \ - [[[XCUIApplication alloc] init] launch]; \ - \ - NSLog(@"Waiting until the app reports that it is ready"); \ - \ - /* Spin the runloop waiting until the app reports that it is ready to report Dart tests */ \ - while (!server.appReady) { \ - [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ - } \ - \ - NSLog(@"listDartTests"); \ - \ -__block NSArray *dartTests = NULL; \ - [appServiceClient listDartTestsWithCompletion:^(NSArray *_Nullable tests, NSError *_Nullable err) { \ - if (err != NULL) { \ - NSLog(@"listDartTests(): failed, err: %@", err); \ - } \ - \ - dartTests = tests; \ - }]; \ - \ - NSLog(@"Spin the runloop waiting"); \ - \ - /* Spin the runloop waiting until the app reports the Dart tests it contains */ \ - while (!dartTests) { \ - [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]]; \ - } \ - \ - NSLog(@"Got %lu Dart tests: %@", dartTests.count, dartTests); \ - \ - NSMutableArray *invocations = [[NSMutableArray alloc] init]; \ - \ - /** \ - * Once Dart tests are available, we: \ - * \ - * Step 1. Dynamically add test case methods that request execution of an individual Dart test file. \ - * \ - * Step 2. Create invocations to the generated methods and return them \ - */ \ - \ - for (NSDictionary * dartTest in dartTests) { \ - /* Step 1 - dynamically create test cases */ \ - NSString * dartTestName = dartTest[@"name"]; \ - BOOL skip = [dartTest[@"skip"] boolValue]; \ - \ - IMP implementation = imp_implementationWithBlock(^(id _self) { \ - [[[XCUIApplication alloc] init] launch]; \ - if (skip) { \ - XCTSkip(@"Skip that test \"%@\"", dartTestName); \ - } \ - \ - __block ObjCRunDartTestResponse *response = NULL; \ - __block NSError *error; \ - [appServiceClient runDartTestWithName:dartTestName \ - completion:^(ObjCRunDartTestResponse *_Nullable r, NSError *_Nullable err) { \ - NSString *status; \ - if (err != NULL) { \ - error = err; \ - status = @"CRASHED"; \ - } else { \ - response = r; \ - status = response.passed ? @"PASSED" : @"FAILED"; \ - } \ - NSLog(@"runDartTest(\"%@\"): call finished, test result: %@", dartTestName, status); \ - }]; \ - \ - /* Wait until Dart test finishes (either fails or passes) or crashes */ \ - while (!response && !error) { \ - [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ - } \ - BOOL passed = response ? response.passed : NO; \ - NSString *details = response ? response.details : @"(no details - app likely crashed)"; \ - XCTAssertTrue(passed, @"%@", details); \ - }); \ - SEL selector = NSSelectorFromString(dartTestName); \ - class_addMethod(self, selector, implementation, "v@:"); \ - \ - /* Step 2 – create invocations to the dynamically created methods */ \ - NSMethodSignature *signature = [self instanceMethodSignatureForSelector:selector]; \ - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; \ - invocation.selector = selector; \ - \ - NSLog(@"RunnerUITests.testInvocations(): selectorName = %@, signature: %@", dartTestName, signature); \ - \ - [invocations addObject:invocation]; \ - } \ - \ - return invocations; \ - } \ - \ +#define PATROL_INTEGRATION_TEST_MACOS_RUNNER(__test_class) \ + @interface __test_class : XCTestCase \ + @end \ + \ + @implementation __test_class \ + \ + +(NSArray *)testInvocations { \ + /* Start native automation gRPC server */ \ + PatrolServer *server = [[PatrolServer alloc] init]; \ + NSError *_Nullable __autoreleasing *_Nullable err = NULL; \ + [server startAndReturnError:err]; \ + if (err != NULL) { \ + NSLog(@"patrolServer.start(): failed, err: %@", err); \ + } \ + \ + NSLog(@"Create PatrolAppServiceClient"); \ + \ + /* Create a client for PatrolAppService, which lets us list and run Dart tests */ \ + __block ObjCPatrolAppServiceClient *appServiceClient = [[ObjCPatrolAppServiceClient alloc] init]; \ + \ + NSLog(@"Run the app for the first time"); \ + \ + /* Run the app for the first time to gather Dart tests */ \ + [[[XCUIApplication alloc] init] launch]; \ + \ + NSLog(@"Waiting until the app reports that it is ready"); \ + \ + /* Spin the runloop waiting until the app reports that it is ready to report Dart tests */ \ + while (!server.appReady) { \ + [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ + } \ + \ + NSLog(@"listDartTests"); \ + \ + __block NSArray *dartTests = NULL; \ + [appServiceClient \ + listDartTestsWithCompletion:^(NSArray *_Nullable tests, NSError *_Nullable err) { \ + if (err != NULL) { \ + NSLog(@"listDartTests(): failed, err: %@", err); \ + } \ + \ + dartTests = tests; \ + }]; \ + \ + NSLog(@"Spin the runloop waiting"); \ + \ + /* Spin the runloop waiting until the app reports the Dart tests it contains */ \ + while (!dartTests) { \ + [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]]; \ + } \ + \ + NSLog(@"Got %lu Dart tests: %@", dartTests.count, dartTests); \ + \ + NSMutableArray *invocations = [[NSMutableArray alloc] init]; \ + \ + /** \ + * Once Dart tests are available, we: \ + * \ + * Step 1. Dynamically add test case methods that request execution of an individual Dart test file. \ + * \ + * Step 2. Create invocations to the generated methods and return them \ + */ \ + \ + for (NSDictionary * dartTest in dartTests) { \ + /* Step 1 - dynamically create test cases */ \ + NSString *dartTestName = dartTest[@"name"]; \ + BOOL skip = [dartTest[@"skip"] boolValue]; \ + \ + IMP implementation = imp_implementationWithBlock(^(id _self) { \ + [[[XCUIApplication alloc] init] launch]; \ + if (skip) { \ + XCTSkip(@"Skip that test \"%@\"", dartTestName); \ + } \ + \ + __block ObjCRunDartTestResponse *response = NULL; \ + __block NSError *error; \ + [appServiceClient \ + runDartTestWithName:dartTestName \ + completion:^(ObjCRunDartTestResponse *_Nullable r, NSError *_Nullable err) { \ + NSString *status; \ + if (err != NULL) { \ + error = err; \ + status = @"CRASHED"; \ + } else { \ + response = r; \ + status = response.passed ? @"PASSED" : @"FAILED"; \ + } \ + NSLog(@"runDartTest(\"%@\"): call finished, test result: %@", dartTestName, status); \ + }]; \ + \ + /* Wait until Dart test finishes (either fails or passes) or crashes */ \ + while (!response && !error) { \ + [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ + } \ + BOOL passed = response ? response.passed : NO; \ + NSString *details = response ? response.details : @"(no details - app likely crashed)"; \ + XCTAssertTrue(passed, @"%@", details); \ + }); \ + SEL selector = NSSelectorFromString(dartTestName); \ + class_addMethod(self, selector, implementation, "v@:"); \ + \ + /* Step 2 – create invocations to the dynamically created methods */ \ + NSMethodSignature *signature = [self instanceMethodSignatureForSelector:selector]; \ + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; \ + invocation.selector = selector; \ + \ + NSLog(@"RunnerUITests.testInvocations(): selectorName = %@, signature: %@", dartTestName, signature); \ + \ + [invocations addObject:invocation]; \ + } \ + \ + return invocations; \ + } \ + \ @end\ From 4f67159b15b2d9ee0a6ab7dccdd176a091b11869 Mon Sep 17 00:00:00 2001 From: pdenert Date: Tue, 30 Jul 2024 10:05:33 +0200 Subject: [PATCH 10/10] Revert unneeded changes --- .../java/pl/leancode/patrol/e2e_app/MainActivityTest.java | 2 +- .../java/pl/leancode/patrol/example/MainActivityTest.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dev/e2e_app/android/app/src/androidTest/java/pl/leancode/patrol/e2e_app/MainActivityTest.java b/dev/e2e_app/android/app/src/androidTest/java/pl/leancode/patrol/e2e_app/MainActivityTest.java index 7b2473027..c59e67de8 100644 --- a/dev/e2e_app/android/app/src/androidTest/java/pl/leancode/patrol/e2e_app/MainActivityTest.java +++ b/dev/e2e_app/android/app/src/androidTest/java/pl/leancode/patrol/e2e_app/MainActivityTest.java @@ -28,4 +28,4 @@ public void runDartTest() { PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation(); instrumentation.runDartTest(dartTestName); } -} \ No newline at end of file +} diff --git a/packages/patrol/example/android/app/src/androidTest/java/pl/leancode/patrol/example/MainActivityTest.java b/packages/patrol/example/android/app/src/androidTest/java/pl/leancode/patrol/example/MainActivityTest.java index 43a972216..675e46464 100644 --- a/packages/patrol/example/android/app/src/androidTest/java/pl/leancode/patrol/example/MainActivityTest.java +++ b/packages/patrol/example/android/app/src/androidTest/java/pl/leancode/patrol/example/MainActivityTest.java @@ -1,4 +1,4 @@ -package pl.leancode.patrol.e2e_app; +package pl.leancode.patrol.example; import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; @@ -17,11 +17,11 @@ public static Object[] testCases() { return instrumentation.listDartTests(); } - public MainActivityTest(String dartTestCaseNamesList) { + public MainActivityTest(String dartTestName) { this.dartTestName = dartTestName; } - private String dartTestName; + private final String dartTestName; @Test public void runDartTest() {