Set up iOS native integration_tests in ui example project (#84596)

diff --git a/dev/devicelab/bin/tasks/native_ui_tests_ios32.dart b/dev/devicelab/bin/tasks/native_ui_tests_ios32.dart
index abfb566..8d09eb8 100644
--- a/dev/devicelab/bin/tasks/native_ui_tests_ios32.dart
+++ b/dev/devicelab/bin/tasks/native_ui_tests_ios32.dart
@@ -4,14 +4,11 @@
 
 // @dart = 2.8
 
-import 'dart:io';
-
 import 'package:flutter_devicelab/framework/devices.dart';
 import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/framework/host_agent.dart';
+import 'package:flutter_devicelab/framework/ios.dart';
 import 'package:flutter_devicelab/framework/task_result.dart';
 import 'package:flutter_devicelab/framework/utils.dart';
-import 'package:path/path.dart' as path;
 
 Future<void> main() async {
   deviceOperatingSystem = DeviceOperatingSystem.ios;
@@ -38,59 +35,8 @@
     });
 
     section('Run platform unit tests');
-
     final Device device = await devices.workingDevice;
-    final Map<String, String> environment = Platform.environment;
-    // If not running on CI, inject the Flutter team code signing properties.
-    final String developmentTeam = environment['FLUTTER_XCODE_DEVELOPMENT_TEAM'] ?? 'S8QB4VV633';
-    final String codeSignStyle = environment['FLUTTER_XCODE_CODE_SIGN_STYLE'];
-    final String provisioningProfile = environment['FLUTTER_XCODE_PROVISIONING_PROFILE_SPECIFIER'];
-
-    final String resultBundleTemp = Directory.systemTemp.createTempSync('flutter_native_ui_tests_ios32_xcresult.').path;
-    final String resultBundlePath = path.join(resultBundleTemp, 'result');
-    final int testResultExit = await exec(
-      'xcodebuild',
-      <String>[
-        '-workspace',
-        'Runner.xcworkspace',
-        '-scheme',
-        'Runner',
-        '-configuration',
-        'Release',
-        '-destination',
-        'id=${device.deviceId}',
-        '-resultBundlePath',
-        resultBundlePath,
-        'test',
-        'COMPILER_INDEX_STORE_ENABLE=NO',
-        'DEVELOPMENT_TEAM=$developmentTeam',
-        if (codeSignStyle != null)
-          'CODE_SIGN_STYLE=$codeSignStyle',
-        if (provisioningProfile != null)
-          'PROVISIONING_PROFILE_SPECIFIER=$provisioningProfile',
-      ],
-      workingDirectory: path.join(projectDirectory, 'ios'),
-      canFail: true,
-    );
-
-    if (testResultExit != 0) {
-      // Zip the test results to the artifacts directory for upload.
-      final String zipPath = path.join(hostAgent.dumpDirectory.path,
-          'native_ui_tests_ios32-${DateTime.now().toLocal().toIso8601String()}.zip');
-      await exec(
-        'zip',
-        <String>[
-          '-r',
-          '-9',
-          zipPath,
-          'result.xcresult',
-        ],
-        workingDirectory: resultBundleTemp,
-        canFail: true, // Best effort to get the logs.
-      );
-
-      return TaskResult.failure('Platform unit tests failed');
-    }
+    await runNativeIosXcodeTests(device, projectDirectory);
 
     return TaskResult.success(null);
   });
diff --git a/dev/devicelab/lib/framework/devices.dart b/dev/devicelab/lib/framework/devices.dart
index b83880b..a750488 100644
--- a/dev/devicelab/lib/framework/devices.dart
+++ b/dev/devicelab/lib/framework/devices.dart
@@ -879,8 +879,8 @@
   Future<void> stopLoggingToSink() async {
     assert(_loggingProcess != null);
     _abortedLogging = true;
-    _loggingProcess.kill();
-    await _loggingProcess.exitCode;
+    _loggingProcess?.kill();
+    await _loggingProcess?.exitCode;
   }
 
   // The methods below are stubs for now. They will need to be expanded.
diff --git a/dev/devicelab/lib/framework/framework.dart b/dev/devicelab/lib/framework/framework.dart
index 2056f48..e73e373 100644
--- a/dev/devicelab/lib/framework/framework.dart
+++ b/dev/devicelab/lib/framework/framework.dart
@@ -161,9 +161,8 @@
         result = await futureResult;
       } finally {
         if (device != null && device.canStreamLogs) {
-          assert(sink != null);
           await device.stopLoggingToSink();
-          await sink.close();
+          await sink?.close();
         }
       }
 
diff --git a/dev/devicelab/lib/framework/ios.dart b/dev/devicelab/lib/framework/ios.dart
index 6598011..25f2e62 100644
--- a/dev/devicelab/lib/framework/ios.dart
+++ b/dev/devicelab/lib/framework/ios.dart
@@ -5,7 +5,13 @@
 // @dart = 2.8
 
 import 'dart:convert';
+import 'dart:io';
 
+import 'package:path/path.dart' as path;
+
+import 'devices.dart';
+import 'host_agent.dart';
+import 'task_result.dart';
 import 'utils.dart';
 
 typedef SimulatorFunction = Future<void> Function(String deviceId);
@@ -144,3 +150,57 @@
     );
   }
 }
+
+Future<void> runNativeIosXcodeTests(Device device, String projectDirectory) async {
+  final Map<String, String> environment = Platform.environment;
+  // If not running on CI, inject the Flutter team code signing properties.
+  final String developmentTeam = environment['FLUTTER_XCODE_DEVELOPMENT_TEAM'] ?? 'S8QB4VV633';
+  final String codeSignStyle = environment['FLUTTER_XCODE_CODE_SIGN_STYLE'];
+  final String provisioningProfile = environment['FLUTTER_XCODE_PROVISIONING_PROFILE_SPECIFIER'];
+
+  final String resultBundleTemp = Directory.systemTemp.createTempSync('flutter_native_ios_tests_xcresult.').path;
+  final String resultBundlePath = path.join(resultBundleTemp, 'result');
+  final int testResultExit = await exec(
+    'xcodebuild',
+    <String>[
+      '-workspace',
+      'Runner.xcworkspace',
+      '-scheme',
+      'Runner',
+      '-configuration',
+      'Release',
+      '-destination',
+      'id=${device.deviceId}',
+      '-resultBundlePath',
+      resultBundlePath,
+      'test',
+      'COMPILER_INDEX_STORE_ENABLE=NO',
+      'DEVELOPMENT_TEAM=$developmentTeam',
+      if (codeSignStyle != null) 'CODE_SIGN_STYLE=$codeSignStyle',
+      if (provisioningProfile != null) 'PROVISIONING_PROFILE_SPECIFIER=$provisioningProfile',
+    ],
+    workingDirectory: path.join(projectDirectory, 'ios'),
+    canFail: true,
+  );
+
+  if (testResultExit != 0) {
+    if (hostAgent.dumpDirectory != null) {
+      // Zip the test results to the artifacts directory for upload.
+      final String zipPath = path.join(
+          hostAgent.dumpDirectory.path, 'xcode_test_results_-${DateTime.now().toLocal().toIso8601String()}.zip');
+      await exec(
+        'zip',
+        <String>[
+          '-r',
+          '-9',
+          zipPath,
+          'result.xcresult',
+        ],
+        workingDirectory: resultBundleTemp,
+        canFail: true, // Best effort to get the logs.
+      );
+    }
+
+    throw TaskResult.failure('Xcode iOS unit tests failed');
+  }
+}
diff --git a/dev/devicelab/lib/tasks/integration_tests.dart b/dev/devicelab/lib/tasks/integration_tests.dart
index c917524..ad834fa 100644
--- a/dev/devicelab/lib/tasks/integration_tests.dart
+++ b/dev/devicelab/lib/tasks/integration_tests.dart
@@ -7,6 +7,7 @@
 import '../framework/devices.dart';
 import '../framework/framework.dart';
 import '../framework/host_agent.dart';
+import '../framework/ios.dart';
 import '../framework/task_result.dart';
 import '../framework/utils.dart';
 
@@ -193,6 +194,19 @@
       ];
       await flutter('test', options: options);
 
+      if (device is IosDevice) {
+        section('Run integration_test from Xcode');
+
+        final List<String> options = <String>[
+          'ios',
+          '--config-only',
+          testTarget,
+        ];
+        await flutter('build', options: options);
+
+        await runNativeIosXcodeTests(device, testDirectory);
+      }
+
       return TaskResult.success(null);
     });
   }
diff --git a/dev/integration_tests/ui/integration_test/integration_test.dart b/dev/integration_tests/ui/integration_test/integration_test.dart
index 63a1b8d..2225213 100644
--- a/dev/integration_tests/ui/integration_test/integration_test.dart
+++ b/dev/integration_tests/ui/integration_test/integration_test.dart
@@ -3,10 +3,13 @@
 // found in the LICENSE file.
 
 import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
 import 'package:integration_ui/build_mode.dart' as app;
 
 void main() {
   group('Integration Test', () {
+    IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
     testWidgets('smoke test', (WidgetTester tester) async {
         app.main();
         await tester.pumpAndSettle();
diff --git a/dev/integration_tests/ui/ios/Podfile b/dev/integration_tests/ui/ios/Podfile
index f7d6a5e..5424d96 100644
--- a/dev/integration_tests/ui/ios/Podfile
+++ b/dev/integration_tests/ui/ios/Podfile
@@ -29,6 +29,10 @@
 
 target 'Runner' do
   flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+
+  target 'RunnerTests' do
+    inherit! :search_paths
+  end
 end
 
 post_install do |installer|
diff --git a/dev/integration_tests/ui/ios/Runner.xcodeproj/project.pbxproj b/dev/integration_tests/ui/ios/Runner.xcodeproj/project.pbxproj
index bddee37..a76951b 100644
--- a/dev/integration_tests/ui/ios/Runner.xcodeproj/project.pbxproj
+++ b/dev/integration_tests/ui/ios/Runner.xcodeproj/project.pbxproj
@@ -8,13 +8,26 @@
 
 /* Begin PBXBuildFile section */
 		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+		58800B94798CA5A104E28521 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 99734369176CE92FE3C3FAE7 /* libPods-RunnerTests.a */; };
 		74970F771EDC361B000507F3 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 74970F761EDC361B000507F3 /* GeneratedPluginRegistrant.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 */; };
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+		F49130393BCEFA115E8CB3C9 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A15F4FB18A2BCF2850EDF204 /* libPods-Runner.a */; };
+		F708CDEC2677EA0F00ABB9ED /* RunnerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F708CDEB2677EA0F00ABB9ED /* RunnerTests.m */; };
 /* End PBXBuildFile section */
 
+/* Begin PBXContainerItemProxy section */
+		F708CDEE2677EA0F00ABB9ED /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 97C146E61CF9000F007C117D /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 97C146ED1CF9000F007C117D;
+			remoteInfo = Runner;
+		};
+/* End PBXContainerItemProxy section */
+
 /* Begin PBXCopyFilesBuildPhase section */
 		9705A1C41CF9048500538489 /* Embed Frameworks */ = {
 			isa = PBXCopyFilesBuildPhase;
@@ -29,7 +42,10 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
+		01AF17B48AF2178A9A21F160 /* 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 = "<group>"; };
+		2EF453C5A2F05D9EF0501B26 /* 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 = "<group>"; };
 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
+		45A5ABE999C406678E4045B7 /* 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 = "<group>"; };
 		74970F751EDC361B000507F3 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
 		74970F761EDC361B000507F3 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
@@ -42,6 +58,14 @@
 		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
 		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		99734369176CE92FE3C3FAE7 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+		A15F4FB18A2BCF2850EDF204 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+		A49A3F3BC45A9B1B2FD61B93 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
+		A7C98B4E2E0891529C5D69AD /* 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>"; };
+		CD8582A0DE9EFCD6D7C46A44 /* 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 = "<group>"; };
+		F708CDE92677EA0F00ABB9ED /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		F708CDEB2677EA0F00ABB9ED /* RunnerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RunnerTests.m; sourceTree = "<group>"; };
+		F708CDED2677EA0F00ABB9ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -49,12 +73,34 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				F49130393BCEFA115E8CB3C9 /* libPods-Runner.a in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		F708CDE62677EA0F00ABB9ED /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				58800B94798CA5A104E28521 /* libPods-RunnerTests.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		2DEFC5CD867757A6689F5788 /* Pods */ = {
+			isa = PBXGroup;
+			children = (
+				2EF453C5A2F05D9EF0501B26 /* Pods-Runner.debug.xcconfig */,
+				A7C98B4E2E0891529C5D69AD /* Pods-Runner.release.xcconfig */,
+				CD8582A0DE9EFCD6D7C46A44 /* Pods-Runner.profile.xcconfig */,
+				A49A3F3BC45A9B1B2FD61B93 /* Pods-RunnerTests.debug.xcconfig */,
+				01AF17B48AF2178A9A21F160 /* Pods-RunnerTests.release.xcconfig */,
+				45A5ABE999C406678E4045B7 /* Pods-RunnerTests.profile.xcconfig */,
+			);
+			path = Pods;
+			sourceTree = "<group>";
+		};
 		9740EEB11CF90186004384FC /* Flutter */ = {
 			isa = PBXGroup;
 			children = (
@@ -71,8 +117,10 @@
 			children = (
 				9740EEB11CF90186004384FC /* Flutter */,
 				97C146F01CF9000F007C117D /* Runner */,
+				F708CDEA2677EA0F00ABB9ED /* RunnerTests */,
 				97C146EF1CF9000F007C117D /* Products */,
 				CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
+				2DEFC5CD867757A6689F5788 /* Pods */,
 			);
 			sourceTree = "<group>";
 		};
@@ -80,6 +128,7 @@
 			isa = PBXGroup;
 			children = (
 				97C146EE1CF9000F007C117D /* Runner.app */,
+				F708CDE92677EA0F00ABB9ED /* RunnerTests.xctest */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -110,10 +159,21 @@
 		CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				A15F4FB18A2BCF2850EDF204 /* libPods-Runner.a */,
+				99734369176CE92FE3C3FAE7 /* libPods-RunnerTests.a */,
 			);
 			name = Frameworks;
 			sourceTree = "<group>";
 		};
+		F708CDEA2677EA0F00ABB9ED /* RunnerTests */ = {
+			isa = PBXGroup;
+			children = (
+				F708CDEB2677EA0F00ABB9ED /* RunnerTests.m */,
+				F708CDED2677EA0F00ABB9ED /* Info.plist */,
+			);
+			path = RunnerTests;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -121,6 +181,7 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
 			buildPhases = (
+				F9E98175D006386223B65F5F /* [CP] Check Pods Manifest.lock */,
 				9740EEB61CF901F6004384FC /* Run Script */,
 				97C146EA1CF9000F007C117D /* Sources */,
 				97C146EB1CF9000F007C117D /* Frameworks */,
@@ -137,6 +198,25 @@
 			productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
 			productType = "com.apple.product-type.application";
 		};
+		F708CDE82677EA0F00ABB9ED /* RunnerTests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = F708CDF32677EA0F00ABB9ED /* Build configuration list for PBXNativeTarget "RunnerTests" */;
+			buildPhases = (
+				2ACBD5BF73FCAAA10AE73F3F /* [CP] Check Pods Manifest.lock */,
+				F708CDE52677EA0F00ABB9ED /* Sources */,
+				F708CDE62677EA0F00ABB9ED /* Frameworks */,
+				F708CDE72677EA0F00ABB9ED /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				F708CDEF2677EA0F00ABB9ED /* PBXTargetDependency */,
+			);
+			name = RunnerTests;
+			productName = RunnerTests;
+			productReference = F708CDE92677EA0F00ABB9ED /* RunnerTests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
 /* End PBXNativeTarget section */
 
 /* Begin PBXProject section */
@@ -149,6 +229,11 @@
 					97C146ED1CF9000F007C117D = {
 						CreatedOnToolsVersion = 7.3.1;
 					};
+					F708CDE82677EA0F00ABB9ED = {
+						CreatedOnToolsVersion = 12.5;
+						ProvisioningStyle = Automatic;
+						TestTargetID = 97C146ED1CF9000F007C117D;
+					};
 				};
 			};
 			buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
