From aa8d6cf7684ffe31c39353d64885c72e3dd90448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Boni=20Garc=C3=ADa?= Date: Wed, 7 Dec 2022 14:16:04 +0100 Subject: [PATCH] [rust] Support for beta/dev/canary browser version detection with Selenium Manager (#11239) (#11334) [rust] Support for beta/dev/canary browser version detection with Selenium Manager --- rust/README.md | 6 +-- rust/src/chrome.rs | 95 +++++++++++++++++++++++++++++++---------- rust/src/edge.rs | 95 +++++++++++++++++++++++++++++++---------- rust/src/firefox.rs | 85 +++++++++++++++++++++++++++--------- rust/src/iexplorer.rs | 9 +++- rust/src/main.rs | 12 ++---- rust/src/manager.rs | 90 ++++++++++++++++++++++++++++++++++---- rust/tests/cli_tests.rs | 3 ++ 8 files changed, 309 insertions(+), 86 deletions(-) diff --git a/rust/README.md b/rust/README.md index 608124686d2b3..caa3db8da2a72 100644 --- a/rust/README.md +++ b/rust/README.md @@ -22,13 +22,13 @@ Automated driver management for Selenium Usage: selenium-manager [OPTIONS] Options: -b, --browser - Browser name (chrome, firefox, or edge) [default: ] + Browser name (chrome, firefox, edge, or iexplorer) [default: ] -d, --driver - Driver name (chromedriver, geckodriver, or msedgedriver) [default: ] + Driver name (chromedriver, geckodriver, msedgedriver, or IEDriverServer) [default: ] -v, --driver-version Driver version (e.g., 106.0.5249.61, 0.31.0, etc.) [default: ] -B, --browser-version - Major browser version (e.g., 105, 106, etc.) [default: ] + Major browser version (e.g., 105, 106, etc. Also: beta, dev, canary -or nightly- is accepted) [default: ] -D, --debug Display DEBUG messages -T, --trace diff --git a/rust/src/chrome.rs b/rust/src/chrome.rs index f3d420e98170c..af8623b020e5a 100644 --- a/rust/src/chrome.rs +++ b/rust/src/chrome.rs @@ -15,14 +15,20 @@ // specific language governing permissions and limitations // under the License. +use std::collections::HashMap; use std::error::Error; use std::path::PathBuf; use crate::downloads::read_content_from_link; use crate::files::compose_driver_path_in_cache; +use crate::is_unstable; use crate::manager::ARCH::ARM64; -use crate::manager::OS::{MACOS, WINDOWS}; -use crate::manager::{detect_browser_version, get_major_version, BrowserManager}; +use crate::manager::OS::{LINUX, MACOS, WINDOWS}; +use crate::manager::{ + detect_browser_version, format_one_arg, format_two_args, get_major_version, BrowserManager, + BrowserPath, BETA, DASH_DASH_VERSION, DEV, ENV_LOCALAPPDATA, ENV_PROGRAM_FILES, + ENV_PROGRAM_FILES_X86, NIGHTLY, REG_QUERY, STABLE, WMIC_COMMAND, +}; use crate::metadata::{ create_driver_metadata, get_driver_version_from_metadata, get_metadata, write_metadata, }; @@ -51,28 +57,73 @@ impl BrowserManager for ChromeManager { self.browser_name } - fn get_browser_version(&self, os: &str) -> Option { - let (shell, flag, args) = if WINDOWS.is(os) { + fn get_browser_path_map(&self) -> HashMap { + HashMap::from([ ( - "cmd", - "/C", - vec![ - r#"wmic datafile where name='%PROGRAMFILES:\=\\%\\Google\\Chrome\\Application\\chrome.exe' get Version /value"#, - r#"wmic datafile where name='%PROGRAMFILES(X86):\=\\%\\Google\\Chrome\\Application\\chrome.exe' get Version /value"#, - r#"wmic datafile where name='%LOCALAPPDATA:\=\\%\\Google\\Chrome\\Application\\chrome.exe' get Version /value"#, - r#"REG QUERY HKCU\Software\Google\Chrome\BLBeacon /v version"#, - ], - ) - } else if MACOS.is(os) { + BrowserPath::new(WINDOWS, STABLE), + r#"\\Google\\Chrome\\Application\\chrome.exe"#, + ), ( - "sh", - "-c", - vec![r#"/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version"#], - ) - } else { - ("sh", "-c", vec!["google-chrome --version"]) - }; - detect_browser_version(self.browser_name, shell, flag, args) + BrowserPath::new(WINDOWS, BETA), + r#"\\Google\\Chrome Beta\\Application\\chrome.exe"#, + ), + ( + BrowserPath::new(WINDOWS, DEV), + r#"\\Google\\Chrome Dev\\Application\\chrome.exe"#, + ), + ( + BrowserPath::new(WINDOWS, NIGHTLY), + r#"\\Google\\Chrome SxS\\Application\\chrome.exe"#, + ), + ( + BrowserPath::new(MACOS, STABLE), + r#"/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome"#, + ), + ( + BrowserPath::new(MACOS, BETA), + r#"/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta"#, + ), + ( + BrowserPath::new(MACOS, DEV), + r#"/Applications/Google\ Chrome\ Dev.app/Contents/MacOS/Google\ Chrome\ Dev"#, + ), + ( + BrowserPath::new(MACOS, NIGHTLY), + r#"/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary"#, + ), + (BrowserPath::new(LINUX, STABLE), "google-chrome"), + (BrowserPath::new(LINUX, BETA), "google-chrome-beta"), + (BrowserPath::new(LINUX, DEV), "google-chrome-unstable"), + ]) + } + + fn get_browser_version(&self, os: &str, browser_version: &str) -> Option { + match self.get_browser_path(os, browser_version) { + Some(browser_path) => { + let (shell, flag, args) = if WINDOWS.is(os) { + let mut commands = vec![ + format_two_args(WMIC_COMMAND, ENV_PROGRAM_FILES, browser_path), + format_two_args(WMIC_COMMAND, ENV_PROGRAM_FILES_X86, browser_path), + format_two_args(WMIC_COMMAND, ENV_LOCALAPPDATA, browser_path), + ]; + if !is_unstable(browser_version) { + commands.push(format_one_arg( + REG_QUERY, + r#"HKCU\Software\Google\Chrome\BLBeacon"#, + )); + } + ("cmd", "/C", commands) + } else { + ( + "sh", + "-c", + vec![format_one_arg(DASH_DASH_VERSION, browser_path)], + ) + }; + detect_browser_version(self.browser_name, shell, flag, args) + } + _ => None, + } } fn get_driver_name(&self) -> &str { diff --git a/rust/src/edge.rs b/rust/src/edge.rs index 564a2932b763a..635db162431f6 100644 --- a/rust/src/edge.rs +++ b/rust/src/edge.rs @@ -15,14 +15,20 @@ // specific language governing permissions and limitations // under the License. +use std::collections::HashMap; use std::error::Error; use std::path::PathBuf; use crate::downloads::read_content_from_link; use crate::files::compose_driver_path_in_cache; +use crate::is_unstable; use crate::manager::ARCH::{ARM64, X32}; -use crate::manager::OS::{MACOS, WINDOWS}; -use crate::manager::{detect_browser_version, BrowserManager}; +use crate::manager::OS::{LINUX, MACOS, WINDOWS}; +use crate::manager::{ + detect_browser_version, format_one_arg, format_two_args, BrowserManager, BrowserPath, BETA, + DASH_DASH_VERSION, DEV, ENV_PROGRAM_FILES, ENV_PROGRAM_FILES_X86, NIGHTLY, REG_QUERY, STABLE, + WMIC_COMMAND, +}; use crate::metadata::{ create_driver_metadata, get_driver_version_from_metadata, get_metadata, write_metadata, }; @@ -52,29 +58,72 @@ impl BrowserManager for EdgeManager { self.browser_name } - fn get_browser_version(&self, os: &str) -> Option { - let (shell, flag, args) = if WINDOWS.is(os) { + fn get_browser_path_map(&self) -> HashMap { + HashMap::from([ ( - "cmd", - "/C", - vec![ - r#"wmic datafile where name='%PROGRAMFILES(X86):\=\\%\\Microsoft\\Edge\\Application\\msedge.exe' get Version /value"#, - r#"wmic datafile where name='%PROGRAMFILES:\=\\%\\Microsoft\\Edge\\Application\\msedge.exe' get Version /value"#, - r#"REG QUERY HKCU\Software\Microsoft\Edge\BLBeacon /v version"#, - ], - ) - } else if MACOS.is(os) { + BrowserPath::new(WINDOWS, STABLE), + r#"\\Microsoft\\Edge\\Application\\msedge.exe"#, + ), ( - "sh", - "-c", - vec![ - r#"/Applications/Microsoft\ Edge.app/Contents/MacOS/Microsoft\ Edge -version"#, - ], - ) - } else { - ("sh", "-c", vec!["microsoft-edge --version"]) - }; - detect_browser_version(self.browser_name, shell, flag, args) + BrowserPath::new(WINDOWS, BETA), + r#"\\Microsoft\\Edge Beta\\Application\\msedge.exe"#, + ), + ( + BrowserPath::new(WINDOWS, DEV), + r#"\\Microsoft\\Edge Dev\\Application\\msedge.exe"#, + ), + ( + BrowserPath::new(WINDOWS, NIGHTLY), + r#"\\Microsoft\\Edge SxS\\Application\\msedge.exe"#, + ), + ( + BrowserPath::new(MACOS, STABLE), + r#"/Applications/Microsoft\ Edge.app/Contents/MacOS/Microsoft\ Edge"#, + ), + ( + BrowserPath::new(MACOS, BETA), + r#"/Applications/Microsoft\ Edge\ Beta.app/Contents/MacOS/Microsoft\ Edge\ Beta"#, + ), + ( + BrowserPath::new(MACOS, DEV), + r#"/Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev"#, + ), + ( + BrowserPath::new(MACOS, NIGHTLY), + r#"/Applications/Microsoft\ Edge\ Canary.app/Contents/MacOS/Microsoft\ Edge\ Canary"#, + ), + (BrowserPath::new(LINUX, STABLE), "microsoft-edge"), + (BrowserPath::new(LINUX, BETA), "microsoft-edge-beta"), + (BrowserPath::new(LINUX, DEV), "microsoft-edge-dev"), + ]) + } + + fn get_browser_version(&self, os: &str, browser_version: &str) -> Option { + match self.get_browser_path(os, browser_version) { + Some(browser_path) => { + let (shell, flag, args) = if WINDOWS.is(os) { + let mut commands = vec![ + format_two_args(WMIC_COMMAND, ENV_PROGRAM_FILES_X86, browser_path), + format_two_args(WMIC_COMMAND, ENV_PROGRAM_FILES, browser_path), + ]; + if !is_unstable(browser_version) { + commands.push(format_one_arg( + REG_QUERY, + r#"REG QUERY HKCU\Software\Microsoft\Edge\BLBeacon"#, + )); + } + ("cmd", "/C", commands) + } else { + ( + "sh", + "-c", + vec![format_one_arg(DASH_DASH_VERSION, browser_path)], + ) + }; + detect_browser_version(self.browser_name, shell, flag, args) + } + _ => None, + } } fn get_driver_name(&self) -> &str { diff --git a/rust/src/firefox.rs b/rust/src/firefox.rs index 7107385ba62b6..0293cc1e654d5 100644 --- a/rust/src/firefox.rs +++ b/rust/src/firefox.rs @@ -15,14 +15,19 @@ // specific language governing permissions and limitations // under the License. +use std::collections::HashMap; use std::error::Error; use std::path::PathBuf; use crate::downloads::read_redirect_from_link; use crate::files::compose_driver_path_in_cache; use crate::manager::ARCH::{ARM64, X32}; -use crate::manager::OS::{MACOS, WINDOWS}; -use crate::manager::{detect_browser_version, get_minor_version, BrowserManager}; +use crate::manager::OS::{LINUX, MACOS, WINDOWS}; +use crate::manager::{ + detect_browser_version, format_one_arg, format_two_args, get_minor_version, BrowserManager, + BrowserPath, BETA, DASH_VERSION, DEV, ENV_PROGRAM_FILES, ENV_PROGRAM_FILES_X86, NIGHTLY, + STABLE, WMIC_COMMAND, +}; use crate::metadata::{ create_driver_metadata, get_driver_version_from_metadata, get_metadata, write_metadata, }; @@ -51,26 +56,66 @@ impl BrowserManager for FirefoxManager { self.browser_name } - fn get_browser_version(&self, os: &str) -> Option { - let (shell, flag, args) = if WINDOWS.is(os) { + fn get_browser_path_map(&self) -> HashMap { + HashMap::from([ ( - "cmd", - "/C", - vec![ - r#"cmd.exe /C wmic datafile where name='%PROGRAMFILES:\=\\%\\Mozilla Firefox\\firefox.exe' get Version /value"#, - r#"cmd.exe /C wmic datafile where name='%PROGRAMFILES(X86):\=\\%\\Mozilla Firefox\\firefox.exe' get Version /value' get Version /value"#, - ], - ) - } else if MACOS.is(os) { + BrowserPath::new(WINDOWS, STABLE), + r#"\\Mozilla Firefox\\firefox.exe"#, + ), ( - "sh", - "-c", - vec![r#"/Applications/Firefox.app/Contents/MacOS/firefox -v"#], - ) - } else { - ("sh", "-c", vec!["firefox -v"]) - }; - detect_browser_version(self.browser_name, shell, flag, args) + BrowserPath::new(WINDOWS, BETA), + r#"\\Mozilla Firefox\\firefox.exe"#, + ), + ( + BrowserPath::new(WINDOWS, DEV), + r#"\\Firefox Developer Edition\\firefox.exe"#, + ), + ( + BrowserPath::new(WINDOWS, NIGHTLY), + r#"\\Firefox Nightly\\firefox.exe"#, + ), + ( + BrowserPath::new(MACOS, STABLE), + r#"/Applications/Firefox.app/Contents/MacOS/firefox"#, + ), + ( + BrowserPath::new(MACOS, BETA), + r#"/Applications/Firefox.app/Contents/MacOS/firefox"#, + ), + ( + BrowserPath::new(MACOS, DEV), + r#"/Applications/Firefox\ Developer\ Edition.app/Contents/MacOS/firefox"#, + ), + ( + BrowserPath::new(MACOS, NIGHTLY), + r#"/Applications/Firefox\ Nightly.app/Contents/MacOS/firefox"#, + ), + (BrowserPath::new(LINUX, STABLE), "firefox"), + (BrowserPath::new(LINUX, BETA), "firefox"), + (BrowserPath::new(LINUX, DEV), "firefox"), + (BrowserPath::new(LINUX, NIGHTLY), "firefox-trunk"), + ]) + } + + fn get_browser_version(&self, os: &str, browser_version: &str) -> Option { + match self.get_browser_path(os, browser_version) { + Some(browser_path) => { + let (shell, flag, args) = if WINDOWS.is(os) { + ( + "cmd", + "/C", + vec![ + format_two_args(WMIC_COMMAND, ENV_PROGRAM_FILES, browser_path), + format_two_args(WMIC_COMMAND, ENV_PROGRAM_FILES_X86, browser_path), + ], + ) + } else { + ("sh", "-c", vec![format_one_arg(DASH_VERSION, browser_path)]) + }; + detect_browser_version(self.browser_name, shell, flag, args) + } + _ => None, + } } fn get_driver_name(&self) -> &str { diff --git a/rust/src/iexplorer.rs b/rust/src/iexplorer.rs index c4ba266a98f71..c1952e8ec5b0e 100644 --- a/rust/src/iexplorer.rs +++ b/rust/src/iexplorer.rs @@ -15,13 +15,14 @@ // specific language governing permissions and limitations // under the License. +use std::collections::HashMap; use std::error::Error; use std::path::PathBuf; use crate::downloads::read_redirect_from_link; use crate::files::compose_driver_path_in_cache; -use crate::manager::{get_minor_version, BrowserManager}; +use crate::manager::{get_minor_version, BrowserManager, BrowserPath}; use crate::metadata::{ create_driver_metadata, get_driver_version_from_metadata, get_metadata, write_metadata, @@ -51,7 +52,11 @@ impl BrowserManager for IExplorerManager { self.browser_name } - fn get_browser_version(&self, _os: &str) -> Option { + fn get_browser_path_map(&self) -> HashMap { + HashMap::new() + } + + fn get_browser_version(&self, _os: &str, _browser_version: &str) -> Option { None } diff --git a/rust/src/main.rs b/rust/src/main.rs index bf081ad3934c2..ed3c5b07ba77f 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -32,7 +32,7 @@ use crate::edge::EdgeManager; use crate::files::clear_cache; use crate::firefox::FirefoxManager; use crate::iexplorer::IExplorerManager; -use crate::manager::BrowserManager; +use crate::manager::{is_unstable, BrowserManager}; mod chrome; mod downloads; @@ -63,7 +63,7 @@ struct Cli { #[clap(short = 'v', long, value_parser, default_value = "")] driver_version: String, - /// Major browser version (e.g., 105, 106, etc.) + /// Major browser version (e.g., 105, 106, etc. Also: beta, dev, canary -or nightly- is accepted) #[clap(short = 'B', long, value_parser, default_value = "")] browser_version: String, @@ -97,10 +97,6 @@ fn main() -> Result<(), Box> { } else if browser_name.eq_ignore_ascii_case("firefox") || driver_name.eq_ignore_ascii_case("geckodriver") { - if !browser_version.is_empty() { - log::warn!("Currently it is not possible to force a given Firefox version"); - browser_version = "".to_string(); - } FirefoxManager::new() } else if browser_name.eq_ignore_ascii_case("edge") || driver_name.eq_ignore_ascii_case("msedgedriver") @@ -124,8 +120,8 @@ fn main() -> Result<(), Box> { } if driver_version.is_empty() { - if browser_version.is_empty() { - match browser_manager.get_browser_version(os) { + if browser_version.is_empty() || is_unstable(&browser_version) { + match browser_manager.get_browser_version(os, &browser_version) { Some(version) => { browser_version = version; log::debug!("Detected browser: {} {}", browser_name, browser_version); diff --git a/rust/src/manager.rs b/rust/src/manager.rs index 4d939937c6f13..5f2a366891620 100644 --- a/rust/src/manager.rs +++ b/rust/src/manager.rs @@ -15,20 +15,38 @@ // specific language governing permissions and limitations // under the License. +use std::collections::HashMap; use std::error::Error; use std::path::PathBuf; use std::process::Command; use crate::downloads::download_driver_to_tmp_folder; use crate::files::{parse_version, uncompress}; +use crate::manager::OS::{LINUX, MACOS, WINDOWS}; + use crate::metadata::{ create_browser_metadata, get_browser_version_from_metadata, get_metadata, write_metadata, }; +pub const STABLE: &str = "stable"; +pub const BETA: &str = "beta"; +pub const DEV: &str = "dev"; +pub const CANARY: &str = "canary"; +pub const NIGHTLY: &str = "nightly"; +pub const WMIC_COMMAND: &str = r#"wmic datafile where name='%{}:\=\\%{}' get Version /value"#; +pub const REG_QUERY: &str = r#"REG QUERY {} /v version"#; +pub const DASH_VERSION: &str = "{} -v"; +pub const DASH_DASH_VERSION: &str = "{} --version"; +pub const ENV_PROGRAM_FILES: &str = "PROGRAMFILES"; +pub const ENV_PROGRAM_FILES_X86: &str = "PROGRAMFILES(X86)"; +pub const ENV_LOCALAPPDATA: &str = "LOCALAPPDATA"; + pub trait BrowserManager { fn get_browser_name(&self) -> &str; - fn get_browser_version(&self, os: &str) -> Option; + fn get_browser_path_map(&self) -> HashMap; + + fn get_browser_version(&self, os: &str, browser_version: &str) -> Option; fn get_driver_name(&self) -> &str; @@ -55,10 +73,22 @@ pub trait BrowserManager { let driver_path_in_cache = Self::get_driver_path_in_cache(self, driver_version, os, arch); uncompress(&driver_zip_file, driver_path_in_cache) } + + fn get_browser_path(&self, os: &str, mut browser_version: &str) -> Option<&str> { + if browser_version.eq_ignore_ascii_case(CANARY) { + browser_version = NIGHTLY; + } else if browser_version.is_empty() { + browser_version = STABLE; + } + self.get_browser_path_map() + .get(&BrowserPath::new(str_to_os(os), browser_version)) + .cloned() + } } #[allow(dead_code)] #[allow(clippy::upper_case_acronyms)] +#[derive(Hash, Eq, PartialEq, Debug)] pub enum OS { WINDOWS, MACOS, @@ -68,9 +98,9 @@ pub enum OS { impl OS { pub fn to_str(&self) -> &str { match self { - OS::WINDOWS => "windows", - OS::MACOS => "macos", - OS::LINUX => "linux", + WINDOWS => "windows", + MACOS => "macos", + LINUX => "linux", } } @@ -79,6 +109,16 @@ impl OS { } } +fn str_to_os(os: &str) -> OS { + if WINDOWS.is(os) { + WINDOWS + } else if MACOS.is(os) { + MACOS + } else { + LINUX + } +} + #[allow(dead_code)] #[allow(clippy::upper_case_acronyms)] pub enum ARCH { @@ -101,9 +141,28 @@ impl ARCH { } } -pub fn run_shell_command(command: &str, flag: &str, args: &str) -> Result> { +#[derive(Hash, Eq, PartialEq, Debug)] +pub struct BrowserPath { + os: OS, + channel: String, +} + +impl BrowserPath { + pub fn new(os: OS, channel: &str) -> BrowserPath { + BrowserPath { + os, + channel: channel.to_string(), + } + } +} + +pub fn run_shell_command( + command: &str, + flag: &str, + args: String, +) -> Result> { log::debug!("Running {} command: {:?}", command, args); - let output = Command::new(command).args([flag, args]).output()?; + let output = Command::new(command).args([flag, args.as_str()]).output()?; log::debug!("{:?}", output); Ok(String::from_utf8_lossy(&output.stdout).to_string()) @@ -113,7 +172,7 @@ pub fn detect_browser_version( browser_name: &str, shell: &str, flag: &str, - args: Vec<&str>, + args: Vec, ) -> Option { let mut metadata = get_metadata(); @@ -129,7 +188,7 @@ pub fn detect_browser_version( log::debug!("Using shell command to find out {} version", browser_name); let mut browser_version = "".to_string(); for arg in args.iter() { - let output = match run_shell_command(shell, flag, *arg) { + let output = match run_shell_command(shell, flag, arg.to_string()) { Ok(out) => out, Err(_e) => continue, }; @@ -173,3 +232,18 @@ fn get_index_version(full_version: &str, index: usize) -> Result String { + string.replacen("{}", arg1, 1) +} + +pub fn format_two_args(string: &str, arg1: &str, arg2: &str) -> String { + string.replacen("{}", arg1, 1).replacen("{}", arg2, 2) +} + +pub fn is_unstable(browser_version: &str) -> bool { + browser_version.eq_ignore_ascii_case(BETA) + || browser_version.eq_ignore_ascii_case(DEV) + || browser_version.eq_ignore_ascii_case(NIGHTLY) + || browser_version.eq_ignore_ascii_case(CANARY) +} diff --git a/rust/tests/cli_tests.rs b/rust/tests/cli_tests.rs index 204f55ebd7501..288bb291a8161 100644 --- a/rust/tests/cli_tests.rs +++ b/rust/tests/cli_tests.rs @@ -24,11 +24,14 @@ use std::str; #[case("chrome", "chromedriver", "", "")] #[case("chrome", "chromedriver", "105", "105.0.5195.52")] #[case("chrome", "chromedriver", "106", "106.0.5249.61")] +#[case("chrome", "chromedriver", "beta", "")] #[case("edge", "msedgedriver", "", "")] #[case("edge", "msedgedriver", "105", "105.0")] #[case("edge", "msedgedriver", "106", "106.0")] +#[case("edge", "msedgedriver", "beta", "")] #[case("firefox", "geckodriver", "", "")] #[case("firefox", "geckodriver", "105", "0.32.0")] +#[case("firefox", "geckodriver", "beta", "")] #[case("iexplorer", "IEDriverServer", "", "")] fn ok_test( #[case] browser: String,