[e2e] Creates basic support/documentation/example to iOS. (#2394)
* * Updates E2EPlugin and add skeleton iOS test case E2EIosTest.
* Adds instructions to README.md about e2e testing on iOS device.
* Applies iOS e2e to example.
Co-Authored-By: Collin Jackson <jackson@google.com>
diff --git a/packages/e2e/CHANGELOG.md b/packages/e2e/CHANGELOG.md
index 1b09c89..083e4d8 100644
--- a/packages/e2e/CHANGELOG.md
+++ b/packages/e2e/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 0.2.3
+
+* Updates `E2EPlugin` and add skeleton iOS test case `E2EIosTest`.
+* Adds instructions to README.md about e2e testing on iOS devices.
+* Adds iOS e2e testing to example.
+
## 0.2.2+3
* Remove the deprecated `author:` field from pubspec.yaml
diff --git a/packages/e2e/README.md b/packages/e2e/README.md
index bafbff1..b3c19ec 100644
--- a/packages/e2e/README.md
+++ b/packages/e2e/README.md
@@ -151,4 +151,33 @@
devices you want to test on. See
[gcloud firebase test android run](https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run).
-iOS support for Firebase Test Lab is not yet available, but is planned.
+## iOS device testing
+
+You need to change `iOS/Podfile` to avoid test target statically linking to the plugins. One way is to
+link all of the plugins dynamically:
+
+```
+target 'Runner' do
+ use_frameworks!
+ ...
+end
+```
+
+To e2e test on your iOS device (simulator or real), rebuild your iOS targets with Flutter tool.
+
+```
+flutter build ios -t test_driver/<package_name>_e2e.dart (--simulator)
+```
+
+Open Xcode project (by default, it's `ios/Runner.xcodeproj`). Create a test target
+(navigating `File > New > Target...` and set up the values) and a test file `RunnerTests.m` and
+change the code. You can change `RunnerTests.m` to the name of your choice.
+
+```objective-c
+#import <XCTest/XCTest.h>
+#import <e2e/E2EIosTest.h>
+
+E2E_IOS_RUNNER(RunnerTests)
+```
+
+Now you can start RunnerTests to kick out e2e tests!
diff --git a/packages/e2e/example/ios/Runner.xcodeproj/project.pbxproj b/packages/e2e/example/ios/Runner.xcodeproj/project.pbxproj
index 88aca9f..b96fa2f 100644
--- a/packages/e2e/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/e2e/example/ios/Runner.xcodeproj/project.pbxproj
@@ -11,6 +11,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 769541CB23A0351900E5C350 /* RunnerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 769541CA23A0351900E5C350 /* RunnerTests.m */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
@@ -18,9 +19,19 @@
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 */; };
- F81AEF02CE63DA0020B29F57 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 241E53603CE376E3BCB194D3 /* libPods-Runner.a */; };
+ C2A5EDF11F4FDBF3ABFD7006 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 625A5A90428602E25C0DE2F6 /* libPods-Runner.a */; };
/* End PBXBuildFile section */
+/* Begin PBXContainerItemProxy section */
+ 769541CD23A0351900E5C350 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 97C146E61CF9000F007C117D /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 97C146ED1CF9000F007C117D;
+ remoteInfo = Runner;
+ };
+/* End PBXContainerItemProxy section */
+
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
@@ -40,9 +51,13 @@
0D6F1CB5DBBEBCC75AFAD041 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
- 241E53603CE376E3BCB194D3 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
+ 625A5A90428602E25C0DE2F6 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 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 /* RunnerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RunnerTests.m; sourceTree = "<group>"; };
+ 769541CC23A0351900E5C350 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
@@ -60,13 +75,20 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 769541C523A0351900E5C350 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
- F81AEF02CE63DA0020B29F57 /* libPods-Runner.a in Frameworks */,
+ C2A5EDF11F4FDBF3ABFD7006 /* libPods-Runner.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -76,11 +98,21 @@
42D734D13B733A64B01A24A9 /* Frameworks */ = {
isa = PBXGroup;
children = (
- 241E53603CE376E3BCB194D3 /* libPods-Runner.a */,
+ 769541BF23A0337200E5C350 /* XCTest.framework */,
+ 625A5A90428602E25C0DE2F6 /* libPods-Runner.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
+ 769541C923A0351900E5C350 /* RunnerTests */ = {
+ isa = PBXGroup;
+ children = (
+ 769541CA23A0351900E5C350 /* RunnerTests.m */,
+ 769541CC23A0351900E5C350 /* Info.plist */,
+ );
+ path = RunnerTests;
+ sourceTree = "<group>";
+ };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@@ -99,6 +131,7 @@
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
+ 769541C923A0351900E5C350 /* RunnerTests */,
97C146EF1CF9000F007C117D /* Products */,
BAB55133DD7BD81A2557E916 /* Pods */,
42D734D13B733A64B01A24A9 /* Frameworks */,
@@ -109,6 +142,7 @@
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
+ 769541C823A0351900E5C350 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
@@ -144,13 +178,30 @@
0D6F1CB5DBBEBCC75AFAD041 /* Pods-Runner.release.xcconfig */,
E23EF4D45DAE46B9DDB9B445 /* Pods-Runner.profile.xcconfig */,
);
- name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
+ 769541C723A0351900E5C350 /* RunnerTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 769541CF23A0351900E5C350 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
+ buildPhases = (
+ 769541C423A0351900E5C350 /* Sources */,
+ 769541C523A0351900E5C350 /* Frameworks */,
+ 769541C623A0351900E5C350 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 769541CE23A0351900E5C350 /* PBXTargetDependency */,
+ );
+ name = RunnerTests;
+ productName = RunnerTests;
+ productReference = 769541C823A0351900E5C350 /* RunnerTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
@@ -182,6 +233,11 @@
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
+ 769541C723A0351900E5C350 = {
+ CreatedOnToolsVersion = 11.0;
+ ProvisioningStyle = Automatic;
+ TestTargetID = 97C146ED1CF9000F007C117D;
+ };
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
};
@@ -201,11 +257,19 @@
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
+ 769541C723A0351900E5C350 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
+ 769541C623A0351900E5C350 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -288,6 +352,14 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
+ 769541C423A0351900E5C350 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 769541CB23A0351900E5C350 /* RunnerTests.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -300,6 +372,14 @@
};
/* End PBXSourcesBuildPhase section */
+/* Begin PBXTargetDependency section */
+ 769541CE23A0351900E5C350 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 97C146ED1CF9000F007C117D /* Runner */;
+ targetProxy = 769541CD23A0351900E5C350 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
@@ -394,6 +474,73 @@
};
name = Profile;
};
+ 769541D023A0351900E5C350 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = RunnerTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.instrumentationAdapterExample.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
+ };
+ name = Debug;
+ };
+ 769541D123A0351900E5C350 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = RunnerTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.instrumentationAdapterExample.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
+ };
+ name = Release;
+ };
+ 769541D223A0351900E5C350 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = RunnerTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.instrumentationAdapterExample.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
+ };
+ name = Profile;
+ };
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
@@ -550,6 +697,16 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ 769541CF23A0351900E5C350 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 769541D023A0351900E5C350 /* Debug */,
+ 769541D123A0351900E5C350 /* Release */,
+ 769541D223A0351900E5C350 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/packages/e2e/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/e2e/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index a28140c..72fa146 100644
--- a/packages/e2e/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/packages/e2e/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
- <Testables>
- </Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@@ -38,8 +36,18 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
- <AdditionalOptions>
- </AdditionalOptions>
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "769541C723A0351900E5C350"
+ BuildableName = "RunnerTests.xctest"
+ BlueprintName = "RunnerTests"
+ ReferencedContainer = "container:Runner.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -61,8 +69,6 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
- <AdditionalOptions>
- </AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
diff --git a/packages/e2e/example/ios/RunnerTests/Info.plist b/packages/e2e/example/ios/RunnerTests/Info.plist
new file mode 100644
index 0000000..64d65ca
--- /dev/null
+++ b/packages/e2e/example/ios/RunnerTests/Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+</dict>
+</plist>
diff --git a/packages/e2e/example/ios/RunnerTests/RunnerTests.m b/packages/e2e/example/ios/RunnerTests/RunnerTests.m
new file mode 100644
index 0000000..9614c65
--- /dev/null
+++ b/packages/e2e/example/ios/RunnerTests/RunnerTests.m
@@ -0,0 +1,4 @@
+#import <XCTest/XCTest.h>
+#import <e2e/E2EIosTest.h>
+
+E2E_IOS_RUNNER(RunnerTests)
diff --git a/packages/e2e/ios/Classes/E2EIosTest.h b/packages/e2e/ios/Classes/E2EIosTest.h
new file mode 100644
index 0000000..1d76514
--- /dev/null
+++ b/packages/e2e/ios/Classes/E2EIosTest.h
@@ -0,0 +1,22 @@
+#import <Foundation/Foundation.h>
+
+@interface E2EIosTest : NSObject
+
+- (BOOL)testE2E:(NSString **)testResult;
+
+@end
+
+#define E2E_IOS_RUNNER(__test_class) \
+ @interface __test_class : XCTestCase \
+ @end \
+ \
+ @implementation __test_class \
+ \
+ -(void)testE2E { \
+ NSString *testResult; \
+ E2EIosTest *e2eIosTest = [[E2EIosTest alloc] init]; \
+ BOOL testPass = [e2eIosTest testE2E:&testResult]; \
+ XCTAssertTrue(testPass, @"%@", testResult); \
+ } \
+ \
+ @end
diff --git a/packages/e2e/ios/Classes/E2EIosTest.m b/packages/e2e/ios/Classes/E2EIosTest.m
new file mode 100644
index 0000000..587e3cd
--- /dev/null
+++ b/packages/e2e/ios/Classes/E2EIosTest.m
@@ -0,0 +1,35 @@
+#import "E2EIosTest.h"
+#import "E2EPlugin.h"
+
+@implementation E2EIosTest
+
+- (BOOL)testE2E:(NSString **)testResult {
+ E2EPlugin *e2ePlugin = [E2EPlugin instance];
+ while (!e2ePlugin.testResults) {
+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.f, NO);
+ }
+ NSDictionary<NSString *, NSString *> *testResults = e2ePlugin.testResults;
+ NSMutableArray<NSString *> *passedTests = [NSMutableArray array];
+ NSMutableArray<NSString *> *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);
+ [failedTests addObject:test];
+ }
+ }
+ NSLog(@"================== Test Results End ====================");
+ BOOL testPass = failedTests.count == 0;
+ if (!testPass && testResult) {
+ *testResult =
+ [NSString stringWithFormat:@"Detected failed E2E test(s) %@ among %@",
+ failedTests.description, testResults.allKeys.description];
+ }
+ return testPass;
+}
+
+@end
diff --git a/packages/e2e/ios/Classes/E2EPlugin.h b/packages/e2e/ios/Classes/E2EPlugin.h
index 1411dce..b0b296c 100644
--- a/packages/e2e/ios/Classes/E2EPlugin.h
+++ b/packages/e2e/ios/Classes/E2EPlugin.h
@@ -1,4 +1,21 @@
#import <Flutter/Flutter.h>
+NS_ASSUME_NONNULL_BEGIN
+
+/** A Flutter plugin that's responsible for communicating the test results back to iOS XCTest. */
@interface E2EPlugin : NSObject <FlutterPlugin>
+
+/**
+ * Test results that are sent from Dart when E2E test completes. Before the completion, it is
+ * @c nil.
+ */
+@property(nonatomic, readonly, nullable) NSDictionary<NSString *, NSString *> *testResults;
+
+/** Fetches the singleton instance of the plugin. */
++ (E2EPlugin *)instance;
+
+- (instancetype)init NS_UNAVAILABLE;
+
@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/e2e/ios/Classes/E2EPlugin.m b/packages/e2e/ios/Classes/E2EPlugin.m
index 4f19f3a..aafdf33 100644
--- a/packages/e2e/ios/Classes/E2EPlugin.m
+++ b/packages/e2e/ios/Classes/E2EPlugin.m
@@ -1,16 +1,40 @@
#import "E2EPlugin.h"
-@implementation E2EPlugin
-+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
- FlutterMethodChannel* channel =
- [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.dev/e2e"
- binaryMessenger:[registrar messenger]];
- E2EPlugin* instance = [[E2EPlugin alloc] init];
- [registrar addMethodCallDelegate:instance channel:channel];
+static NSString *const kE2EPluginChannel = @"plugins.flutter.io/e2e";
+static NSString *const kMethodTestFinished = @"allTestsFinished";
+
+@interface E2EPlugin ()
+
+@property(nonatomic, readwrite) NSDictionary<NSString *, NSString *> *testResults;
+
+@end
+
+@implementation E2EPlugin {
+ NSDictionary<NSString *, NSString *> *_testResults;
}
-- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
- if ([@"allTestsFinished" isEqualToString:call.method]) {
++ (E2EPlugin *)instance {
+ static dispatch_once_t onceToken;
+ static E2EPlugin *sInstance;
+ dispatch_once(&onceToken, ^{
+ sInstance = [[E2EPlugin alloc] initForRegistration];
+ });
+ return sInstance;
+}
+
+- (instancetype)initForRegistration {
+ return [super init];
+}
+
++ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
+ FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:kE2EPluginChannel
+ binaryMessenger:registrar.messenger];
+ [registrar addMethodCallDelegate:[E2EPlugin instance] channel:channel];
+}
+
+- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
+ if ([kMethodTestFinished isEqual:call.method]) {
+ self.testResults = call.arguments[@"results"];
result(nil);
} else {
result(FlutterMethodNotImplemented);
diff --git a/packages/e2e/pubspec.yaml b/packages/e2e/pubspec.yaml
index 1806c41..504edd4 100644
--- a/packages/e2e/pubspec.yaml
+++ b/packages/e2e/pubspec.yaml
@@ -1,6 +1,6 @@
name: e2e
description: Runs tests that use the flutter_test API as integration tests.
-version: 0.2.2+3
+version: 0.2.3
homepage: https://github.com/flutter/plugins/tree/master/packages/e2e
environment: