diff --git a/packages/integration_test/README.md b/packages/integration_test/README.md index f9d01ef1c1..08371120c6 100644 --- a/packages/integration_test/README.md +++ b/packages/integration_test/README.md @@ -281,27 +281,14 @@ To build `integration_test/foo_test.dart` from the command line, run: flutter build ios --config-only integration_test/foo_test.dart ``` -In Xcode, add a test file called `RunnerTests.m` or `RunnerTests.swift` (or any name of your choice) to the new target and +In Xcode, add a test file called `RunnerTests.m` (or any name of your choice) to the new target and replace the file: ```objective-c @import XCTest; @import integration_test; -@interface RunnerTests : FLTIntegrationTestCase -@end - -@implementation RunnerTests -@end -``` -or in Swift: -````swift -import integration_test -import XCTest - -class RunnerSwiftTests: FLTIntegrationTestCase { -} - +INTEGRATION_TEST_IOS_RUNNER(RunnerTests) ``` Run `Product > Test` to run the integration tests on your selected device. diff --git a/packages/integration_test/example/integration_test/_extended_test_io.dart b/packages/integration_test/example/integration_test/_extended_test_io.dart index 8c2456ac41..377aa42995 100644 --- a/packages/integration_test/example/integration_test/_extended_test_io.dart +++ b/packages/integration_test/example/integration_test/_extended_test_io.dart @@ -25,24 +25,6 @@ void main() { // Build our app. app.main(); - // Pump a frame. - await tester.pumpAndSettle(); - - // Verify that platform version is retrieved. - expect( - find.byWidgetPredicate( - (Widget widget) => - widget is Text && - widget.data!.startsWith('Platform: ${Platform.operatingSystem}'), - ), - findsOneWidget, - ); - }); - - testWidgets('verify screenshot', (WidgetTester tester) async { - // Build our app. - app.main(); - // On Android, this is required prior to taking the screenshot. await binding.convertFlutterSurfaceToImage(); @@ -57,5 +39,15 @@ void main() { expect(secondPng.isNotEmpty, isTrue); expect(listEquals(firstPng, secondPng), isTrue); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => + widget is Text && + widget.data!.startsWith('Platform: ${Platform.operatingSystem}'), + ), + findsOneWidget, + ); }); } diff --git a/packages/integration_test/example/ios/Runner.xcodeproj/project.pbxproj b/packages/integration_test/example/ios/Runner.xcodeproj/project.pbxproj index d3b7c244bb..a519162de3 100644 --- a/packages/integration_test/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/integration_test/example/ios/Runner.xcodeproj/project.pbxproj @@ -10,14 +10,13 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 4DB404AC7CF2C89658A01173 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 81BF64028CE7AE2E6196250D /* libPods-RunnerTests.a */; }; - 769541CB23A0351900E5C350 /* RunnerObjCTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 769541CA23A0351900E5C350 /* RunnerObjCTests.m */; }; + 769541CB23A0351900E5C350 /* RunnerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 769541CA23A0351900E5C350 /* RunnerTests.m */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; C2A5EDF11F4FDBF3ABFD7006 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 625A5A90428602E25C0DE2F6 /* libPods-Runner.a */; }; - F77B951926C3504400F785B3 /* RunnerSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77B951826C3504400F785B3 /* RunnerSwiftTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -53,7 +52,7 @@ 750225973AAB5D7832AFA60C /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 769541BF23A0337200E5C350 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 769541C823A0351900E5C350 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 769541CA23A0351900E5C350 /* RunnerObjCTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RunnerObjCTests.m; sourceTree = ""; }; + 769541CA23A0351900E5C350 /* RunnerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RunnerTests.m; sourceTree = ""; }; 769541CC23A0351900E5C350 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -69,7 +68,6 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D69CCAD5F82E76E2E22BFA96 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; E23EF4D45DAE46B9DDB9B445 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - F77B951826C3504400F785B3 /* RunnerSwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerSwiftTests.swift; sourceTree = ""; }; FCE3953801588FC13ED9E898 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -106,8 +104,7 @@ 769541C923A0351900E5C350 /* RunnerTests */ = { isa = PBXGroup; children = ( - 769541CA23A0351900E5C350 /* RunnerObjCTests.m */, - F77B951826C3504400F785B3 /* RunnerSwiftTests.swift */, + 769541CA23A0351900E5C350 /* RunnerTests.m */, 769541CC23A0351900E5C350 /* Info.plist */, ); path = RunnerTests; @@ -236,7 +233,6 @@ TargetAttributes = { 769541C723A0351900E5C350 = { CreatedOnToolsVersion = 11.0; - LastSwiftMigration = 1300; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; @@ -365,8 +361,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 769541CB23A0351900E5C350 /* RunnerObjCTests.m in Sources */, - F77B951926C3504400F785B3 /* RunnerSwiftTests.swift in Sources */, + 769541CB23A0351900E5C350 /* RunnerTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -480,14 +475,11 @@ baseConfigurationReference = 09505407E99803EF7AA92DE7 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.example.instrumentationAdapterExample.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Debug; @@ -497,13 +489,11 @@ baseConfigurationReference = FCE3953801588FC13ED9E898 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.example.instrumentationAdapterExample.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; @@ -513,13 +503,11 @@ baseConfigurationReference = 750225973AAB5D7832AFA60C /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.example.instrumentationAdapterExample.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Profile; diff --git a/packages/integration_test/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/integration_test/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ea00904e8d..72fa1469f5 100644 --- a/packages/integration_test/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/integration_test/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -20,20 +20,6 @@ ReferencedContainer = "container:Runner.xcodeproj"> - - - - *)testInvocations { - // Add a test to verify the Flutter dart tests have been dynamically added to this test case. - SEL selector = @selector(testDynamicTestMethods); - NSMethodSignature *signature = [self instanceMethodSignatureForSelector:selector]; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; - invocation.selector = selector; - - return [super.testInvocations arrayByAddingObject:invocation]; -} - -- (void)testDynamicTestMethods { - XCTAssertTrue([self respondsToSelector:NSSelectorFromString(@"testVerifyScreenshot")]); - XCTAssertTrue([self respondsToSelector:NSSelectorFromString(@"testVerifyText")]); - XCTAssertTrue([self respondsToSelector:NSSelectorFromString(@"screenshotPlaceholder")]); -} - -@end - -// Test deprecated macro. Do not use. -INTEGRATION_TEST_IOS_RUNNER(RunnerObjCMacroTests) - -@interface DeprecatedIntegrationTestIosTests : XCTestCase -@end - -@implementation DeprecatedIntegrationTestIosTests - -- (void)testIntegrationTest { - NSString *testResult; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - BOOL testPass = [[IntegrationTestIosTest new] testIntegrationTest:&testResult]; -#pragma clang diagnostic pop - XCTAssertTrue(testPass, @"%@", testResult); -} - -@end diff --git a/packages/integration_test/example/ios/RunnerTests/RunnerSwiftTests.swift b/packages/integration_test/example/ios/RunnerTests/RunnerTests.m similarity index 65% rename from packages/integration_test/example/ios/RunnerTests/RunnerSwiftTests.swift rename to packages/integration_test/example/ios/RunnerTests/RunnerTests.m index 433f828856..edd7f102c7 100644 --- a/packages/integration_test/example/ios/RunnerTests/RunnerSwiftTests.swift +++ b/packages/integration_test/example/ios/RunnerTests/RunnerTests.m @@ -2,8 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import integration_test -import XCTest +@import XCTest; +@import integration_test; -class RunnerSwiftTests: FLTIntegrationTestCase { -} +INTEGRATION_TEST_IOS_RUNNER(RunnerTests) diff --git a/packages/integration_test/ios/Classes/FLTIntegrationTestCase.h b/packages/integration_test/ios/Classes/FLTIntegrationTestCase.h deleted file mode 100644 index 64a6ac6fba..0000000000 --- a/packages/integration_test/ios/Classes/FLTIntegrationTestCase.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// XCTest is weakly linked. -#if __has_include() - -@import XCTest; - -NS_ASSUME_NONNULL_BEGIN - -@interface FLTIntegrationTestCase : XCTestCase -@end - -/*! - Deprecated. Prefer directly inheriting from @c FLTIntegrationTestCase - */ -#define INTEGRATION_TEST_IOS_RUNNER(__test_class) \ - @interface __test_class : FLTIntegrationTestCase \ - @end \ - \ - @implementation __test_class \ - @end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/packages/integration_test/ios/Classes/FLTIntegrationTestCase.m b/packages/integration_test/ios/Classes/FLTIntegrationTestCase.m deleted file mode 100644 index e75bcf8715..0000000000 --- a/packages/integration_test/ios/Classes/FLTIntegrationTestCase.m +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// XCTest is weakly linked. -#if __has_include() - -#import "FLTIntegrationTestCase.h" - -#import "FLTIntegrationTestRunner.h" -#import "IntegrationTestPlugin.h" - -@import ObjectiveC.runtime; -@import XCTest; - -@implementation FLTIntegrationTestCase - -+ (NSArray *)testInvocations { - if (self == [FLTIntegrationTestCase class]) { - // Do not add any tests for this base class. - return @[]; - } - FLTIntegrationTestRunner *integrationTestRunner = [FLTIntegrationTestRunner new]; - NSMutableArray *testInvocations = [NSMutableArray new]; - [integrationTestRunner testIntegrationTestWithResults:^(NSString *testName, BOOL success, NSString *failureMessage) { - // 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. - IMP assertImplementation = imp_implementationWithBlock(^(id _self) { - XCTAssertTrue(success, @"%@", failureMessage); - }); - - // Create an appropriate XCTest method name based on the dart test name. - // Example: dart test "verify widget" becomes "testVerifyWidget" - NSString *upperCamelTestName = [testName.localizedCapitalizedString stringByReplacingOccurrencesOfString:@" " withString:@""]; - NSString *testSelectorName = [NSString stringWithFormat:@"test%@", upperCamelTestName]; - SEL testSelector = NSSelectorFromString(testSelectorName); - class_addMethod(self, testSelector, assertImplementation, "v@:"); - - // Add the new class method as a test invocation to the XCTestCase. - NSMethodSignature *signature = [self instanceMethodSignatureForSelector:testSelector]; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; - invocation.selector = testSelector; - - [testInvocations addObject:invocation]; - }]; - - NSDictionary *capturedScreenshotsByName = integrationTestRunner.capturedScreenshotsByName; - if (capturedScreenshotsByName.count > 0) { - // If the Flutter dart tests have captured screenshots, add them to the XCTest bundle. - IMP screenshotImplementation = imp_implementationWithBlock(^(id _self) { - [capturedScreenshotsByName enumerateKeysAndObjectsUsingBlock:^(NSString *name, UIImage *screenshot, BOOL *stop) { - XCTAttachment *attachment = [XCTAttachment attachmentWithImage:screenshot]; - attachment.lifetime = XCTAttachmentLifetimeKeepAlways; - if (name != nil) { - attachment.name = name; - } - [_self addAttachment:attachment]; - }]; - }); - - SEL attachmentSelector = NSSelectorFromString(@"screenshotPlaceholder"); - class_addMethod(self, attachmentSelector, screenshotImplementation, "v@:"); - - NSMethodSignature *attachmentSignature = [self instanceMethodSignatureForSelector:attachmentSelector]; - NSInvocation *attachmentInvocation = [NSInvocation invocationWithMethodSignature:attachmentSignature]; - attachmentInvocation.selector = attachmentSelector; - - [testInvocations addObject:attachmentInvocation]; - } - return testInvocations; -} - -@end - -#endif diff --git a/packages/integration_test/ios/Classes/FLTIntegrationTestRunner.h b/packages/integration_test/ios/Classes/FLTIntegrationTestRunner.h deleted file mode 100644 index 3b0407b6d0..0000000000 --- a/packages/integration_test/ios/Classes/FLTIntegrationTestRunner.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import Foundation; - -@class UIImage; - -NS_ASSUME_NONNULL_BEGIN - -typedef void (^FLTIntegrationTestResults)(NSString *testName, BOOL success, NSString *_Nullable failureMessage); - - -@interface FLTIntegrationTestRunner : NSObject - -/** - * Any screenshots captured by the plugin. - */ -@property (copy, readonly) NSDictionary *capturedScreenshotsByName; - -/*! - Start dart tests and wait for results. - - @param testResult Will be called once per every completed dart test. - */ -- (void)testIntegrationTestWithResults:(NS_NOESCAPE FLTIntegrationTestResults)testResult; - -@end - -DEPRECATED_MSG_ATTRIBUTE("Use FLTIntegrationTestRunner instead.") -@interface IntegrationTestIosTest : NSObject - -/*! - Initate dart tests and wait for results. - - @param testResult Will be set to a string describing the results. - @returns @c YES if all tests succeeded. - */ -- (BOOL)testIntegrationTest:(NSString *_Nullable *_Nullable)testResult; - -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/integration_test/ios/Classes/FLTIntegrationTestRunner.m b/packages/integration_test/ios/Classes/FLTIntegrationTestRunner.m deleted file mode 100644 index 766102f17f..0000000000 --- a/packages/integration_test/ios/Classes/FLTIntegrationTestRunner.m +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "FLTIntegrationTestRunner.h" - -#import "IntegrationTestPlugin.h" - -@import UIKit; - -@interface FLTIntegrationTestRunner () - -@property IntegrationTestPlugin *integrationTestPlugin; - -@end - -@implementation FLTIntegrationTestRunner - -- (instancetype)init { - self = [super init]; - _integrationTestPlugin = [IntegrationTestPlugin instance]; - - return self; -} - -- (void)testIntegrationTestWithResults:(NS_NOESCAPE FLTIntegrationTestResults)testResult { - IntegrationTestPlugin *integrationTestPlugin = self.integrationTestPlugin; - UIViewController *rootViewController = UIApplication.sharedApplication.delegate.window.rootViewController; - if (![rootViewController isKindOfClass:[FlutterViewController class]]) { - testResult(@"setup", NO, @"rootViewController was not expected FlutterViewController"); - } - FlutterViewController *flutterViewController = (FlutterViewController *)rootViewController; - [integrationTestPlugin setupChannels:flutterViewController.engine.binaryMessenger]; - - // Spin the runloop. - while (!integrationTestPlugin.testResults) { - [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; - } - - [integrationTestPlugin.testResults enumerateKeysAndObjectsUsingBlock:^(NSString *test, NSString *result, BOOL *stop) { - if ([result isEqualToString:@"success"]) { - testResult(test, YES, nil); - } else { - testResult(test, NO, result); - } - }]; -} - -- (NSDictionary *)capturedScreenshotsByName { - return self.integrationTestPlugin.capturedScreenshotsByName; -} - -@end - -#pragma mark - Deprecated - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" - -@implementation IntegrationTestIosTest - -- (BOOL)testIntegrationTest:(NSString **)testResult { - NSLog(@"==================== Test Results ====================="); - NSMutableArray *failedTests = [NSMutableArray array]; - NSMutableArray *testNames = [NSMutableArray array]; - [[FLTIntegrationTestRunner new] testIntegrationTestWithResults:^(NSString *testName, BOOL success, NSString *message) { - [testNames addObject:testName]; - if (success) { - NSLog(@"%@ passed.", testName); - } else { - NSLog(@"%@ failed: %@", testName, message); - [failedTests addObject:testName]; - } - }]; - NSLog(@"================== Test Results End ===================="); - BOOL testPass = failedTests.count == 0; - if (!testPass && testResult != NULL) { - *testResult = - [NSString stringWithFormat:@"Detected failed integration test(s) %@ among %@", - failedTests.description, testNames.description]; - } - return testPass; -} - -@end -#pragma clang diagnostic pop diff --git a/packages/integration_test/ios/Classes/IntegrationTestIosTest.h b/packages/integration_test/ios/Classes/IntegrationTestIosTest.h new file mode 100644 index 0000000000..333b0ece93 --- /dev/null +++ b/packages/integration_test/ios/Classes/IntegrationTestIosTest.h @@ -0,0 +1,48 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol FLTIntegrationTestScreenshotDelegate; + +@interface IntegrationTestIosTest : NSObject + +- (instancetype)initWithScreenshotDelegate:(nullable id)delegate NS_DESIGNATED_INITIALIZER; + +/** + * Initate dart tests and wait for results. @c testResult will be set to a string describing the results. + * + * @return @c YES if all tests succeeded. + */ +- (BOOL)testIntegrationTest:(NSString *_Nullable *_Nullable)testResult; + +@end + +#define INTEGRATION_TEST_IOS_RUNNER(__test_class) \ + @interface __test_class : XCTestCase \ + @end \ + \ + @implementation __test_class \ + \ + - (void)testIntegrationTest { \ + NSString *testResult; \ + IntegrationTestIosTest *integrationTestIosTest = integrationTestIosTest = [[IntegrationTestIosTest alloc] initWithScreenshotDelegate:self]; \ + BOOL testPass = [integrationTestIosTest testIntegrationTest:&testResult]; \ + XCTAssertTrue(testPass, @"%@", testResult); \ + } \ + \ + - (void)didTakeScreenshot:(UIImage *)screenshot attachmentName:(NSString *)name { \ + XCTAttachment *attachment = [XCTAttachment attachmentWithImage:screenshot]; \ + attachment.lifetime = XCTAttachmentLifetimeKeepAlways; \ + if (name != nil) { \ + attachment.name = name; \ + } \ + [self addAttachment:attachment]; \ + } \ + \ + @end + +NS_ASSUME_NONNULL_END diff --git a/packages/integration_test/ios/Classes/IntegrationTestIosTest.m b/packages/integration_test/ios/Classes/IntegrationTestIosTest.m new file mode 100644 index 0000000000..6a54ed2c77 --- /dev/null +++ b/packages/integration_test/ios/Classes/IntegrationTestIosTest.m @@ -0,0 +1,63 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "IntegrationTestIosTest.h" +#import "IntegrationTestPlugin.h" + +@interface IntegrationTestIosTest() +@property (nonatomic) IntegrationTestPlugin *integrationTestPlugin; +@end + +@implementation IntegrationTestIosTest + +- (instancetype)initWithScreenshotDelegate:(id)delegate { + self = [super init]; + _integrationTestPlugin = [IntegrationTestPlugin instance]; + _integrationTestPlugin.screenshotDelegate = delegate; + return self; +} + +- (instancetype)init { + return [self initWithScreenshotDelegate:nil]; +} + +- (BOOL)testIntegrationTest:(NSString **)testResult { + IntegrationTestPlugin *integrationTestPlugin = self.integrationTestPlugin; + + UIViewController *rootViewController = + [[[[UIApplication sharedApplication] delegate] window] rootViewController]; + if (![rootViewController isKindOfClass:[FlutterViewController class]]) { + NSLog(@"expected FlutterViewController as rootViewController."); + return NO; + } + FlutterViewController *flutterViewController = (FlutterViewController *)rootViewController; + [integrationTestPlugin setupChannels:flutterViewController.engine.binaryMessenger]; + while (!integrationTestPlugin.testResults) { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.f, NO); + } + NSDictionary *testResults = integrationTestPlugin.testResults; + NSMutableArray *passedTests = [NSMutableArray array]; + NSMutableArray *failedTests = [NSMutableArray array]; + NSLog(@"==================== Test Results ====================="); + for (NSString *test in testResults.allKeys) { + NSString *result = testResults[test]; + if ([result isEqualToString:@"success"]) { + NSLog(@"%@ passed.", test); + [passedTests addObject:test]; + } else { + NSLog(@"%@ failed: %@", test, result); + [failedTests addObject:test]; + } + } + NSLog(@"================== Test Results End ===================="); + BOOL testPass = failedTests.count == 0; + if (!testPass && testResult) { + *testResult = + [NSString stringWithFormat:@"Detected failed integration test(s) %@ among %@", + failedTests.description, testResults.allKeys.description]; + } + return testPass; +} + +@end diff --git a/packages/integration_test/ios/Classes/IntegrationTestPlugin.h b/packages/integration_test/ios/Classes/IntegrationTestPlugin.h index 4836339a5f..9684835acf 100644 --- a/packages/integration_test/ios/Classes/IntegrationTestPlugin.h +++ b/packages/integration_test/ios/Classes/IntegrationTestPlugin.h @@ -6,6 +6,13 @@ NS_ASSUME_NONNULL_BEGIN +@protocol FLTIntegrationTestScreenshotDelegate + +/** This will be called when a dart integration test triggers a window screenshot with @c takeScreenshot. */ +- (void)didTakeScreenshot:(UIImage *)screenshot attachmentName:(nullable NSString *)name; + +@end + /** A Flutter plugin that's responsible for communicating the test results back * to iOS XCTest. */ @interface IntegrationTestPlugin : NSObject @@ -16,11 +23,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property(nonatomic, readonly, nullable) NSDictionary *testResults; -/** - * Mapping of screenshot images by suggested names, captured by the dart tests. - */ -@property (copy, readonly) NSDictionary *capturedScreenshotsByName; - /** Fetches the singleton instance of the plugin. */ + (IntegrationTestPlugin *)instance; @@ -28,6 +30,8 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init NS_UNAVAILABLE; +@property(weak, nonatomic) id screenshotDelegate; + @end NS_ASSUME_NONNULL_END diff --git a/packages/integration_test/ios/Classes/IntegrationTestPlugin.m b/packages/integration_test/ios/Classes/IntegrationTestPlugin.m index a8a80b6785..82d263595e 100644 --- a/packages/integration_test/ios/Classes/IntegrationTestPlugin.m +++ b/packages/integration_test/ios/Classes/IntegrationTestPlugin.m @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "IntegrationTestPlugin.h" - @import UIKit; +#import "IntegrationTestPlugin.h" + static NSString *const kIntegrationTestPluginChannel = @"plugins.flutter.io/integration_test"; static NSString *const kMethodTestFinished = @"allTestsFinished"; static NSString *const kMethodScreenshot = @"captureScreenshot"; @@ -16,13 +16,10 @@ static NSString *const kMethodRevertImage = @"revertFlutterImage"; @property(nonatomic, readwrite) NSDictionary *testResults; -- (instancetype)init NS_DESIGNATED_INITIALIZER; - @end @implementation IntegrationTestPlugin { NSDictionary *_testResults; - NSMutableDictionary *_capturedScreenshotsByName; } + (IntegrationTestPlugin *)instance { @@ -35,13 +32,7 @@ static NSString *const kMethodRevertImage = @"revertFlutterImage"; } - (instancetype)initForRegistration { - return [self init]; -} - -- (instancetype)init { - self = [super init]; - _capturedScreenshotsByName = [NSMutableDictionary new]; - return self; + return [super init]; } + (void)registerWithRegistrar:(NSObject *)registrar { @@ -68,7 +59,7 @@ static NSString *const kMethodRevertImage = @"revertFlutterImage"; // If running as a native Xcode test, attach to test. UIImage *screenshot = [self capturePngScreenshot]; NSString *name = call.arguments[@"name"]; - _capturedScreenshotsByName[name] = screenshot; + [self.screenshotDelegate didTakeScreenshot:screenshot attachmentName:name]; // Also pass back along the channel for the driver to handle. NSData *pngData = UIImagePNGRepresentation(screenshot); diff --git a/packages/integration_test/ios/integration_test.podspec b/packages/integration_test/ios/integration_test.podspec index 525507384b..fb24cb0b8f 100644 --- a/packages/integration_test/ios/integration_test.podspec +++ b/packages/integration_test/ios/integration_test.podspec @@ -19,14 +19,7 @@ LICENSE s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' s.ios.framework = 'UIKit' - # Weakly link for parts of API that need to be run in XCTest targets. - s.ios.weak_framework = 'XCTest' s.platform = :ios, '8.0' - s.pod_target_xcconfig = { - 'DEFINES_MODULE' => 'YES', - 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', - # Find XCTest framework. - 'FRAMEWORK_SEARCH_PATHS' => '$(PLATFORM_DIR)/Developer/Library/Frameworks', - } + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } end