From 3985ddbcd4bd19e2b4c8e9978cf0aa022d8efe86 Mon Sep 17 00:00:00 2001 From: xster Date: Thu, 16 Feb 2017 12:50:27 -0800 Subject: [PATCH] Move simulator screenshot logic to use simctl (#8216) * Move simulator screenshot logic to use simctl * Add simulator screenshot tests --- packages/flutter_tools/lib/src/ios/mac.dart | 12 +++- .../flutter_tools/lib/src/ios/simulators.dart | 48 ++++--------- .../test/src/ios/simulators_test.dart | 72 ++++++++++++++++++- 3 files changed, 93 insertions(+), 39 deletions(-) diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index fa229e04033..20cc97a1ca0 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -76,6 +76,12 @@ class Xcode { 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 @@ class Xcode { String version = xcodeVersionRegex.firstMatch(xcodeVersionText).group(1); List 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 69143fdc6b0..ebf23f30533 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart @@ -249,6 +249,10 @@ class SimControl { args.addAll(launchArgs); runCheckedSync(args); } + + void takeScreenshot(String outputPath) { + runCheckedSync([_xcrunPath, 'simctl', 'io', 'booted', 'screenshot', outputPath]); + } } /// Enumerates all data sections of `xcrun simctl list --json` command. @@ -582,44 +586,18 @@ class IOSSimulator extends Device { logFile.writeAsBytesSync([]); } - @override - bool get supportsScreenshot => true; + bool get _xcodeVersionSupportsScreenshot { + return Xcode.instance.xcodeMajorVersion > 8 || + (Xcode.instance.xcodeMajorVersion == 8 && Xcode.instance.xcodeMinorVersion >= 2); + } @override - Future 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 getScreenshots() { - return new Set.from(desktopDir.listSync().where((FileSystemEntity entity) { - String name = fs.path.basename(entity.path); - return entity is File && name.startsWith('Simulator') && name.endsWith('.png'); - })); - } - - Set existingScreenshots = getScreenshots(); - - runSync([ - '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.delayed(new Duration(seconds: 1)); - - Set 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 takeScreenshot(File outputFile) { + SimControl.instance.takeScreenshot(outputFile.path); + return new Future.value(); } } diff --git a/packages/flutter_tools/test/src/ios/simulators_test.dart b/packages/flutter_tools/test/src/ios/simulators_test.dart index cb73c6a7d15..37202e0a8ef 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 @@ void main() { 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: { 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( + [ + '/usr/bin/xcrun', + 'simctl', + 'io', + 'booted', + 'screenshot', + '/some/path/to/screenshot.png' + ], + environment: null, + workingDirectory: null + )); + }, + overrides: { + ProcessManager: () => mockProcessManager, + // Test a real one. Screenshot doesn't require instance states. + SimControl: () => new SimControl(), + Xcode: () => mockXcode, + } + ); + }); }