Skip to content

Commit 3fb3b61

Browse files
committed
[rust] Automated management of Selenium Grid binaries (selenium-server.jar)
1 parent c49361a commit 3fb3b61

8 files changed

Lines changed: 361 additions & 25 deletions

File tree

rust/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ Options:
2525
Browser name (chrome, firefox, edge, iexplorer, safari, or safaritp)
2626
--driver <DRIVER>
2727
Driver name (chromedriver, geckodriver, msedgedriver, IEDriverServer, or safaridriver)
28+
--grid [<GRID_VERSION>]
29+
Selenium Grid. If version is not provided, the latest version is downloaded
2830
--driver-version <DRIVER_VERSION>
2931
Driver version (e.g., 106.0.5249.61, 0.31.0, etc.)
3032
--browser-version <BROWSER_VERSION>

rust/src/downloads.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18-
use reqwest::Client;
18+
use reqwest::{Client, StatusCode};
1919
use serde::{Deserialize, Serialize};
2020
use std::error::Error;
2121
use std::fs::File;
@@ -40,7 +40,12 @@ pub async fn download_driver_to_tmp_folder(
4040
tmp_dir.path()
4141
));
4242

43-
let response = http_client.get(url).send().await?;
43+
let response = http_client.get(&url).send().await?;
44+
let status_code = response.status();
45+
if status_code != StatusCode::OK {
46+
return Err(format!("Unsuccessful response ({}) for URL {}", status_code, url).into());
47+
}
48+
4449
let target_path;
4550
let mut tmp_file = {
4651
let target_name = response

rust/src/files.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ impl BrowserPath {
5454
}
5555
}
5656

