├── .gitignore ├── LICENSE ├── PATENT_GRANT ├── README.md └── packages ├── simulators ├── README.md ├── lib │ └── simulator_manager.dart ├── pubspec.yaml └── test │ └── simulator_manager_test.dart └── web_drivers ├── README.md ├── driver_version.yaml ├── lib ├── chrome_driver_command.dart ├── chrome_driver_installer.dart ├── firefox_driver_command.dart ├── firefox_driver_installer.dart ├── safari_driver_command.dart ├── safari_driver_runner.dart ├── src │ └── common.dart └── web_driver_installer.dart ├── pubspec.yaml └── test └── chrome_driver_installer_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | *.dart_tool* 2 | *.packages 3 | *pubspec.lock 4 | chromedriver 5 | firefoxdriver 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Flutter 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /PATENT_GRANT: -------------------------------------------------------------------------------- 1 | Google hereby grants to you a perpetual, worldwide, non-exclusive, 2 | no-charge, royalty-free, irrevocable (except as stated in this 3 | section) patent license to make, have made, use, offer to sell, sell, 4 | import, transfer, and otherwise run, modify and propagate the contents 5 | of this implementation, where such license applies only to those 6 | patent claims, both currently owned by Google and acquired in the 7 | future, licensable by Google that are necessarily infringed by this 8 | implementation. This grant does not include claims that would be 9 | infringed only as a consequence of further modification of this 10 | implementation. If you or your agent or exclusive licensee institute 11 | or order or agree to the institution of patent litigation or any other 12 | patent enforcement activity against any entity (including a 13 | cross-claim or counterclaim in a lawsuit) alleging that this 14 | implementation constitutes direct or contributory patent infringement, 15 | or inducement of patent infringement, then any patent rights granted 16 | to you under this License for this implementation shall terminate as 17 | of the date such litigation is filed. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WARNING 2 | 3 | This repository is no longer maintained, and the code is not used by Flutter's CI any more. If the code here is still useful to your project, consider forking it and maintaining it on the fork. 4 | 5 | # web_installers 6 | 7 | Web install scripts for CI for Flutter Web 8 | -------------------------------------------------------------------------------- /packages/simulators/README.md: -------------------------------------------------------------------------------- 1 | ## What is Simulators 2 | 3 | Simulators is a set of libraries for controlling mobile simulators/emulators. 4 | Currently there is support for iOS Simulators. 5 | 6 | ## What can I use Simulators library for 7 | 8 | Currently only iOS is supported. There are two main classes `IosSimulator` and 9 | `IosSimulatorManager`. `IosSimulatorManager` can be used for accesing `IosSimulator` 10 | instances. 11 | 12 | 1. Creating an iOS Simulator for given iOS version and phone name: 13 | 14 | ``` 15 | IosSimulatorManager simulatorManager = IosSimulatorManager(); 16 | IosSimulator simulator = 17 | await simulatorManager.createSimulator(13, 5, 'iPad mini 4'); 18 | ``` 19 | 20 | 2. Get an existing simulator, given the iOS version and the phone name: 21 | 22 | 23 | ``` 24 | IosSimulatorManager simulatorManager = IosSimulatorManager(); 25 | IosSimulator simulator = 26 | await simulatorManager.getSimulator(13, 1, 'iPhone 11 Pro'); 27 | ``` 28 | 29 | `IosSimulator` can be used to boot and shutdown a simulator. It's constructor is private. 30 | It can be created using `IosSimulatorManager` methods. 31 | 32 | 1. **boot**: Boots the iOS Simulator: 33 | 34 | ``` 35 | IosSimulator simulator = 36 | await simulatorManager.getSimulator(13, 1, 'iPhone 11 Pro'); 37 | await simulator.boot(); 38 | ``` 39 | 40 | 2. **shutdown**: Shutsdown the iOS Simulator that's id is given: 41 | 42 | ``` 43 | await simulator.shutdown(); 44 | ``` 45 | -------------------------------------------------------------------------------- /packages/simulators/lib/simulator_manager.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:io' as io; 6 | 7 | /// Manages iOS Simulators. 8 | /// 9 | /// Creates a simulator or provides access to an existing simulator by 10 | /// returning [IOSSimulator] instances. 11 | /// 12 | /// Uses `xcrun simctl` command to manage the simulators. 13 | /// 14 | /// Run `xcrun simctl --help` to learn more on the details of this tool. 15 | class IosSimulatorManager { 16 | IosSimulatorManager() { 17 | if (!io.Platform.isMacOS) { 18 | throw Exception('Platform ${io.Platform.operatingSystem} is not supported' 19 | '. This class should only be used on macOS. It uses xcrun ' 20 | 'simctl command line tool to manage the iOS simulators'); 21 | } 22 | } 23 | 24 | /// Uses `xcrun simctl create` command to create an iOS Simulator. 25 | /// 26 | /// Runs `xcrun simctl list runtimes` to list the runtimes existing on your 27 | /// macOS. If runtime derived from [majorVersion] and [minorVersion] is not 28 | /// available an exception will be thrown. Use Xcode to install more versions. 29 | /// 30 | /// [device] example iPhone 11 Pro. Run `xcrun simctl list devicetypes` to 31 | /// list the device types available. If [device] is not available, an 32 | /// exception will be thrown. Use Xcode to install more versions. 33 | /// 34 | /// Use `xcrun simctl create --help` for more details. 35 | Future createSimulator( 36 | int majorVersion, int minorVersion, String device) async { 37 | final String runtime = 'iOS ${majorVersion}.${minorVersion}'; 38 | 39 | // Check if the runtime is available. 40 | final io.ProcessResult runtimeListResult = 41 | await io.Process.run('xcrun', ['simctl', 'list', 'runtimes']); 42 | 43 | if (runtimeListResult.exitCode != 0) { 44 | throw Exception('Failed to boot list runtimes(versions). Command used: ' 45 | 'xcrun simctl list runtimes'); 46 | } 47 | 48 | final String output = runtimeListResult.stdout as String; 49 | if (!output.contains(runtime)) { 50 | print(output); 51 | throw Exception('Mac does not have the requested $runtime ' 52 | 'available for simulators. Please use Xcode to install.'); 53 | } 54 | 55 | // Check if the device is available. 56 | final io.ProcessResult deviceListResult = 57 | await io.Process.run('xcrun', ['simctl', 'list', 'devicetypes']); 58 | 59 | if (deviceListResult.exitCode != 0) { 60 | throw Exception('Failed to boot list available simulator device types.' 61 | 'Command used: xcrun simctl list devicetypes'); 62 | } 63 | 64 | final String deviceListOutput = deviceListResult.stdout as String; 65 | if (!deviceListOutput.contains(device)) { 66 | print(deviceListOutput); 67 | throw Exception('Mac does not have the requested device type $device ' 68 | 'available for simulators. Please use Xcode to install.'); 69 | } 70 | 71 | // Prepate device type argument. It should look like: 72 | // com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro 73 | final String deviceTypeAsArg = 74 | 'com.apple.CoreSimulator.SimDeviceType.${device.replaceAll(' ', '-')}'; 75 | 76 | // Prepare runtime as argument using the versions. It should look like: 77 | // com.apple.CoreSimulator.SimRuntime.iOS-13-1. 78 | final String runtimeTypeAsArg = 79 | 'com.apple.CoreSimulator.SimRuntime.iOS-${majorVersion}-${minorVersion}'; 80 | 81 | final io.ProcessResult createResult = await io.Process.run('xcrun', 82 | ['simctl', 'create', device, deviceTypeAsArg, runtimeTypeAsArg]); 83 | 84 | if (createResult.exitCode != 0) { 85 | throw Exception('Failed to create requested simulator using $device ' 86 | '$deviceTypeAsArg $runtimeTypeAsArg arguments.'); 87 | } 88 | 89 | // Output will have the simulator id. 90 | final String simulatorId = createResult.stdout as String; 91 | return IosSimulator._(false, simulatorId.trim()); 92 | } 93 | 94 | /// Returns an [IosSimulator] instance to control the simulator, 95 | /// if a simulator corresponding to given [osVersion] and [phone] information 96 | /// exits. 97 | /// 98 | /// Throws if such a simulator is not available. 99 | Future getSimulator( 100 | int osMajorVersion, int osMinorVersion, String phone) async { 101 | final String simulatorVersion = 102 | '-- iOS ${osMajorVersion}.${osMinorVersion} --'; 103 | final String simulatorsList = 104 | await _listExistingSimulators(osMajorVersion, osMinorVersion); 105 | 106 | // The simulator list, have the version string followed by a list of phone 107 | // names along with their ids and their statuses. Example output 1: 108 | // -- iOS 13.5 -- 109 | // iPhone 8 (2A437C91-3B85-4D7B-BB91-32561DA07B85) (Shutdown) 110 | // iPhone 8 Plus (170207A8-7631-4CBE-940E-86A7815AEB2B) (Shutdown) 111 | // iPhone 11 (7AEC5FB9-E08A-4F7F-8CA2-1518CE3A3E0D) (Booted) 112 | // iPhone 11 Pro (D8074C8B-35A5-4DA5-9AB2-4CE738A5E5FC) (Shutdown) 113 | // iPhone 11 Pro Max (3F33AD9A-805E-43E0-A86C-8FC70464A390) (Shutdown) 114 | // -- iOS 13.6 -- 115 | // iPhone 8 (2A437C91-3B85-4D7B-BB91-32561DA07B85) (Shutdown) 116 | // iPhone 8 Plus (170207A8-7631-4CBE-940E-86A7815AEB2B) (Shutdown) 117 | // iPhone 11 (7AEC5FB9-E08A-4F7F-8CA2-1518CE3A3E0D) (Booted) 118 | // iPhone 11 Pro (D8074C8B-35A5-4DA5-9AB2-4CE738A5E5FC) (Shutdown) 119 | // -- Device Pairs -- 120 | // Example output 2 (from Mac Web Engine try bots): 121 | // == Devices == 122 | // -- iOS 13.0 -- 123 | // iPhone 8 (C142C9F5-C26E-4EB5-A2B8-915D5BD62FA5) (Shutdown) 124 | // iPhone 8 Plus (C1FE8FAA-5797-478E-8BEE-D7AD4811F08C) (Shutdown) 125 | // iPhone 11 (28A3E6C0-76E7-4EE3-9B34-B059C4BBE5CA) (Shutdown) 126 | // iPhone 11 Pro (0AD4BBA5-7BE7-415D-B9FD-D962FA8E1782) (Shutdown) 127 | // iPhone 11 Pro Max (1280DE05-B334-4E60-956F-4A62220DEFA3) (Shutdown) 128 | // iPad Pro (9.7-inch) (EDE46501-CB2B-4EA4-8B5C-13FAC6F2EC91) (Shutdown) 129 | // iPad Pro (11-inch) (E0B89C9C-6200-495C-B18B-0078CCAAC688) (Shutdown) 130 | // iPad Pro (12.9-inch) (3rd generation) (DB3EB7A8-C4D2-4F86-AFC1-D652FB0579E8) (Shutdown) 131 | // iPad Air (3rd generation) (9237DCD8-8F0E-40A6-96DF-B33C915AFE1B) (Shutdown) 132 | // == Device Pairs == 133 | final int indexOfVersionListStart = 134 | simulatorsList.indexOf(simulatorVersion); 135 | final String restOfTheOutput = simulatorsList 136 | .substring(indexOfVersionListStart + simulatorVersion.length); 137 | int indexOfNextVersion = restOfTheOutput.indexOf('--'); 138 | if (indexOfNextVersion == -1) { 139 | // Search for `== Device Pairs ==`. 140 | indexOfNextVersion = restOfTheOutput.indexOf('=='); 141 | } 142 | if (indexOfNextVersion == -1) { 143 | // Set to end of file. 144 | indexOfNextVersion = restOfTheOutput.length; 145 | } 146 | 147 | final String listOfPhones = 148 | restOfTheOutput.substring(0, indexOfNextVersion); 149 | 150 | final int indexOfPhone = listOfPhones.indexOf(phone); 151 | if (indexOfPhone == -1) { 152 | print(simulatorsList); 153 | throw Exception('Simulator of $phone is not available for iOS version ' 154 | '${osMajorVersion}.${osMinorVersion}'); 155 | } 156 | 157 | final String phoneInfo = listOfPhones.substring(indexOfPhone); 158 | final int endIndexOfPhoneId = phoneInfo.indexOf(')'); 159 | final String simulatorId = 160 | phoneInfo.substring(phoneInfo.indexOf('(') + 1, endIndexOfPhoneId); 161 | 162 | final String phoneInfoAfterId = phoneInfo.substring(endIndexOfPhoneId + 1); 163 | final String simulatorStatus = phoneInfoAfterId.substring( 164 | phoneInfoAfterId.indexOf('(') + 1, phoneInfoAfterId.indexOf(')')); 165 | return IosSimulator._(simulatorStatus == 'Booted', simulatorId); 166 | } 167 | 168 | Future _listExistingSimulators( 169 | int osMajorVersion, int osMinorVersion) async { 170 | final io.ProcessResult versionResult = 171 | await io.Process.run('xcrun', ['simctl', 'list']); 172 | 173 | if (versionResult.exitCode != 0) { 174 | throw Exception('Failed to list iOS simulators.'); 175 | } 176 | final String output = versionResult.stdout as String; 177 | // If the requested iOS version simulators exists, there should be a block 178 | // starting with: `-- iOS osMajorVersion.osMinorVersion --` 179 | final bool versionCheck = 180 | output.contains('-- iOS ${osMajorVersion}.${osMinorVersion} --'); 181 | 182 | if (!versionCheck) { 183 | print(output); 184 | throw Exception( 185 | 'Requested simulator version iOS ${osMajorVersion}.${osMinorVersion} ' 186 | 'is not available.'); 187 | } 188 | 189 | return output; 190 | } 191 | } 192 | 193 | /// A class that can be used to boot/shutdown an iOS Simulator. 194 | class IosSimulator { 195 | final String id; 196 | 197 | bool _booted; 198 | bool get booted => _booted; 199 | 200 | IosSimulator._(this._booted, this.id); 201 | 202 | /// Boots the iOS Simulator using the simulator [id]. 203 | /// 204 | /// Uses `xcrun simctl boot` command to boot an iOS Simulator. 205 | /// 206 | /// If it is already booted the command will fail. 207 | Future boot() async { 208 | final io.ProcessResult versionResult = 209 | await io.Process.run('xcrun', ['simctl', 'boot', '$id']); 210 | 211 | if (versionResult.exitCode != 0) { 212 | throw Exception('Failed to boot iOS simulators with id: $id.'); 213 | } 214 | this._booted = true; 215 | return; 216 | } 217 | 218 | /// Shuts down the iOS Simulator using the simulator [id]. 219 | /// 220 | /// Uses `xcrun simctl shutdown` command to boot an iOS Simulator. 221 | /// 222 | /// If the simulator is not booted, the command will fail. 223 | Future shutdown() async { 224 | final io.ProcessResult versionResult = 225 | await io.Process.run('xcrun', ['simctl', 'shutdown', '$id']); 226 | 227 | if (versionResult.exitCode != 0) { 228 | throw Exception('Failed to shutdown iOS simulators with id: $id.'); 229 | } 230 | 231 | this._booted = false; 232 | return; 233 | } 234 | 235 | /// Take the screenshot of the simulator. 236 | /// 237 | /// This method takes screenshot of the entire simulator, not the 238 | /// "open application's content". 239 | Future takeScreenshot( 240 | String fileName, io.Directory workingDirectory) async { 241 | final io.ProcessResult versionResult = await io.Process.run( 242 | 'xcrun', ['simctl', 'io', id, 'screenshot', fileName], 243 | workingDirectory: workingDirectory.path); 244 | 245 | if (versionResult.exitCode != 0) { 246 | throw Exception('Failed to run xcrun screenshot on iOS simulator for ' 247 | 'simulator id: $id'); 248 | } 249 | } 250 | 251 | @override 252 | String toString() { 253 | return 'iOS Simulator id: $id status: $booted'; 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /packages/simulators/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: simulators 2 | description: For creating and managing iOS simulators. 3 | 4 | environment: 5 | sdk: ">=2.12.0 <3.0.0" 6 | 7 | dev_dependencies: 8 | test: ">=1.6.5 <2.0.0" 9 | -------------------------------------------------------------------------------- /packages/simulators/test/simulator_manager_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:test/test.dart'; 6 | 7 | import '../lib/simulator_manager.dart'; 8 | 9 | void main() async { 10 | test('boot simulator', () async { 11 | IosSimulatorManager simulatorManager = IosSimulatorManager(); 12 | IosSimulator simulator = 13 | await simulatorManager.getSimulator(13, 0, 'iPhone 11'); 14 | await simulator.boot(); 15 | }); 16 | 17 | test('shutdown simulator', () async { 18 | IosSimulatorManager simulatorManager = IosSimulatorManager(); 19 | IosSimulator simulator = 20 | await simulatorManager.getSimulator(13, 0, 'iPhone 11'); 21 | await simulator.shutdown(); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /packages/web_drivers/README.md: -------------------------------------------------------------------------------- 1 | **To install and start Chrome Driver.** 2 | 3 | ```dart lib/web_driver_installer.dart chromedriver``` 4 | 5 | **If one wants to keep driver running after terminal shutdown add &** 6 | 7 | ```dart lib/web_driver_installer.dart chromedriver &``` 8 | 9 | **Chrome Driver versions are stored in driver_version.yaml.yaml. Driver version can also be given as an argument.** 10 | 11 | ```dart lib/web_driver_installer.dart chromedriver --driver-version="78.0.3904.105"``` 12 | 13 | **There might be an already installed version of the driver. If one wants to override they can use the following flag, otherwise the existing driver is used.** 14 | 15 | ```dart lib/web_driver_installer.dart chromedriver --always-install``` 16 | 17 | **To only install the chrome driver** 18 | 19 | ```dart lib/web_driver_installer.dart chromedriver --install-only``` 20 | 21 | **To enable and start Safari Driver.** 22 | 23 | ```dart lib/web_driver_installer.dart safaridriver``` 24 | 25 | **Start a specific version of the driver.** 26 | This will end with an error if the exiting version differs from the system version. This is useful for failing fast when running on CI environments to see if the expected version is installed. 27 | 28 | ```dart lib/web_driver_installer.dart safaridriver --driver-version="13.0.5"``` 29 | 30 | **No-options also currently defaults to install and start Chrome Driver. It will be deprecated soon.** 31 | 32 | ```dart lib/web_driver_installer.dart &``` 33 | 34 | **For Firefox Driver** 35 | 36 | To install and keep running: 37 | 38 | ```dart lib/web_driver_installer.dart firefoxdriver&``` 39 | 40 | Firefox uses gecko driver. The default is taken from driver_version.yaml file. 41 | One can provide different release versions for gecko driver. 42 | For releases see: https://github.com/mozilla/geckodriver/releases 43 | 44 | ```dart lib/web_driver_installer.dart firefoxdriver --driver-version="v0.25.0"``` 45 | 46 | Note that `always-install` and `install-only` commands can still be used for Firefox. 47 | 48 | ```dart lib/web_driver_installer.dart chromedriver --always-install``` 49 | 50 | ```dart lib/web_driver_installer.dart chromedriver --install-only``` 51 | -------------------------------------------------------------------------------- /packages/web_drivers/driver_version.yaml: -------------------------------------------------------------------------------- 1 | 2 | ## Map for driver versions to use for each browser version. 3 | ## See: https://chromedriver.chromium.org/downloads 4 | chrome: 5 | 114: '114.0.5735.90' 6 | 113: '113.0.5672.63' 7 | 112: '112.0.5615.49' 8 | 111: '111.0.5563.64' 9 | 110: '110.0.5481.77' 10 | 109: '109.0.5414.74' 11 | 108: '108.0.5359.71' 12 | 107: '107.0.5304.62' 13 | 106: '106.0.5249.61' 14 | 105: '105.0.5195.52' 15 | 104: '104.0.5112.79' 16 | 103: '103.0.5060.134' 17 | 102: '102.0.5005.61' 18 | 101: '101.0.4951.41' 19 | 100: '100.0.4896.60' 20 | 99: '99.0.4844.51' 21 | 98: '98.0.4758.102' 22 | 97: '97.0.4692.71' 23 | 95: '95.0.4638.10' 24 | 94: '94.0.4606.41' 25 | 93: '93.0.4577.15' 26 | 92: '92.0.4515.43' 27 | 91: '91.0.4472.101' 28 | 90: '90.0.4430.24' 29 | 89: '89.0.4389.23' 30 | 88: '88.0.4324.96' 31 | 87: '87.0.4280.88' 32 | 86: '86.0.4240.22' 33 | 85: '85.0.4183.38' 34 | 84: '84.0.4147.30' 35 | 83: '83.0.4103.39' 36 | 81: '81.0.4044.69' 37 | 80: '80.0.3987.106' 38 | 79: '79.0.3945.36' 39 | 78: '78.0.3904.105' 40 | 77: '77.0.3865.40' 41 | 76: '76.0.3809.126' 42 | 75: '75.0.3770.140' 43 | 74: '74.0.3729.6' 44 | ## geckodriver is used for testing Firefox Browser. It works with multiple 45 | ## Firefox Browser versions. 46 | ## See: https://github.com/mozilla/geckodriver/releases 47 | gecko: 'v0.26.0' 48 | -------------------------------------------------------------------------------- /packages/web_drivers/lib/chrome_driver_command.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:args/command_runner.dart'; 6 | import 'package:web_driver_installer/chrome_driver_installer.dart'; 7 | 8 | /// Wrapper class on top of [ChromeDriverInstaller] to use it as a command. 9 | class ChromeDriverCommand extends Command { 10 | @override 11 | String get description => 'Chrome Driver installer.'; 12 | 13 | @override 14 | String get name => 'chromedriver'; 15 | 16 | static const String defaultDriverVersion = 'fromlockfile'; 17 | 18 | ChromeDriverCommand() { 19 | argParser 20 | ..addFlag('always-install', 21 | defaultsTo: false, 22 | help: 'Overwrites an existing driver installation, if any. There ' 23 | 'might be an already installed version of the driver. This ' 24 | 'flag will delete it before installing a new one.') 25 | ..addFlag('install-only', 26 | defaultsTo: false, 27 | help: 'Only installs the driver. Does not start it. Default is false') 28 | ..addOption('driver-version', 29 | defaultsTo: defaultDriverVersion, 30 | help: 'Install the given version of the driver. If driver version is ' 31 | 'not provided use version from the driver_version.yaml.'); 32 | } 33 | 34 | /// If the version is provided as an argument, initialize using the version 35 | /// otherwise use the `driver_version.yaml` file. 36 | /// 37 | /// See [_initializeChromeDriverInstaller]. 38 | late ChromeDriverInstaller chromeDriverInstaller; 39 | 40 | @override 41 | Future run() async { 42 | final bool installOnly = argResults!['install-only']; 43 | final bool alwaysInstall = argResults!['always-install']; 44 | 45 | _initializeChromeDriverInstaller(); 46 | 47 | try { 48 | if (installOnly) { 49 | await chromeDriverInstaller.install(alwaysInstall: alwaysInstall); 50 | } else { 51 | await chromeDriverInstaller.start(alwaysInstall: alwaysInstall); 52 | } 53 | return true; 54 | } catch (e) { 55 | print('Exception during install $e'); 56 | return false; 57 | } 58 | } 59 | 60 | void _initializeChromeDriverInstaller() { 61 | final String driverVersion = argResults!['driver-version']; 62 | if (driverVersion == defaultDriverVersion) { 63 | chromeDriverInstaller = ChromeDriverInstaller(); 64 | } else { 65 | chromeDriverInstaller = ChromeDriverInstaller.withVersion(driverVersion); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/web_drivers/lib/chrome_driver_installer.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:io' as io; 6 | 7 | import 'package:http/http.dart'; 8 | import 'package:path/path.dart' as path; 9 | import 'package:yaml/yaml.dart'; 10 | 11 | import 'src/common.dart'; 12 | 13 | class ChromeDriverInstaller { 14 | /// HTTP client used to download Chrome Driver. 15 | final Client client = Client(); 16 | 17 | /// Installation directory for Chrome Driver. 18 | final io.Directory driverDir = io.Directory('chromedriver'); 19 | 20 | static const String chromeDriverUrl = 21 | 'https://chromedriver.storage.googleapis.com/'; 22 | 23 | String chromeDriverVersion; 24 | 25 | io.File? driverDownload; 26 | 27 | String get downloadUrl => 28 | '$chromeDriverUrl$chromeDriverVersion/${driverName()}'; 29 | 30 | io.File get installation => 31 | io.File(path.join(driverDir.path, 'chromedriver')); 32 | 33 | bool get isInstalled => installation.existsSync(); 34 | 35 | ChromeDriverInstaller() : this.chromeDriverVersion = ''; 36 | 37 | ChromeDriverInstaller.withVersion(String version) 38 | : this.chromeDriverVersion = version; 39 | 40 | Future start({bool alwaysInstall = false}) async { 41 | // Install Chrome Driver. 42 | try { 43 | await install(alwaysInstall: alwaysInstall); 44 | // Start using chromedriver --port=4444 45 | print('INFO: Starting Chrome Driver on port 4444'); 46 | await runDriver(); 47 | } finally { 48 | // Only delete if the user is planning to override the installs. 49 | // Keeping the existing version might make local development easier. 50 | // Also if a CI build runs multiple felt commands using an existing 51 | // version speeds up the build. 52 | if (!alwaysInstall) { 53 | driverDownload?.deleteSync(); 54 | } 55 | } 56 | } 57 | 58 | Future install({bool alwaysInstall = false}) async { 59 | if (!isInstalled || alwaysInstall) { 60 | await _installDriver(); 61 | } else { 62 | print('INFO: Installation skipped. The driver is installed: ' 63 | '$isInstalled. User requested force install: $alwaysInstall'); 64 | } 65 | } 66 | 67 | Future _installDriver() async { 68 | // If this method is called, clean the previous installations. 69 | if (isInstalled) { 70 | installation.deleteSync(recursive: true); 71 | } 72 | 73 | // Figure out which driver version to install if it's not given during 74 | // initialization. 75 | if (chromeDriverVersion.isEmpty) { 76 | final int chromeVersion = await _querySystemChromeVersion(); 77 | 78 | if (chromeVersion < 74) { 79 | throw Exception('Unsupported Chrome version: $chromeVersion'); 80 | } 81 | 82 | final YamlMap browserLock = DriverLock.instance.configuration; 83 | final YamlMap chromeDrivers = browserLock['chrome']; 84 | final String? chromeDriverVersion = chromeDrivers[chromeVersion]; 85 | if (chromeDriverVersion == null) { 86 | throw Exception( 87 | 'No known chromedriver version for Chrome version $chromeVersion.\n' 88 | 'Known versions are:\n${chromeDrivers.entries.map((e) => '${e.key}: ${e.value}').join('\n')}', 89 | ); 90 | } else { 91 | this.chromeDriverVersion = chromeDriverVersion; 92 | } 93 | } 94 | 95 | try { 96 | driverDownload = await _downloadDriver(); 97 | } catch (e) { 98 | throw Exception( 99 | 'Failed to download chrome driver $chromeDriverVersion. $e'); 100 | } finally { 101 | client.close(); 102 | } 103 | 104 | await _uncompress(); 105 | } 106 | 107 | Future _querySystemChromeVersion() async { 108 | String chromeExecutable = ''; 109 | if (io.Platform.isLinux) { 110 | chromeExecutable = 'google-chrome'; 111 | } else if (io.Platform.isMacOS) { 112 | chromeExecutable = await findChromeExecutableOnMac(); 113 | } else { 114 | throw UnimplementedError('Web installers only work on Linux and Mac.'); 115 | } 116 | 117 | final io.ProcessResult versionResult = 118 | await io.Process.run('$chromeExecutable', ['--version']); 119 | 120 | if (versionResult.exitCode != 0) { 121 | throw Exception('Failed to locate system Chrome.'); 122 | } 123 | // The output looks like: Google Chrome 79.0.3945.36. 124 | final String output = versionResult.stdout as String; 125 | 126 | print('INFO: chrome version in use $output'); 127 | 128 | // Version number such as 79.0.3945.36. 129 | final String versionAsString = output.split(' ')[2]; 130 | 131 | final String versionNo = versionAsString.split('.')[0]; 132 | 133 | return int.parse(versionNo); 134 | } 135 | 136 | /// Find Google Chrome App on Mac. 137 | Future findChromeExecutableOnMac() async { 138 | io.Directory chromeDirectory = io.Directory('/Applications') 139 | .listSync() 140 | .whereType() 141 | .firstWhere( 142 | (d) => path.basename(d.path).endsWith('Chrome.app'), 143 | orElse: () => throw Exception('Failed to locate system Chrome'), 144 | ); 145 | 146 | final io.File chromeExecutableDir = io.File( 147 | path.join(chromeDirectory.path, 'Contents', 'MacOS', 'Google Chrome')); 148 | 149 | return chromeExecutableDir.path; 150 | } 151 | 152 | Future _downloadDriver() async { 153 | if (driverDir.existsSync()) { 154 | driverDir.deleteSync(recursive: true); 155 | } 156 | 157 | driverDir.createSync(recursive: true); 158 | 159 | print('downloading file from $downloadUrl'); 160 | 161 | final StreamedResponse download = await client.send(Request( 162 | 'GET', 163 | Uri.parse(downloadUrl), 164 | )); 165 | 166 | final io.File downloadedFile = 167 | io.File(path.join(driverDir.path, driverName())); 168 | await download.stream.pipe(downloadedFile.openWrite()); 169 | 170 | return downloadedFile; 171 | } 172 | 173 | /// Uncompress the downloaded driver file. 174 | Future _uncompress() async { 175 | final io.ProcessResult unzipResult = await io.Process.run('unzip', [ 176 | driverDownload!.path, 177 | '-d', 178 | driverDir.path, 179 | ]); 180 | 181 | if (unzipResult.exitCode != 0) { 182 | throw Exception( 183 | 'Failed to unzip the downloaded Chrome driver ${driverDownload!.path}.\n' 184 | 'With the driver path ${driverDir.path}\n' 185 | 'The unzip process exited with code ${unzipResult.exitCode}.'); 186 | } 187 | } 188 | 189 | Future runDriver() async { 190 | await io.Process.run('chromedriver/chromedriver', ['--port=4444']); 191 | } 192 | 193 | /// Driver name for operating system. 194 | /// 195 | /// Chrome provide 3 different drivers per version. As an example, see: 196 | /// https://chromedriver.storage.googleapis.com/index.html?path=76.0.3809.126/ 197 | static String driverName() { 198 | if (io.Platform.isMacOS) { 199 | return 'chromedriver_mac64.zip'; 200 | } else if (io.Platform.isLinux) { 201 | return 'chromedriver_linux64.zip'; 202 | } else if (io.Platform.isWindows) { 203 | return 'chromedriver_win32.zip'; 204 | } else { 205 | throw UnimplementedError('Automated testing not supported on this OS.' 206 | 'Platform name: ${io.Platform.operatingSystem}'); 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /packages/web_drivers/lib/firefox_driver_command.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:args/command_runner.dart'; 6 | import 'package:web_driver_installer/firefox_driver_installer.dart'; 7 | 8 | /// Wrapper class on top of [FirefoxDriverInstaller] to use it as a command. 9 | class FirefoxDriverCommand extends Command { 10 | @override 11 | String get description => 'Firefox Driver installer.'; 12 | 13 | @override 14 | String get name => 'firefoxdriver'; 15 | 16 | static const String defaultDriverVersion = 'fromlockfile'; 17 | 18 | FirefoxDriverCommand() { 19 | argParser 20 | ..addFlag('always-install', 21 | defaultsTo: false, 22 | help: 'Overwrites an existing driver installation, if any. There ' 23 | 'might be an already installed version of the driver. This ' 24 | 'flag will delete it before installing a new one.') 25 | ..addFlag('install-only', 26 | defaultsTo: false, 27 | help: 'Only installs the driver. Does not start it. Default is false') 28 | ..addOption('driver-version', 29 | defaultsTo: defaultDriverVersion, 30 | help: 'Install the given release from the geckodriver releases. If ' 31 | 'release version is not provided use version from the ' 32 | 'driver_version.yaml. For releases see: ' 33 | 'https://github.com/mozilla/geckodriver/releases'); 34 | } 35 | 36 | /// If the gecko driver release version is provided as an argument, 37 | /// initialize using it otherwise use the one from `driver_version.yaml` 38 | /// file. 39 | /// 40 | /// See [_initializeFirefoxDriverInstaller]. 41 | late FirefoxDriverInstaller firefoxDriverInstaller; 42 | 43 | @override 44 | Future run() async { 45 | final bool installOnly = argResults!['install-only']; 46 | final bool alwaysInstall = argResults!['always-install']; 47 | 48 | _initializeFirefoxDriverInstaller(); 49 | 50 | try { 51 | if (installOnly) { 52 | await firefoxDriverInstaller.install(alwaysInstall: alwaysInstall); 53 | } else { 54 | await firefoxDriverInstaller.start(alwaysInstall: alwaysInstall); 55 | } 56 | return true; 57 | } catch (e) { 58 | throw Exception('Exception during Firefox command: $e'); 59 | } 60 | } 61 | 62 | void _initializeFirefoxDriverInstaller() { 63 | final String driverVersion = argResults!['driver-version']; 64 | if (driverVersion == defaultDriverVersion) { 65 | firefoxDriverInstaller = FirefoxDriverInstaller(); 66 | } else { 67 | firefoxDriverInstaller = 68 | FirefoxDriverInstaller(geckoDriverVersion: driverVersion); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/web_drivers/lib/firefox_driver_installer.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:io' as io; 6 | 7 | import 'package:http/http.dart'; 8 | import 'package:path/path.dart' as path; 9 | import 'package:yaml/yaml.dart'; 10 | 11 | import 'src/common.dart'; 12 | 13 | class FirefoxDriverInstaller { 14 | /// HTTP client used to download Firefox Driver. 15 | final Client client = Client(); 16 | 17 | /// Installation directory for Firefox Driver. 18 | final io.Directory driverDir = io.Directory('firefoxdriver'); 19 | 20 | static const String geckoDriverReleasesUrl = 21 | 'https://github.com/mozilla/geckodriver/releases/download'; 22 | 23 | /// Version of geckodriver release at github.com/mozilla/geckodriver project. 24 | /// 25 | /// This is not the version of Firefox Browser. 26 | /// 27 | /// Geckodriver works for multiple versions of Firefox Browser. 28 | late String geckoDriverVersion; 29 | 30 | FirefoxDriverInstaller({String geckoDriverVersion = ''}) { 31 | if (geckoDriverVersion.isEmpty) { 32 | print('INFO: No geckoDriverVersion is given. Using geckodriver from ' 33 | 'driver_version.yaml file.'); 34 | final YamlMap driverLock = DriverLock.instance.configuration; 35 | this.geckoDriverVersion = driverLock['gecko'] as String; 36 | } 37 | this.geckoDriverVersion = geckoDriverVersion; 38 | } 39 | 40 | io.File? driverDownload; 41 | 42 | String get downloadUrl => '$geckoDriverReleasesUrl/${driverUrlPath()}'; 43 | 44 | bool get isInstalled => driverDir.existsSync(); 45 | 46 | Future start({bool alwaysInstall = false}) async { 47 | // Install Driver. 48 | try { 49 | await install(alwaysInstall: alwaysInstall); 50 | await runDriver(); 51 | } finally { 52 | // Only delete if the user is planning to override the installs. 53 | // Keeping the existing version might make local development easier. 54 | // Also if a CI build runs multiple felt commands using an existing 55 | // version speeds up the build. 56 | if (!alwaysInstall) { 57 | driverDownload?.deleteSync(); 58 | } 59 | } 60 | } 61 | 62 | Future install({bool alwaysInstall = false}) async { 63 | if (!isInstalled || alwaysInstall) { 64 | await _installDriver(); 65 | } else { 66 | print('INFO: Installation skipped. The driver is installed: ' 67 | '$isInstalled. User requested force install: $alwaysInstall'); 68 | } 69 | } 70 | 71 | Future _installDriver() async { 72 | // If this method is called, clean the previous installations. 73 | try { 74 | driverDownload = await _downloadDriver(); 75 | } catch (e) { 76 | throw Exception( 77 | 'Failed to download driver Firefox from link $downloadUrl. $e'); 78 | } finally { 79 | client.close(); 80 | } 81 | 82 | if (io.Platform.isWindows) { 83 | await _uncompressWithZip(driverDownload!); 84 | } else { 85 | await _uncompressWithTar(driverDownload!); 86 | } 87 | } 88 | 89 | Future _downloadDriver() async { 90 | if (driverDir.existsSync()) { 91 | driverDir.deleteSync(recursive: true); 92 | } 93 | 94 | driverDir.createSync(recursive: true); 95 | 96 | final StreamedResponse download = await client.send(Request( 97 | 'GET', 98 | Uri.parse(downloadUrl), 99 | )); 100 | 101 | final io.File downloadedFile = 102 | io.File(path.join(driverDir.path, geckoDriverVersion)); 103 | 104 | final io.IOSink sink = downloadedFile.openWrite(); 105 | await download.stream.pipe(sink); 106 | await sink.flush(); 107 | await sink.close(); 108 | 109 | return downloadedFile; 110 | } 111 | 112 | /// Uncompress the downloaded driver file. 113 | Future _uncompressWithZip(io.File downloadedFile) async { 114 | final io.ProcessResult unzipResult = await io.Process.run('unzip', [ 115 | driverDownload!.path, 116 | '-d', 117 | driverDir.path, 118 | ]); 119 | 120 | if (unzipResult.exitCode != 0) { 121 | throw Exception( 122 | 'Failed to unzip the downloaded gecko driver ${driverDownload!.path}.\n' 123 | 'With the driver path ${driverDir.path}\n' 124 | 'The unzip process exited with code ${unzipResult.exitCode}.'); 125 | } 126 | } 127 | 128 | /// Start using firefoxdriver --port=4444 129 | Future runDriver() async { 130 | print('INFO: Starting Firefox Driver on port 4444'); 131 | return await io.Process.run( 132 | 'firefoxdriver/geckodriver', ['--port=4444']); 133 | } 134 | 135 | /// Uncompress the downloaded browser files for operating systems that 136 | /// use a zip archive. 137 | /// See [version]. 138 | Future _uncompressWithTar(io.File downloadedFile) async { 139 | final io.ProcessResult unzipResult = await io.Process.run('tar', [ 140 | '-x', 141 | '-f', 142 | downloadedFile.path, 143 | '-C', 144 | driverDir.path, 145 | ]); 146 | 147 | if (unzipResult.exitCode != 0) { 148 | throw Exception( 149 | 'Failed to unzip the downloaded Firefox archive ${downloadedFile.path}.\n' 150 | 'The unzip process exited with code ${unzipResult.exitCode}.'); 151 | } 152 | } 153 | 154 | /// Driver name for operating system. 155 | /// 156 | /// Drivers can be found at: 157 | /// https://github.com/mozilla/geckodriver/releases/ 158 | String driverUrlPath() { 159 | if (io.Platform.isMacOS) { 160 | return '${geckoDriverVersion}/geckodriver-${geckoDriverVersion}-' 161 | 'macos.tar.gz'; 162 | } else if (io.Platform.isLinux) { 163 | return '${geckoDriverVersion}/geckodriver-${geckoDriverVersion}-' 164 | 'linux64.tar.gz'; 165 | } else if (io.Platform.isWindows) { 166 | return '${geckoDriverVersion}/geckodriver-${geckoDriverVersion}-' 167 | 'win64.zip'; 168 | } else { 169 | throw UnimplementedError('Automated testing not supported on this OS.' 170 | 'Platform name: ${io.Platform.operatingSystem}'); 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /packages/web_drivers/lib/safari_driver_command.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:args/command_runner.dart'; 6 | import 'package:web_driver_installer/safari_driver_runner.dart'; 7 | 8 | /// Wrapper class on top of [SafariDriverRunner] to use it as a command. 9 | class SafariDriverCommand extends Command { 10 | @override 11 | String get description => 'Safari Driver runner.'; 12 | 13 | @override 14 | String get name => 'safaridriver'; 15 | 16 | /// If the version is provided as an argument, it is checked against the 17 | /// system version and an exception is thrown if they do not match. 18 | final String defaultDriverVersion = 'system'; 19 | 20 | SafariDriverCommand() { 21 | argParser 22 | ..addOption('driver-version', 23 | defaultsTo: defaultDriverVersion, 24 | help: 'Run the given version of the driver. If the given version ' 25 | 'does not exists throw an error.'); 26 | } 27 | 28 | final SafariDriverRunner safariDriverRunner = SafariDriverRunner(); 29 | 30 | @override 31 | Future run() async { 32 | final String driverVersion = argResults!['driver-version']; 33 | try { 34 | await safariDriverRunner.start(version: driverVersion); 35 | return true; 36 | } catch (e) { 37 | print('Exception during running the safari driver: $e'); 38 | return false; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/web_drivers/lib/safari_driver_runner.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:io' as io; 6 | 7 | /// Running Safari Driver which comes installed in every macOS operation system. 8 | /// 9 | /// The version of driver is the same as the version of the Safari installed 10 | /// in the system. 11 | class SafariDriverRunner { 12 | /// Start Safari Driver installed in the macOS operating system. 13 | /// 14 | /// If a specific version is requested, it will check the existing system 15 | /// version and throw and exception the versions do not match. 16 | /// 17 | /// If the operating system is not macOS, the method will throw an exception. 18 | Future start({String version = 'system'}) async { 19 | if (!io.Platform.isMacOS) { 20 | throw AssertionError('The operating system must be MacOS: ' 21 | '${io.Platform.operatingSystem}'); 22 | } 23 | final io.Process process = await runDriver(version: version); 24 | final int exitCode = await process.exitCode; 25 | if (exitCode != 0) { 26 | throw Exception('Driver failed with exitcode: $exitCode'); 27 | } 28 | } 29 | 30 | /// Safari Driver needs to be enabled before it is used. 31 | /// 32 | /// The driver will need to get re-enabled after the machine is restarted. 33 | /// 34 | /// The enabling waits for user input for authentication. 35 | void _enableDriver() { 36 | io.Process.runSync('/usr/bin/safaridriver', ['--enable']); 37 | } 38 | 39 | /// Compare the version of the installed driver to the requested version. 40 | /// 41 | /// Throw an exception if they don't match. 42 | Future _compareDriverVersion(String version) async { 43 | io.Process.run('/usr/bin/safaridriver', ['--version']); 44 | 45 | final io.ProcessResult versionResult = 46 | await io.Process.run('/usr/bin/safaridriver', ['--version']); 47 | 48 | if (versionResult.exitCode != 0) { 49 | throw Exception('Failed to get the safari driver version.'); 50 | } 51 | // The output generally looks like: Included with Safari 13.0.5 (14608.5.12) 52 | final String output = versionResult.stdout as String; 53 | final String rest = 54 | output.substring(output.indexOf('Safari')); 55 | 56 | print('INFO: driver version in the system: $rest'); 57 | 58 | // Version number such as 13.0.5. 59 | final String versionAsString = rest.trim().split(' ')[1]; 60 | 61 | if (versionAsString != version) { 62 | throw Exception('System version $versionAsString did not match requested ' 63 | 'version $version'); 64 | } 65 | } 66 | 67 | /// Run Safari Driver installed in the macOS operating system and return 68 | /// the driver Process. 69 | /// 70 | /// Returns a `Future` that completes with a 71 | /// Process instance when the process has been successfully 72 | /// started. That [Process] object can be used to interact with the 73 | /// process. If the process cannot be started the returned [Future] 74 | /// completes with an exception. 75 | /// 76 | /// If a specific version is requested, it will check the existing system 77 | /// version and throw and exception the versions do not match. 78 | Future runDriver({String version = 'system'}) async { 79 | _enableDriver(); 80 | if (version != 'system') { 81 | await _compareDriverVersion(version); 82 | print('INFO: Safari Driver will start with version $version on port ' 83 | '4444'); 84 | } else { 85 | // Driver does not have any output. 86 | // Printing this one as a info message. 87 | print('INFO: Safari Driver will start on port 4444.'); 88 | } 89 | 90 | return io.Process.start('/usr/bin/safaridriver', ['--port=4444']); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /packages/web_drivers/lib/src/common.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | library common; 6 | 7 | import 'dart:io' as io; 8 | 9 | import 'package:yaml/yaml.dart'; 10 | 11 | /// Provides access to the contents of the `driver_lock.yaml` file. 12 | class DriverLock { 13 | DriverLock._() { 14 | final io.File lockFile = io.File('driver_version.yaml'); 15 | _configuration = loadYaml(lockFile.readAsStringSync()) as YamlMap; 16 | } 17 | 18 | /// Initializes the [DriverLock] singleton. 19 | static final DriverLock _singletonInstance = DriverLock._(); 20 | 21 | /// The [DriverLock] singleton. 22 | static DriverLock get instance => _singletonInstance; 23 | 24 | YamlMap _configuration = YamlMap(); 25 | YamlMap get configuration => _configuration; 26 | } 27 | -------------------------------------------------------------------------------- /packages/web_drivers/lib/web_driver_installer.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:io' as io; 6 | 7 | import 'package:args/command_runner.dart'; 8 | import 'package:web_driver_installer/firefox_driver_command.dart'; 9 | 10 | import 'chrome_driver_command.dart'; 11 | import 'safari_driver_command.dart'; 12 | 13 | CommandRunner runner = CommandRunner( 14 | 'webdriver-install', 15 | 'Command-line utility for installing web drivers for web integration tests ' 16 | 'with flutter driver.', 17 | ) 18 | ..addCommand(ChromeDriverCommand()) 19 | ..addCommand(FirefoxDriverCommand()) 20 | ..addCommand(SafariDriverCommand()); 21 | 22 | void main(List args) async { 23 | // TODO(nurhan): Add more browsers' drivers. Control with command line args. 24 | try { 25 | // For now add chromedriver if no argument exists. This is not to break 26 | // exisiting tests. 27 | // TODO(nurhan): Fix smoke test in flutter to pass chromedriver as an arg. 28 | if (args.isEmpty) { 29 | await runner.run(['chromedriver']); 30 | } else { 31 | await runner.run(args); 32 | } 33 | } on UsageException catch (e) { 34 | print(e); 35 | io.exit(64); // Exit code 64 indicates a usage error. 36 | } catch (e) { 37 | rethrow; 38 | } 39 | 40 | // Sometimes the Dart VM refuses to quit. 41 | io.exit(io.exitCode); 42 | } 43 | -------------------------------------------------------------------------------- /packages/web_drivers/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: web_driver_installer 2 | description: For downloading/installing different web drivers. Used in tests. 3 | 4 | environment: 5 | sdk: ">=2.12.0 <3.0.0" 6 | 7 | dependencies: 8 | args: ">=1.5.2 <3.0.0" 9 | http: ">=0.12.0 <1.0.0" 10 | path: ">=1.6.4 <2.0.0" 11 | test: ">=1.6.5 <2.0.0" 12 | yaml: ">=3.0.0 <4.0.0" 13 | -------------------------------------------------------------------------------- /packages/web_drivers/test/chrome_driver_installer_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:io' as io; 6 | 7 | import 'package:test/test.dart'; 8 | import 'package:web_driver_installer/chrome_driver_installer.dart'; 9 | 10 | void main() async { 11 | void deleteInstallationIfExists() { 12 | final io.Directory driverInstallationDir = io.Directory('chromedriver'); 13 | 14 | if (driverInstallationDir.existsSync()) { 15 | driverInstallationDir.deleteSync(recursive: true); 16 | } 17 | } 18 | 19 | setUpAll(() { 20 | deleteInstallationIfExists(); 21 | }); 22 | 23 | tearDown(() { 24 | deleteInstallationIfExists(); 25 | }); 26 | 27 | test('installs chrome driver', () async { 28 | ChromeDriverInstaller command = ChromeDriverInstaller(); 29 | expect(command.isInstalled, isFalse); 30 | await command.install(); 31 | 32 | expectLater(command.isInstalled, isTrue); 33 | }); 34 | 35 | test('get chrome version on MacOS', () async { 36 | String executable = 37 | await ChromeDriverInstaller().findChromeExecutableOnMac(); 38 | final io.ProcessResult processResult = 39 | await io.Process.run(executable, ['--version']); 40 | 41 | expect(processResult.exitCode, 0); 42 | expect(processResult.stdout.toString().startsWith('Google Chrome'), isTrue); 43 | }, skip: !io.Platform.isMacOS); 44 | } 45 | --------------------------------------------------------------------------------