@@ -165,6 +250,7 @@
 			projectRoot = "";
 			targets = (
 				97C146ED1CF9000F007C117D /* Runner */,
+				F708CDE82677EA0F00ABB9ED /* RunnerTests */,
 			);
 		};
 /* End PBXProject section */
@@ -180,9 +266,38 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		F708CDE72677EA0F00ABB9ED /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXShellScriptBuildPhase section */
+		2ACBD5BF73FCAAA10AE73F3F /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			showEnvVarsInLog = 0;
+		};
 		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
@@ -211,6 +326,28 @@
 			shellPath = /bin/sh;
 			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
 		};
+		F9E98175D006386223B65F5F /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			showEnvVarsInLog = 0;
+		};
 /* End PBXShellScriptBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
@@ -224,8 +361,24 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		F708CDE52677EA0F00ABB9ED /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				F708CDEC2677EA0F00ABB9ED /* RunnerTests.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXSourcesBuildPhase section */
 
+/* Begin PBXTargetDependency section */
+		F708CDEF2677EA0F00ABB9ED /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 97C146ED1CF9000F007C117D /* Runner */;
+			targetProxy = F708CDEE2677EA0F00ABB9ED /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
 /* Begin PBXVariantGroup section */
 		97C146FA1CF9000F007C117D /* Main.storyboard */ = {
 			isa = PBXVariantGroup;
@@ -414,6 +567,48 @@
 			};
 			name = Release;
 		};