57+
pub fn create_parent_path_if_not_exists(path: &Path) {
58+
if let Some(p) = path.parent() {
59+
create_path_if_not_exists(p);
60+
}
61+
}
62+
5763
pub fn create_path_if_not_exists(path: &Path) {
5864
if !path.exists() {
5965
fs::create_dir_all(path).unwrap();
@@ -123,9 +129,7 @@ pub fn unzip(file: File, target: &Path, log: &Logger) -> Result<(), Box<dyn Erro
123129
target.display(),
124130
file.size()
125131
));
126-
if let Some(p) = target.parent() {
127-
create_path_if_not_exists(p);
128-
}
132+
create_parent_path_if_not_exists(target);
129133
if !target.exists() {
130134
let mut outfile = File::create(&target)?;
131135

rust/src/grid.rs

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
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::config::ManagerConfig;
19+
use reqwest::Client;
20+
use std::collections::HashMap;
21+
use std::error::Error;
22+
use std::path::PathBuf;
23+
24+
use crate::files::{get_cache_folder, BrowserPath};
25+
26+
use crate::downloads::parse_json_from_url;
27+
use crate::{create_http_client, parse_version, Logger, SeleniumManager, SNAPSHOT};
28+
29+
use crate::metadata::{
30+
create_driver_metadata, get_driver_version_from_metadata, get_metadata, write_metadata,
31+
};
32+
use crate::mirror::{Assets, SeleniumRelease, MIRROR_URL};
33+
34+
pub const GRID_NAME: &str = "grid";
35+
const GRID_RELEASE: &str = "selenium-server";
36+
const GRID_EXTENSION: &str = "jar";
37+
const DRIVER_URL: &str = "https://github.com/SeleniumHQ/selenium/releases/";
38+
39+
// Although Selenium Grid is not a browser, we can use the same manager struct, where:
40+
// - browser_name refers to the product name, i.e., grid
41+
// - driver_name refers to the downloadable artifact, i.e., selenium-server (.jar)
42+
pub struct GridManager {
43+
pub browser_name: &'static str,
44+
pub driver_name: &'static str,
45+
pub config: ManagerConfig,
46+
pub http_client: Client,
47+
pub log: Logger,
48+
pub driver_url: Option<String>,
49+
}
50+
51+
impl GridManager {
52+
pub fn new(driver_version: String) -> Result<Box<Self>, Box<dyn Error>> {
53+
let browser_name = GRID_NAME;
54+
let driver_name = GRID_RELEASE;
55+
let mut config = ManagerConfig::default(browser_name, driver_name);
56+
config.driver_version = driver_version;
57+
let default_timeout = config.timeout.to_owned();
58+
let default_proxy = &config.proxy;
59+
Ok(Box::new(GridManager {
60+
browser_name,
61+
driver_name,
62+
http_client: create_http_client(default_timeout, default_proxy)?,
63+
config,
64+
log: Logger::default(),
65+
driver_url: None,
66+
}))
67+
}
68+
}
69+
70+
impl SeleniumManager for GridManager {
71+
fn get_browser_name(&self) -> &str {
72+
self.browser_name
73+
}
74+
75+
fn get_http_client(&self) -> &Client {
76+
&self.http_client
77+
}
78+
79+
fn set_http_client(&mut self, http_client: Client) {
80+
self.http_client = http_client;
81+
}
82+
83+
fn get_browser_path_map(&self) -> HashMap<BrowserPath, &str> {
84+
HashMap::new()
85+
}
86+
87+
fn discover_browser_version(&self) -> Option<String> {
88+
None
89+
}
90+
91+
fn get_driver_name(&self) -> &str {
92+
self.driver_name
93+
}
94+
95+
fn request_driver_version(&mut self) -> Result<String, Box<dyn Error>> {
96+
let browser_version_binding = self.get_major_browser_version();
97+
let browser_version = browser_version_binding.as_str();
98+
let mut metadata = get_metadata(self.get_logger());
99+
100+
match get_driver_version_from_metadata(&metadata.drivers, self.driver_name, browser_version)
101+
{
102+
Some(driver_version) => {
103+
self.log.trace(format!(
104+
"Driver TTL is valid. Getting {} version from metadata",
105+
&self.driver_name
106+
));
107+
Ok(driver_version)
108+
}
109+
_ => {
110+
let selenium_releases = parse_json_from_url::<Vec<SeleniumRelease>>(
111+
self.get_http_client(),
112+
MIRROR_URL.to_string(),
113+
)?;
114+
115+
let filtered_releases: Vec<SeleniumRelease> = selenium_releases
116+
.into_iter()
117+
.filter(|r| {
118+
r.assets.iter().any(|url| {
119+
url.browser_download_url.contains(GRID_RELEASE)
120+
&& !url.browser_download_url.contains(SNAPSHOT)
121+
})
122+
})
123+
.collect();
124+
125+
if !filtered_releases.is_empty() {
126+
let assets = &filtered_releases.get(0).unwrap().assets;
127+
let driver_releases: Vec<&Assets> = assets
128+
.iter()
129+
.filter(|url| {
130+
url.browser_download_url.contains(GRID_RELEASE)
131+
&& url.browser_download_url.contains(GRID_EXTENSION)
132+
})
133+
.collect();
134+
let driver_url = &driver_releases.last().unwrap().browser_download_url;
135+
self.driver_url = Some(driver_url.to_string());
136+
137+
let index_release =
138+
driver_url.rfind(GRID_RELEASE).unwrap() + GRID_RELEASE.len() + 1;
139+
let driver_version = parse_version(
140+
driver_url.as_str()[index_release..].to_string(),
141+
self.get_logger(),
142+
)?;
143+
144+
let driver_ttl = self.get_driver_ttl();
145+
if driver_ttl > 0 {
146+
metadata.drivers.push(create_driver_metadata(
147+
browser_version,
148+
self.driver_name,
149+
&driver_version,
150+
driver_ttl,
151+
));
152+
write_metadata(&metadata, self.get_logger());
153+
}
154+
155+
Ok(driver_version)
156+
} else {
157+
Err(format!("{} release not available", self.get_driver_name()).into())
158+
}
159+
}
160+
}
161+
}
162+
163+
fn get_driver_url(&mut self) -> Result<String, Box<dyn Error>> {
164+
if self.driver_url.is_some() {
165+
return Ok(self.driver_url.as_ref().unwrap().to_string());
166+
}
167+
168+
let release_version = self.get_selenium_release_version()?;
169+
Ok(format!(
170+
"{}download/{}/{}-{}.{}",
171+
DRIVER_URL,
172+
release_version,
173+
GRID_RELEASE,
174+
self.get_driver_version(),
175+
GRID_EXTENSION
176+
))
177+
}
178+
179+
fn get_driver_path_in_cache(&self) -> PathBuf {
180+
let browser_name = self.get_browser_name();
181+
let driver_name = self.get_driver_name();
182+
let driver_version = self.get_driver_version();
183+
get_cache_folder()
184+
.join(browser_name)
185+
.join(driver_version)
186+
.join(format!("{driver_name}-{driver_version}.{GRID_EXTENSION}"))
187+
}
188+
189+
fn get_config(&self) -> &ManagerConfig {
190+
&self.config
191+
}
192+
193+
fn get_config_mut(&mut self) -> &mut ManagerConfig {
194+
&mut self.config
195+
}
196+
197+
fn set_config(&mut self, config: ManagerConfig) {
198+
self.config = config;
199+
}
200+
201+
fn get_logger(&self) -> &Logger {
202+
&self.log
203+
}
204+
205+
fn set_logger(&mut self, log: Logger) {
206+
self.log = log;
207+
}
208+
}

rust/src/iexplorer.rs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -164,22 +164,13 @@ impl SeleniumManager for IExplorerManager {
164164
return Ok(self.driver_url.as_ref().unwrap().to_string());
165165
}
166166

167-
let driver_version = self.get_driver_version();
168-
let mut release_version = driver_version.to_string();
169-
if !driver_version.ends_with('0') {
170-
// E.g.: version 4.8.1 is shipped within release 4.8.0
171-
let error_message = format!(
172-
"Wrong {} version: '{}'",
173-
self.get_driver_name(),
174-
driver_version
175-
);
176-
let index = release_version.rfind('.').ok_or(error_message)? + 1;
177-
release_version = release_version[..index].to_string();
178-
release_version.push('0');
179-
}
167+
let release_version = self.get_selenium_release_version()?;
180168
Ok(format!(
181-
"{}download/selenium-{}/{}{}.zip",
182-
DRIVER_URL, release_version, IEDRIVER_RELEASE, driver_version
169+
"{}download/{}/{}{}.zip",
170+
DRIVER_URL,
171+
release_version,
172+
IEDRIVER_RELEASE,
173+
self.get_driver_version()
183174
))
184175
}
185176

0 commit comments

Comments
 (0)