diff --git a/lib/web_ui/dev/README.md b/lib/web_ui/dev/README.md index 0dd98d82589..5903f07e932 100644 --- a/lib/web_ui/dev/README.md +++ b/lib/web_ui/dev/README.md @@ -83,12 +83,34 @@ For Chrome and Firefox, the tests run on a version locked on the [browser_lock.y felt test --browser=firefox --firefox-version=70.0.1 ``` -To run tests on Safari use the following command. It works on MacOS devices and it uses the Safari installed on the OS. Currently there is no option for using another Safari version. +To run tests on Safari use the following command. It works on macOS devices and it uses the Safari installed on the OS. Currently there is no option for using another Safari version. ``` felt test --browser=safari ``` +One can also use Safari running in iOS Simulator for running unit tests. There are few prerequisite steps: + +1. Please make sure that you installed Xcode. + +2. The default version used in the tests are in browser_lock.yaml file. Install the ios version to use for simulators: Xcode > Preferences > Components + +3. run `xcrun simctl list devices`. If the simulator you want is not installed use step 4. + +4. Use felt to create a simulator: + +``` +felt create_simulator --type='iOS' --version='13.1' --device='iPhone.11.Pro' +``` + +To run tests on ios-safari use the one of the following commands: + +``` +felt test --browser=ios-safari +felt test --browser=ios-safari --version='13.1' --device='iPhone.11.Pro' +felt test --browser=ios-safari test/alarm_clock_test.dart +``` + To run tests on Windows Edge use the following command. It works on Windows devices and it uses the Edge installed on the OS. ``` diff --git a/lib/web_ui/dev/browser_lock.yaml b/lib/web_ui/dev/browser_lock.yaml index f83847d484b..486d7ec242e 100644 --- a/lib/web_ui/dev/browser_lock.yaml +++ b/lib/web_ui/dev/browser_lock.yaml @@ -10,3 +10,7 @@ firefox: version: '72.0' edge: launcher_version: '1.2.0.0' +ios-safari: + majorVersion: 13 + minorVersion: 5 + device: 'iPhone 11' diff --git a/lib/web_ui/dev/create_simulator.dart b/lib/web_ui/dev/create_simulator.dart new file mode 100644 index 00000000000..f180c3beaaf --- /dev/null +++ b/lib/web_ui/dev/create_simulator.dart @@ -0,0 +1,57 @@ +// Copyright 2013 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. + +// @dart = 2.6 +import 'dart:async'; + +import 'package:args/command_runner.dart'; +import 'package:simulators/simulator_manager.dart'; + +import 'safari_installation.dart'; +import 'utils.dart'; + +class CreateSimulatorCommand extends Command with ArgUtils { + CreateSimulatorCommand() { + IosSafariArgParser.instance.populateOptions(argParser); + argParser + ..addOption( + 'type', + defaultsTo: _defaultType, + help: 'Type of the mobile simulator. Currently the only iOS ' + 'Simulators are supported. Android will be added soon. This option ' + 'is not case sensitive, ios, iOS, IOS are all valid.', + ); + } + + /// Currently the only iOS Simulators are supported. + static final String _defaultType = 'iOS'; + + @override + String get name => 'create_simulator'; + + @override + String get description => 'Creates mobile simulators.'; + + @override + FutureOr run() async { + IosSafariArgParser.instance.parseOptions(argResults); + final String simulatorType = argResults['type'] as String; + if (simulatorType.toUpperCase() != 'IOS') { + throw Exception('Currently the only iOS Simulators are supported'); + } + final IosSimulatorManager iosSimulatorManager = IosSimulatorManager(); + try { + final IosSimulator simulator = await iosSimulatorManager.createSimulator( + IosSafariArgParser.instance.iosMajorVersion, + IosSafariArgParser.instance.iosMinorVersion, + IosSafariArgParser.instance.iosDevice); + print('INFO: Simulator created ${simulator.toString()}'); + } catch (e) { + throw Exception('Error creating requested simulator. You can use Xcode ' + 'to install more versions: XCode > Preferences > Components.' + ' Exception: $e'); + } + return true; + } +} diff --git a/lib/web_ui/dev/felt.dart b/lib/web_ui/dev/felt.dart index 6366b615299..a81d4bcafc7 100644 --- a/lib/web_ui/dev/felt.dart +++ b/lib/web_ui/dev/felt.dart @@ -9,6 +9,7 @@ import 'package:args/command_runner.dart'; import 'build.dart'; import 'clean.dart'; +import 'create_simulator.dart'; import 'licenses.dart'; import 'exceptions.dart'; import 'test_runner.dart'; @@ -19,6 +20,7 @@ CommandRunner runner = CommandRunner( 'Command-line utility for building and testing Flutter web engine.', ) ..addCommand(CleanCommand()) + ..addCommand(CreateSimulatorCommand()) ..addCommand(LicensesCommand()) ..addCommand(TestCommand()) ..addCommand(BuildCommand()); diff --git a/lib/web_ui/dev/safari_installation.dart b/lib/web_ui/dev/safari_installation.dart index 896535bc427..aff17a967e4 100644 --- a/lib/web_ui/dev/safari_installation.dart +++ b/lib/web_ui/dev/safari_installation.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'dart:io' as io; import 'package:args/args.dart'; +import 'package:yaml/yaml.dart'; import 'common.dart'; @@ -31,6 +32,9 @@ class SafariArgParser extends BrowserArgParser { 'Soon we will add support for using different versions using the ' 'tech previews.', ); + + // Populate options for Ios Safari. + IosSafariArgParser.instance.populateOptions(argParser); } @override @@ -45,10 +49,73 @@ class SafariArgParser extends BrowserArgParser { String get version => _version; bool _isMobileBrowser; - bool get isMobileBrowser => _isMobileBrowser; } +class IosSafariArgParser extends BrowserArgParser { + static final IosSafariArgParser _singletonInstance = IosSafariArgParser._(); + + /// The [IosSafariArgParser] singleton. + static IosSafariArgParser get instance => _singletonInstance; + + String get version => 'iOS ${iosMajorVersion}.${iosMinorVersion}'; + + int _pinnedIosMajorVersion; + int _iosMajorVersion; + int get iosMajorVersion => _iosMajorVersion ?? _pinnedIosMajorVersion; + + int _pinnedIosMinorVersion; + int _iosMinorVersion; + int get iosMinorVersion => _iosMinorVersion ?? _pinnedIosMinorVersion; + + String _pinnedIosDevice; + String _iosDevice; + String get iosDevice => _iosDevice ?? _pinnedIosDevice; + + IosSafariArgParser._(); + + @override + void populateOptions(ArgParser argParser) { + final YamlMap browserLock = BrowserLock.instance.configuration; + _pinnedIosMajorVersion = browserLock['ios-safari']['majorVersion'] as int; + _pinnedIosMinorVersion = browserLock['ios-safari']['minorVersion'] as int; + final pinnedIosVersion = + '${_pinnedIosMajorVersion}.${_pinnedIosMinorVersion}'; + _pinnedIosDevice = browserLock['ios-safari']['device'] as String; + argParser + ..addOption('version', + defaultsTo: '$pinnedIosVersion', + help: 'The version for the iOS operating system the iOS Simulator ' + 'will use for tests. For example for testing with iOS 13.2, ' + 'use `13.2`. Use command: ' + '`xcrun simctl list runtimes` to list available versions. Use ' + 'XCode to install more versions: Xcode > Preferences > Components' + 'If this value is not filled version locked in the ' + 'browser_lock.yaml file will be user.') + ..addOption('device', + defaultsTo: '$_pinnedIosDevice', + help: 'The device to be used for the iOS Simulator during the tests. ' + 'Use `.` instead of space for seperating the words. ' + 'Common examples: iPhone.8, iPhone.8.Plus, iPhone.11, ' + 'iPhone 11 Pro. Use command: ' + '`xcrun simctl list devices` for listing the available ' + 'devices. If this value is not filled device locked in the ' + 'browser_lock.yaml file will be user.'); + } + + @override + void parseOptions(ArgResults argResults) { + final String iosVersion = argResults['version'] as String; + // The version will contain major and minor version seperated by a comma, + // for example: 13.1, 12.2 + assert(iosVersion.split('.').length == 2, + 'The version should be in format 13.5'); + _iosMajorVersion = int.parse(iosVersion.split('.')[0]); + _iosMinorVersion = int.parse(iosVersion.split('.')[1]); + _iosDevice = (argResults['device'] as String).replaceAll('.', ' '); + } +} + /// Returns the installation of Safari. /// /// Currently uses the Safari version installed on the operating system. @@ -62,7 +129,6 @@ Future getOrInstallSafari( String requestedVersion, { StringSink infoLog, }) async { - // These tests are aimed to run only on macOS machines local or on LUCI. if (!io.Platform.isMacOS) { throw UnimplementedError('Safari on ${io.Platform.operatingSystem} is' diff --git a/lib/web_ui/dev/test_runner.dart b/lib/web_ui/dev/test_runner.dart index 948b5f69534..b33c73e6686 100644 --- a/lib/web_ui/dev/test_runner.dart +++ b/lib/web_ui/dev/test_runner.dart @@ -14,10 +14,12 @@ import 'package:test_core/src/runner/hack_register_platform.dart' import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports import 'package:test_core/src/executable.dart' as test; // ignore: implementation_imports +import 'package:simulators/simulator_manager.dart'; import 'environment.dart'; import 'exceptions.dart'; import 'integration_tests_manager.dart'; +import 'safari_installation.dart'; import 'supported_browsers.dart'; import 'test_platform.dart'; import 'utils.dart'; @@ -159,23 +161,30 @@ class TestCommand extends Command with ArgUtils { await _runPubGet(); } - // Many tabs will be left open after Safari runs, quit Safari during - // cleanup. - if (browser == 'safari') { - cleanupCallbacks.add(() async { - // Only close Safari if felt is running in CI environments. Do not close - // Safari for the local testing. - if (io.Platform.environment['LUCI_CONTEXT'] != null || isCirrus) { - print('INFO: Safari tests ran. Quit Safari.'); - await runProcess( - 'sudo', - ['pkill', '-lf', 'Safari'], - workingDirectory: environment.webUiRootDir.path, - ); - } else { - print('INFO: Safari tests ran. Please quit Safari tabs.'); - } - }); + // In order to run iOS Safari unit tests we need to make sure iOS Simulator + // is booted. + if (browser == 'ios-safari') { + final IosSimulatorManager iosSimulatorManager = IosSimulatorManager(); + IosSimulator iosSimulator; + try { + iosSimulator = await iosSimulatorManager.getSimulator( + IosSafariArgParser.instance.iosMajorVersion, + IosSafariArgParser.instance.iosMinorVersion, + IosSafariArgParser.instance.iosDevice); + } catch (e) { + throw Exception('Error getting requested simulator. Try running ' + '`felt create` command first before running the tests. exception: ' + '$e'); + } + + if (!iosSimulator.booted) { + await iosSimulator.boot(); + print('INFO: Simulator ${iosSimulator.id} booted.'); + cleanupCallbacks.add(() async { + await iosSimulator.shutdown(); + print('INFO: Simulator ${iosSimulator.id} shutdown.'); + }); + } } await _buildTargets(); diff --git a/lib/web_ui/dev/utils.dart b/lib/web_ui/dev/utils.dart index e9e5279c696..66184b413ef 100644 --- a/lib/web_ui/dev/utils.dart +++ b/lib/web_ui/dev/utils.dart @@ -219,7 +219,7 @@ void cleanup() async { } } - cleanupCallbacks.forEach((element) { - element.call(); - }); + for (final AsyncCallback callback in cleanupCallbacks) { + await callback(); + } } diff --git a/lib/web_ui/pubspec.yaml b/lib/web_ui/pubspec.yaml index 9b52fdde9d3..4fc67c35a19 100644 --- a/lib/web_ui/pubspec.yaml +++ b/lib/web_ui/pubspec.yaml @@ -23,6 +23,11 @@ dev_dependencies: watcher: 0.9.7+12 web_engine_tester: path: ../../web_sdk/web_engine_tester + simulators: + git: + url: git://github.com/flutter/web_installers.git + path: packages/simulators/ + ref: 9ede7e3c069180b28322bb3f0d0307c824071fb5 web_driver_installer: git: url: git://github.com/flutter/web_installers.git