Skip to content

Commit 84cb7f5

Browse files
committed
[rust] Add support for Chrome arm64 on Linux
1 parent aa36b38 commit 84cb7f5

9 files changed

Lines changed: 142 additions & 111 deletions

rust/src/chrome.rs

Lines changed: 89 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const CFT_MACOS_APP_NAME: &str =
6565
"Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing";
6666
const MIN_CHROME_VERSION_CFT: i32 = 113;
6767
const MIN_CHROMEDRIVER_VERSION_CFT: i32 = 115;
68+
const MIN_CHROME_VERSION_LINUX_ARM64: i32 = 153;
6869
const CHROMIUM_SNAP_LINK: &str = "/snap/bin/chromium";
6970
const CHROMIUM_SNAP_BINARY: &str = "/snap/chromium/current/usr/lib/chromium-browser/chrome";
7071

@@ -132,6 +133,23 @@ impl ChromeManager {
132133
self.create_cft_url(&self.get_driver_mirror_url_or_default(CFT_URL), endpoint)
133134
}
134135

136+
fn find_platform_url(&self, downloads: &[PlatformUrl]) -> Option<String> {
137+
downloads
138+
.iter()
139+
.find(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label()))
140+
.map(|p| p.url.to_string())
141+
}
142+
143+
// CfT publishes Linux arm64 binaries later than the rest of the platforms, so the minimum
144+
// version available for download is higher there than the general CfT minimum.
145+
fn min_version_for_platform(&self, min_cft_version: i32) -> i32 {
146+
if LINUX.is(self.get_os()) && ARM64.is(self.get_arch()) {
147+
MIN_CHROME_VERSION_LINUX_ARM64.max(min_cft_version)
148+
} else {
149+
min_cft_version
150+
}
151+
}
152+
135153
fn request_driver_version_from_latest(&self, driver_url: &str) -> Result<String, Error> {
136154
self.log.debug(format!(
137155
"Reading {} version from {}",
@@ -169,18 +187,16 @@ impl ChromeManager {
169187
return self.request_driver_version_from_latest(&self.create_latest_release_url());
170188
}
171189

172-
let platform_url: Vec<&PlatformUrl> = chromedriver
173-
.as_ref()
174-
.unwrap()
175-
.iter()
176-
.filter(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label()))
177-
.collect();
190+
let platform_url = self.find_platform_url(chromedriver.as_ref().unwrap());
178191
self.log.trace(format!(
179-
"CfT URLs for downloading {}: {:?}",
192+
"CfT URL for downloading {}: {:?}",
180193
self.get_driver_name(),
181194
platform_url
182195
));
183-
self.driver_url = Some(platform_url.first().unwrap().url.to_string());
196+
match platform_url {
197+
Some(url) => self.driver_url = Some(url),
198+
None => return self.unavailable_discovery(),
199+
}
184200

185201
Ok(stable_channel.version)
186202
}
@@ -203,10 +219,19 @@ impl ChromeManager {
203219
let good_versions_url = self.create_cft_url_for_drivers(GOOD_VERSIONS_ENDPOINT);
204220
let all_versions =
205221
self.request_versions_from_online::<VersionsWithDownloads>(&good_versions_url)?;
222+
// Not every version is published for every platform, so the availability of a download
223+
// for the current platform is part of the filter (and not a later check) to let the most
224+
// recent usable version win
206225
let filtered_versions: Vec<Version> = all_versions
207226
.versions
208227
.into_iter()
209-
.filter(|r| r.version.starts_with(version_for_filtering.as_str()))
228+
.filter(|r| {
229+
r.version.starts_with(version_for_filtering.as_str())
230+
&& r.downloads
231+
.chromedriver
232+
.as_deref()
233+
.is_some_and(|d| self.find_platform_url(d).is_some())
234+
})
210235
.collect();
211236
if filtered_versions.is_empty() {
212237
return Err(anyhow!(format!(
@@ -216,23 +241,25 @@ impl ChromeManager {
216241
self.get_driver_name(),
217242
version_for_filtering.as_str(),
218243
self.get_arch(),
219-
&MIN_CHROMEDRIVER_VERSION_CFT.to_string(),
244+
&self
245+
.min_version_for_platform(MIN_CHROMEDRIVER_VERSION_CFT)
246+
.to_string(),
220247
),
221248
CFT_URL
222249
)));
223250
}
224251

225252
let driver_version = filtered_versions.last().unwrap();
226-
let url: Vec<&PlatformUrl> = driver_version
253+
let url = driver_version
227254
.downloads
228255
.chromedriver
229-
.as_ref()
230-
.unwrap()
231-
.iter()
232-
.filter(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label()))
233-
.collect();
234-
self.log.trace(format!("URLs for CfT: {:?}", url));
235-
self.driver_url = Some(url.first().unwrap().url.to_string());
256+
.as_deref()
257+
.and_then(|d| self.find_platform_url(d));
258+
self.log.trace(format!("URL for CfT: {:?}", url));
259+
match url {
260+
Some(url) => self.driver_url = Some(url),
261+
None => return self.unavailable_discovery(),
262+
}
236263

237264
Ok(driver_version.version.to_string())
238265
}
@@ -420,9 +447,12 @@ impl SeleniumManager for ChromeManager {
420447
"mac64"
421448
}
422449
} else if LINUX.is(os) && ARM64.is(arch) {
423-
return Err(anyhow!(
424-
"Linux arm64 is not supported yet by Google Chrome. Please try another browser."
425-
));
450+
// This URL scheme only serves chromedriver 114-, which was never published for Linux
451+
// arm64. Newer versions are served by the CfT endpoints and never reach this point.
452+
return Err(anyhow!(format!(
453+
"Linux arm64 requires {} {} or above",
454+
self.driver_name, MIN_CHROME_VERSION_LINUX_ARM64
455+
)));
426456
} else {
427457
"linux64"
428458
};
@@ -484,6 +514,8 @@ impl SeleniumManager for ChromeManager {
484514
} else {
485515
"mac-x64"
486516
}
517+
} else if ARM64.is(arch) {
518+
"linux-arm64"
487519
} else {
488520
"linux64"
489521
}
@@ -494,14 +526,6 @@ impl SeleniumManager for ChromeManager {
494526
_browser_version: &str,
495527
) -> Result<String, Error> {
496528
let browser_name = self.browser_name;
497-
let os = self.get_os();
498-
let arch = self.get_arch();
499-
if LINUX.is(os) && ARM64.is(arch) {
500-
return Err(anyhow!(format!(
501-
"Linux arm64 is not supported yet by {}. Please try another browser.",
502-
browser_name
503-
)));
504-
}
505529
self.get_logger().trace(format!(
506530
"Using Chrome for Testing (CfT) endpoints to find out latest stable {} version",
507531
browser_name
@@ -511,29 +535,27 @@ impl SeleniumManager for ChromeManager {
511535
let versions_with_downloads =
512536
self.request_versions_from_online::<LatestVersionsWithDownloads>(&latest_versions_url)?;
513537
let stable_channel = versions_with_downloads.channels.stable;
514-
let chrome = stable_channel.downloads.chrome;
515538

516-
let platform_url: Vec<&PlatformUrl> = chrome
517-
.iter()
518-
.filter(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label()))
519-
.collect();
539+
let platform_url = self.find_platform_url(&stable_channel.downloads.chrome);
520540
self.log.trace(format!(
521-
"CfT URLs for downloading {}: {:?}",
541+
"CfT URL for downloading {}: {:?}",
522542
self.get_browser_name(),
523543
platform_url
524544
));
525-
let browser_version = stable_channel.version;
526-
self.browser_url = Some(platform_url.first().unwrap().url.to_string());
545+
match platform_url {
546+
Some(url) => self.browser_url = Some(url),
547+
None => return self.unavailable_discovery(),
548+
}
527549

528-
Ok(browser_version)
550+
Ok(stable_channel.version)
529551
}
530552

531553
fn request_fixed_browser_version_from_online(
532554
&mut self,
533555
_browser_version: &str,
534556
) -> Result<String, Error> {
535557
let browser_name = self.browser_name;
536-
let mut browser_version = self.get_browser_version().to_string();
558+
let browser_version = self.get_browser_version().to_string();
537559
let major_browser_version = self.get_major_browser_version();
538560
self.get_logger().trace(format!(
539561
"Using Chrome for Testing (CfT) endpoints to find out {} {}",
@@ -553,48 +575,50 @@ impl SeleniumManager for ChromeManager {
553575
} else {
554576
versions_with_downloads.channels.canary
555577
};
556-
browser_version = channel.version;
557-
let platform_url: Vec<&PlatformUrl> = channel
558-
.downloads
559-
.chrome
560-
.iter()
561-
.filter(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label()))
562-
.collect();
563-
self.browser_url = Some(platform_url.first().unwrap().url.to_string());
578+
let platform_url = self.find_platform_url(&channel.downloads.chrome);
579+
match platform_url {
580+
Some(url) => self.browser_url = Some(url),
581+
None => return self.unavailable_discovery(),
582+
}
564583

565-
Ok(browser_version)
584+
Ok(channel.version)
566585
} else {
567586
let good_versions_url = self.create_cft_url_for_browsers(GOOD_VERSIONS_ENDPOINT);
568587
let all_versions =
569588
self.request_versions_from_online::<VersionsWithDownloads>(&good_versions_url)?;
570-
let iter_versions = all_versions.versions.into_iter();
571-
let filtered_versions: Vec<Version> = if self.is_browser_version_specific() {
572-
iter_versions
573-
.filter(|r| r.version.eq(browser_version.as_str()))
574-
.collect()
575-
} else {
576-
iter_versions
577-
.filter(|r| r.version.starts_with(major_browser_version.as_str()))
578-
.collect()
589+
let version_matches = |r: &Version| {
590+
if self.is_browser_version_specific() {
591+
r.version.eq(browser_version.as_str())
592+
} else {
593+
r.version.starts_with(major_browser_version.as_str())
594+
}
579595
};
596+
// Not every version is published for every platform, so the availability of a download
597+
// for the current platform is part of the filter (and not a later check) to let the
598+
// most recent usable version win
599+
let filtered_versions: Vec<Version> = all_versions
600+
.versions
601+
.into_iter()
602+
.filter(|r| {
603+
version_matches(r) && self.find_platform_url(&r.downloads.chrome).is_some()
604+
})
605+
.collect();
580606
if filtered_versions.is_empty() {
581607
return self.unavailable_download();
582608
}
583609
let last_browser = filtered_versions.last().unwrap();
584-
let platform_url: Vec<&PlatformUrl> = last_browser
585-
.downloads
586-
.chrome
587-
.iter()
588-
.filter(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label()))
589-
.collect();
590-
self.browser_url = Some(platform_url.first().unwrap().url.to_string());
610+
let platform_url = self.find_platform_url(&last_browser.downloads.chrome);
611+
match platform_url {
612+
Some(url) => self.browser_url = Some(url),
613+
None => return self.unavailable_discovery(),
614+
}
591615

592616
Ok(last_browser.version.to_string())
593617
}
594618
}
595619

596620
fn get_min_browser_version_for_download(&self) -> Result<i32, Error> {
597-
Ok(MIN_CHROME_VERSION_CFT)
621+
Ok(self.min_version_for_platform(MIN_CHROME_VERSION_CFT))
598622
}
599623

600624
fn get_browser_binary_path(&mut self, _browser_version: &str) -> Result<PathBuf, Error> {

rust/tests/browser_download_tests.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18+
use crate::common::is_linux_arm64;
1819
use crate::common::{assert_browser, assert_driver, get_selenium_manager};
1920

2021
use rstest::rstest;
21-
use std::env::consts::ARCH;
2222
use std::env::consts::OS;
2323

2424
mod common;
@@ -30,7 +30,7 @@ mod common;
3030
fn browser_latest_download_test(#[case] browser: String) {
3131
if browser.eq("edge") && OS.eq("windows") {
3232
return;
33-
} else if OS.eq("linux") && ARCH.eq("aarch64") && !browser.eq("firefox") {
33+
} else if is_linux_arm64() && browser.eq("edge") {
3434
return;
3535
}
3636

@@ -54,6 +54,7 @@ fn browser_latest_download_test(#[case] browser: String) {
5454
#[rstest]
5555
#[case("chrome", "131")]
5656
#[case("chrome", "131.0.6778.264")]
57+
#[case("chrome", "153")]
5758
#[case("chrome", "beta")]
5859
#[case("firefox", "121")]
5960
#[case("firefox", "121.0.1")]
@@ -66,17 +67,15 @@ fn browser_version_download_test(#[case] browser: String, #[case] browser_versio
6667
println!(
6768
"Skipping Edge download test on Windows since the installation requires admin privileges"
6869
);
69-
} else if OS.eq("linux") && ARCH.eq("aarch64") && !browser.eq("firefox") {
70+
} else if is_linux_arm64() && browser.eq("edge") {
71+
println!("Skipping Edge download test on Linux arm64 since it's not supported yet");
72+
} else if is_linux_arm64() && browser.eq("firefox") && browser_version.starts_with("121") {
7073
println!(
71-
"Skipping non-Firefox download test on Linux arm64 since no other browsers are supported yet"
74+
"Skipping Firefox 121 download test on Linux arm64 since arm64 builds are only available from version 136 onwards"
7275
);
73-
} else if OS.eq("linux")
74-
&& ARCH.eq("aarch64")
75-
&& browser.eq("firefox")
76-
&& browser_version.starts_with("121")
77-
{
76+
} else if is_linux_arm64() && browser.eq("chrome") && browser_version.starts_with("131") {
7877
println!(
79-
"Skipping Firefox 121 download test on Linux arm64 since arm64 builds are only available from version 136 onwards"
78+
"Skipping Chrome 131 download test on Linux arm64 since arm64 builds are only available from version 153 onwards"
8079
);
8180
} else {
8281
let mut cmd = get_selenium_manager();

0 commit comments

Comments
 (0)