Move simulator screenshot logic to use simctl (#8216)

* Move simulator screenshot logic to use simctl

* Add simulator screenshot tests
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index fa229e0..20cc97a 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -76,6 +76,12 @@
   String _xcodeVersionText;
   String get xcodeVersionText => _xcodeVersionText;
 
+  int _xcodeMajorVersion;
+  int get xcodeMajorVersion => _xcodeMajorVersion;
+
+  int _xcodeMinorVersion;
+  int get xcodeMinorVersion => _xcodeMinorVersion;
+
   final RegExp xcodeVersionRegex = new RegExp(r'Xcode ([0-9.]+)');
 
   bool get xcodeVersionSatisfactory {
@@ -85,10 +91,10 @@
     String version = xcodeVersionRegex.firstMatch(xcodeVersionText).group(1);
     List<String> components = version.split('.');
 
-    int major = int.parse(components[0]);
-    int minor = components.length == 1 ? 0 : int.parse(components[1]);
+    _xcodeMajorVersion = int.parse(components[0]);
+    _xcodeMinorVersion = components.length == 1 ? 0 : int.parse(components[1]);
 
-    return _xcodeVersionCheckValid(major, minor);
+    return _xcodeVersionCheckValid(_xcodeMajorVersion, _xcodeMinorVersion);
   }
 }
 
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 69143fd..ebf23f3 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -249,6 +249,10 @@
       args.addAll(launchArgs);
     runCheckedSync(args);
   }
+
+  void takeScreenshot(String outputPath) {
+    runCheckedSync(<String>[_xcrunPath, 'simctl', 'io', 'booted', 'screenshot', outputPath]);
+  }
 }
 
 /// Enumerates all data sections of `xcrun simctl list --json` command.
@@ -582,44 +586,18 @@
       logFile.writeAsBytesSync(<int>[]);
   }
 
-  @override
-  bool get supportsScreenshot => true;
+  bool get _xcodeVersionSupportsScreenshot {
+    return Xcode.instance.xcodeMajorVersion > 8 ||
+        (Xcode.instance.xcodeMajorVersion == 8 && Xcode.instance.xcodeMinorVersion >= 2);
+  }
 
   @override
-  Future<Null> takeScreenshot(File outputFile) async {
-    Directory desktopDir = fs.directory(fs.path.join(homeDirPath, 'Desktop'));
+  bool get supportsScreenshot => _xcodeVersionSupportsScreenshot;
 
-    // 'Simulator Screen Shot Mar 25, 2016, 2.59.43 PM.png'
-
-    Set<File> getScreenshots() {
-      return new Set<File>.from(desktopDir.listSync().where((FileSystemEntity entity) {
-        String name = fs.path.basename(entity.path);
-        return entity is File && name.startsWith('Simulator') && name.endsWith('.png');
-      }));
-    }
-
-    Set<File> existingScreenshots = getScreenshots();
-
-    runSync(<String>[
-      'osascript',
-      '-e',
-      'activate application "Simulator"\n'
-        'tell application "System Events" to keystroke "s" using command down'
-    ]);
-
-    // There is some latency here from the applescript call.
-    await new Future<Null>.delayed(new Duration(seconds: 1));
-
-    Set<File> shots = getScreenshots().difference(existingScreenshots);
-
-    if (shots.isEmpty) {
-      printError('Unable to locate the screenshot file.');
-      return false;
-    }
-
-    File shot = shots.first;
-    outputFile.writeAsBytesSync(shot.readAsBytesSync());
-    shot.delete();
+  @override
+  Future<Null> takeScreenshot(File outputFile) {
+    SimControl.instance.takeScreenshot(outputFile.path);
+    return new Future<Null>.value();
   }
 }
 
diff --git a/packages/flutter_tools/test/src/ios/simulators_test.dart b/packages/flutter_tools/test/src/ios/simulators_test.dart
index cb73c6a..37202e0 100644
--- a/packages/flutter_tools/test/src/ios/simulators_test.dart
+++ b/packages/flutter_tools/test/src/ios/simulators_test.dart
@@ -1,6 +1,17 @@
+import 'dart:io' show ProcessResult;
+
+import 'package:file/file.dart';
+import 'package:flutter_tools/src/ios/mac.dart';
+import 'package:flutter_tools/src/ios/simulators.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
 import 'package:test/test.dart';
 
-import 'package:flutter_tools/src/ios/simulators.dart';
+import '../context.dart';
+
+class MockXcode extends Mock implements Xcode {}
+class MockFile extends Mock implements File {}
+class MockProcessManager extends Mock implements ProcessManager {}
 
 void main() {
   group('compareIosVersions', () {
@@ -85,4 +96,63 @@
       expect(new IOSSimulator('x', name: 'iPhone 7 Plus').isSupported(), true);
     });
   });
+
+  group('Simulator screenshot', () {
+    MockXcode mockXcode;
+    MockProcessManager mockProcessManager;
+    IOSSimulator deviceUnderTest;
+
+    setUp(() {
+      mockXcode = new MockXcode();
+      mockProcessManager = new MockProcessManager();
+      // Let everything else return exit code 0 so process.dart doesn't crash.
+      when(
+        mockProcessManager.runSync(any, environment: null, workingDirectory:  null)
+      ).thenReturn(
+        new ProcessResult(2, 0, '', null)
+      );
+      // Doesn't matter what the device is.
+      deviceUnderTest = new IOSSimulator('x', name: 'iPhone SE');
+    });
+
+    testUsingContext(
+      'old Xcode doesn\'t support screenshot',
+      () {
+        when(mockXcode.xcodeMajorVersion).thenReturn(7);
+        when(mockXcode.xcodeMinorVersion).thenReturn(1);
+        expect(deviceUnderTest.supportsScreenshot, false);
+      },
+      overrides: <Type, Generator>{ Xcode: () => mockXcode }
+    );
+
+    testUsingContext(
+      'Xcode 8.2+ supports screenshots',
+      () {
+        when(mockXcode.xcodeMajorVersion).thenReturn(8);
+        when(mockXcode.xcodeMinorVersion).thenReturn(2);
+        expect(deviceUnderTest.supportsScreenshot, true);
+        MockFile mockFile = new MockFile();
+        when(mockFile.path).thenReturn('/some/path/to/screenshot.png');
+        deviceUnderTest.takeScreenshot(mockFile);
+        verify(mockProcessManager.runSync(
+          <String>[
+              '/usr/bin/xcrun',
+              'simctl',
+              'io',
+              'booted',
+              'screenshot',
+              '/some/path/to/screenshot.png'
+          ],
+          environment: null,
+          workingDirectory: null
+        ));
+      },
+      overrides: <Type, Generator>{
+        ProcessManager: () => mockProcessManager,
+        // Test a real one. Screenshot doesn't require instance states.
+        SimControl: () => new SimControl(),
+        Xcode: () => mockXcode,
+      }
+    );
+  });
 }