diff --git a/lib/web_ui/dev/browser_lock.yaml b/lib/web_ui/dev/browser_lock.yaml index 486d7ec242ec2..ddb7b388e3942 100644 --- a/lib/web_ui/dev/browser_lock.yaml +++ b/lib/web_ui/dev/browser_lock.yaml @@ -14,3 +14,7 @@ ios-safari: majorVersion: 13 minorVersion: 5 device: 'iPhone 11' +## geckodriver is used for testing Firefox Browser. It works with multiple +## Firefox Browser versions. +## See: https://github.com/mozilla/geckodriver/releases +geckodriver: 'v0.26.0' diff --git a/lib/web_ui/dev/driver_manager.dart b/lib/web_ui/dev/driver_manager.dart index 309e3e3513dc9..de15c467c5510 100644 --- a/lib/web_ui/dev/driver_manager.dart +++ b/lib/web_ui/dev/driver_manager.dart @@ -7,7 +7,9 @@ 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'; @@ -16,10 +18,11 @@ import 'utils.dart'; /// [DriverManager] implementation for Chrome. /// -/// This manager can be used for both MacOS and Linux. +/// This manager can be used for both macOS and Linux. class ChromeDriverManager extends DriverManager { ChromeDriverManager(String browser) : super(browser); + @override Future _installDriver() async { if (_browserDriverDir.existsSync()) { _browserDriverDir.deleteSync(recursive: true); @@ -45,6 +48,7 @@ class ChromeDriverManager extends DriverManager { /// Throw an error if driver directory does not exists. /// /// Driver should already exist on LUCI as a CIPD package. + @override Future _verifyDriverForLUCI() { if (!_browserDriverDir.existsSync()) { throw StateError('Failed to locate Chrome driver on LUCI on path:' @@ -53,6 +57,7 @@ class ChromeDriverManager extends DriverManager { return Future.value(); } + @override Future _startDriver(String driverPath) async { await startProcess('./chromedriver/chromedriver', ['--port=4444'], workingDirectory: driverPath); @@ -60,24 +65,85 @@ class ChromeDriverManager extends DriverManager { } } +/// [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(String driverPath) async { + await startProcess('./firefoxdriver/geckodriver', ['--port=4444'], + workingDirectory: driverPath); + 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. +/// 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(String driverPath) async { final SafariDriverRunner safariDriverRunner = SafariDriverRunner(); @@ -137,11 +203,13 @@ abstract class DriverManager { 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 Chrome or ' - 'on Safari (running on MacOS)'); + throw StateError('Integration tests are only supported on Firefox, Chrome' + ' and on Safari (running on macOS)'); } } } diff --git a/lib/web_ui/dev/integration_tests_manager.dart b/lib/web_ui/dev/integration_tests_manager.dart index 76a42a1ae76ed..c86209259bcf3 100644 --- a/lib/web_ui/dev/integration_tests_manager.dart +++ b/lib/web_ui/dev/integration_tests_manager.dart @@ -14,8 +14,8 @@ import 'exceptions.dart'; import 'common.dart'; import 'utils.dart'; -const String _unsupportedConfigurationWarning = 'WARNING: integration ' - 'tests are only supported on Chrome or on Safari (running on MacOS)'; +const String _unsupportedConfigurationWarning = 'WARNING: integration tests ' + 'are only supported on Chrome, Firefox and on Safari (running on macOS)'; class IntegrationTestsManager { final String _browser; @@ -285,10 +285,13 @@ class IntegrationTestsManager { /// Validate the given `browser`, `platform` combination is suitable for /// integration tests to run. bool validateIfTestsShouldRun() { - // Chrome tests should run at all Platforms (Linux, MacOS, Windows). + // Chrome tests should run at all Platforms (Linux, macOS, Windows). // They can also run successfully on CI and local. if (_browser == 'chrome') { return true; + } else if (_browser == 'firefox' && + (io.Platform.isLinux || io.Platform.isMacOS)) { + return true; } else if (_browser == 'safari' && io.Platform.isMacOS && !isLuci) { return true; } else { @@ -306,6 +309,8 @@ abstract class IntegrationArguments { factory IntegrationArguments.fromBrowser(String browser) { if (browser == 'chrome') { return ChromeIntegrationArguments(); + } else if (browser == 'firefox') { + return FirefoxIntegrationArguments(); } else if (browser == 'safari' && io.Platform.isMacOS) { return SafariIntegrationArguments(); } else { @@ -346,6 +351,25 @@ class ChromeIntegrationArguments extends IntegrationArguments { } } +/// Arguments to give `flutter drive` to run the integration tests on Firefox. +class FirefoxIntegrationArguments extends IntegrationArguments { + List getTestArguments(String testName, String mode) { + return [ + 'drive', + '--target=test_driver/${testName}', + '-d', + 'web-server', + '--$mode', + '--browser-name=firefox', + '--headless', + '--local-engine=host_debug_unopt', + ]; + } + + String getCommandToRun(String testName, String mode) => + 'flutter ${getTestArguments(testName, mode).join(' ')}'; +} + /// Arguments to give `flutter drive` to run the integration tests on Safari. class SafariIntegrationArguments extends IntegrationArguments { SafariIntegrationArguments(); @@ -397,4 +421,12 @@ const Map> blockedTestsListsMap = >{ 'target_platform_android_e2e.dart', 'image_loading_e2e.dart', ], + 'firefox-linux': [ + 'target_platform_ios_e2e.dart', + 'target_platform_macos_e2e.dart', + ], + 'firefox-macos': [ + 'target_platform_android_e2e.dart', + 'target_platform_ios_e2e.dart', + ], }; diff --git a/lib/web_ui/dev/test_runner.dart b/lib/web_ui/dev/test_runner.dart index ae6edc408458a..d76711398bd8c 100644 --- a/lib/web_ui/dev/test_runner.dart +++ b/lib/web_ui/dev/test_runner.dart @@ -106,7 +106,7 @@ class TestCommand extends Command with ArgUtils { print('Running the unit tests only'); return TestTypesRequested.unit; } else if (boolArg('integration-tests-only')) { - if (!isChrome && !isSafariOnMacOS) { + if (!isChrome && !isSafariOnMacOS && !isFirefox) { throw UnimplementedError( 'Integration tests are only available on Chrome Desktop for now'); } @@ -132,7 +132,7 @@ class TestCommand extends Command with ArgUtils { case TestTypesRequested.all: // TODO(nurhan): https://github.com/flutter/flutter/issues/53322 // TODO(nurhan): Expand browser matrix for felt integration tests. - if (runAllTests && (isChrome || isSafariOnMacOS)) { + if (runAllTests && (isChrome || isSafariOnMacOS || isFirefox)) { bool unitTestResult = await runUnitTests(); bool integrationTestResult = await runIntegrationTests(); if (integrationTestResult != unitTestResult) { @@ -263,6 +263,9 @@ class TestCommand extends Command with ArgUtils { /// Whether [browser] is set to "chrome". bool get isChrome => browser == 'chrome'; + /// Whether [browser] is set to "firefox". + bool get isFirefox => browser == 'firefox'; + /// Whether [browser] is set to "safari". bool get isSafariOnMacOS => browser == 'safari' && io.Platform.isMacOS; diff --git a/lib/web_ui/pubspec.yaml b/lib/web_ui/pubspec.yaml index 5082fa6bc1b3c..aa32ad46b8d32 100644 --- a/lib/web_ui/pubspec.yaml +++ b/lib/web_ui/pubspec.yaml @@ -32,4 +32,4 @@ dev_dependencies: git: url: git://github.com/flutter/web_installers.git path: packages/web_drivers/ - ref: 41f96bb55d2f064dac3c9fc727ebdf4b0cdf79c4 + ref: 1cea0d79cad1ebc217c4bcbeba1be41470674a49