Skip to content

[rust] Electron support in Selenium-Manager (#13954) #15752

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 275 additions & 0 deletions rust/src/electron.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

use crate::chrome::CHROMEDRIVER_NAME;
use crate::config::ManagerConfig;
use crate::config::ARCH::{ARM64, X32};
use crate::config::OS::MACOS;
use crate::downloads::read_redirect_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, LATEST_RELEASE, OFFLINE_REQUEST_ERR_MSG, WINDOWS,
};
use anyhow::Error;
use reqwest::Client;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};

pub const ELECTRON_NAME: &str = "electron";
const DRIVER_URL: &str = "https://github.com/electron/electron/releases/";

pub struct ElectronManager {
pub browser_name: &'static str,
pub driver_name: &'static str,
pub config: ManagerConfig,
pub http_client: Client,
pub log: Logger,
pub tx: Sender<String>,
pub rx: Receiver<String>,
pub download_browser: bool,
pub driver_url: Option<String>,
}

impl ElectronManager {
pub fn new() -> Result<Box<Self>, Error> {
let browser_name = ELECTRON_NAME;
let driver_name = CHROMEDRIVER_NAME;
let config = ManagerConfig::default(browser_name, driver_name);
let default_timeout = config.timeout.to_owned();
let default_proxy = &config.proxy;
let (tx, rx): (Sender<String>, Receiver<String>) = mpsc::channel();
Ok(Box::new(ElectronManager {
browser_name,
driver_name,
http_client: create_http_client(default_timeout, default_proxy)?,
config,
log: Logger::new(),
tx,
rx,
download_browser: false,
driver_url: None,
}))
}
}

