From 0f8e018ad7be07127b8b45e9f6894e36762e8b14 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Boni=20Garc=C3=ADa?= <boni.garcia@uc3m.es>
Date: Sat, 7 Oct 2023 16:43:07 +0200
Subject: [PATCH] [rust] Automated Edge management (#11681 and #11683) (#12835)

* [rust] Automated Edge management (macOS) (#11681)

* [rust] Check also unstable versions for Edge management

* [rust] Include logic to check fixed versions of Edge

* [rust] Get browser url again if empty

* [rust] Include logic for stable label

* [rust] Automated Edge management (Linux) (#11681 and #11683)

* [rust] Fix paths used to extract edge

* [rust] Clean extract label and fix searched version

* [rust] Refactor logic for downloading browsers in a common function

* [rust] Install Edge in Windows through the MSI installer

* [rust] Check admin permissions in Windows before downloading MSI installer

* [rust] Use browser version in functions for requesting online repos

* [rust] Refactor common logic when unavailable download or discovery

* [rust] Fix condition checking Firefox nightly in mac

* [rust] Update cargo bazel lock file

* [rust] Separate function to ensure empty parent path before moving files

---------

Co-authored-by: titusfortner <titus.fortner@gmail.com>
Co-authored-by: Titus Fortner <titusfortner@users.noreply.github.com>
---
 rust/Cargo.Bazel.lock                | 306 +++++++++++++++++++++++-
 rust/Cargo.lock                      |  62 ++++-
 rust/Cargo.toml                      |   1 +
 rust/src/chrome.rs                   | 184 +++++---------
 rust/src/edge.rs                     | 252 +++++++++++++++++++-
 rust/src/files.rs                    |  67 +++++-
 rust/src/firefox.rs                  | 342 ++++++++++-----------------
 rust/src/grid.rs                     |  39 ++-
 rust/src/iexplorer.rs                |  39 ++-
 rust/src/lib.rs                      | 282 +++++++++++++++++++---
 rust/src/safari.rs                   |  39 ++-
 rust/src/safaritp.rs                 |  39 ++-
 rust/tests/browser_download_tests.rs |  11 +-
 13 files changed, 1247 insertions(+), 416 deletions(-)

diff --git a/rust/Cargo.Bazel.lock b/rust/Cargo.Bazel.lock
index 58046d9a2e4db..8188b66f1681c 100644
--- a/rust/Cargo.Bazel.lock
+++ b/rust/Cargo.Bazel.lock
@@ -1,5 +1,5 @@
 {
-  "checksum": "495be0038fd3bd2826baeb0433f22c042c1b03eeeb117631135bc6339799addf",
+  "checksum": "7bb62c0cb24820374fb08c7eb1d2c1661ceb1a296f8cf2cad91579a7d2687eaf",
   "crates": {
     "addr2line 0.19.0": {
       "name": "addr2line",
@@ -478,6 +478,73 @@
       },
       "license": "MIT OR Apache-2.0"
     },
+    "ar 0.9.0": {
+      "name": "ar",
+      "version": "0.9.0",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/ar/0.9.0/download",
+          "sha256": "d67af77d68a931ecd5cbd8a3b5987d63a1d1d1278f7f6a60ae33db485cdebb69"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "ar",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "ar",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "edition": "2015",
+        "version": "0.9.0"
+      },
+      "license": "MIT"
+    },
+    "arrayvec 0.7.4": {
+      "name": "arrayvec",
+      "version": "0.7.4",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/arrayvec/0.7.4/download",
+          "sha256": "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "arrayvec",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "arrayvec",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "crate_features": {
+          "common": [
+            "default",
+            "std"
+          ],
+          "selects": {}
+        },
+        "edition": "2018",
+        "version": "0.7.4"
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "assert_cmd 2.0.12": {
       "name": "assert_cmd",
       "version": "2.0.12",
@@ -2021,6 +2088,81 @@
       },
       "license": "MIT OR Apache-2.0"
     },
+    "debpkg 0.6.0": {
+      "name": "debpkg",
+      "version": "0.6.0",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/debpkg/0.6.0/download",
+          "sha256": "7ffffa9a03449467cfac11c9a4260556f477de22a935bb5e9475153de323c337"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "debpkg",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "debpkg",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "deps": {
+          "common": [
+            {
+              "id": "ar 0.9.0",
+              "target": "ar"
+            },
+            {
+              "id": "arrayvec 0.7.4",
+              "target": "arrayvec"
+            },
+            {
+              "id": "bzip2 0.4.4",
+              "target": "bzip2"
+            },
+            {
+              "id": "flate2 1.0.27",
+              "target": "flate2"
+            },
+            {
+              "id": "indexmap 1.9.2",
+              "target": "indexmap"
+            },
+            {
+              "id": "infer 0.8.1",
+              "target": "infer"
+            },
+            {
+              "id": "log 0.4.20",
+              "target": "log"
+            },
+            {
+              "id": "tar 0.4.40",
+              "target": "tar"
+            },
+            {
+              "id": "xz2 0.1.7",
+              "target": "xz2"
+            },
+            {
+              "id": "zstd 0.11.2+zstd.1.5.2",
+              "target": "zstd"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2018",
+        "version": "0.6.0"
+      },
+      "license": "MIT"
+    },
     "difflib 0.4.0": {
       "name": "difflib",
       "version": "0.4.0",
@@ -4647,6 +4789,54 @@
       },
       "license": "MIT"
     },
+    "infer 0.8.1": {
+      "name": "infer",
+      "version": "0.8.1",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/infer/0.8.1/download",
+          "sha256": "e035cede526e0b21d5adffc9fa0eb4ef5d6026fe9c5b0bfe8084b9472b587a55"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "infer",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "infer",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "crate_features": {
+          "common": [
+            "alloc",
+            "cfb",
+            "default",
+            "std"
+          ],
+          "selects": {}
+        },
+        "deps": {
+          "common": [
+            {
+              "id": "cfb 0.7.3",
+              "target": "cfb"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2018",
+        "version": "0.8.1"
+      },
+      "license": "MIT"
+    },
     "inout 0.1.3": {
       "name": "inout",
       "version": "0.1.3",
@@ -5280,6 +5470,77 @@
       },
       "license": "Apache-2.0"
     },
+    "lzma-sys 0.1.20": {
+      "name": "lzma-sys",
+      "version": "0.1.20",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/lzma-sys/0.1.20/download",
+          "sha256": "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "lzma_sys",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        },
+        {
+          "BuildScript": {
+            "crate_name": "build_script_build",
+            "crate_root": "build.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "lzma_sys",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "deps": {
+          "common": [
+            {
+              "id": "libc 0.2.147",
+              "target": "libc"
+            },
+            {
+              "id": "lzma-sys 0.1.20",
+              "target": "build_script_build"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2018",
+        "version": "0.1.20"
+      },
+      "build_script_attrs": {
+        "data_glob": [
+          "**"
+        ],
+        "deps": {
+          "common": [
+            {
+              "id": "cc 1.0.79",
+              "target": "cc"
+            },
+            {
+              "id": "pkg-config 0.3.26",
+              "target": "pkg_config"
+            }
+          ],
+          "selects": {}
+        },
+        "links": "lzma"
+      },
+      "license": "MIT/Apache-2.0"
+    },
     "memchr 2.5.0": {
       "name": "memchr",
       "version": "2.5.0",
@@ -7750,6 +8011,10 @@
               "id": "clap 4.3.23",
               "target": "clap"
             },
+            {
+              "id": "debpkg 0.6.0",
+              "target": "debpkg"
+            },
             {
               "id": "directories 5.0.1",
               "target": "directories"
@@ -12349,6 +12614,45 @@
       },
       "license": "MIT/Apache-2.0"
     },
+    "xz2 0.1.7": {
+      "name": "xz2",
+      "version": "0.1.7",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/xz2/0.1.7/download",
+          "sha256": "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "xz2",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "xz2",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "deps": {
+          "common": [
+            {
+              "id": "lzma-sys 0.1.20",
+              "target": "lzma_sys"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2018",
+        "version": "0.1.7"
+      },
+      "license": "MIT/Apache-2.0"
+    },
     "zip 0.6.6": {
       "name": "zip",
       "version": "0.6.6",
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index 27bbb73d5360b..f1cf6b94a6b64 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -98,6 +98,18 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "ar"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d67af77d68a931ecd5cbd8a3b5987d63a1d1d1278f7f6a60ae33db485cdebb69"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+
 [[package]]
 name = "assert_cmd"
 version = "2.0.12"
@@ -393,6 +405,24 @@ dependencies = [
  "typenum",
 ]
 
+[[package]]
+name = "debpkg"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffffa9a03449467cfac11c9a4260556f477de22a935bb5e9475153de323c337"
+dependencies = [
+ "ar",
+ "arrayvec",
+ "bzip2",
+ "flate2",
+ "indexmap 1.9.2",
+ "infer 0.8.1",
+ "log",
+ "tar",
+ "xz2",
+ "zstd",
+]
+
 [[package]]
 name = "difflib"
 version = "0.4.0"
@@ -868,6 +898,15 @@ dependencies = [
  "hashbrown 0.14.0",
 ]
 
+[[package]]
+name = "infer"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e035cede526e0b21d5adffc9fa0eb4ef5d6026fe9c5b0bfe8084b9472b587a55"
+dependencies = [
+ "cfb",
+]
+
 [[package]]
 name = "infer"
 version = "0.15.0"
@@ -990,6 +1029,17 @@ dependencies = [
  "byteorder",
 ]
 
+[[package]]
+name = "lzma-sys"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
 [[package]]
 name = "memchr"
 version = "2.5.0"
@@ -1445,11 +1495,12 @@ dependencies = [
  "assert_cmd",
  "bzip2",
  "clap",
+ "debpkg",
  "directories",
  "env_logger",
  "exitcode",
  "flate2",
- "infer",
+ "infer 0.15.0",
  "is_executable",
  "log",
  "regex",
@@ -2233,6 +2284,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "xz2"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
+dependencies = [
+ "lzma-sys",
+]
+
 [[package]]
 name = "zip"
 version = "0.6.6"
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index b6dcf3d853af7..1fe680ee5a0a4 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -32,6 +32,7 @@ toml = "0.7.6"
 bzip2 = "0.4.4"
 sevenz-rust = "0.5.2"
 walkdir = "2.4.0"
+debpkg = "0.6.0"
 
 [dev-dependencies]
 assert_cmd = "2.0.12"
diff --git a/rust/src/chrome.rs b/rust/src/chrome.rs
index b488aaadb34b9..54692ee4f5c65 100644
--- a/rust/src/chrome.rs
+++ b/rust/src/chrome.rs
@@ -26,15 +26,15 @@ use std::path::PathBuf;
 use crate::config::ARCH::{ARM64, X32};
 use crate::config::OS::{LINUX, MACOS, WINDOWS};
 use crate::downloads::{parse_json_from_url, read_version_from_link};
-use crate::files::{compose_driver_path_in_cache, path_to_string, BrowserPath};
+use crate::files::{compose_driver_path_in_cache, BrowserPath};
 use crate::logger::Logger;
 use crate::metadata::{
     create_driver_metadata, get_driver_version_from_metadata, get_metadata, write_metadata,
 };
 use crate::{
-    create_browser_metadata, create_http_client, download_to_tmp_folder, format_three_args,
-    get_browser_version_from_metadata, uncompress, SeleniumManager, BETA, DASH_DASH_VERSION, DEV,
-    NIGHTLY, OFFLINE_REQUEST_ERR_MSG, REG_VERSION_ARG, STABLE,
+    create_http_client, format_three_args, SeleniumManager, BETA, DASH_DASH_VERSION, DEV, NIGHTLY,
+    OFFLINE_REQUEST_ERR_MSG, REG_VERSION_ARG, STABLE,
+    UNAVAILABLE_DOWNLOAD_WITH_MIN_VERSION_ERR_MSG,
 };
 
 pub const CHROME_NAME: &str = "chrome";
@@ -48,8 +48,6 @@ 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 UNAVAILABLE_CFT_ERROR_MESSAGE: &str =
-    "{} {} not available for download in Chrome for Testing (minimum version: {})";
 
 pub struct ChromeManager {
     pub browser_name: &'static str,
@@ -173,7 +171,7 @@ impl ChromeManager {
             .collect();
         if filtered_versions.is_empty() {
             return Err(format_three_args(
-                UNAVAILABLE_CFT_ERROR_MESSAGE,
+                UNAVAILABLE_DOWNLOAD_WITH_MIN_VERSION_ERR_MSG,
                 self.get_driver_name(),
                 &version_for_filtering,
                 &MIN_CHROMEDRIVER_VERSION_CFT.to_string(),
@@ -195,15 +193,6 @@ impl ChromeManager {
 
         Ok(driver_version.version.to_string())
     }
-
-    fn get_browser_binary_path_in_cache(&self) -> Result<PathBuf, Box<dyn Error>> {
-        let browser_in_cache = self.get_browser_path_in_cache()?;
-        if MACOS.is(self.get_os()) {
-            Ok(browser_in_cache.join(CFT_MACOS_APP_NAME))
-        } else {
-            Ok(browser_in_cache.join(self.get_browser_name_with_extension()))
-        }
-    }
 }
 
 impl SeleniumManager for ChromeManager {
@@ -414,118 +403,6 @@ impl SeleniumManager for ChromeManager {
         self.log = log;
     }
 
-    fn download_browser(&mut self) -> Result<Option<PathBuf>, Box<dyn Error>> {
-        let browser_version;
-        let browser_name = self.browser_name;
-        let cache_path = self.get_cache_path()?;
-        let mut metadata = get_metadata(self.get_logger(), &cache_path);
-        let major_browser_version = self.get_major_browser_version();
-        let major_browser_version_int = major_browser_version.parse::<i32>().unwrap_or_default();
-
-        // Browser version should be available in the CfT endpoints (>= 113)
-        if !self.is_browser_version_unstable()
-            && !self.is_browser_version_stable()
-            && !self.is_browser_version_empty()
-            && major_browser_version_int < MIN_CHROME_VERSION_CFT
-        {
-            return Err(format_three_args(
-                UNAVAILABLE_CFT_ERROR_MESSAGE,
-                browser_name,
-                &major_browser_version,
-                &MIN_CHROME_VERSION_CFT.to_string(),
-            )
-            .into());
-        }
-
-        // Browser version is checked in the local metadata
-        match get_browser_version_from_metadata(
-            &metadata.browsers,
-            browser_name,
-            &major_browser_version,
-        ) {
-            Some(version) => {
-                self.get_logger().trace(format!(
-                    "Browser with valid TTL. Getting {} version from metadata",
-                    browser_name
-                ));
-                browser_version = version;
-                self.set_browser_version(browser_version.clone());
-            }
-            _ => {
-                // If not in metadata, discover version using Chrome for Testing (CfT) endpoints
-                if self.is_browser_version_stable() || self.is_browser_version_empty() {
-                    browser_version = self.request_latest_browser_version_from_online()?;
-                } else {
-                    browser_version = self.request_fixed_browser_version_from_online()?;
-                }
-                self.set_browser_version(browser_version.clone());
-
-                let browser_ttl = self.get_ttl();
-                if browser_ttl > 0
-                    && !self.is_browser_version_empty()
-                    && !self.is_browser_version_stable()
-                {
-                    metadata.browsers.push(create_browser_metadata(
-                        browser_name,
-                        &major_browser_version,
-                        &browser_version,
-                        browser_ttl,
-                    ));
-                    write_metadata(&metadata, self.get_logger(), cache_path);
-                }
-            }
-        }
-        self.get_logger().debug(format!(
-            "Required browser: {} {}",
-            browser_name, browser_version
-        ));
-
-        // Checking if browser version is in the cache
-        let browser_binary_path = self.get_browser_binary_path_in_cache()?;
-        if browser_binary_path.exists() {
-            self.get_logger().debug(format!(
-                "{} {} already in the cache",
-                browser_name, browser_version
-            ));
-        } else {
-            // If browser is not in the cache, download it
-            let browser_url = if let Some(url) = self.browser_url.clone() {
-                url
-            } else {
-                if self.is_browser_version_stable() || self.is_browser_version_empty() {
-                    self.request_latest_browser_version_from_online()?;
-                } else {
-                    self.request_fixed_browser_version_from_online()?;
-                }
-                self.browser_url.clone().unwrap()
-            };
-            self.get_logger().debug(format!(
-                "Downloading {} {} from {}",
-                self.get_browser_name(),
-                self.get_browser_version(),
-                browser_url
-            ));
-            let (_tmp_folder, driver_zip_file) =
-                download_to_tmp_folder(self.get_http_client(), browser_url, self.get_logger())?;
-
-            uncompress(
-                &driver_zip_file,
-                &self.get_browser_path_in_cache()?,
-                self.get_logger(),
-                self.get_os(),
-                None,
-                None,
-                None,
-            )?;
-        }
-        if browser_binary_path.exists() {
-            self.set_browser_path(path_to_string(&browser_binary_path));
-            Ok(Some(browser_binary_path))
-        } else {
-            Ok(None)
-        }
-    }
-
     fn get_platform_label(&self) -> &str {
         let os = self.get_os();
         let arch = self.get_arch();
@@ -546,7 +423,10 @@ impl SeleniumManager for ChromeManager {
         }
     }
 
-    fn request_latest_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>> {
+    fn request_latest_browser_version_from_online(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
         let browser_name = self.browser_name;
         self.get_logger().trace(format!(
             "Using Chrome for Testing (CfT) endpoints to find out latest stable {} version",
@@ -574,7 +454,10 @@ impl SeleniumManager for ChromeManager {
         Ok(browser_version)
     }
 
-    fn request_fixed_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>> {
+    fn request_fixed_browser_version_from_online(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
         let browser_name = self.browser_name;
         let mut browser_version = self.get_browser_version().to_string();
         let major_browser_version = self.get_major_browser_version();
@@ -615,7 +498,7 @@ impl SeleniumManager for ChromeManager {
                 .collect();
             if filtered_versions.is_empty() {
                 return Err(format_three_args(
-                    UNAVAILABLE_CFT_ERROR_MESSAGE,
+                    UNAVAILABLE_DOWNLOAD_WITH_MIN_VERSION_ERR_MSG,
                     browser_name,
                     &major_browser_version,
                     &MIN_CHROME_VERSION_CFT.to_string(),
@@ -634,6 +517,45 @@ impl SeleniumManager for ChromeManager {
             Ok(last_browser.version.to_string())
         }
     }
+
+    fn get_min_browser_version_for_download(&self) -> Result<i32, Box<dyn Error>> {
+        Ok(MIN_CHROME_VERSION_CFT)
+    }
+
+    fn get_browser_binary_path(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<PathBuf, Box<dyn Error>> {
+        let browser_in_cache = self.get_browser_path_in_cache()?;
+        if MACOS.is(self.get_os()) {
+            Ok(browser_in_cache.join(CFT_MACOS_APP_NAME))
+        } else {
+            Ok(browser_in_cache.join(self.get_browser_name_with_extension()))
+        }
+    }
+
+    fn get_browser_url_for_download(
+        &mut self,
+        browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
+        if let Some(browser_url) = self.browser_url.clone() {
+            Ok(browser_url)
+        } else {
+            if self.is_browser_version_stable() || self.is_browser_version_empty() {
+                self.request_latest_browser_version_from_online(browser_version)?;
+            } else {
+                self.request_fixed_browser_version_from_online(browser_version)?;
+            }
+            Ok(self.browser_url.clone().unwrap())
+        }
+    }
+
+    fn get_browser_label_for_download(
+        &self,
+        _browser_version: &str,
+    ) -> Result<Option<&str>, Box<dyn Error>> {
+        Ok(None)
+    }
 }
 
 #[derive(Serialize, Deserialize)]
diff --git a/rust/src/edge.rs b/rust/src/edge.rs
index 18e69bc41b207..2913f20ec1227 100644
--- a/rust/src/edge.rs
+++ b/rust/src/edge.rs
@@ -17,20 +17,23 @@
 
 use crate::config::ManagerConfig;
 use reqwest::Client;
+use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
+use std::env;
 use std::error::Error;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 
 use crate::config::ARCH::{ARM64, X32};
 use crate::config::OS::{LINUX, MACOS, WINDOWS};
-use crate::downloads::read_version_from_link;
+use crate::downloads::{parse_json_from_url, read_version_from_link};
 use crate::files::{compose_driver_path_in_cache, BrowserPath};
 use crate::metadata::{
     create_driver_metadata, get_driver_version_from_metadata, get_metadata, write_metadata,
 };
 use crate::{
-    create_http_client, Logger, SeleniumManager, BETA, DASH_DASH_VERSION, DEV, NIGHTLY,
-    OFFLINE_REQUEST_ERR_MSG, REG_VERSION_ARG, STABLE,
+    create_http_client, get_binary_extension, path_to_string, Logger, SeleniumManager, BETA,
+    DASH_DASH_VERSION, DEV, ENV_PROGRAM_FILES_X86, NIGHTLY, OFFLINE_REQUEST_ERR_MSG,
+    REG_VERSION_ARG, STABLE,
 };
 
 pub const EDGE_NAMES: &[&str] = &["edge", "msedge", "microsoftedge"];
@@ -38,6 +41,14 @@ pub const EDGEDRIVER_NAME: &str = "msedgedriver";
 const DRIVER_URL: &str = "https://msedgedriver.azureedge.net/";
 const LATEST_STABLE: &str = "LATEST_STABLE";
 const LATEST_RELEASE: &str = "LATEST_RELEASE";
+const BROWSER_URL: &str = "https://edgeupdates.microsoft.com/api/products";
+const MIN_EDGE_VERSION_DOWNLOAD: i32 = 113;
+const EDGE_WINDOWS_AND_LINUX_APP_NAME: &str = "msedge";
+const EDGE_MACOS_APP_NAME: &str = "Microsoft Edge.app/Contents/MacOS/Microsoft Edge";
+const EDGE_BETA_MACOS_APP_NAME: &str = "Microsoft Edge Beta.app/Contents/MacOS/Microsoft Edge Beta";
+const EDGE_DEV_MACOS_APP_NAME: &str = "Microsoft Edge Dev.app/Contents/MacOS/Microsoft Edge Dev";
+const EDGE_CANARY_MACOS_APP_NAME: &str =
+    "Microsoft Edge Canary.app/Contents/MacOS/Microsoft Edge Canary";
 
 pub struct EdgeManager {
     pub browser_name: &'static str,
@@ -45,6 +56,7 @@ pub struct EdgeManager {
     pub config: ManagerConfig,
     pub http_client: Client,
     pub log: Logger,
+    pub browser_url: Option<String>,
 }
 
 impl EdgeManager {
@@ -60,6 +72,7 @@ impl EdgeManager {
             http_client: create_http_client(default_timeout, default_proxy)?,
             config,
             log: Logger::new(),
+            browser_url: None,
         }))
     }
 }
@@ -268,10 +281,6 @@ impl SeleniumManager for EdgeManager {
         self.log = log;
     }
 
-    fn download_browser(&mut self) -> Result<Option<PathBuf>, Box<dyn Error>> {
-        Ok(None)
-    }
-
     fn get_platform_label(&self) -> &str {
         let os = self.get_os();
         let arch = self.get_arch();
@@ -294,11 +303,230 @@ impl SeleniumManager for EdgeManager {
         }
     }
 
-    fn request_latest_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>> {
-        self.unavailable_download()
+    fn request_latest_browser_version_from_online(
+        &mut self,
+        browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
+        let browser_name = self.browser_name;
+        let is_fixed_browser_version = !self.is_empty(browser_version)
+            && !self.is_stable(browser_version)
+            && !self.is_unstable(browser_version);
+        let edge_updates_url = if is_fixed_browser_version {
+            format!("{}?view=enterprise", BROWSER_URL)
+        } else {
+            BROWSER_URL.to_string()
+        };
+        self.get_logger().debug(format!(
+            "Checking {} releases on {}",
+            browser_name, edge_updates_url
+        ));
+
+        let edge_products =
+            parse_json_from_url::<Vec<EdgeProduct>>(self.get_http_client(), edge_updates_url)?;
+
+        let edge_channel = if self.is_beta(browser_version) {
+            "Beta"
+        } else if self.is_dev(browser_version) {
+            "Dev"
+        } else if self.is_nightly(browser_version) {
+            "Canary"
+        } else {
+            "Stable"
+        };
+        let products: Vec<&EdgeProduct> = edge_products
+            .iter()
+            .filter(|p| p.product.eq_ignore_ascii_case(edge_channel))
+            .collect();
+        self.get_logger().trace(format!("Products: {:?}", products));
+
+        let os = self.get_os();
+        let arch = self.get_arch();
+        let os_label;
+        let arch_label = if WINDOWS.is(os) {
+            os_label = "Windows";
+            if ARM64.is(arch) {
+                "arm64"
+            } else if X32.is(arch) {
+                "x86"
+            } else {
+                "x64"
+            }
+        } else if MACOS.is(os) {
+            os_label = "MacOS";
+            "universal"
+        } else {
+            os_label = "Linux";
+            "x64"
+        };
+        if products.is_empty() {
+            return self.unavailable_discovery();
+        }
+
+        let releases: Vec<&Release> = products
+            .first()
+            .unwrap()
+            .releases
+            .iter()
+            .filter(|r| {
+                let os_arch = r.platform.eq_ignore_ascii_case(os_label)
+                    && r.architecture.eq_ignore_ascii_case(arch_label);
+                if is_fixed_browser_version {
+                    os_arch && r.product_version.starts_with(browser_version)
+                } else {
+                    os_arch
+                }
+            })
+            .collect();
+        self.get_logger().trace(format!("Releases: {:?}", releases));
+
+        let package_label = if WINDOWS.is(os) {
+            "msi"
+        } else if MACOS.is(os) {
+            "pkg"
+        } else {
+            "deb"
+        };
+        if releases.is_empty() {
+            return self.unavailable_discovery();
+        }
+
+        let release = releases.first().unwrap();
+        let artifacts: Vec<&Artifact> = release
+            .artifacts
+            .iter()
+            .filter(|a| a.artifact_name.eq_ignore_ascii_case(package_label))
+            .collect();
+        self.get_logger()
+            .trace(format!("Artifacts: {:?}", artifacts));
+
+        if artifacts.is_empty() {
+            return self.unavailable_discovery();
+        }
+        let artifact = artifacts.first().unwrap();
+        let browser_version = release.product_version.clone();
+        self.browser_url = Some(artifact.location.clone());
+
+        Ok(browser_version)
+    }
+
+    fn request_fixed_browser_version_from_online(
+        &mut self,
+        browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
+        self.request_latest_browser_version_from_online(browser_version)
     }
 
-    fn request_fixed_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>> {
-        self.unavailable_download()
+    fn get_min_browser_version_for_download(&self) -> Result<i32, Box<dyn Error>> {
+        Ok(MIN_EDGE_VERSION_DOWNLOAD)
+    }
+
+    fn get_browser_binary_path(
+        &mut self,
+        browser_version: &str,
+    ) -> Result<PathBuf, Box<dyn Error>> {
+        let browser_in_cache = self.get_browser_path_in_cache()?;
+        if MACOS.is(self.get_os()) {
+            let macos_app_name = if self.is_beta(browser_version) {
+                EDGE_BETA_MACOS_APP_NAME
+            } else if self.is_dev(browser_version) {
+                EDGE_DEV_MACOS_APP_NAME
+            } else if self.is_nightly(browser_version) {
+                EDGE_CANARY_MACOS_APP_NAME
+            } else {
+                EDGE_MACOS_APP_NAME
+            };
+            Ok(browser_in_cache.join(macos_app_name))
+        } else if WINDOWS.is(self.get_os()) {
+            let browser_path = if self.is_unstable(browser_version) {
+                self.get_browser_path_from_version(browser_version)
+                    .to_string()
+            } else {
+                format!(
+                    r#"Microsoft\Edge\Application\{}\msedge.exe"#,
+                    self.get_browser_version()
+                )
+            };
+            let mut full_browser_path = Path::new(&browser_path).to_path_buf();
+            if WINDOWS.is(self.get_os()) {
+                let env_value = env::var(ENV_PROGRAM_FILES_X86).unwrap_or_default();
+                let parent_path = Path::new(&env_value);
+                full_browser_path = parent_path.join(&browser_path);
+            }
+            Ok((&path_to_string(&full_browser_path)).into())
+        } else {
+            Ok(browser_in_cache.join(format!(
+                "{}{}",
+                EDGE_WINDOWS_AND_LINUX_APP_NAME,
+                get_binary_extension(self.get_os())
+            )))
+        }
     }
+
+    fn get_browser_url_for_download(
+        &mut self,
+        browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
+        if self.browser_url.is_none() {
+            self.request_latest_browser_version_from_online(browser_version)?;
+        }
+        Ok(self.browser_url.clone().unwrap())
+    }
+
+    fn get_browser_label_for_download(
+        &self,
+        browser_version: &str,
+    ) -> Result<Option<&str>, Box<dyn Error>> {
+        let browser_label = if self.is_beta(browser_version) {
+            "msedge-beta"
+        } else if self.is_dev(browser_version) {
+            "msedge-dev"
+        } else if self.is_nightly(browser_version) {
+            "msedge-canary"
+        } else {
+            "msedge"
+        };
+        Ok(Some(browser_label))
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct EdgeProduct {
+    #[serde(rename = "Product")]
+    pub product: String,
+    #[serde(rename = "Releases")]
+    pub releases: Vec<Release>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct Release {
+    #[serde(rename = "ReleaseId")]
+    pub release_id: u32,
+    #[serde(rename = "Platform")]
+    pub platform: String,
+    #[serde(rename = "Architecture")]
+    pub architecture: String,
+    #[serde(rename = "CVEs")]
+    pub cves: Vec<String>,
+    #[serde(rename = "ProductVersion")]
+    pub product_version: String,
+    #[serde(rename = "Artifacts")]
+    pub artifacts: Vec<Artifact>,
+    #[serde(rename = "PublishedTime")]
+    pub published_time: String,
+    #[serde(rename = "ExpectedExpiryDate")]
+    pub expected_expiry_date: String,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct Artifact {
+    #[serde(rename = "ArtifactName")]
+    pub artifact_name: String,
+    #[serde(rename = "Location")]
+    pub location: String,
+    #[serde(rename = "Hash")]
+    pub hash: String,
+    #[serde(rename = "HashAlgorithm")]
+    pub hash_algorithm: String,
+    #[serde(rename = "SizeInBytes")]
+    pub size_in_bytes: u32,
 }
diff --git a/rust/src/files.rs b/rust/src/files.rs
index 06fb9b4bd290a..2eff09e160ad6 100644
--- a/rust/src/files.rs
+++ b/rust/src/files.rs
@@ -35,8 +35,8 @@ use zip::ZipArchive;
 use crate::config::OS::WINDOWS;
 use crate::{
     format_one_arg, format_three_args, format_two_args, run_shell_command_by_os, Command, Logger,
-    CP_VOLUME_COMMAND, HDIUTIL_ATTACH_COMMAND, HDIUTIL_DETACH_COMMAND, MACOS, MV_PAYLOAD_COMMAND,
-    MV_PAYLOAD_OLD_VERSIONS_COMMAND, PKGUTIL_COMMAND,
+    CP_VOLUME_COMMAND, HDIUTIL_ATTACH_COMMAND, HDIUTIL_DETACH_COMMAND, MACOS,
+    MSIEXEC_INSTALL_COMMAND, MV_PAYLOAD_COMMAND, MV_PAYLOAD_OLD_VERSIONS_COMMAND, PKGUTIL_COMMAND,
 };
 
 pub const PARSE_ERROR: &str = "Wrong browser/driver version";
@@ -49,6 +49,8 @@ const BZ2: &str = "bz2";
 const PKG: &str = "pkg";
 const DMG: &str = "dmg";
 const EXE: &str = "exe";
+const DEB: &str = "deb";
+const MSI: &str = "msi";
 const SEVEN_ZIP_HEADER: &[u8; 6] = b"7z\xBC\xAF\x27\x1C";
 const UNCOMPRESS_MACOS_ERR_MSG: &str = "{} files are only supported in macOS";
 
@@ -67,6 +69,14 @@ impl BrowserPath {
     }
 }
 
+pub fn create_empty_parent_path_if_not_exists(path: &Path) -> Result<(), Box<dyn Error>> {
+    if let Some(p) = path.parent() {
+        create_path_if_not_exists(p)?;
+        fs::remove_dir_all(p).and_then(|_| fs::create_dir(p))?;
+    }
+    Ok(())
+}
+
 pub fn create_parent_path_if_not_exists(path: &Path) -> Result<(), Box<dyn Error>> {
     if let Some(p) = path.parent() {
         create_path_if_not_exists(p)?;
@@ -136,6 +146,10 @@ pub fn uncompress(
         uncompress_dmg(compressed_file, target, log, os, volume.unwrap_or_default())?
     } else if extension.eq_ignore_ascii_case(EXE) {
         uncompress_sfx(compressed_file, target, log)?
+    } else if extension.eq_ignore_ascii_case(DEB) {
+        uncompress_deb(compressed_file, target, log, volume.unwrap_or_default())?
+    } else if extension.eq_ignore_ascii_case(MSI) {
+        install_msi(compressed_file, log, os)?
     } else if extension.eq_ignore_ascii_case(XML) || extension.eq_ignore_ascii_case(HTML) {
         log.debug(format!(
             "Wrong downloaded driver: {}",
@@ -177,7 +191,7 @@ pub fn uncompress_sfx(
         "Moving extracted files and folders from {} to {}",
         core_str, target_str
     ));
-    create_parent_path_if_not_exists(target)?;
+    create_empty_parent_path_if_not_exists(target)?;
     fs::rename(&core_str, &target_str)?;
 
     Ok(())
@@ -259,6 +273,53 @@ pub fn uncompress_dmg(
     Ok(())
 }
 
+pub fn uncompress_deb(
+    compressed_file: &str,
+    target: &Path,
+    log: &Logger,
+    label: &str,
+) -> Result<(), Box<dyn Error>> {
+    let zip_parent = Path::new(compressed_file).parent().unwrap();
+    log.trace(format!(
+        "Extracting from {} to {}",
+        compressed_file,
+        zip_parent.display()
+    ));
+
+    let deb_file = File::open(compressed_file)?;
+    let mut deb_pkg = debpkg::DebPkg::parse(deb_file)?;
+    deb_pkg.data()?.unpack(zip_parent)?;
+
+    let zip_parent_str = path_to_string(zip_parent);
+    let target_str = path_to_string(target);
+    let opt_edge_str = format!("{}/opt/microsoft/{}", zip_parent_str, label);
+    log.trace(format!(
+        "Moving extracted files and folders from {} to {}",
+        opt_edge_str, target_str
+    ));
+    create_empty_parent_path_if_not_exists(target)?;
+    fs::rename(&opt_edge_str, &target_str)?;
+
+    Ok(())
+}
+
+pub fn install_msi(msi_file: &str, log: &Logger, os: &str) -> Result<(), Box<dyn Error>> {
+    let msi_file_name = Path::new(msi_file)
+        .file_name()
+        .unwrap_or_default()
+        .to_os_string();
+    log.debug(format!(
+        "Installing {}",
+        msi_file_name.to_str().unwrap_or_default()
+    ));
+
+    let command = Command::new_single(format_one_arg(MSIEXEC_INSTALL_COMMAND, msi_file));
+    log.trace(format!("Running command: {}", command.display()));
+    run_shell_command_by_os(os, command)?;
+
+    Ok(())
+}
+
 pub fn untargz(compressed_file: &str, target: &Path, log: &Logger) -> Result<(), Box<dyn Error>> {
     log.trace(format!(
         "Untargz {} to {}",
diff --git a/rust/src/firefox.rs b/rust/src/firefox.rs
index 297f2248449d3..bd701c098d0aa 100644
--- a/rust/src/firefox.rs
+++ b/rust/src/firefox.rs
@@ -32,10 +32,8 @@ use crate::metadata::{
     create_driver_metadata, get_driver_version_from_metadata, get_metadata, write_metadata,
 };
 use crate::{
-    create_browser_metadata, create_http_client, download_to_tmp_folder, format_three_args,
-    format_two_args, get_browser_version_from_metadata, path_to_string, uncompress, Logger,
-    SeleniumManager, BETA, CANARY, DASH_VERSION, DEV, NIGHTLY, OFFLINE_REQUEST_ERR_MSG,
-    REG_CURRENT_VERSION_ARG, STABLE,
+    create_http_client, format_three_args, format_two_args, Logger, SeleniumManager, BETA,
+    DASH_VERSION, DEV, NIGHTLY, OFFLINE_REQUEST_ERR_MSG, REG_CURRENT_VERSION_ARG, STABLE,
 };
 
 pub const FIREFOX_NAME: &str = "firefox";
@@ -61,7 +59,6 @@ const FIREFOX_NIGHTLY_VOLUME: &str = r#"Firefox\ Nightly"#;
 const MIN_DOWNLOADABLE_FIREFOX_VERSION_WIN: i32 = 13;
 const MIN_DOWNLOADABLE_FIREFOX_VERSION_MAC: i32 = 4;
 const MIN_DOWNLOADABLE_FIREFOX_VERSION_LINUX: i32 = 4;
-const ONLINE_DISCOVERY_ERROR_MESSAGE: &str = "Unable to discover {} {} in online repository";
 const UNAVAILABLE_DOWNLOAD_ERROR_MESSAGE: &str =
     "{} {} not available for downloading (minimum version: {})";
 
@@ -114,94 +111,6 @@ impl FirefoxManager {
             .map(|v| v.to_string())
             .collect())
     }
-
-    fn get_browser_url(
-        &mut self,
-        is_browser_version_nightly: bool,
-    ) -> Result<String, Box<dyn Error>> {
-        let arch = self.get_arch();
-        let os = self.get_os();
-        let platform_label;
-        let artifact_name;
-        let artifact_extension;
-        let major_browser_version = self
-            .get_major_browser_version()
-            .parse::<i32>()
-            .unwrap_or_default();
-
-        if WINDOWS.is(os) {
-            artifact_name = "Firefox%20Setup%20";
-            artifact_extension = "exe";
-            // Before Firefox 42, only Windows 32 was supported
-            if X32.is(arch) || major_browser_version < 42 {
-                platform_label = "win32";
-            } else if ARM64.is(arch) {
-                platform_label = "win-aarch64";
-            } else {
-                platform_label = "win64";
-            }
-        } else if MACOS.is(os) {
-            artifact_name = "Firefox%20";
-            // Before Firefox 68, only DMG was released
-            if major_browser_version < 68 {
-                artifact_extension = "dmg";
-            } else {
-                artifact_extension = "pkg";
-            }
-            if is_browser_version_nightly {
-                platform_label = "osx";
-            } else {
-                platform_label = "mac";
-            }
-        } else {
-            // Linux
-            artifact_name = "firefox-";
-            artifact_extension = "tar.bz2";
-            if X32.is(arch) {
-                platform_label = "linux-i686";
-            } else if is_browser_version_nightly {
-                platform_label = "linux64";
-            } else {
-                platform_label = "linux-x86_64";
-            }
-        }
-
-        // A possible future improvement is to allow downloading language-specific releases
-        let language = FIREFOX_DEFAULT_LANG;
-        if is_browser_version_nightly {
-            Ok(format_two_args(
-                FIREFOX_NIGHTLY_URL,
-                platform_label,
-                language,
-            ))
-        } else {
-            let browser_version = self.get_browser_version();
-            Ok(format!(
-                "{}{}/{}/{}/{}{}.{}",
-                BROWSER_URL,
-                browser_version,
-                platform_label,
-                language,
-                artifact_name,
-                browser_version,
-                artifact_extension
-            ))
-        }
-    }
-
-    fn get_browser_binary_path_in_cache(&self) -> Result<PathBuf, Box<dyn Error>> {
-        let browser_in_cache = self.get_browser_path_in_cache()?;
-        if MACOS.is(self.get_os()) {
-            let macos_app_name = if self.is_browser_version_nightly() {
-                FIREFOX_NIGHTLY_MACOS_APP_NAME
-            } else {
-                FIREFOX_MACOS_APP_NAME
-            };
-            Ok(browser_in_cache.join(macos_app_name))
-        } else {
-            Ok(browser_in_cache.join(self.get_browser_name_with_extension()))
-        }
-    }
 }
 
 impl SeleniumManager for FirefoxManager {
@@ -388,105 +297,6 @@ impl SeleniumManager for FirefoxManager {
         self.log = log;
     }
 
-    fn download_browser(&mut self) -> Result<Option<PathBuf>, Box<dyn Error>> {
-        let browser_version;
-        let browser_name = self.browser_name;
-        let original_browser_version = self.get_config().browser_version.clone();
-        let cache_path = self.get_cache_path()?;
-        let mut metadata = get_metadata(self.get_logger(), &cache_path);
-        let major_browser_version = self.get_major_browser_version();
-        let is_browser_version_nightly = original_browser_version.eq_ignore_ascii_case(NIGHTLY)
-            || original_browser_version.eq_ignore_ascii_case(CANARY);
-
-        // Browser version is checked in the local metadata
-        match get_browser_version_from_metadata(
-            &metadata.browsers,
-            browser_name,
-            &major_browser_version,
-        ) {
-            Some(version) => {
-                self.get_logger().trace(format!(
-                    "Browser with valid TTL. Getting {} version from metadata",
-                    browser_name
-                ));
-                browser_version = version;
-                self.set_browser_version(browser_version.clone());
-            }
-            _ => {
-                // If not in metadata, discover version using Mozilla online metadata
-                if self.is_browser_version_stable() || self.is_browser_version_empty() {
-                    browser_version = self.request_latest_browser_version_from_online()?;
-                } else {
-                    browser_version = self.request_fixed_browser_version_from_online()?;
-                }
-                self.set_browser_version(browser_version.clone());
-
-                let browser_ttl = self.get_ttl();
-                if browser_ttl > 0
-                    && !self.is_browser_version_empty()
-                    && !self.is_browser_version_stable()
-                {
-                    metadata.browsers.push(create_browser_metadata(
-                        browser_name,
-                        &major_browser_version,
-                        &browser_version,
-                        browser_ttl,
-                    ));
-                    write_metadata(&metadata, self.get_logger(), cache_path);
-                }
-            }
-        }
-        self.get_logger().debug(format!(
-            "Required browser: {} {}",
-            browser_name, browser_version
-        ));
-
-        // Checking if browser version is in the cache
-        let browser_binary_path = self.get_browser_binary_path_in_cache()?;
-        if browser_binary_path.exists() {
-            self.get_logger().debug(format!(
-                "{} {} already in the cache",
-                browser_name, browser_version
-            ));
-        } else {
-            // If browser is not in the cache, download it
-            let browser_url = self.get_browser_url(is_browser_version_nightly)?;
-            self.get_logger().debug(format!(
-                "Downloading {} {} from {}",
-                self.get_browser_name(),
-                self.get_browser_version(),
-                browser_url
-            ));
-            let (_tmp_folder, driver_zip_file) =
-                download_to_tmp_folder(self.get_http_client(), browser_url, self.get_logger())?;
-
-            let major_browser_version_int = self
-                .get_major_browser_version()
-                .parse::<i32>()
-                .unwrap_or_default();
-            let volume = if is_browser_version_nightly {
-                FIREFOX_NIGHTLY_VOLUME
-            } else {
-                FIREFOX_VOLUME
-            };
-            uncompress(
-                &driver_zip_file,
-                &self.get_browser_path_in_cache()?,
-                self.get_logger(),
-                self.get_os(),
-                None,
-                Some(volume),
-                Some(major_browser_version_int),
-            )?;
-        }
-        if browser_binary_path.exists() {
-            self.set_browser_path(path_to_string(&browser_binary_path));
-            Ok(Some(browser_binary_path))
-        } else {
-            Ok(None)
-        }
-    }
-
     fn get_platform_label(&self) -> &str {
         let driver_version = self.get_driver_version();
         let os = self.get_os();
@@ -519,7 +329,10 @@ impl SeleniumManager for FirefoxManager {
         }
     }
 
-    fn request_latest_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>> {
+    fn request_latest_browser_version_from_online(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
         let browser_name = self.browser_name;
         self.get_logger().trace(format!(
             "Using Firefox endpoints to find out latest stable {} version",
@@ -538,7 +351,10 @@ impl SeleniumManager for FirefoxManager {
         Ok(browser_version.to_string())
     }
 
-    fn request_fixed_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>> {
+    fn request_fixed_browser_version_from_online(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
         let browser_name = self.browser_name;
         let browser_version = self.get_browser_version().to_string();
         self.get_logger().trace(format!(
@@ -564,19 +380,12 @@ impl SeleniumManager for FirefoxManager {
                 .unwrap();
             Ok(browser_version.to_string())
         } else {
-            let os = self.get_os();
             let major_browser_version = self
                 .get_major_browser_version()
                 .parse::<i32>()
                 .unwrap_or_default();
 
-            let min_downloadable_version = if WINDOWS.is(os) {
-                MIN_DOWNLOADABLE_FIREFOX_VERSION_WIN
-            } else if MACOS.is(os) {
-                MIN_DOWNLOADABLE_FIREFOX_VERSION_MAC
-            } else {
-                MIN_DOWNLOADABLE_FIREFOX_VERSION_LINUX
-            };
+            let min_downloadable_version = self.get_min_browser_version_for_download()?;
             if major_browser_version < min_downloadable_version {
                 return Err(format_three_args(
                     UNAVAILABLE_DOWNLOAD_ERROR_MESSAGE,
@@ -593,12 +402,7 @@ impl SeleniumManager for FirefoxManager {
                 firefox_versions =
                     self.request_versions_from_online(FIREFOX_HISTORY_DEV_ENDPOINT)?;
                 if firefox_versions.is_empty() {
-                    return Err(format_two_args(
-                        ONLINE_DISCOVERY_ERROR_MESSAGE,
-                        browser_name,
-                        self.get_browser_version(),
-                    )
-                    .into());
+                    return self.unavailable_discovery();
                 }
             }
 
@@ -611,14 +415,124 @@ impl SeleniumManager for FirefoxManager {
                     return Ok(version.to_string());
                 }
             }
-            Err(format_two_args(
-                ONLINE_DISCOVERY_ERROR_MESSAGE,
-                browser_name,
-                self.get_browser_version(),
-            )
-            .into())
+            self.unavailable_discovery()
+        }
+    }
+
+    fn get_min_browser_version_for_download(&self) -> Result<i32, Box<dyn Error>> {
+        let os = self.get_os();
+        let min_browser_version_for_download = if WINDOWS.is(os) {
+            MIN_DOWNLOADABLE_FIREFOX_VERSION_WIN
+        } else if MACOS.is(os) {
+            MIN_DOWNLOADABLE_FIREFOX_VERSION_MAC
+        } else {
+            MIN_DOWNLOADABLE_FIREFOX_VERSION_LINUX
+        };
+        Ok(min_browser_version_for_download)
+    }
+
+    fn get_browser_binary_path(
+        &mut self,
+        browser_version: &str,
+    ) -> Result<PathBuf, Box<dyn Error>> {
+        let browser_in_cache = self.get_browser_path_in_cache()?;
+        if MACOS.is(self.get_os()) {
+            let macos_app_name = if self.is_nightly(browser_version) {
+                FIREFOX_NIGHTLY_MACOS_APP_NAME
+            } else {
+                FIREFOX_MACOS_APP_NAME
+            };
+            Ok(browser_in_cache.join(macos_app_name))
+        } else {
+            Ok(browser_in_cache.join(self.get_browser_name_with_extension()))
+        }
+    }
+
+    fn get_browser_url_for_download(
+        &mut self,
+        browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
+        let arch = self.get_arch();
+        let os = self.get_os();
+        let platform_label;
+        let artifact_name;
+        let artifact_extension;
+        let major_browser_version = self
+            .get_major_browser_version()
+            .parse::<i32>()
+            .unwrap_or_default();
+
+        if WINDOWS.is(os) {
+            artifact_name = "Firefox%20Setup%20";
+            artifact_extension = "exe";
+            // Before Firefox 42, only Windows 32 was supported
+            if X32.is(arch) || major_browser_version < 42 {
+                platform_label = "win32";
+            } else if ARM64.is(arch) {
+                platform_label = "win-aarch64";
+            } else {
+                platform_label = "win64";
+            }
+        } else if MACOS.is(os) {
+            artifact_name = "Firefox%20";
+            // Before Firefox 68, only DMG was released
+            if major_browser_version < 68 {
+                artifact_extension = "dmg";
+            } else {
+                artifact_extension = "pkg";
+            }
+            if self.is_nightly(browser_version) {
+                platform_label = "osx";
+            } else {
+                platform_label = "mac";
+            }
+        } else {
+            // Linux
+            artifact_name = "firefox-";
+            artifact_extension = "tar.bz2";
+            if X32.is(arch) {
+                platform_label = "linux-i686";
+            } else if self.is_nightly(browser_version) {
+                platform_label = "linux64";
+            } else {
+                platform_label = "linux-x86_64";
+            }
+        }
+
+        // A possible future improvement is to allow downloading language-specific releases
+        let language = FIREFOX_DEFAULT_LANG;
+        if self.is_nightly(browser_version) {
+            Ok(format_two_args(
+                FIREFOX_NIGHTLY_URL,
+                platform_label,
+                language,
+            ))
+        } else {
+            let browser_version = self.get_browser_version();
+            Ok(format!(
+                "{}{}/{}/{}/{}{}.{}",
+                BROWSER_URL,
+                browser_version,
+                platform_label,
+                language,
+                artifact_name,
+                browser_version,
+                artifact_extension
+            ))
         }
     }
+
+    fn get_browser_label_for_download(
+        &self,
+        browser_version: &str,
+    ) -> Result<Option<&str>, Box<dyn Error>> {
+        let browser_label = if self.is_nightly(browser_version) {
+            FIREFOX_NIGHTLY_VOLUME
+        } else {
+            FIREFOX_VOLUME
+        };
+        Ok(Some(browser_label))
+    }
 }
 
 #[cfg(test)]
diff --git a/rust/src/grid.rs b/rust/src/grid.rs
index 39d3437adeb36..e3855893cacb3 100644
--- a/rust/src/grid.rs
+++ b/rust/src/grid.rs
@@ -224,19 +224,46 @@ impl SeleniumManager for GridManager {
         self.log = log;
     }
 
-    fn download_browser(&mut self) -> Result<Option<PathBuf>, Box<dyn Error>> {
-        Ok(None)
-    }
-
     fn get_platform_label(&self) -> &str {
         ""
     }
 
-    fn request_latest_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>> {
+    fn request_latest_browser_version_from_online(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn request_fixed_browser_version_from_online(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn get_min_browser_version_for_download(&self) -> Result<i32, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn get_browser_binary_path(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<PathBuf, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn get_browser_url_for_download(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
         self.unavailable_download()
     }
 
-    fn request_fixed_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>> {
+    fn get_browser_label_for_download(
+        &self,
+        _browser_version: &str,
+    ) -> Result<Option<&str>, Box<dyn Error>> {
         self.unavailable_download()
     }
 }
diff --git a/rust/src/iexplorer.rs b/rust/src/iexplorer.rs
index 1a60216dfcb0a..a045cae3b1232 100644
--- a/rust/src/iexplorer.rs
+++ b/rust/src/iexplorer.rs
@@ -229,19 +229,46 @@ impl SeleniumManager for IExplorerManager {
         self.log = log;
     }
 
-    fn download_browser(&mut self) -> Result<Option<PathBuf>, Box<dyn Error>> {
-        Ok(None)
-    }
-
     fn get_platform_label(&self) -> &str {
         "win32"
     }
 
-    fn request_latest_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>> {
+    fn request_latest_browser_version_from_online(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn request_fixed_browser_version_from_online(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn get_min_browser_version_for_download(&self) -> Result<i32, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn get_browser_binary_path(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<PathBuf, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn get_browser_url_for_download(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
         self.unavailable_download()
     }
 
-    fn request_fixed_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>> {
+    fn get_browser_label_for_download(
+        &self,
+        _browser_version: &str,
+    ) -> Result<Option<&str>, Box<dyn Error>> {
         self.unavailable_download()
     }
 }
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 28ddece6ae5a6..bbdb34d737e87 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -81,6 +81,8 @@ pub const HDIUTIL_DETACH_COMMAND: &str = "hdiutil detach /Volumes/{}";
 pub const CP_VOLUME_COMMAND: &str = "cp -R /Volumes/{}/{}.app {}";
 pub const MV_PAYLOAD_COMMAND: &str = "mv {}/*{}/Payload/*.app {}";
 pub const MV_PAYLOAD_OLD_VERSIONS_COMMAND: &str = "mv {}/Payload/*.app {}";
+pub const MSIEXEC_INSTALL_COMMAND: &str = "start /wait msiexec /i {} /qn ALLOWDOWNGRADE=1";
+pub const WINDOWS_CHECK_ADMIN_COMMAND: &str = "net session";
 pub const DASH_VERSION: &str = "{}{}{} -v";
 pub const DASH_DASH_VERSION: &str = "{}{}{} --version";
 pub const DOUBLE_QUOTE: &str = "\"";
@@ -101,7 +103,12 @@ pub const ESCAPE_COMMAND: &str = "printf %q \"{}\"";
 pub const SNAPSHOT: &str = "SNAPSHOT";
 pub const OFFLINE_REQUEST_ERR_MSG: &str = "Unable to discover proper {} version in offline mode";
 pub const OFFLINE_DOWNLOAD_ERR_MSG: &str = "Unable to download {} in offline mode";
-pub const UNAVAILABLE_DOWNLOAD_ERR_MSG: &str = "{} not available for downloading";
+pub const UNAVAILABLE_DOWNLOAD_ERR_MSG: &str = "{}{} not available for download";
+pub const UNAVAILABLE_DOWNLOAD_WITH_MIN_VERSION_ERR_MSG: &str =
+    "{} {} not available for download (minimum version: {})";
+pub const NOT_ADMIN_FOR_EDGE_INSTALLER_ERR_MSG: &str =
+    "{} can only be installed in Windows with administrator permissions";
+pub const ONLINE_DISCOVERY_ERROR_MESSAGE: &str = "Unable to discover {}{} in online repository";
 pub const UNC_PREFIX: &str = r#"\\?\"#;
 
 pub trait SeleniumManager {
@@ -141,13 +148,32 @@ pub trait SeleniumManager {
 
     fn set_logger(&mut self, log: Logger);
 
-    fn download_browser(&mut self) -> Result<Option<PathBuf>, Box<dyn Error>>;
-
     fn get_platform_label(&self) -> &str;
 
-    fn request_latest_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>>;
+    fn request_latest_browser_version_from_online(
+        &mut self,
+        browser_version: &str,
+    ) -> Result<String, Box<dyn Error>>;
+
+    fn request_fixed_browser_version_from_online(
+        &mut self,
+        browser_version: &str,
+    ) -> Result<String, Box<dyn Error>>;
+
+    fn get_min_browser_version_for_download(&self) -> Result<i32, Box<dyn Error>>;
+
+    fn get_browser_binary_path(&mut self, browser_version: &str)
+        -> Result<PathBuf, Box<dyn Error>>;
+
+    fn get_browser_url_for_download(
+        &mut self,
+        browser_version: &str,
+    ) -> Result<String, Box<dyn Error>>;
 
-    fn request_fixed_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>>;
+    fn get_browser_label_for_download(
+        &self,
+        _browser_version: &str,
+    ) -> Result<Option<&str>, Box<dyn Error>>;
 
     // ----------------------------------------------------------
     // Shared functions
@@ -183,22 +209,151 @@ pub trait SeleniumManager {
         }
     }
 
-    fn detect_browser_path(&mut self) -> Option<PathBuf> {
-        let mut browser_version = self.get_browser_version();
+    fn download_browser(&mut self) -> Result<Option<PathBuf>, Box<dyn Error>> {
+        if WINDOWS.is(self.get_os()) && self.is_edge() && !self.is_windows_admin() {
+            return Err(format_one_arg(
+                NOT_ADMIN_FOR_EDGE_INSTALLER_ERR_MSG,
+                self.get_browser_name(),
+            )
+            .into());
+        }
+
+        let browser_version;
+        let original_browser_version = self.get_config().browser_version.clone();
+        let cache_path = self.get_cache_path()?;
+        let mut metadata = get_metadata(self.get_logger(), &cache_path);
+        let major_browser_version = self.get_major_browser_version();
+        let major_browser_version_int = major_browser_version.parse::<i32>().unwrap_or_default();
+
+        // Browser version should be available for download
+        let min_browser_version_for_download = self.get_min_browser_version_for_download()?;
+        if !self.is_browser_version_unstable()
+            && !self.is_browser_version_stable()
+            && !self.is_browser_version_empty()
+            && major_browser_version_int < min_browser_version_for_download
+        {
+            return Err(format_three_args(
+                UNAVAILABLE_DOWNLOAD_WITH_MIN_VERSION_ERR_MSG,
+                self.get_browser_name(),
+                &major_browser_version,
+                &min_browser_version_for_download.to_string(),
+            )
+            .into());
+        }
+
+        // Browser version is checked in the local metadata
+        match get_browser_version_from_metadata(
+            &metadata.browsers,
+            self.get_browser_name(),
+            &major_browser_version,
+        ) {
+            Some(version) => {
+                self.get_logger().trace(format!(
+                    "Browser with valid TTL. Getting {} version from metadata",
+                    self.get_browser_name()
+                ));
+                browser_version = version;
+                self.set_browser_version(browser_version.clone());
+            }
+            _ => {
+                // If not in metadata, discover version using online metadata
+                if self.is_browser_version_stable() || self.is_browser_version_empty() {
+                    browser_version =
+                        self.request_latest_browser_version_from_online(&original_browser_version)?;
+                } else {
+                    browser_version =
+                        self.request_fixed_browser_version_from_online(&original_browser_version)?;
+                }
+                self.set_browser_version(browser_version.clone());
+
+                let browser_ttl = self.get_ttl();
+                if browser_ttl > 0
+                    && !self.is_browser_version_empty()
+                    && !self.is_browser_version_stable()
+                {
+                    metadata.browsers.push(create_browser_metadata(
+                        self.get_browser_name(),
+                        &major_browser_version,
+                        &browser_version,
+                        browser_ttl,
+                    ));
+                    write_metadata(&metadata, self.get_logger(), cache_path);
+                }
+            }
+        }
+        self.get_logger().debug(format!(
+            "Required browser: {} {}",
+            self.get_browser_name(),
+            browser_version
+        ));
+
+        // Checking if browser version is in the cache
+        let browser_binary_path = self.get_browser_binary_path(&original_browser_version)?;
+        if browser_binary_path.exists() && !self.is_edge() {
+            self.get_logger().debug(format!(
+                "{} {} already exists",
+                self.get_browser_name(),
+                browser_version
+            ));
+        } else {
+            // If browser is not available, download it
+            let browser_url = self.get_browser_url_for_download(&original_browser_version)?;
+            self.get_logger().debug(format!(
+                "Downloading {} {} from {}",
+                self.get_browser_name(),
+                self.get_browser_version(),
+                browser_url
+            ));
+            let (_tmp_folder, driver_zip_file) =
+                download_to_tmp_folder(self.get_http_client(), browser_url, self.get_logger())?;
+
+            let major_browser_version_int = self
+                .get_major_browser_version()
+                .parse::<i32>()
+                .unwrap_or_default();
+            let browser_label_for_download =
+                self.get_browser_label_for_download(&original_browser_version)?;
+            uncompress(
+                &driver_zip_file,
+                &self.get_browser_path_in_cache()?,
+                self.get_logger(),
+                self.get_os(),
+                None,
+                browser_label_for_download,
+                Some(major_browser_version_int),
+            )?;
+        }
+        if browser_binary_path.exists() {
+            self.set_browser_path(path_to_string(&browser_binary_path));
+            Ok(Some(browser_binary_path))
+        } else {
+            self.get_logger().warn(format!(
+                "Expected {} path does not exists: {}",
+                self.get_browser_name(),
+                browser_binary_path.display()
+            ));
+            Ok(None)
+        }
+    }
+
+    fn get_browser_path_from_version(&self, mut browser_version: &str) -> &str {
         if browser_version.eq_ignore_ascii_case(CANARY) {
             browser_version = NIGHTLY;
-        } else if !self.is_browser_version_unstable() {
+        } else if !self.is_unstable(browser_version) {
             browser_version = STABLE;
         }
-
-        let browser_path = self
-            .get_browser_path_map()
+        self.get_browser_path_map()
             .get(&BrowserPath::new(
                 str_to_os(self.get_os()).unwrap(),
                 browser_version,
             ))
             .cloned()
-            .unwrap_or_default();
+            .unwrap_or_default()
+    }
+
+    fn detect_browser_path(&mut self) -> Option<PathBuf> {
+        let browser_version = self.get_browser_version();
+        let browser_path = self.get_browser_path_from_version(browser_version);
 
         let mut full_browser_path = Path::new(browser_path).to_path_buf();
         if WINDOWS.is(self.get_os()) {
@@ -324,7 +479,7 @@ pub trait SeleniumManager {
                         && !major_browser_version.eq(&discovered_major_browser_version)
                     {
                         self.get_logger().debug(format!(
-                            "Discovered online {} version ({}) different to specified browser version ({})",
+                            "Discovered {} version ({}) different to specified browser version ({})",
                             self.get_browser_name(),
                             discovered_major_browser_version,
                             major_browser_version,
@@ -345,16 +500,21 @@ pub trait SeleniumManager {
             }
         }
 
-        if download_browser && !self.is_avoid_browser_download() {
+        if download_browser
+            && !self.is_avoid_browser_download()
+            && !self.is_iexplorer()
+            && !self.is_grid()
+            && !self.is_safari()
+        {
             let browser_path = self.download_browser()?;
             if browser_path.is_some() {
                 self.get_logger().debug(format!(
-                    "{} {} has been downloaded at {}",
+                    "{} {} is available at {}",
                     self.get_browser_name(),
                     self.get_browser_version(),
                     browser_path.unwrap().display()
                 ));
-            } else if !self.is_iexplorer() && !self.is_grid() {
+            } else if !self.is_iexplorer() && !self.is_grid() && !self.is_safari() {
                 return Err(format!(
                     "{}{} cannot be downloaded",
                     self.get_browser_name(),
@@ -459,6 +619,17 @@ pub trait SeleniumManager {
         }
     }
 
+    fn is_windows_admin(&self) -> bool {
+        let os = self.get_os();
+        if WINDOWS.is(os) {
+            let command = Command::new_single(WINDOWS_CHECK_ADMIN_COMMAND.to_string());
+            let output = run_shell_command_by_os(os, command).unwrap_or_default();
+            !output.is_empty() && !output.contains("error")
+        } else {
+            false
+        }
+    }
+
     fn is_safari(&self) -> bool {
         self.get_browser_name().contains(SAFARI_NAME)
     }
@@ -475,34 +646,60 @@ pub trait SeleniumManager {
         self.get_browser_name().contains(FIREFOX_NAME)
     }
 
+    fn is_edge(&self) -> bool {
+        self.get_browser_name().eq(EDGE_NAMES[0])
+    }
+
     fn is_browser_version_beta(&self) -> bool {
-        self.get_browser_version().eq_ignore_ascii_case(BETA)
+        self.is_beta(self.get_browser_version())
+    }
+
+    fn is_beta(&self, browser_version: &str) -> bool {
+        browser_version.eq_ignore_ascii_case(BETA)
     }
 
     fn is_browser_version_dev(&self) -> bool {
-        self.get_browser_version().eq_ignore_ascii_case(DEV)
+        self.is_dev(self.get_browser_version())
+    }
+
+    fn is_dev(&self, browser_version: &str) -> bool {
+        browser_version.eq_ignore_ascii_case(DEV)
     }
 
     fn is_browser_version_nightly(&self) -> bool {
-        let browser_version = self.get_browser_version();
+        self.is_nightly(self.get_browser_version())
+    }
+
+    fn is_nightly(&self, browser_version: &str) -> bool {
         browser_version.eq_ignore_ascii_case(NIGHTLY)
             || browser_version.eq_ignore_ascii_case(CANARY)
     }
 
-    fn is_browser_version_unstable(&self) -> bool {
-        let browser_version = self.get_browser_version();
+    fn is_unstable(&self, 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)
     }
 
+    fn is_browser_version_unstable(&self) -> bool {
+        self.is_unstable(self.get_browser_version())
+    }
+
+    fn is_empty(&self, browser_version: &str) -> bool {
+        browser_version.is_empty()
+    }
+
     fn is_browser_version_empty(&self) -> bool {
-        self.get_browser_version().is_empty()
+        self.is_empty(self.get_browser_version())
+    }
+
+    fn is_stable(&self, browser_version: &str) -> bool {
+        browser_version.eq_ignore_ascii_case(STABLE)
     }
 
     fn is_browser_version_stable(&self) -> bool {
-        self.get_browser_version().eq_ignore_ascii_case(STABLE)
+        self.is_stable(self.get_browser_version())
     }
 
     fn setup(&mut self) -> Result<PathBuf, Box<dyn Error>> {
@@ -718,6 +915,7 @@ pub trait SeleniumManager {
         browser_name: &str,
     ) -> Result<Option<String>, Box<dyn Error>> {
         let browser_version;
+        let original_browser_version = self.get_config().browser_version.clone();
         let major_browser_version = self.get_major_browser_version();
         let cache_path = self.get_cache_path()?;
         let mut metadata = get_metadata(self.get_logger(), &cache_path);
@@ -737,12 +935,12 @@ pub trait SeleniumManager {
                 self.set_browser_version(browser_version.clone());
             }
             _ => {
-                // If not in metadata, discover version using Chrome for Testing (CfT) endpoints
+                // If not in metadata, discover version using online endpoints
                 browser_version =
                     if major_browser_version.is_empty() || self.is_browser_version_stable() {
-                        self.request_latest_browser_version_from_online()?
+                        self.request_latest_browser_version_from_online(&original_browser_version)?
                     } else {
-                        self.request_fixed_browser_version_from_online()?
+                        self.request_fixed_browser_version_from_online(&original_browser_version)?
                     };
 
                 let browser_ttl = self.get_ttl();
@@ -861,8 +1059,36 @@ pub trait SeleniumManager {
         }
     }
 
-    fn unavailable_download(&mut self) -> Result<String, Box<dyn Error>> {
-        Err(format_one_arg(UNAVAILABLE_DOWNLOAD_ERR_MSG, self.get_browser_name()).into())
+    fn unavailable_download<T>(&self) -> Result<T, Box<dyn Error>>
+    where
+        Self: Sized,
+    {
+        self.throw_error_message(UNAVAILABLE_DOWNLOAD_ERR_MSG)
+    }
+
+    fn unavailable_discovery<T>(&self) -> Result<T, Box<dyn Error>>
+    where
+        Self: Sized,
+    {
+        self.throw_error_message(ONLINE_DISCOVERY_ERROR_MESSAGE)
+    }
+
+    fn throw_error_message<T>(&self, error_message: &str) -> Result<T, Box<dyn Error>>
+    where
+        Self: Sized,
+    {
+        let browser_version = self.get_browser_version();
+        let browser_version_label = if browser_version.is_empty() {
+            "".to_string()
+        } else {
+            format!(" {}", browser_version)
+        };
+        Err(format_two_args(
+            error_message,
+            self.get_browser_name(),
+            &browser_version_label,
+        )
+        .into())
     }
 
     // ----------------------------------------------------------
diff --git a/rust/src/safari.rs b/rust/src/safari.rs
index 4489269e278e5..1a6ae66c4d4df 100644
--- a/rust/src/safari.rs
+++ b/rust/src/safari.rs
@@ -122,19 +122,46 @@ impl SeleniumManager for SafariManager {
         self.log = log;
     }
 
-    fn download_browser(&mut self) -> Result<Option<PathBuf>, Box<dyn Error>> {
-        Ok(None)
-    }
-
     fn get_platform_label(&self) -> &str {
         ""
     }
 
-    fn request_latest_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>> {
+    fn request_latest_browser_version_from_online(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn request_fixed_browser_version_from_online(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn get_min_browser_version_for_download(&self) -> Result<i32, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn get_browser_binary_path(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<PathBuf, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn get_browser_url_for_download(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
         self.unavailable_download()
     }
 
-    fn request_fixed_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>> {
+    fn get_browser_label_for_download(
+        &self,
+        _browser_version: &str,
+    ) -> Result<Option<&str>, Box<dyn Error>> {
         self.unavailable_download()
     }
 }
diff --git a/rust/src/safaritp.rs b/rust/src/safaritp.rs
index 70d1720cc64e8..4ce0ccc617b68 100644
--- a/rust/src/safaritp.rs
+++ b/rust/src/safaritp.rs
@@ -130,19 +130,46 @@ impl SeleniumManager for SafariTPManager {
         self.log = log;
     }
 
-    fn download_browser(&mut self) -> Result<Option<PathBuf>, Box<dyn Error>> {
-        Ok(None)
-    }
-
     fn get_platform_label(&self) -> &str {
         ""
     }
 
-    fn request_latest_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>> {
+    fn request_latest_browser_version_from_online(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn request_fixed_browser_version_from_online(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn get_min_browser_version_for_download(&self) -> Result<i32, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn get_browser_binary_path(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<PathBuf, Box<dyn Error>> {
+        self.unavailable_download()
+    }
+
+    fn get_browser_url_for_download(
+        &mut self,
+        _browser_version: &str,
+    ) -> Result<String, Box<dyn Error>> {
         self.unavailable_download()
     }
 
-    fn request_fixed_browser_version_from_online(&mut self) -> Result<String, Box<dyn Error>> {
+    fn get_browser_label_for_download(
+        &self,
+        _browser_version: &str,
+    ) -> Result<Option<&str>, Box<dyn Error>> {
         self.unavailable_download()
     }
 }
diff --git a/rust/tests/browser_download_tests.rs b/rust/tests/browser_download_tests.rs
index be8daad88d951..404d6b29cb8ab 100644
--- a/rust/tests/browser_download_tests.rs
+++ b/rust/tests/browser_download_tests.rs
@@ -16,6 +16,7 @@
 // under the License.
 
 use assert_cmd::Command;
+use std::env::consts::OS;
 
 use crate::common::{assert_browser, assert_driver};
 use rstest::rstest;
@@ -25,6 +26,7 @@ mod common;
 #[rstest]
 #[case("chrome")]
 #[case("firefox")]
+#[case("edge")]
 fn browser_latest_download_test(#[case] browser: String) {
     let mut cmd = Command::new(env!("CARGO_BIN_EXE_selenium-manager"));
     cmd.args([
@@ -40,7 +42,9 @@ fn browser_latest_download_test(#[case] browser: String) {
     .code(0);
 
     assert_driver(&mut cmd);
-    assert_browser(&mut cmd);
+    if !OS.eq("windows") {
+        assert_browser(&mut cmd);
+    }
 }
 
 #[rstest]
@@ -48,6 +52,7 @@ fn browser_latest_download_test(#[case] browser: String) {
 #[case("chrome", "beta")]
 #[case("firefox", "116")]
 #[case("firefox", "beta")]
+#[case("edge", "beta")]
 fn browser_version_download_test(#[case] browser: String, #[case] browser_version: String) {
     let mut cmd = Command::new(env!("CARGO_BIN_EXE_selenium-manager"));
     cmd.args([
@@ -64,5 +69,7 @@ fn browser_version_download_test(#[case] browser: String, #[case] browser_versio
     .code(0);
 
     assert_driver(&mut cmd);
-    assert_browser(&mut cmd);
+    if !OS.eq("windows") {
+        assert_browser(&mut cmd);
+    }
 }