From 8daa0d16539539a48703ad2be7df2d4c2449ede1 Mon Sep 17 00:00:00 2001 From: Titus Fortner Date: Thu, 30 Jul 2026 07:47:39 -0500 Subject: [PATCH 1/2] [java][js][rb] remove deprecated FTP proxy support (#17846) --- java/src/org/openqa/selenium/Proxy.java | 35 ------------------- java/test/org/openqa/selenium/ProxyTest.java | 20 +---------- javascript/selenium-webdriver/lib/proxy.js | 19 ++-------- .../selenium-webdriver/test/proxy_test.js | 2 +- rb/lib/selenium/webdriver/common/proxy.rb | 8 ----- rb/lib/selenium/webdriver/firefox/profile.rb | 1 - rb/sig/interfaces/proxy.rbs | 2 -- .../lib/selenium/webdriver/common/proxy.rbs | 4 --- .../webdriver/firefox/profile_spec.rb | 3 -- rb/spec/unit/selenium/webdriver/proxy_spec.rb | 3 -- .../webdriver/remote/http/default_spec.rb | 2 +- 11 files changed, 6 insertions(+), 93 deletions(-) diff --git a/java/src/org/openqa/selenium/Proxy.java b/java/src/org/openqa/selenium/Proxy.java index 3b4383a0e40e8..51f661a9e3555 100644 --- a/java/src/org/openqa/selenium/Proxy.java +++ b/java/src/org/openqa/selenium/Proxy.java @@ -64,7 +64,6 @@ public String toString() { } private static final String PROXY_TYPE = "proxyType"; - @Deprecated private static final String FTP_PROXY = "ftpProxy"; private static final String HTTP_PROXY = "httpProxy"; private static final String NO_PROXY = "noProxy"; private static final String SSL_PROXY = "sslProxy"; @@ -77,7 +76,6 @@ public String toString() { private ProxyType proxyType = ProxyType.UNSPECIFIED; private boolean autodetect = false; - @Deprecated private @Nullable String ftpProxy; private @Nullable String httpProxy; private @Nullable String noProxy; private @Nullable String sslProxy; @@ -96,7 +94,6 @@ public Proxy(Map raw) { setters.put( PROXY_TYPE, value -> setProxyType(ProxyType.valueOf(((String) value).toUpperCase(Locale.ENGLISH)))); - setters.put(FTP_PROXY, value -> setFtpProxy((String) value)); setters.put(HTTP_PROXY, value -> setHttpProxy((String) value)); setters.put( NO_PROXY, @@ -132,9 +129,6 @@ public Map toJson() { if (proxyType != ProxyType.UNSPECIFIED) { m.put(PROXY_TYPE, proxyType.toString()); } - if (ftpProxy != null) { - m.put(FTP_PROXY, ftpProxy); - } if (httpProxy != null) { m.put(HTTP_PROXY, httpProxy); } @@ -218,32 +212,6 @@ public Proxy setAutodetect(boolean autodetect) { return this; } - /** - * Gets the FTP proxy. - * - * @return the FTP proxy hostname if present, or null if not set - * @deprecated getFtpProxy is deprecated and will be removed in a future release. - */ - @Deprecated - public @Nullable String getFtpProxy() { - return ftpProxy; - } - - /** - * Specify which proxy to use for FTP connections. - * - * @param ftpProxy the proxy host, expected format is hostname.com:1234 - * @return reference to self - * @deprecated setFtpProxy is deprecated and will be removed in a future release. - */ - @Deprecated - public Proxy setFtpProxy(String ftpProxy) { - verifyProxyTypeCompatibility(ProxyType.MANUAL); - this.proxyType = ProxyType.MANUAL; - this.ftpProxy = ftpProxy; - return this; - } - /** * Gets the HTTP proxy. * @@ -466,7 +434,6 @@ public String toString() { break; } - Optional.ofNullable(getFtpProxy()).ifPresent(p -> builder.append(", ftp=").append(p)); Optional.ofNullable(getHttpProxy()).ifPresent(p -> builder.append(", http=").append(p)); Optional.ofNullable(getSocksProxy()).ifPresent(p -> builder.append(", socks=").append(p)); Optional.ofNullable(getSslProxy()).ifPresent(p -> builder.append(", ssl=").append(p)); @@ -486,7 +453,6 @@ public boolean equals(@Nullable Object o) { Proxy proxy = (Proxy) o; return isAutodetect() == proxy.isAutodetect() && getProxyType() == proxy.getProxyType() - && Objects.equals(getFtpProxy(), proxy.getFtpProxy()) && Objects.equals(getHttpProxy(), proxy.getHttpProxy()) && Objects.equals(getNoProxy(), proxy.getNoProxy()) && Objects.equals(getSslProxy(), proxy.getSslProxy()) @@ -502,7 +468,6 @@ public int hashCode() { return Objects.hash( getProxyType(), isAutodetect(), - getFtpProxy(), getHttpProxy(), getNoProxy(), getSslProxy(), diff --git a/java/test/org/openqa/selenium/ProxyTest.java b/java/test/org/openqa/selenium/ProxyTest.java index 976008061ee29..e12a38c16062c 100644 --- a/java/test/org/openqa/selenium/ProxyTest.java +++ b/java/test/org/openqa/selenium/ProxyTest.java @@ -45,7 +45,6 @@ void testNotInitializedProxy() { assertThat(proxy.getProxyType()).isEqualTo(UNSPECIFIED); - assertThat(proxy.getFtpProxy()).isNull(); assertThat(proxy.getHttpProxy()).isNull(); assertThat(proxy.getSslProxy()).isNull(); assertThat(proxy.getSocksProxy()).isNull(); @@ -74,8 +73,6 @@ void testCanNotChangeAlreadyInitializedProxyType() { assertThatExceptionOfType(IllegalStateException.class) .isThrownBy(() -> proxy.setSocksProxy("")); - assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> proxy.setFtpProxy("")); - assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> proxy.setHttpProxy("")); assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> proxy.setNoProxy("")); @@ -104,7 +101,6 @@ void testManualProxy() { proxy .setHttpProxy("http.proxy:1234") - .setFtpProxy("ftp.proxy") .setSslProxy("ssl.proxy") .setNoProxy("localhost,127.0.0.*") .setSocksProxy("socks.proxy:65555") @@ -113,7 +109,6 @@ void testManualProxy() { .setSocksPassword("test2"); assertThat(proxy.getProxyType()).isEqualTo(MANUAL); - assertThat(proxy.getFtpProxy()).isEqualTo("ftp.proxy"); assertThat(proxy.getHttpProxy()).isEqualTo("http.proxy:1234"); assertThat(proxy.getSslProxy()).isEqualTo("ssl.proxy"); assertThat(proxy.getSocksProxy()).isEqualTo("socks.proxy:65555"); @@ -134,7 +129,6 @@ void testPACProxy() { assertThat(proxy.getProxyType()).isEqualTo(PAC); assertThat(proxy.getProxyAutoconfigUrl()).isEqualTo("http://aaa/bbb.pac"); - assertThat(proxy.getFtpProxy()).isNull(); assertThat(proxy.getHttpProxy()).isNull(); assertThat(proxy.getSslProxy()).isNull(); assertThat(proxy.getSocksProxy()).isNull(); @@ -153,7 +147,6 @@ void testAutodetectProxy() { assertThat(proxy.getProxyType().name()).isEqualTo(AUTODETECT.name()); assertThat(proxy.isAutodetect()).isTrue(); - assertThat(proxy.getFtpProxy()).isNull(); assertThat(proxy.getHttpProxy()).isNull(); assertThat(proxy.getSslProxy()).isNull(); assertThat(proxy.getSocksProxy()).isNull(); @@ -169,7 +162,6 @@ void manualProxyFromMap() { Map proxyData = new HashMap<>(); proxyData.put("proxyType", "manual"); proxyData.put("httpProxy", "http.proxy:1234"); - proxyData.put("ftpProxy", "ftp.proxy"); proxyData.put("sslProxy", "ssl.proxy"); proxyData.put("noProxy", "localhost,127.0.0.*"); proxyData.put("socksProxy", "socks.proxy:65555"); @@ -180,7 +172,6 @@ void manualProxyFromMap() { Proxy proxy = new Proxy(proxyData); assertThat(proxy.getProxyType()).isEqualTo(MANUAL); - assertThat(proxy.getFtpProxy()).isEqualTo("ftp.proxy"); assertThat(proxy.getHttpProxy()).isEqualTo("http.proxy:1234"); assertThat(proxy.getSslProxy()).isEqualTo("ssl.proxy"); assertThat(proxy.getSocksProxy()).isEqualTo("socks.proxy:65555"); @@ -199,7 +190,6 @@ void longSocksVersionFromMap() { long l = 5; proxyData.put("proxyType", "manual"); proxyData.put("httpProxy", "http.proxy:1234"); - proxyData.put("ftpProxy", "ftp.proxy"); proxyData.put("sslProxy", "ssl.proxy"); proxyData.put("noProxy", "localhost,127.0.0.*"); proxyData.put("socksProxy", "socks.proxy:65555"); @@ -217,7 +207,6 @@ void manualProxyToJson() { Proxy proxy = new Proxy(); proxy.setProxyType(ProxyType.MANUAL); proxy.setHttpProxy("http.proxy:1234"); - proxy.setFtpProxy("ftp.proxy"); proxy.setSslProxy("ssl.proxy"); proxy.setNoProxy("localhost,127.0.0.*"); proxy.setSocksProxy("socks.proxy:65555"); @@ -228,7 +217,6 @@ void manualProxyToJson() { Map json = proxy.toJson(); assertThat(json.get("proxyType")).isEqualTo("manual"); - assertThat(json.get("ftpProxy")).isEqualTo("ftp.proxy"); assertThat(json.get("httpProxy")).isEqualTo("http.proxy:1234"); assertThat(json.get("sslProxy")).isEqualTo("ssl.proxy"); assertThat(json.get("socksProxy")).isEqualTo("socks.proxy:65555"); @@ -236,7 +224,7 @@ void manualProxyToJson() { assertThat(json.get("socksUsername")).isEqualTo("test1"); assertThat(json.get("socksPassword")).isEqualTo("test2"); assertThat(json.get("noProxy")).asInstanceOf(LIST).containsExactly("localhost", "127.0.0.*"); - assertThat(json.entrySet()).hasSize(9); + assertThat(json.entrySet()).hasSize(8); } @Test @@ -250,7 +238,6 @@ void pacProxyFromMap() { assertThat(proxy.getProxyType()).isEqualTo(PAC); assertThat(proxy.getProxyAutoconfigUrl()).isEqualTo("http://aaa/bbb.pac"); - assertThat(proxy.getFtpProxy()).isNull(); assertThat(proxy.getHttpProxy()).isNull(); assertThat(proxy.getSslProxy()).isNull(); assertThat(proxy.getSocksProxy()).isNull(); @@ -285,7 +272,6 @@ void autodetectProxyFromMap() { assertThat(proxy.getProxyType()).isEqualTo(AUTODETECT); assertThat(proxy.isAutodetect()).isTrue(); - assertThat(proxy.getFtpProxy()).isNull(); assertThat(proxy.getHttpProxy()).isNull(); assertThat(proxy.getSslProxy()).isNull(); assertThat(proxy.getSocksProxy()).isNull(); @@ -318,7 +304,6 @@ void systemProxyFromMap() { assertThat(proxy.getProxyType()).isEqualTo(SYSTEM); - assertThat(proxy.getFtpProxy()).isNull(); assertThat(proxy.getHttpProxy()).isNull(); assertThat(proxy.getSslProxy()).isNull(); assertThat(proxy.getSocksProxy()).isNull(); @@ -350,7 +335,6 @@ void directProxyFromMap() { assertThat(proxy.getProxyType()).isEqualTo(DIRECT); - assertThat(proxy.getFtpProxy()).isNull(); assertThat(proxy.getHttpProxy()).isNull(); assertThat(proxy.getSslProxy()).isNull(); assertThat(proxy.getSocksProxy()).isNull(); @@ -376,14 +360,12 @@ void directProxyToJson() { @Test void constructingWithNullKeysWorksAsExpected() { Map rawProxy = new HashMap<>(); - rawProxy.put("ftpProxy", null); rawProxy.put("httpProxy", "http://www.example.com"); rawProxy.put("autodetect", null); Capabilities caps = new ImmutableCapabilities(PROXY, rawProxy); Proxy proxy = Proxy.extractFrom(caps); - assertThat(proxy.getFtpProxy()).isNull(); assertThat(proxy.isAutodetect()).isFalse(); assertThat(proxy.getHttpProxy()).isEqualTo("http://www.example.com"); } diff --git a/javascript/selenium-webdriver/lib/proxy.js b/javascript/selenium-webdriver/lib/proxy.js index ac191c1173001..35b3f38fd3bff 100644 --- a/javascript/selenium-webdriver/lib/proxy.js +++ b/javascript/selenium-webdriver/lib/proxy.js @@ -77,13 +77,6 @@ PacConfig.prototype.proxyAutoconfigUrl */ function ManualConfig() {} -/** - * The proxy host for FTP requests. - * - * @type {(string|undefined)} - */ -ManualConfig.prototype.ftpProxy - /** * The proxy host for HTTP requests. * @@ -138,30 +131,24 @@ function direct() { * Manually configures the browser proxy. The following options are * supported: * - * - `ftp`: Proxy host to use for FTP requests * - `http`: Proxy host to use for HTTP requests * - `https`: Proxy host to use for HTTPS requests * - `bypass`: A list of hosts requests should directly connect to, * bypassing any other proxies for that request. May be specified as a * comma separated string, or a list of strings. * - * Behavior is undefined for FTP, HTTP, and HTTPS requests if the + * Behavior is undefined for HTTP and HTTPS requests if the * corresponding key is omitted from the configuration options. * - * @param {{ftp: (string|undefined), - * http: (string|undefined), + * @param {{http: (string|undefined), * https: (string|undefined), * bypass: (Array|undefined)}} options Proxy * configuration options. * @return {!ManualConfig} A new proxy configuration object. */ -function manual({ ftp, http, https, bypass }) { - if (ftp !== undefined) { - console.warn('ftpProxy is deprecated and will be removed in the future') - } +function manual({ http, https, bypass }) { return { proxyType: Type.MANUAL, - ftpProxy: ftp, httpProxy: http, sslProxy: https, noProxy: bypass, diff --git a/javascript/selenium-webdriver/test/proxy_test.js b/javascript/selenium-webdriver/test/proxy_test.js index 06816194e46f9..b4e92bb060eb2 100644 --- a/javascript/selenium-webdriver/test/proxy_test.js +++ b/javascript/selenium-webdriver/test/proxy_test.js @@ -145,7 +145,7 @@ test.suite(function (env) { assert.strictEqual(await driver.findElement({ tagName: 'h3' }).getText(), 'This is the proxy landing page') }) - // TODO: test ftp and https proxies. + // TODO: test https proxies. }) // PhantomJS does not support PAC file proxy configuration. diff --git a/rb/lib/selenium/webdriver/common/proxy.rb b/rb/lib/selenium/webdriver/common/proxy.rb index 4c804338f15e4..c79b269faebdb 100644 --- a/rb/lib/selenium/webdriver/common/proxy.rb +++ b/rb/lib/selenium/webdriver/common/proxy.rb @@ -29,7 +29,6 @@ class Proxy }.freeze ALLOWED = {type: 'proxyType', - ftp: 'ftpProxy', http: 'httpProxy', no_proxy: 'noProxy', pac: 'proxyAutoconfigUrl', @@ -76,12 +75,6 @@ def ==(other) end alias eql? == - def ftp=(value) - WebDriver.logger.deprecate('FTP proxy support', nil, id: :ftp_proxy) - self.type = :manual - @ftp = value - end - def http=(value) self.type = :manual @http = value @@ -143,7 +136,6 @@ def type=(type) def as_json(*) json_result = { 'proxyType' => TYPES[type].downcase, - 'ftpProxy' => ftp, 'httpProxy' => http, 'noProxy' => no_proxy.is_a?(String) ? no_proxy.split(',').map(&:strip).reject(&:empty?) : no_proxy, 'proxyAutoconfigUrl' => pac, diff --git a/rb/lib/selenium/webdriver/firefox/profile.rb b/rb/lib/selenium/webdriver/firefox/profile.rb index f018c5e2c7541..55d7d7bc1afb6 100644 --- a/rb/lib/selenium/webdriver/firefox/profile.rb +++ b/rb/lib/selenium/webdriver/firefox/profile.rb @@ -133,7 +133,6 @@ def proxy=(proxy) when :manual self['network.proxy.type'] = 1 - set_manual_proxy_preference 'ftp', proxy.ftp set_manual_proxy_preference 'http', proxy.http set_manual_proxy_preference 'ssl', proxy.ssl set_manual_proxy_preference 'socks', proxy.socks diff --git a/rb/sig/interfaces/proxy.rbs b/rb/sig/interfaces/proxy.rbs index c529d88dffde2..4ead81f1c04c7 100644 --- a/rb/sig/interfaces/proxy.rbs +++ b/rb/sig/interfaces/proxy.rbs @@ -21,8 +21,6 @@ interface _Proxy def type: -> untyped - def ftp: -> untyped - def pac: -> untyped def http: -> untyped diff --git a/rb/sig/lib/selenium/webdriver/common/proxy.rbs b/rb/sig/lib/selenium/webdriver/common/proxy.rbs index 86538d01c2b19..319caf514b7bb 100644 --- a/rb/sig/lib/selenium/webdriver/common/proxy.rbs +++ b/rb/sig/lib/selenium/webdriver/common/proxy.rbs @@ -21,8 +21,6 @@ module Selenium class Proxy include _Proxy - @ftp: untyped - @http: untyped @no_proxy: untyped @@ -55,8 +53,6 @@ module Selenium alias eql? == - def ftp=: (untyped value) -> untyped - def http=: (untyped value) -> untyped def no_proxy=: (untyped value) -> untyped diff --git a/rb/spec/unit/selenium/webdriver/firefox/profile_spec.rb b/rb/spec/unit/selenium/webdriver/firefox/profile_spec.rb index 1d811a3987613..7a460764f227e 100644 --- a/rb/spec/unit/selenium/webdriver/firefox/profile_spec.rb +++ b/rb/spec/unit/selenium/webdriver/firefox/profile_spec.rb @@ -97,7 +97,6 @@ def read_generated_prefs(from = nil) it 'can configure a manual proxy' do proxy = Proxy.new( http: 'foo:123', - ftp: 'bar:234', ssl: 'baz:345', no_proxy: 'localhost' ) @@ -105,8 +104,6 @@ def read_generated_prefs(from = nil) profile.proxy = proxy expect(read_generated_prefs).to include('user_pref("network.proxy.http", "foo")', 'user_pref("network.proxy.http_port", 123)', - 'user_pref("network.proxy.ftp", "bar")', - 'user_pref("network.proxy.ftp_port", 234)', 'user_pref("network.proxy.ssl", "baz")', 'user_pref("network.proxy.ssl_port", 345)', 'user_pref("network.proxy.no_proxies_on", "localhost")', diff --git a/rb/spec/unit/selenium/webdriver/proxy_spec.rb b/rb/spec/unit/selenium/webdriver/proxy_spec.rb index 4dd76c9e2325b..661ec7fba35ae 100644 --- a/rb/spec/unit/selenium/webdriver/proxy_spec.rb +++ b/rb/spec/unit/selenium/webdriver/proxy_spec.rb @@ -24,7 +24,6 @@ module WebDriver describe Proxy do let :proxy_settings do # manual proxy settings { - ftp: 'mythicalftpproxy:21', http: 'mythicalproxy:80', no_proxy: 'noproxy', ssl: 'mythicalsslproxy', @@ -57,7 +56,6 @@ module WebDriver it 'allows valid options for a manual proxy', :aggregate_failures do proxy = described_class.new(proxy_settings) - expect(proxy.ftp).to eq(proxy_settings[:ftp]) expect(proxy.http).to eq(proxy_settings[:http]) expect(proxy.no_proxy).to eq(proxy_settings[:no_proxy]) expect(proxy.ssl).to eq(proxy_settings[:ssl]) @@ -71,7 +69,6 @@ module WebDriver proxy_json = described_class.new(proxy_settings).as_json expect(proxy_json['proxyType']).to eq('manual') - expect(proxy_json['ftpProxy']).to eq(proxy_settings[:ftp]) expect(proxy_json['httpProxy']).to eq(proxy_settings[:http]) expect(proxy_json['noProxy']).to eq([proxy_settings[:no_proxy]]) expect(proxy_json['sslProxy']).to eq(proxy_settings[:ssl]) diff --git a/rb/spec/unit/selenium/webdriver/remote/http/default_spec.rb b/rb/spec/unit/selenium/webdriver/remote/http/default_spec.rb index d738ce5553f34..35e1e67cbd6c3 100644 --- a/rb/spec/unit/selenium/webdriver/remote/http/default_spec.rb +++ b/rb/spec/unit/selenium/webdriver/remote/http/default_spec.rb @@ -111,7 +111,7 @@ module Http end it 'raises an error if the proxy is not an HTTP proxy' do - client.proxy = Proxy.new(ftp: 'ftp://example.com') + client.proxy = Proxy.new(ssl: 'ssl://example.com') expect { client.send :http }.to raise_error(Error::WebDriverError) end From bf18c110ff2daac46888f12b43b00948dd917fc8 Mon Sep 17 00:00:00 2001 From: Titus Fortner Date: Thu, 30 Jul 2026 07:59:05 -0500 Subject: [PATCH 2/2] [rust] locate Chrome and Edge in known install directories (#17838) * [rust] locate Chrome and Edge in known install directories * [rust] honor --skip-browser-in-path and keep WebView2 out of Edge browser lookup --- rust/src/chrome.rs | 31 +++++++++++++- rust/src/edge.rs | 33 ++++++++++++++- rust/src/files.rs | 14 +++++++ rust/src/lib.rs | 23 +++++++++- rust/tests/browser_tests.rs | 84 +++++++++++++++++++++++++++++++++++++ 5 files changed, 180 insertions(+), 5 deletions(-) diff --git a/rust/src/chrome.rs b/rust/src/chrome.rs index 61224379a30f6..b0ed10af90194 100644 --- a/rust/src/chrome.rs +++ b/rust/src/chrome.rs @@ -19,7 +19,7 @@ use crate::config::ARCH::{ARM64, X32}; use crate::config::ManagerConfig; use crate::config::OS::{LINUX, MACOS, WINDOWS}; use crate::downloads::{parse_json_from_url, read_version_from_link}; -use crate::files::{BrowserPath, compose_driver_path_in_cache}; +use crate::files::{BrowserPath, compose_driver_path_in_cache, first_existing_path}; use crate::logger::Logger; use crate::metadata::{ create_driver_metadata, get_driver_version_from_metadata, get_metadata, write_metadata, @@ -41,6 +41,20 @@ use std::sync::mpsc::{Receiver, Sender}; pub const CHROME_NAME: &str = "chrome"; pub const CHROMEDRIVER_NAME: &str = "chromedriver"; + +// Directories and names chromedriver searches to locate Chrome/Chromium on Linux (chrome_finder.cc). +pub const CHROME_KNOWN_DIRS: &[&str] = &[ + "/usr/local/sbin", + "/usr/local/bin", + "/usr/sbin", + "/usr/bin", + "/sbin", + "/bin", + "/opt/google/chrome", + "/opt/chromium.org/chromium", +]; +pub const CHROME_KNOWN_NAMES: &[&str] = + &["chrome", "google-chrome", "chromium", "chromium-browser"]; const DRIVER_URL: &str = "https://chromedriver.storage.googleapis.com/"; const LATEST_RELEASE: &str = "LATEST_RELEASE"; const CFT_URL: &str = "https://googlechromelabs.github.io/chrome-for-testing/"; @@ -228,7 +242,20 @@ impl SeleniumManager for ChromeManager { } fn get_browser_names_in_path(&self) -> Vec<&str> { - vec![self.get_browser_name(), "chromium-browser", "chromium"] + vec![ + self.get_browser_name(), + "google-chrome", + "chromium", + "chromium-browser", + ] + } + + fn detect_browser_in_known_locations(&self) -> Option { + // chromedriver's fixed directory search (chrome_finder.cc) is Linux-only. + if !LINUX.is(self.get_os()) { + return None; + } + first_existing_path(CHROME_KNOWN_DIRS, CHROME_KNOWN_NAMES) } fn get_http_client(&self) -> &Client { diff --git a/rust/src/edge.rs b/rust/src/edge.rs index 042f805244dd0..32822382af6d8 100644 --- a/rust/src/edge.rs +++ b/rust/src/edge.rs @@ -19,7 +19,7 @@ use crate::config::ARCH::{ARM64, X32}; use crate::config::ManagerConfig; use crate::config::OS::{LINUX, MACOS, WINDOWS}; use crate::downloads::{parse_json_from_url, read_version_from_link}; -use crate::files::{BrowserPath, compose_driver_path_in_cache}; +use crate::files::{BrowserPath, compose_driver_path_in_cache, first_existing_path}; use crate::metadata::{ create_driver_metadata, get_driver_version_from_metadata, get_metadata, write_metadata, }; @@ -45,6 +45,18 @@ pub const EDGE_NAMES: &[&str] = &[ ]; pub const EDGEDRIVER_NAME: &str = "msedgedriver"; pub const WEBVIEW2_NAME: &str = "webview2"; + +// Directories and names msedgedriver (a chromedriver fork) searches to locate Edge on Linux. +pub const EDGE_KNOWN_DIRS: &[&str] = &[ + "/usr/local/sbin", + "/usr/local/bin", + "/usr/sbin", + "/usr/bin", + "/sbin", + "/bin", + "/opt/microsoft/msedge", +]; +pub const EDGE_KNOWN_NAMES: &[&str] = &["msedge", "microsoft-edge", "microsoft-edge-stable"]; const DRIVER_URL: &str = "https://msedgedriver.microsoft.com/"; const LATEST_STABLE: &str = "LATEST_STABLE"; const LATEST_RELEASE: &str = "LATEST_RELEASE"; @@ -102,7 +114,24 @@ impl SeleniumManager for EdgeManager { } fn get_browser_names_in_path(&self) -> Vec<&str> { - vec![self.get_browser_name()] + // WebView2 is not the Edge browser, so it must not inherit Edge's executable names. + if self.is_webview2() { + return vec![self.get_browser_name()]; + } + vec![ + self.get_browser_name(), + "microsoft-edge", + "microsoft-edge-stable", + ] + } + + fn detect_browser_in_known_locations(&self) -> Option { + // msedgedriver is built from chromedriver and searches the same fixed directories (Linux-only); + // WebView2 is a different runtime, so it must not resolve to an Edge browser binary. + if self.is_webview2() || !LINUX.is(self.get_os()) { + return None; + } + first_existing_path(EDGE_KNOWN_DIRS, EDGE_KNOWN_NAMES) } fn get_http_client(&self) -> &Client { diff --git a/rust/src/files.rs b/rust/src/files.rs index 6d0198cf43c66..0072644332dca 100644 --- a/rust/src/files.rs +++ b/rust/src/files.rs @@ -78,6 +78,20 @@ impl BrowserPath { } } +// Returns the first `/` that exists, searched name-major (every dir tried for a +// name before moving to the next name), matching how a browser's own driver walks candidates. +pub fn first_existing_path(dirs: &[&str], names: &[&str]) -> Option { + for name in names { + for dir in dirs { + let candidate = Path::new(dir).join(name); + if candidate.exists() { + return Some(candidate); + } + } + } + None +} + pub fn create_parent_path_if_not_exists(path: &Path) -> Result<(), Error> { if let Some(p) = path.parent() { create_path_if_not_exists(p)?; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 2186054c409b3..6cf8f325278cd 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -421,7 +421,27 @@ pub trait SeleniumManager { .unwrap_or_default() } + fn detect_browser_in_known_locations(&self) -> Option { + None + } + fn detect_browser_path(&mut self) -> Option { + // A driver's binary search is channel-agnostic and finds system browsers, so mirror it only + // for the default channel and when the user hasn't asked to skip browsers in the path. + if !self.is_browser_version_unstable() + && !self.is_skip_browser_in_path() + && let Some(browser_path) = self.detect_browser_in_known_locations() + { + let canon_browser_path = self.canonicalize_path(browser_path); + self.get_logger().debug(format!( + "{} detected at {}", + self.get_browser_name(), + canon_browser_path + )); + self.set_browser_path(canon_browser_path.clone()); + return Some(Path::new(&canon_browser_path).to_path_buf()); + } + let browser_version = self.get_browser_version(); let browser_path = self.get_browser_path_from_version(browser_version); @@ -771,7 +791,8 @@ pub trait SeleniumManager { } fn is_webview2(&self) -> bool { - self.get_browser_name().eq(WEBVIEW2_NAME) + // Browser selection matches case-insensitively but keeps the original casing (e.g. "WebView2"). + self.get_browser_name().eq_ignore_ascii_case(WEBVIEW2_NAME) } fn is_browser_version_beta(&self) -> bool { diff --git a/rust/tests/browser_tests.rs b/rust/tests/browser_tests.rs index b29f57ef5c706..a6d25982a5674 100644 --- a/rust/tests/browser_tests.rs +++ b/rust/tests/browser_tests.rs @@ -19,6 +19,9 @@ use crate::common::{assert_output, get_selenium_manager, get_stdout}; use exitcode::DATAERR; use rstest::rstest; +use selenium_manager::SeleniumManager; +use selenium_manager::chrome::ChromeManager; +use selenium_manager::edge::EdgeManager; use std::env::consts::OS; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; @@ -234,3 +237,84 @@ fn browser_path_major_version_mismatch_test() { "Should mention requested version" ); } + +#[test] +fn chrome_matches_chromedriver_binary_names() { + let manager = ChromeManager::new().unwrap(); + assert_eq!( + manager.get_browser_names_in_path(), + vec!["chrome", "google-chrome", "chromium", "chromium-browser"] + ); +} + +#[test] +fn chrome_detect_browser_in_known_locations_is_linux_only() { + let mut manager = ChromeManager::new().unwrap(); + manager.config.os = "macos".to_string(); + assert!(manager.detect_browser_in_known_locations().is_none()); +} + +#[test] +fn edge_matches_msedgedriver_binary_names() { + let manager = EdgeManager::new().unwrap(); + assert_eq!( + manager.get_browser_names_in_path(), + vec!["edge", "microsoft-edge", "microsoft-edge-stable"] + ); +} + +#[test] +fn edge_detect_browser_in_known_locations_is_linux_only() { + let mut manager = EdgeManager::new().unwrap(); + manager.config.os = "macos".to_string(); + assert!(manager.detect_browser_in_known_locations().is_none()); +} + +#[rstest] +#[case("webview2")] +#[case("WebView2")] +fn edge_webview2_is_not_treated_as_edge_browser(#[case] name: String) { + let mut manager = EdgeManager::new_with_name(name.clone()).unwrap(); + assert_eq!(manager.get_browser_names_in_path(), vec![name.as_str()]); + manager.config.os = "linux".to_string(); + assert!(manager.detect_browser_in_known_locations().is_none()); +} + +#[test] +fn chrome_known_locations_include_opt_install_dirs() { + use selenium_manager::chrome::{CHROME_KNOWN_DIRS, CHROME_KNOWN_NAMES}; + assert!(CHROME_KNOWN_DIRS.contains(&"/opt/google/chrome")); + assert!(CHROME_KNOWN_DIRS.contains(&"/opt/chromium.org/chromium")); + assert!(CHROME_KNOWN_NAMES.contains(&"chrome")); +} + +#[test] +fn edge_known_locations_include_opt_install_dir() { + use selenium_manager::edge::{EDGE_KNOWN_DIRS, EDGE_KNOWN_NAMES}; + assert!(EDGE_KNOWN_DIRS.contains(&"/opt/microsoft/msedge")); + assert!(EDGE_KNOWN_NAMES.contains(&"msedge")); +} + +#[test] +fn first_existing_path_searches_name_major() { + use selenium_manager::files::first_existing_path; + use std::fs; + + let base = tempfile::tempdir().unwrap(); + let dir_a = base.path().join("a"); + let dir_b = base.path().join("b"); + fs::create_dir_all(&dir_a).unwrap(); + fs::create_dir_all(&dir_b).unwrap(); + // "wanted" only exists in the later dir; "other" only in the earlier dir. + fs::write(dir_a.join("other"), "").unwrap(); + fs::write(dir_b.join("wanted"), "").unwrap(); + + let dirs = [dir_a.to_str().unwrap(), dir_b.to_str().unwrap()]; + // Name-major: "wanted" is tried across every dir before "other", so it wins despite + // "other" sitting in an earlier directory. + assert_eq!( + first_existing_path(&dirs, &["wanted", "other"]), + Some(dir_b.join("wanted")) + ); + assert!(first_existing_path(&dirs, &["missing"]).is_none()); +}