diff --git a/rust/src/chrome.rs b/rust/src/chrome.rs index 9681662b8a1ed..bd67ecf079e90 100644 --- a/rust/src/chrome.rs +++ b/rust/src/chrome.rs @@ -65,6 +65,7 @@ const CFT_MACOS_APP_NAME: &str = "Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"; const MIN_CHROME_VERSION_CFT: i32 = 113; const MIN_CHROMEDRIVER_VERSION_CFT: i32 = 115; +const MIN_CHROME_VERSION_LINUX_ARM64: i32 = 153; const CHROMIUM_SNAP_LINK: &str = "/snap/bin/chromium"; const CHROMIUM_SNAP_BINARY: &str = "/snap/chromium/current/usr/lib/chromium-browser/chrome"; @@ -132,6 +133,23 @@ impl ChromeManager { self.create_cft_url(&self.get_driver_mirror_url_or_default(CFT_URL), endpoint) } + fn find_platform_url<'a>(&self, downloads: &'a [PlatformUrl]) -> Option<&'a str> { + downloads + .iter() + .find(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label())) + .map(|p| p.url.as_str()) + } + + // CfT publishes Linux arm64 binaries later than the rest of the platforms, so the minimum + // version available for download is higher there than the general CfT minimum. + fn min_version_for_platform(&self, min_cft_version: i32) -> i32 { + if LINUX.is(self.get_os()) && ARM64.is(self.get_arch()) { + MIN_CHROME_VERSION_LINUX_ARM64.max(min_cft_version) + } else { + min_cft_version + } + } + fn request_driver_version_from_latest(&self, driver_url: &str) -> Result { self.log.debug(format!( "Reading {} version from {}", @@ -169,18 +187,16 @@ impl ChromeManager { return self.request_driver_version_from_latest(&self.create_latest_release_url()); } - let platform_url: Vec<&PlatformUrl> = chromedriver - .as_ref() - .unwrap() - .iter() - .filter(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label())) - .collect(); + let platform_url = self.find_platform_url(chromedriver.as_ref().unwrap()); self.log.trace(format!( - "CfT URLs for downloading {}: {:?}", + "CfT URL for downloading {}: {:?}", self.get_driver_name(), platform_url )); - self.driver_url = Some(platform_url.first().unwrap().url.to_string()); + match platform_url { + Some(url) => self.driver_url = Some(url.to_string()), + None => return self.unavailable_discovery(), + } Ok(stable_channel.version) } @@ -203,10 +219,19 @@ impl ChromeManager { let good_versions_url = self.create_cft_url_for_drivers(GOOD_VERSIONS_ENDPOINT); let all_versions = self.request_versions_from_online::(&good_versions_url)?; + // Not every version is published for every platform, so the availability of a download + // for the current platform is part of the filter (and not a later check) to let the most + // recent usable version win let filtered_versions: Vec = all_versions .versions .into_iter() - .filter(|r| r.version.starts_with(version_for_filtering.as_str())) + .filter(|r| { + r.version.starts_with(version_for_filtering.as_str()) + && r.downloads + .chromedriver + .as_deref() + .is_some_and(|d| self.find_platform_url(d).is_some()) + }) .collect(); if filtered_versions.is_empty() { return Err(anyhow!(format!( @@ -216,23 +241,25 @@ impl ChromeManager { self.get_driver_name(), version_for_filtering.as_str(), self.get_arch(), - &MIN_CHROMEDRIVER_VERSION_CFT.to_string(), + &self + .min_version_for_platform(MIN_CHROMEDRIVER_VERSION_CFT) + .to_string(), ), CFT_URL ))); } let driver_version = filtered_versions.last().unwrap(); - let url: Vec<&PlatformUrl> = driver_version + let url = driver_version .downloads .chromedriver - .as_ref() - .unwrap() - .iter() - .filter(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label())) - .collect(); - self.log.trace(format!("URLs for CfT: {:?}", url)); - self.driver_url = Some(url.first().unwrap().url.to_string()); + .as_deref() + .and_then(|d| self.find_platform_url(d)); + self.log.trace(format!("URL for CfT: {:?}", url)); + match url { + Some(url) => self.driver_url = Some(url.to_string()), + None => return self.unavailable_discovery(), + } Ok(driver_version.version.to_string()) } @@ -420,9 +447,12 @@ impl SeleniumManager for ChromeManager { "mac64" } } else if LINUX.is(os) && ARM64.is(arch) { - return Err(anyhow!( - "Linux arm64 is not supported yet by Google Chrome. Please try another browser." - )); + // This URL scheme only serves chromedriver 114-, which was never published for Linux + // arm64. Newer versions are served by the CfT endpoints and never reach this point. + return Err(anyhow!(format!( + "Linux arm64 requires {} {} or above", + self.driver_name, MIN_CHROME_VERSION_LINUX_ARM64 + ))); } else { "linux64" }; @@ -484,6 +514,8 @@ impl SeleniumManager for ChromeManager { } else { "mac-x64" } + } else if ARM64.is(arch) { + "linux-arm64" } else { "linux64" } @@ -494,14 +526,6 @@ impl SeleniumManager for ChromeManager { _browser_version: &str, ) -> Result { let browser_name = self.browser_name; - let os = self.get_os(); - let arch = self.get_arch(); - if LINUX.is(os) && ARM64.is(arch) { - return Err(anyhow!(format!( - "Linux arm64 is not supported yet by {}. Please try another browser.", - browser_name - ))); - } self.get_logger().trace(format!( "Using Chrome for Testing (CfT) endpoints to find out latest stable {} version", browser_name @@ -511,21 +535,19 @@ impl SeleniumManager for ChromeManager { let versions_with_downloads = self.request_versions_from_online::(&latest_versions_url)?; let stable_channel = versions_with_downloads.channels.stable; - let chrome = stable_channel.downloads.chrome; - let platform_url: Vec<&PlatformUrl> = chrome - .iter() - .filter(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label())) - .collect(); + let platform_url = self.find_platform_url(&stable_channel.downloads.chrome); self.log.trace(format!( - "CfT URLs for downloading {}: {:?}", + "CfT URL for downloading {}: {:?}", self.get_browser_name(), platform_url )); - let browser_version = stable_channel.version; - self.browser_url = Some(platform_url.first().unwrap().url.to_string()); + match platform_url { + Some(url) => self.browser_url = Some(url.to_string()), + None => return self.unavailable_discovery(), + } - Ok(browser_version) + Ok(stable_channel.version) } fn request_fixed_browser_version_from_online( @@ -533,7 +555,7 @@ impl SeleniumManager for ChromeManager { _browser_version: &str, ) -> Result { let browser_name = self.browser_name; - let mut browser_version = self.get_browser_version().to_string(); + let browser_version = self.get_browser_version().to_string(); let major_browser_version = self.get_major_browser_version(); self.get_logger().trace(format!( "Using Chrome for Testing (CfT) endpoints to find out {} {}", @@ -553,48 +575,50 @@ impl SeleniumManager for ChromeManager { } else { versions_with_downloads.channels.canary }; - browser_version = channel.version; - let platform_url: Vec<&PlatformUrl> = channel - .downloads - .chrome - .iter() - .filter(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label())) - .collect(); - self.browser_url = Some(platform_url.first().unwrap().url.to_string()); + let platform_url = self.find_platform_url(&channel.downloads.chrome); + match platform_url { + Some(url) => self.browser_url = Some(url.to_string()), + None => return self.unavailable_discovery(), + } - Ok(browser_version) + Ok(channel.version) } else { let good_versions_url = self.create_cft_url_for_browsers(GOOD_VERSIONS_ENDPOINT); let all_versions = self.request_versions_from_online::(&good_versions_url)?; - let iter_versions = all_versions.versions.into_iter(); - let filtered_versions: Vec = if self.is_browser_version_specific() { - iter_versions - .filter(|r| r.version.eq(browser_version.as_str())) - .collect() - } else { - iter_versions - .filter(|r| r.version.starts_with(major_browser_version.as_str())) - .collect() + let version_matches = |r: &Version| { + if self.is_browser_version_specific() { + r.version.eq(browser_version.as_str()) + } else { + r.version.starts_with(major_browser_version.as_str()) + } }; + // Not every version is published for every platform, so the availability of a download + // for the current platform is part of the filter (and not a later check) to let the + // most recent usable version win + let filtered_versions: Vec = all_versions + .versions + .into_iter() + .filter(|r| { + version_matches(r) && self.find_platform_url(&r.downloads.chrome).is_some() + }) + .collect(); if filtered_versions.is_empty() { return self.unavailable_download(); } let last_browser = filtered_versions.last().unwrap(); - let platform_url: Vec<&PlatformUrl> = last_browser - .downloads - .chrome - .iter() - .filter(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label())) - .collect(); - self.browser_url = Some(platform_url.first().unwrap().url.to_string()); + let platform_url = self.find_platform_url(&last_browser.downloads.chrome); + match platform_url { + Some(url) => self.browser_url = Some(url.to_string()), + None => return self.unavailable_discovery(), + } Ok(last_browser.version.to_string()) } } fn get_min_browser_version_for_download(&self) -> Result { - Ok(MIN_CHROME_VERSION_CFT) + Ok(self.min_version_for_platform(MIN_CHROME_VERSION_CFT)) } fn get_browser_binary_path(&mut self, _browser_version: &str) -> Result { diff --git a/rust/src/config.rs b/rust/src/config.rs index 913e276d049b4..efb55688864c5 100644 --- a/rust/src/config.rs +++ b/rust/src/config.rs @@ -179,8 +179,9 @@ impl ARCH { match self { ARCH::X32 => vec![ARCH_X86, "i386", "x32", "i686"], ARCH::X64 => vec![ARCH_X64, "amd64", "x64", "ia64"], - ARCH::ARM64 => vec![ARCH_ARM64, "aarch64", "arm"], - ARCH::ARMV7 => vec![ARCH_ARM7L, "armv7l"], + ARCH::ARM64 => vec![ARCH_ARM64, "aarch64"], + // "arm" is the conventional name for 32-bit ARM (e.g. std::env::consts::ARCH) + ARCH::ARMV7 => vec![ARCH_ARM7L, "armv7l", "arm"], } } diff --git a/rust/tests/browser_download_tests.rs b/rust/tests/browser_download_tests.rs index 38c8bdd548ae9..8de93dc934aaa 100644 --- a/rust/tests/browser_download_tests.rs +++ b/rust/tests/browser_download_tests.rs @@ -15,10 +15,10 @@ // specific language governing permissions and limitations // under the License. +use crate::common::is_linux_arm64; use crate::common::{assert_browser, assert_driver, get_selenium_manager}; use rstest::rstest; -use std::env::consts::ARCH; use std::env::consts::OS; mod common; @@ -30,7 +30,7 @@ mod common; fn browser_latest_download_test(#[case] browser: String) { if browser.eq("edge") && OS.eq("windows") { return; - } else if OS.eq("linux") && ARCH.eq("aarch64") && !browser.eq("firefox") { + } else if is_linux_arm64() && browser.eq("edge") { return; } @@ -54,6 +54,7 @@ fn browser_latest_download_test(#[case] browser: String) { #[rstest] #[case("chrome", "131")] #[case("chrome", "131.0.6778.264")] +#[case("chrome", "153")] #[case("chrome", "beta")] #[case("firefox", "121")] #[case("firefox", "121.0.1")] @@ -66,17 +67,15 @@ fn browser_version_download_test(#[case] browser: String, #[case] browser_versio println!( "Skipping Edge download test on Windows since the installation requires admin privileges" ); - } else if OS.eq("linux") && ARCH.eq("aarch64") && !browser.eq("firefox") { + } else if is_linux_arm64() && browser.eq("edge") { + println!("Skipping Edge download test on Linux arm64 since it's not supported yet"); + } else if is_linux_arm64() && browser.eq("firefox") && browser_version.starts_with("121") { println!( - "Skipping non-Firefox download test on Linux arm64 since no other browsers are supported yet" + "Skipping Firefox 121 download test on Linux arm64 since arm64 builds are only available from version 136 onwards" ); - } else if OS.eq("linux") - && ARCH.eq("aarch64") - && browser.eq("firefox") - && browser_version.starts_with("121") - { + } else if is_linux_arm64() && browser.eq("chrome") && browser_version.starts_with("131") { println!( - "Skipping Firefox 121 download test on Linux arm64 since arm64 builds are only available from version 136 onwards" + "Skipping Chrome 131 download test on Linux arm64 since arm64 builds are only available from version 153 onwards" ); } else { let mut cmd = get_selenium_manager(); diff --git a/rust/tests/browser_tests.rs b/rust/tests/browser_tests.rs index f380f37433618..ef113d441e1dc 100644 --- a/rust/tests/browser_tests.rs +++ b/rust/tests/browser_tests.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use crate::common::is_linux_arm64; use crate::common::{assert_output, get_selenium_manager, get_stdout}; use exitcode::DATAERR; @@ -22,7 +23,6 @@ use rstest::rstest; use selenium_manager::SeleniumManager; use selenium_manager::chrome::ChromeManager; use selenium_manager::edge::EdgeManager; -use std::env::consts::ARCH; use std::env::consts::OS; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; @@ -33,6 +33,7 @@ mod common; #[rstest] #[case("chrome", "chromedriver", "114", "114.0.5735.90")] #[case("chrome", "chromedriver", "115", "115.0.5790")] +#[case("chrome", "chromedriver", "153", "153.0")] #[case("edge", "msedgedriver", "140", "140.0")] #[case("edge", "msedgedriver", "141", "141.0")] #[case("firefox", "geckodriver", "101", "0.31.0")] @@ -45,7 +46,11 @@ fn browser_version_test( #[case] browser_version: String, #[case] driver_version: String, ) { - if OS.eq("linux") && ARCH.eq("aarch64") { + // Chrome is not published for Linux arm64 below 153, and Edge not at all + if is_linux_arm64() + && (browser.eq("edge") + || (browser.eq("chrome") && browser_version.parse::().unwrap_or_default() < 153)) + { return; } @@ -87,7 +92,7 @@ fn wrong_parameters_test( #[case] driver_version: String, #[case] error_code: i32, ) { - if OS.eq("linux") && ARCH.eq("aarch64") && !browser.eq("firefox") { + if is_linux_arm64() && browser.eq("edge") { return; } @@ -130,17 +135,6 @@ fn invalid_geckodriver_version_test() { ); } -#[test] -fn chrome_is_unsupported_on_linux_arm64() { - let mut manager = ChromeManager::new().unwrap(); - manager.config.os = "linux".to_string(); - manager.config.arch = "aarch64".to_string(); - let error = manager - .request_latest_browser_version_from_online("") - .unwrap_err(); - assert!(error.to_string().contains("not supported yet")); -} - #[test] fn edge_is_unsupported_on_linux_arm64() { let mut manager = EdgeManager::new().unwrap(); @@ -179,6 +173,33 @@ fn firefox_below_min_version_on_linux_arm64_test() { ); } +#[test] +fn chrome_below_min_version_on_linux_arm64_test() { + let mut cmd = get_selenium_manager(); + let result = cmd + .args([ + "--browser", + "chrome", + "--browser-version", + "152", + "--os", + "linux", + "--arch", + "arm64", + "--force-browser-download", + "--debug", + ]) + .assert() + .try_success(); + + assert_output( + &mut cmd, + result, + vec!["not available for download"], + DATAERR, + ); +} + #[rstest] #[case( "windows", diff --git a/rust/tests/cache_tests.rs b/rust/tests/cache_tests.rs index af08794170ff1..fa4b649f6c4b4 100644 --- a/rust/tests/cache_tests.rs +++ b/rust/tests/cache_tests.rs @@ -23,7 +23,6 @@ use std::path::Path; mod common; -#[cfg(not(all(target_os = "linux", target_arch = "aarch64")))] #[rstest] #[case("../tmp")] #[case("../áèîö")] diff --git a/rust/tests/cache_unit_tests.rs b/rust/tests/cache_unit_tests.rs index 62aac245b7cf3..40dd67269f033 100644 --- a/rust/tests/cache_unit_tests.rs +++ b/rust/tests/cache_unit_tests.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use selenium_manager::SeleniumManager; use selenium_manager::files::{collect_files_from_cache, find_latest_from_cache}; use selenium_manager::get_manager_by_browser; use selenium_manager::metadata::{ diff --git a/rust/tests/config_tests.rs b/rust/tests/config_tests.rs index eb9e3a3b34ce4..6832ef1ff277f 100644 --- a/rust/tests/config_tests.rs +++ b/rust/tests/config_tests.rs @@ -15,12 +15,11 @@ // specific language governing permissions and limitations // under the License. +use crate::common::is_linux_arm64; use crate::common::{assert_browser, assert_driver, get_selenium_manager, get_stdout}; use rstest::rstest; -use std::env::consts::ARCH; -use std::env::consts::OS; use std::fs::File; use std::io::{BufWriter, Write}; use tempfile::Builder; @@ -32,7 +31,7 @@ mod common; #[case("firefox")] #[case("edge")] fn config_test(#[case] browser_name: String) { - if OS.eq("linux") && ARCH.eq("aarch64") && !browser_name.eq("firefox") { + if is_linux_arm64() && browser_name.eq("edge") { return; } let tmp_dir = Builder::new().prefix("sm-config-test").tempdir().unwrap(); diff --git a/rust/tests/config_unit_tests.rs b/rust/tests/config_unit_tests.rs index 9f43da39f5ed3..76b5217d5d00a 100644 --- a/rust/tests/config_unit_tests.rs +++ b/rust/tests/config_unit_tests.rs @@ -81,9 +81,9 @@ fn os_is_does_not_match(#[case] os: OS, #[case] candidate: &str) { #[case(X64, "ia64")] #[case(ARM64, "arm64")] #[case(ARM64, "aarch64")] -#[case(ARM64, "arm")] #[case(ARMV7, "arm7l")] #[case(ARMV7, "armv7l")] +#[case(ARMV7, "arm")] fn arch_is_matches(#[case] arch: ARCH, #[case] candidate: &str) { assert!(arch.is(candidate)); } @@ -95,6 +95,7 @@ fn arch_is_matches(#[case] arch: ARCH, #[case] candidate: &str) { #[case(X64, "i686")] #[case(X64, "arm7l")] #[case(ARM64, "x86_64")] +#[case(ARM64, "arm")] #[case(ARMV7, "aarch64")] fn arch_is_does_not_match(#[case] arch: ARCH, #[case] candidate: &str) { assert!(!arch.is(candidate)); diff --git a/rust/tests/exec_driver_tests.rs b/rust/tests/exec_driver_tests.rs index 33e394730f838..3cd8f2474fd55 100644 --- a/rust/tests/exec_driver_tests.rs +++ b/rust/tests/exec_driver_tests.rs @@ -15,10 +15,10 @@ // specific language governing permissions and limitations // under the License. +use crate::common::is_linux_arm64; use crate::common::{assert_browser, assert_driver, exec_driver, get_selenium_manager}; use rstest::rstest; -use std::env::consts::ARCH; use std::env::consts::OS; mod common; @@ -29,7 +29,7 @@ mod common; #[case("firefox", "geckodriver")] #[case("iexplorer", "IEDriverServer")] fn exec_driver_test(#[case] browser_name: String, #[case] driver_name: String) { - if OS.eq("linux") && ARCH.eq("aarch64") && !browser_name.eq("firefox") { + if is_linux_arm64() && browser_name.eq("edge") { return; } diff --git a/rust/tests/offline_tests.rs b/rust/tests/offline_tests.rs index 114b8e9982117..2d1020a4532f3 100644 --- a/rust/tests/offline_tests.rs +++ b/rust/tests/offline_tests.rs @@ -15,26 +15,17 @@ // specific language governing permissions and limitations // under the License. -use crate::common::{get_selenium_manager, get_stdout, is_linux_arm64}; +use crate::common::{get_selenium_manager, get_stdout}; mod common; #[test] fn offline_test() { let mut cmd = get_selenium_manager(); - cmd.args([ - "--debug", - "--browser", - if is_linux_arm64() { - "firefox" - } else { - "chrome" - }, - "--offline", - ]) - .assert() - .success() - .code(0); + cmd.args(["--debug", "--browser", "chrome", "--offline"]) + .assert() + .success() + .code(0); let stdout = get_stdout(&mut cmd); diff --git a/rust/tests/version_unit_tests.rs b/rust/tests/version_unit_tests.rs index f47c7b49409dd..6d5b884764334 100644 --- a/rust/tests/version_unit_tests.rs +++ b/rust/tests/version_unit_tests.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use selenium_manager::SeleniumManager; use selenium_manager::files::parse_version; use selenium_manager::get_manager_by_browser; use selenium_manager::logger::Logger;