+		F708CDF02677EA0F00ABB9ED /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = A49A3F3BC45A9B1B2FD61B93 /* Pods-RunnerTests.debug.xcconfig */;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CODE_SIGN_STYLE = Automatic;
+				INFOPLIST_FILE = RunnerTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = io.flutter.e2e.RunnerTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
+			};
+			name = Debug;
+		};
+		F708CDF12677EA0F00ABB9ED /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 01AF17B48AF2178A9A21F160 /* Pods-RunnerTests.release.xcconfig */;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CODE_SIGN_STYLE = Automatic;
+				INFOPLIST_FILE = RunnerTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = io.flutter.e2e.RunnerTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
+			};
+			name = Release;
+		};
+		F708CDF22677EA0F00ABB9ED /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 45A5ABE999C406678E4045B7 /* Pods-RunnerTests.profile.xcconfig */;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CODE_SIGN_STYLE = Automatic;
+				INFOPLIST_FILE = RunnerTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = io.flutter.e2e.RunnerTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
+			};
+			name = Profile;
+		};
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
@@ -437,6 +632,16 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
+		F708CDF32677EA0F00ABB9ED /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				F708CDF02677EA0F00ABB9ED /* Debug */,
+				F708CDF12677EA0F00ABB9ED /* Release */,
+				F708CDF22677EA0F00ABB9ED /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 /* End XCConfigurationList section */
 	};
 	rootObject = 97C146E61CF9000F007C117D /* Project object */;
diff --git a/dev/integration_tests/ui/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/dev/integration_tests/ui/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index c959d74..80ae737 100644
--- a/dev/integration_tests/ui/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/dev/integration_tests/ui/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 = "F708CDE82677EA0F00ABB9ED"
+               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/dev/integration_tests/ui/ios/Runner.xcworkspace/contents.xcworkspacedata b/dev/integration_tests/ui/ios/Runner.xcworkspace/contents.xcworkspacedata
index 1d526a1..21a3cc1 100644
--- a/dev/integration_tests/ui/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ b/dev/integration_tests/ui/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -4,4 +4,7 @@
    <FileRef
       location = "group:Runner.xcodeproj">
    </FileRef>
+   <FileRef
+      location = "group:Pods/Pods.xcodeproj">
+   </FileRef>
 </Workspace>
diff --git a/dev/integration_tests/ui/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/dev/integration_tests/ui/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/dev/integration_tests/ui/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?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>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/dev/integration_tests/ui/ios/RunnerTests/Info.plist b/dev/integration_tests/ui/ios/RunnerTests/Info.plist
new file mode 100644
index 0000000..64d65ca
--- /dev/null
+++ b/dev/integration_tests/ui/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/dev/integration_tests/ui/ios/RunnerTests/RunnerTests.m b/dev/integration_tests/ui/ios/RunnerTests/RunnerTests.m
new file mode 100644
index 0000000..edd7f10
--- /dev/null
+++ b/dev/integration_tests/ui/ios/RunnerTests/RunnerTests.m
@@ -0,0 +1,8 @@
+// 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 XCTest;
+@import integration_test;
+
+INTEGRATION_TEST_IOS_RUNNER(RunnerTests)