// 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:io' as io; import 'package:meta/meta.dart'; import 'package:path/path.dart' as pathlib; import 'package:web_driver_installer/chrome_driver_installer.dart'; import 'package:web_driver_installer/firefox_driver_installer.dart'; import 'package:web_driver_installer/safari_driver_runner.dart'; import 'package:yaml/yaml.dart'; import 'chrome_installer.dart'; import 'common.dart'; import 'environment.dart'; import 'utils.dart'; /// [DriverManager] implementation for Chrome. /// /// This manager can be used for both macOS and Linux. class ChromeDriverManager extends DriverManager { /// Directory which contains the Chrome's major version. /// /// On LUCI we are using the CIPD packages to control Chrome binaries we use. /// There will be multiple CIPD packages loaded at the same time. Yaml file /// `driver_version.yaml` contains the version number we want to use. /// /// Initialized to the current first to avoid the `Non-nullable` error. // TODO: https://github.com/flutter/flutter/issues/53179. Local integration // tests are still using the system Chrome. io.Directory _browserDriverDirWithVersion; ChromeDriverManager(String browser) : super(browser) { final io.File lockFile = io.File(pathlib.join( environment.webUiRootDir.path, 'dev', 'browser_lock.yaml')); final YamlMap _configuration = loadYaml(lockFile.readAsStringSync()) as YamlMap; final String requiredChromeDriverVersion = _configuration['required_driver_version']['chrome'] as String; print('INFO: Major version for Chrome Driver $requiredChromeDriverVersion'); _browserDriverDirWithVersion = io.Directory(pathlib.join( environment.webUiDartToolDir.path, 'drivers', browser, requiredChromeDriverVersion, '${browser}driver-${io.Platform.operatingSystem.toString()}')); } @override Future _installDriver() async { if (_browserDriverDirWithVersion.existsSync()) { _browserDriverDirWithVersion.deleteSync(recursive: true); } _browserDriverDirWithVersion.createSync(recursive: true); temporaryDirectories.add(_drivers); final io.Directory temp = io.Directory.current; io.Directory.current = _browserDriverDirWithVersion; try { // TODO(nurhan): https://github.com/flutter/flutter/issues/53179 final String chromeDriverVersion = await queryChromeDriverVersion(); ChromeDriverInstaller chromeDriverInstaller = ChromeDriverInstaller.withVersion(chromeDriverVersion); await chromeDriverInstaller.install(alwaysInstall: true); } finally { io.Directory.current = temp; } } /// Throw an error if driver directory does not exists. /// /// Driver should already exist on LUCI as a CIPD package. @override Future _verifyDriverForLUCI() { if (!_browserDriverDirWithVersion.existsSync()) { throw StateError('Failed to locate Chrome driver on LUCI on path:' '${_browserDriverDirWithVersion.path}'); } return Future.value(); } @override Future _startDriver() async { await startProcess('./chromedriver/chromedriver', ['--port=4444'], workingDirectory: _browserDriverDirWithVersion.path); print('INFO: Driver started'); } } /// [DriverManager] implementation for Firefox. /// /// This manager can be used for both macOS and Linux. class FirefoxDriverManager extends DriverManager { FirefoxDriverManager(String browser) : super(browser); FirefoxDriverInstaller firefoxDriverInstaller = FirefoxDriverInstaller(geckoDriverVersion: getLockedGeckoDriverVersion()); @override Future _installDriver() async { if (_browserDriverDir.existsSync()) { _browserDriverDir.deleteSync(recursive: true); } _browserDriverDir.createSync(recursive: true); temporaryDirectories.add(_drivers); final io.Directory temp = io.Directory.current; io.Directory.current = _browserDriverDir; try { await firefoxDriverInstaller.install(alwaysInstall: false); } finally { io.Directory.current = temp; } } /// Throw an error if driver directory does not exist. /// /// Driver should already exist on LUCI as a CIPD package. @override Future _verifyDriverForLUCI() { if (!_browserDriverDir.existsSync()) { throw StateError('Failed to locate Firefox driver on LUCI on path:' '${_browserDriverDir.path}'); } return Future.value(); } @override Future _startDriver() async { await startProcess('./firefoxdriver/geckodriver', ['--port=4444'], workingDirectory: _browserDriverDir.path); print('INFO: Driver started'); } /// Get the geckodriver version to be used with [FirefoxDriverInstaller]. /// /// For different versions of geckodriver. See: /// https://github.com/mozilla/geckodriver/releases static String getLockedGeckoDriverVersion() { final YamlMap browserLock = BrowserLock.instance.configuration; String geckoDriverReleaseVersion = browserLock['geckodriver'] as String; return geckoDriverReleaseVersion; } } /// [DriverManager] implementation for Safari. /// /// This manager is will only be created/used for macOS. class SafariDriverManager extends DriverManager { SafariDriverManager(String browser) : super(browser); @override Future _installDriver() { // No-op. // macOS comes with Safari Driver installed. return new Future.value(); } @override Future _verifyDriverForLUCI() { // No-op. // macOS comes with Safari Driver installed. return Future.value(); } @override Future _startDriver() async { final SafariDriverRunner safariDriverRunner = SafariDriverRunner(); final io.Process process = await safariDriverRunner.runDriver(version: 'system'); processesToCleanUp.add(process); } } /// Abstract class for preparing the browser driver before running the integration /// tests. abstract class DriverManager { /// Installation directory for browser's driver. @protected final io.Directory _browserDriverDir; /// This is the parent directory for all drivers. /// /// This directory is saved to [temporaryDirectories] and deleted before /// tests shutdown. @protected final io.Directory _drivers; DriverManager(String browser) : this._browserDriverDir = io.Directory(pathlib.join( environment.webUiDartToolDir.path, 'drivers', browser, '${browser}driver-${io.Platform.operatingSystem.toString()}')), this._drivers = io.Directory( pathlib.join(environment.webUiDartToolDir.path, 'drivers')); Future prepareDriver() async { if (!isLuci) { // LUCI installs driver from CIPD, so we skip installing it on LUCI. await _installDriver(); } else { await _verifyDriverForLUCI(); } await _startDriver(); } /// Always re-install since driver can change frequently. /// It usually changes with each the browser version changes. /// A better solution would be installing the browser and the driver at the /// same time. /// TODO(nurhan): https://github.com/flutter/flutter/issues/53179. Partly // solved. Remaining local integration tests using the locked Chrome version. Future _installDriver(); Future _verifyDriverForLUCI(); @protected Future _startDriver(); static DriverManager chooseDriver(String browser) { if (browser == 'chrome') { return ChromeDriverManager(browser); } else if (browser == 'firefox') { return FirefoxDriverManager(browser); } else if (browser == 'safari' && io.Platform.isMacOS) { return SafariDriverManager(browser); } else { throw StateError('Integration tests are only supported on Firefox, Chrome' ' and on Safari (running on macOS)'); } } }