Skip to content

Commit e477491

Browse files
bonigarciadiemol
andauthored
[rust] Electron support in Selenium-Manager (#13954) (#15752)
Co-authored-by: Diego Molina <diemol@users.noreply.github.com>
1 parent cbcae0c commit e477491

File tree

5 files changed

+329
-5
lines changed

5 files changed

+329
-5
lines changed

rust/src/electron.rs

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use crate::chrome::CHROMEDRIVER_NAME;
19+
use crate::config::ManagerConfig;
20+
use crate::config::ARCH::{ARM64, X32};
21+
use crate::config::OS::MACOS;
22+
use crate::downloads::read_redirect_from_link;
23+
use crate::files::{compose_driver_path_in_cache, BrowserPath};
24+
use crate::metadata::{
25+
create_driver_metadata, get_driver_version_from_metadata, get_metadata, write_metadata,
26+
};
27+
use crate::{
28+
create_http_client, Logger, SeleniumManager, LATEST_RELEASE, OFFLINE_REQUEST_ERR_MSG, WINDOWS,
29+
};
30+
use anyhow::Error;
31+
use reqwest::Client;
32+
use std::collections::HashMap;
33+
use std::path::PathBuf;
34+
use std::sync::mpsc;
35+
use std::sync::mpsc::{Receiver, Sender};
36+
37+
pub const ELECTRON_NAME: &str = "electron";
38+
const DRIVER_URL: &str = "https://github.com/electron/electron/releases/";
39+
40+
pub struct ElectronManager {
41+
pub browser_name: &'static str,
42+
pub driver_name: &'static str,
43+
pub config: ManagerConfig,
44+
pub http_client: Client,
45+
pub log: Logger,
46+
pub tx: Sender<String>,
47+
pub rx: Receiver<String>,
48+
pub download_browser: bool,
49+
pub driver_url: Option<String>,
50+
}
51+
52+
impl ElectronManager {
53+
pub fn new() -> Result<Box<Self>, Error> {
54+
let browser_name = ELECTRON_NAME;
55+
let driver_name = CHROMEDRIVER_NAME;
56+
let config = ManagerConfig::default(browser_name, driver_name);
57+
let default_timeout = config.timeout.to_owned();
58+
let default_proxy = &config.proxy;
59+
let (tx, rx): (Sender<String>, Receiver<String>) = mpsc::channel();
60+
Ok(Box::new(ElectronManager {
61+
browser_name,
62+
driver_name,
63+
http_client: create_http_client(default_timeout, default_proxy)?,
64+
config,
65+
log: Logger::new(),
66+
tx,
67+
rx,
68+
download_browser: false,
69+
driver_url: None,
70+
}))
71+
}
72+
}
73+
74+
impl SeleniumManager for ElectronManager {
75+
fn get_browser_name(&self) -> &str {
76+
self.browser_name
77+
}
78+
79+
fn get_browser_names_in_path(&self) -> Vec<&str> {
80+
vec![self.get_browser_name()]
81+
}
82+
83+
fn get_http_client(&self) -> &Client {
84+
&self.http_client
85+
}
86+
87+
fn set_http_client(&mut self, http_client: Client) {
88+
self.http_client = http_client;
89+
}
90+
91+
fn get_browser_path_map(&self) -> HashMap<BrowserPath, &str> {
92+
HashMap::new()
93+
}
94+
95+
fn discover_browser_version(&mut self) -> Result<Option<String>, Error> {
96+
Ok(None)
97+
}
98+
99+
fn get_driver_name(&self) -> &str {
100+
self.driver_name
101+
}
102+
103+
fn request_driver_version(&mut self) -> Result<String, Error> {
104+
let major_browser_version_binding = self.get_major_browser_version();
105+
let major_browser_version = major_browser_version_binding.as_str();
106+
let cache_path = self.get_cache_path()?;
107+
let mut metadata = get_metadata(self.get_logger(), &cache_path);
108+
109+
match get_driver_version_from_metadata(
110+
&metadata.drivers,
111+
self.driver_name,
112+
major_browser_version,
113+
) {
114+
Some(driver_version) => {
115+
self.log.trace(format!(
116+
"Driver TTL is valid. Getting {} version from metadata",
117+
&self.driver_name
118+
));
119+
Ok(driver_version)
120+
}
121+
_ => {
122+
self.assert_online_or_err(OFFLINE_REQUEST_ERR_MSG)?;
123+
124+
let latest_url = format!(
125+
"{}{}",
126+
self.get_driver_mirror_url_or_default(DRIVER_URL),
127+
LATEST_RELEASE
128+
);
129+
let driver_version =
130+
read_redirect_from_link(self.get_http_client(), latest_url, self.get_logger())?;
131+
let driver_ttl = self.get_ttl();
132+
if driver_ttl > 0 {
133+
metadata.drivers.push(create_driver_metadata(
134+
major_browser_version,
135+
self.driver_name,
136+
&driver_version,
137+
driver_ttl,
138+
));
139+
write_metadata(&metadata, self.get_logger(), cache_path);
140+
}
141+
Ok(driver_version)
142+
}
143+
}
144+
}
145+
146+
fn request_browser_version(&mut self) -> Result<Option<String>, Error> {
147+
Ok(None)
148+
}
149+
150+
fn get_driver_url(&mut self) -> Result<String, Error> {
151+
if self.driver_url.is_some() {
152+
return Ok(self.driver_url.as_ref().unwrap().to_string());
153+
}
154+
155+
Ok(format!(
156+
"{}download/v{}/{}-v{}-{}.zip",
157+
self.get_driver_mirror_url_or_default(DRIVER_URL),
158+
self.get_driver_version(),
159+
CHROMEDRIVER_NAME,
160+
self.get_driver_version(),
161+
self.get_platform_label()
162+
))
163+
}
164+
165+
fn get_driver_path_in_cache(&self) -> Result<PathBuf, Error> {
166+
Ok(compose_driver_path_in_cache(
167+
self.get_cache_path()?.unwrap_or_default(),
168+
self.driver_name,
169+
self.get_os(),
170+
self.get_platform_label(),
171+
self.get_driver_version(),
172+
))
173+
}
174+
175+
fn get_config(&self) -> &ManagerConfig {
176+
&self.config
177+
}
178+
179+
fn get_config_mut(&mut self) -> &mut ManagerConfig {
180+
&mut self.config
181+
}
182+
183+
fn set_config(&mut self, config: ManagerConfig) {
184+
self.config = config;
185+
}
186+
187+
fn get_logger(&self) -> &Logger {
188+
&self.log
189+
}
190+
191+
fn set_logger(&mut self, log: Logger) {
192+
self.log = log;
193+
}
194+
195+
fn get_sender(&self) -> &Sender<String> {
196+
&self.tx
197+
}
198+
199+
fn get_receiver(&self) -> &Receiver<String> {
200+
&self.rx
201+
}
202+
203+
fn get_platform_label(&self) -> &str {
204+
let os = self.get_os();
205+
let arch = self.get_arch();
206+
if WINDOWS.is(os) {
207+
if X32.is(arch) {
208+
"win32-ia32"
209+
} else if ARM64.is(arch) {
210+
"win32-arm64-x64"
211+
} else {
212+
"win32-x64"
213+
}
214+
} else if MACOS.is(os) {
215+
if ARM64.is(arch) {
216+
"mas-arm64"
217+
} else {
218+
"mas-x64"
219+
}
220+
} else if ARM64.is(arch) {
221+
"linux-arm64"
222+
} else {
223+
"linux-x64"
224+
}
225+
}
226+
227+
fn request_latest_browser_version_from_online(
228+
&mut self,
229+
_browser_version: &str,
230+
) -> Result<String, Error> {
231+
self.unavailable_download()
232+
}
233+
234+
fn request_fixed_browser_version_from_online(
235+
&mut self,
236+
_browser_version: &str,
237+
) -> Result<String, Error> {
238+
self.unavailable_download()
239+
}
240+
241+
fn get_min_browser_version_for_download(&self) -> Result<i32, Error> {
242+
self.unavailable_download()
243+
}
244+
245+
fn get_browser_binary_path(&mut self, _browser_version: &str) -> Result<PathBuf, Error> {
246+
self.unavailable_download()
247+
}
248+
249+
fn get_browser_url_for_download(&mut self, _browser_version: &str) -> Result<String, Error> {
250+
self.unavailable_download()
251+
}
252+
253+
fn get_browser_label_for_download(
254+
&self,
255+
_browser_version: &str,
256+
) -> Result<Option<&str>, Error> {
257+
self.unavailable_download()
258+
}
259+
260+
fn is_download_browser(&self) -> bool {
261+
self.download_browser
262+
}
263+
264+
fn set_download_browser(&mut self, download_browser: bool) {
265+
self.download_browser = download_browser;
266+
}
267+
268+
fn is_snap(&self, _browser_path: &str) -> bool {
269+
false
270+
}
271+
272+
fn get_snap_path(&self) -> Option<PathBuf> {
273+
None
274+
}
275+
}

rust/src/firefox.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ use crate::metadata::{
2525
};
2626
use crate::{
2727
create_http_client, format_three_args, format_two_args, Logger, SeleniumManager, BETA,
28-
DASH_VERSION, DEV, ESR, NIGHTLY, OFFLINE_REQUEST_ERR_MSG, REG_CURRENT_VERSION_ARG, STABLE,
28+
DASH_VERSION, DEV, ESR, LATEST_RELEASE, NIGHTLY, OFFLINE_REQUEST_ERR_MSG,
29+
REG_CURRENT_VERSION_ARG, STABLE,
2930
};
3031
use anyhow::anyhow;
3132
use anyhow::Error;
@@ -41,7 +42,6 @@ use std::sync::mpsc::{Receiver, Sender};
4142
pub const FIREFOX_NAME: &str = "firefox";
4243
pub const GECKODRIVER_NAME: &str = "geckodriver";
4344
const DRIVER_URL: &str = "https://github.com/mozilla/geckodriver/releases/";
44-
const LATEST_RELEASE: &str = "latest";
4545
const DRIVER_VERSIONS_URL: &str = "https://raw.githubusercontent.com/SeleniumHQ/selenium/trunk/common/geckodriver/geckodriver-support.json";
4646
const BROWSER_URL: &str = "https://ftp.mozilla.org/pub/firefox/releases/";
4747
const FIREFOX_DEFAULT_LANG: &str = "en-US";

rust/src/lib.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::config::OS::{MACOS, WINDOWS};
2020
use crate::config::{str_to_os, ManagerConfig};
2121
use crate::downloads::download_to_tmp_folder;
2222
use crate::edge::{EdgeManager, EDGEDRIVER_NAME, EDGE_NAMES, WEBVIEW2_NAME};
23+
use crate::electron::{ElectronManager, ELECTRON_NAME};
2324
use crate::files::get_win_file_version;
2425
use crate::files::{
2526
capitalize, collect_files_from_cache, create_path_if_not_exists, default_cache_folder,
@@ -56,6 +57,7 @@ pub mod chrome;
5657
pub mod config;
5758
pub mod downloads;
5859
pub mod edge;
60+
pub mod electron;
5961
pub mod files;
6062
pub mod firefox;
6163
pub mod grid;
@@ -112,6 +114,7 @@ pub const NOT_ADMIN_FOR_EDGE_INSTALLER_ERR_MSG: &str =
112114
pub const ONLINE_DISCOVERY_ERROR_MESSAGE: &str = "Unable to discover {}{} in online repository";
113115
pub const UNC_PREFIX: &str = r"\\?\";
114116
pub const SM_BETA_LABEL: &str = "0.";
117+
pub const LATEST_RELEASE: &str = "latest";
115118

116119
pub trait SeleniumManager {
117120
// ----------------------------------------------------------
@@ -473,7 +476,7 @@ pub trait SeleniumManager {
473476

474477
fn discover_local_browser(&mut self) -> Result<(), Error> {
475478
let mut download_browser = self.is_force_browser_download();
476-
if !download_browser {
479+
if !download_browser && !self.is_electron() {
477480
let major_browser_version = self.get_major_browser_version();
478481
match self.discover_browser_version()? {
479482
Some(discovered_version) => {
@@ -563,6 +566,7 @@ pub trait SeleniumManager {
563566
&& !self.is_grid()
564567
&& !self.is_safari()
565568
&& !self.is_webview2()
569+
&& !self.is_electron()
566570
{
567571
let browser_path = self.download_browser(original_browser_version)?;
568572
if browser_path.is_some() {
@@ -691,6 +695,10 @@ pub trait SeleniumManager {
691695
self.get_browser_name().eq(GRID_NAME)
692696
}
693697

698+
fn is_electron(&self) -> bool {
699+
self.get_browser_name().eq_ignore_ascii_case(ELECTRON_NAME)
700+
}
701+
694702
fn is_firefox(&self) -> bool {
695703
self.get_browser_name().contains(FIREFOX_NAME)
696704
}
@@ -778,7 +786,7 @@ pub trait SeleniumManager {
778786
let original_browser_version = self.get_config().browser_version.clone();
779787

780788
// Try to find driver in PATH
781-
if !self.is_safari() && !self.is_grid() {
789+
if !self.is_safari() && !self.is_grid() && !self.is_electron() {
782790
self.get_logger()
783791
.trace(format!("Checking {} in PATH", self.get_driver_name()));
784792
(driver_in_path_version, driver_in_path) = self.find_driver_in_path();
@@ -1619,6 +1627,8 @@ pub fn get_manager_by_browser(browser_name: String) -> Result<Box<dyn SeleniumMa
16191627
Ok(SafariManager::new()?)
16201628
} else if SAFARITP_NAMES.contains(&browser_name_lower_case.as_str()) {
16211629
Ok(SafariTPManager::new()?)
1630+
} else if browser_name_lower_case.eq(ELECTRON_NAME) {
1631+
Ok(ElectronManager::new()?)
16221632
} else {
16231633
Err(anyhow!(format!("Invalid browser name: {browser_name}")))
16241634
}

rust/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ use std::sync::mpsc::Receiver;
4343
{usage-heading} {usage}
4444
{all-args}")]
4545
struct Cli {
46-
/// Browser name (chrome, firefox, edge, iexplorer, safari, safaritp, or webview2)
46+
/// Browser name (chrome, firefox, edge, iexplorer, safari, safaritp, webview2, or electron)
4747
#[clap(long, value_parser)]
4848
browser: Option<String>,
4949

0 commit comments

Comments
 (0)