impl SeleniumManager for ElectronManager {
fn get_browser_name(&self) -> &str {
self.browser_name
}

fn get_browser_names_in_path(&self) -> Vec<&str> {
vec![self.get_browser_name()]
}

fn get_http_client(&self) -> &Client {
&self.http_client
}

fn set_http_client(&mut self, http_client: Client) {
self.http_client = http_client;
}

fn get_browser_path_map(&self) -> HashMap<BrowserPath, &str> {
HashMap::new()
}

fn discover_browser_version(&mut self) -> Result<Option<String>, Error> {
Ok(None)
}

fn get_driver_name(&self) -> &str {
self.driver_name
}

fn request_driver_version(&mut self) -> Result<String, Error> {
let major_browser_version_binding = self.get_major_browser_version();
let major_browser_version = major_browser_version_binding.as_str();
let cache_path = self.get_cache_path()?;
let mut metadata = get_metadata(self.get_logger(), &cache_path);

match get_driver_version_from_metadata(
&metadata.drivers,
self.driver_name,
major_browser_version,
) {
Some(driver_version) => {
self.log.trace(format!(
"Driver TTL is valid. Getting {} version from metadata",
&self.driver_name
));
Ok(driver_version)
}
_ => {
self.assert_online_or_err(OFFLINE_REQUEST_ERR_MSG)?;

let latest_url = format!(
"{}{}",
self.get_driver_mirror_url_or_default(DRIVER_URL),
LATEST_RELEASE
);
let driver_version =
read_redirect_from_link(self.get_http_client(), latest_url, self.get_logger())?;
let driver_ttl = self.get_ttl();
if driver_ttl > 0 {
metadata.drivers.push(create_driver_metadata(
major_browser_version,
self.driver_name,
&driver_version,
driver_ttl,
));
write_metadata(&metadata, self.get_logger(), cache_path);
}
Ok(driver_version)
}
}
}

fn request_browser_version(&mut self) -> Result<Option<String>, Error> {
Ok(None)
}

fn get_driver_url(&mut self) -> Result<String, Error> {
if self.driver_url.is_some() {
return Ok(self.driver_url.as_ref().unwrap().to_string());
}

Ok(format!(
"{}download/v{}/{}-v{}-{}.zip",
self.get_driver_mirror_url_or_default(DRIVER_URL),
self.get_driver_version(),
CHROMEDRIVER_NAME,
self.get_driver_version(),
self.get_platform_label()
))
}

fn get_driver_path_in_cache(&self) -> Result<PathBuf, Error> {
Ok(compose_driver_path_in_cache(
self.get_cache_path()?.unwrap_or_default(),
self.driver_name,
self.get_os(),
self.get_platform_label(),
self.get_driver_version(),
))
}

fn get_config(&self) -> &ManagerConfig {
&self.config
}

fn get_config_mut(&mut self) -> &mut ManagerConfig {
&mut self.config
}

fn set_config(&mut self, config: ManagerConfig) {
self.config = config;
}

fn get_logger(&self) -> &Logger {
&self.log
}

fn set_logger(&mut self, log: Logger) {
self.log = log;
}

fn get_sender(&self) -> &Sender<String> {
&self.tx
}

fn get_receiver(&self) -> &Receiver<String> {
&self.rx
}

fn get_platform_label(&self) -> &str {
let os = self.get_os();
let arch = self.get_arch();
if WINDOWS.is(os) {
if X32.is(arch) {
"win32-ia32"
} else if ARM64.is(arch) {
"win32-arm64-x64"
} else {
"win32-x64"
}
} else if MACOS.is(os) {
if ARM64.is(arch) {
"mas-arm64"
} else {
"mas-x64"
}
} else if ARM64.is(arch) {
"linux-arm64"
} else {
"linux-x64"
}
}

fn request_latest_browser_version_from_online(
&mut self,
_browser_version: &str,
) -> Result<String, Error> {
self.unavailable_download()
}

fn request_fixed_browser_version_from_online(
&mut self,
_browser_version: &str,
) -> Result<String, Error> {
self.unavailable_download()
}

fn get_min_browser_version_for_download(&self) -> Result<i32, Error> {
self.unavailable_download()
}

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

fn get_browser_url_for_download(&mut self, _browser_version: &str) -> Result<String, Error> {
self.unavailable_download()
}

fn get_browser_label_for_download(
&self,
_browser_version: &str,
) -> Result<Option<&str>, Error> {
self.unavailable_download()
}

fn is_download_browser(&self) -> bool {
self.download_browser
}

fn set_download_browser(&mut self, download_browser: bool) {
self.download_browser = download_browser;
}

fn is_snap(&self, _browser_path: &str) -> bool {
false
}

fn get_snap_path(&self) -> Option<PathBuf> {
None
}
}
4 changes: 2 additions & 2 deletions rust/src/firefox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ use crate::metadata::{
};
use crate::{
create_http_client, format_three_args, format_two_args, Logger, SeleniumManager, BETA,
DASH_VERSION, DEV, ESR, NIGHTLY, OFFLINE_REQUEST_ERR_MSG, REG_CURRENT_VERSION_ARG, STABLE,
DASH_VERSION, DEV, ESR, LATEST_RELEASE, NIGHTLY, OFFLINE_REQUEST_ERR_MSG,
REG_CURRENT_VERSION_ARG, STABLE,
};
use anyhow::anyhow;
use anyhow::Error;
Expand All @@ -41,7 +42,6 @@ use std::sync::mpsc::{Receiver, Sender};
pub const FIREFOX_NAME: &str = "firefox";
pub const GECKODRIVER_NAME: &str = "geckodriver";
const DRIVER_URL: &str = "https://github.com/mozilla/geckodriver/releases/";
const LATEST_RELEASE: &str = "latest";
const DRIVER_VERSIONS_URL: &str = "https://raw.githubusercontent.com/SeleniumHQ/selenium/trunk/common/geckodriver/geckodriver-support.json";
const BROWSER_URL: &str = "https://ftp.mozilla.org/pub/firefox/releases/";
const FIREFOX_DEFAULT_LANG: &str = "en-US";
Expand Down
14 changes: 12 additions & 2 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::config::OS::{MACOS, WINDOWS};
use crate::config::{str_to_os, ManagerConfig};
use crate::downloads::download_to_tmp_folder;
use crate::edge::{EdgeManager, EDGEDRIVER_NAME, EDGE_NAMES, WEBVIEW2_NAME};
use crate::electron::{ElectronManager, ELECTRON_NAME};
use crate::files::get_win_file_version;
use crate::files::{
capitalize, collect_files_from_cache, create_path_if_not_exists, default_cache_folder,
Expand Down Expand Up @@ -56,6 +57,7 @@ pub mod chrome;
pub mod config;
pub mod downloads;
pub mod edge;
pub mod electron;
pub mod files;
pub mod firefox;
pub mod grid;
Expand Down Expand Up @@ -112,6 +114,7 @@ pub const NOT_ADMIN_FOR_EDGE_INSTALLER_ERR_MSG: &str =
pub const ONLINE_DISCOVERY_ERROR_MESSAGE: &str = "Unable to discover {}{} in online repository";
pub const UNC_PREFIX: &str = r"\\?\";
pub const SM_BETA_LABEL: &str = "0.";
pub const LATEST_RELEASE: &str = "latest";

pub trait SeleniumManager {
// ----------------------------------------------------------
Expand Down Expand Up @@ -473,7 +476,7 @@ pub trait SeleniumManager {

fn discover_local_browser(&mut self) -> Result<(), Error> {
let mut download_browser = self.is_force_browser_download();
if !download_browser {
if !download_browser && !self.is_electron() {
let major_browser_version = self.get_major_browser_version();
match self.discover_browser_version()? {
Some(discovered_version) => {
Expand Down Expand Up @@ -563,6 +566,7 @@ pub trait SeleniumManager {
&& !self.is_grid()
&& !self.is_safari()
&& !self.is_webview2()
&& !self.is_electron()
{
let browser_path = self.download_browser(original_browser_version)?;
if browser_path.is_some() {
Expand Down Expand Up @@ -691,6 +695,10 @@ pub trait SeleniumManager {
self.get_browser_name().eq(GRID_NAME)
}

fn is_electron(&self) -> bool {
self.get_browser_name().eq_ignore_ascii_case(ELECTRON_NAME)
}

fn is_firefox(&self) -> bool {
self.get_browser_name().contains(FIREFOX_NAME)
}
Expand Down Expand Up @@ -778,7 +786,7 @@ pub trait SeleniumManager {
let original_browser_version = self.get_config().browser_version.clone();

// Try to find driver in PATH
if !self.is_safari() && !self.is_grid() {
if !self.is_safari() && !self.is_grid() && !self.is_electron() {
self.get_logger()
.trace(format!("Checking {} in PATH", self.get_driver_name()));
(driver_in_path_version, driver_in_path) = self.find_driver_in_path();
Expand Down Expand Up @@ -1619,6 +1627,8 @@ pub fn get_manager_by_browser(browser_name: String) -> Result<Box<dyn SeleniumMa
Ok(SafariManager::new()?)
} else if SAFARITP_NAMES.contains(&browser_name_lower_case.as_str()) {
Ok(SafariTPManager::new()?)
} else if browser_name_lower_case.eq(ELECTRON_NAME) {
Ok(ElectronManager::new()?)
} else {
Err(anyhow!(format!("Invalid browser name: {browser_name}")))
}
Expand Down
2 changes: 1 addition & 1 deletion rust/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ use std::sync::mpsc::Receiver;
{usage-heading} {usage}
{all-args}")]
struct Cli {
/// Browser name (chrome, firefox, edge, iexplorer, safari, safaritp, or webview2)
/// Browser name (chrome, firefox, edge, iexplorer, safari, safaritp, webview2, or electron)
#[clap(long, value_parser)]
browser: Option<String>,

Expand Down
Loading