Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/rust/src/main.rs
2885 views
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 anyhow::Error;
19
use clap::Parser;
20
use exitcode::DATAERR;
21
use exitcode::OK;
22
use exitcode::UNAVAILABLE;
23
use selenium_manager::config::{BooleanKey, StringKey, CACHE_PATH_KEY};
24
use selenium_manager::grid::GridManager;
25
use selenium_manager::lock::clear_lock_if_required;
26
use selenium_manager::logger::{Logger, BROWSER_PATH, DRIVER_PATH};
27
use selenium_manager::metadata::clear_metadata;
28
use selenium_manager::TTL_SEC;
29
use selenium_manager::{
30
clear_cache, get_manager_by_browser, get_manager_by_driver, SeleniumManager,
31
};
32
use selenium_manager::{REQUEST_TIMEOUT_SEC, SM_BETA_LABEL};
33
use std::backtrace::{Backtrace, BacktraceStatus};
34
use std::path::Path;
35
use std::process::exit;
36
use std::sync::mpsc::Receiver;
37
38
/// Automated driver management for Selenium
39
#[derive(Parser, Debug)]
40
#[clap(version, about, long_about = None, help_template = "\
41
{name} {version}
42
{about-with-newline}
43
{usage-heading} {usage}
44
{all-args}")]
45
struct Cli {
46
/// Browser name (chrome, firefox, edge, iexplorer, safari, safaritp, webview2, or electron)
47
#[clap(long, value_parser)]
48
browser: Option<String>,
49
50
/// Driver name (chromedriver, geckodriver, msedgedriver, IEDriverServer, or safaridriver)
51
#[clap(long, value_parser)]
52
driver: Option<String>,
53
54
/// Selenium Grid. If version is not provided, the latest version is downloaded
55
#[clap(long, value_parser, num_args = 0..=1, default_missing_value = "", value_name = "GRID_VERSION")]
56
grid: Option<String>,
57
58
/// Driver version (e.g., 106.0.5249.61, 0.31.0, etc.)
59
#[clap(long, value_parser)]
60
driver_version: Option<String>,
61
62
/// Major browser version (e.g., 105, 106, etc. Also: beta, dev, canary -or nightly-,
63
/// and esr -in Firefox- are accepted)
64
#[clap(long, value_parser)]
65
browser_version: Option<String>,
66
67
/// Browser path (absolute) for browser version detection (e.g., /usr/bin/google-chrome,
68
/// "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
69
/// "C:\Program Files\Google\Chrome\Application\chrome.exe")
70
#[clap(long, value_parser)]
71
browser_path: Option<String>,
72
73
/// Mirror for driver repositories (e.g., https://registry.npmmirror.com/-/binary/chromedriver/)
74
#[clap(long, value_parser)]
75
driver_mirror_url: Option<String>,
76
77
/// Mirror for browser repositories
78
#[clap(long, value_parser)]
79
browser_mirror_url: Option<String>,
80
81
/// Output type: LOGGER (using INFO, WARN, etc.), JSON (custom JSON notation), SHELL (Unix-like),
82
/// or MIXED (INFO, WARN, DEBUG, etc. to stderr and minimal JSON to stdout)
83
#[clap(long, value_parser, default_value = "LOGGER")]
84
output: String,
85
86
/// Operating system (i.e., windows, linux, or macos)
87
#[clap(long, value_parser)]
88
os: Option<String>,
89
90
/// System architecture (i.e., x32, x64, or arm64)
91
#[clap(long, value_parser)]
92
arch: Option<String>,
93
94
/// HTTP proxy for network connection (e.g., https://myproxy.net:8080)
95
#[clap(long, value_parser)]
96
proxy: Option<String>,
97
98
/// Timeout for network requests (in seconds)
99
#[clap(long, value_parser, default_value_t = REQUEST_TIMEOUT_SEC)]
100
timeout: u64,
101
102
/// TTL (time-to-live) for discovered versions (online) of drivers and browsers
103
#[clap(long, value_parser, default_value_t = TTL_SEC)]
104
ttl: u64,
105
106
/// Local folder used to store downloaded assets (drivers and browsers), local metadata,
107
/// and configuration file [default: ~/.cache/selenium]
108
#[clap(long, value_parser)]
109
cache_path: Option<String>,
110
111
/// Clear cache folder (~/.cache/selenium)
112
#[clap(long)]
113
clear_cache: bool,
114
115
/// Clear metadata file (~/.cache/selenium/selenium-manager.json)
116
#[clap(long)]
117
clear_metadata: bool,
118
119
/// Display DEBUG messages
120
#[clap(long)]
121
debug: bool,
122
123
/// Display TRACE messages
124
#[clap(long)]
125
trace: bool,
126
127
/// Level for output messages. The possible values are: info, debug, trace, warn, error
128
#[clap(long)]
129
log_level: Option<String>,
130
131
/// Offline mode (i.e., disabling network requests and downloads)
132
#[clap(long)]
133
offline: bool,
134
135
/// Force to download browser (even when browser is already in the system)
136
#[clap(long)]
137
force_browser_download: bool,
138
139
/// Avoid to download browser (even when browser-version is specified)
140
#[clap(long)]
141
avoid_browser_download: bool,
142
143
/// Selenium language bindings that invokes Selenium Manager (e.g., Java, JavaScript, Python,
144
/// DotNet, Ruby)
145
#[clap(long)]
146
language_binding: Option<String>,
147
148
/// Avoid sends usage statistics to plausible.io
149
#[clap(long)]
150
avoid_stats: bool,
151
152
/// Not using drivers found in the PATH
153
#[clap(long)]
154
skip_driver_in_path: bool,
155
156
/// Not using browsers found in the PATH
157
#[clap(long)]
158
skip_browser_in_path: bool,
159
}
160
161
fn main() {
162
let mut cli = Cli::parse();
163
let cache_path =
164
StringKey(vec![CACHE_PATH_KEY], &cli.cache_path.unwrap_or_default()).get_value();
165
166
let debug = cli.debug || BooleanKey("debug", false).get_value();
167
let trace = cli.trace || BooleanKey("trace", false).get_value();
168
let log_level = StringKey(vec!["log-level"], &cli.log_level.unwrap_or_default()).get_value();
169
let log = Logger::create(&cli.output, debug, trace, &log_level);
170
let grid = cli.grid;
171
let mut browser_name: String = cli.browser.unwrap_or_default();
172
let mut driver_name: String = cli.driver.unwrap_or_default();
173
if browser_name.is_empty() {
174
browser_name = StringKey(vec!["browser"], "").get_value();
175
}
176
if driver_name.is_empty() {
177
driver_name = StringKey(vec!["driver"], "").get_value();
178
}
179
180
let mut selenium_manager: Box<dyn SeleniumManager> = if !browser_name.is_empty() {
181
get_manager_by_browser(browser_name).unwrap_or_else(|err| {
182
log.error(&err);
183
flush_and_exit(DATAERR, &log, Some(err));
184
})
185
} else if !driver_name.is_empty() {
186
get_manager_by_driver(driver_name).unwrap_or_else(|err| {
187
log.error(&err);
188
flush_and_exit(DATAERR, &log, Some(err));
189
})
190
} else if let Some(grid_value) = &grid {
191
GridManager::new(grid_value.to_string()).unwrap_or_else(|err| {
192
log.error(&err);
193
flush_and_exit(DATAERR, &log, Some(err));
194
})
195
} else {
196
log.error("You need to specify a browser or driver");
197
flush_and_exit(DATAERR, &log, None);
198
};
199
200
if cli.offline {
201
if cli.force_browser_download {
202
log.warn("Offline flag set, but also asked to force downloads. Honouring offline flag");
203
}
204
cli.force_browser_download = false;
205
if !cli.avoid_browser_download {
206
log.debug("Offline flag set, but also asked not to avoid browser downloads. Honouring offline flag");
207
}
208
cli.avoid_browser_download = true;
209
}
210
211
// Logger set first so other setters can use it
212
selenium_manager.set_logger(log);
213
selenium_manager.set_browser_version(cli.browser_version.unwrap_or_default());
214
selenium_manager.set_driver_version(cli.driver_version.unwrap_or_default());
215
selenium_manager.set_browser_path(cli.browser_path.unwrap_or_default());
216
selenium_manager.set_driver_mirror_url(cli.driver_mirror_url.unwrap_or_default());
217
selenium_manager.set_browser_mirror_url(cli.browser_mirror_url.unwrap_or_default());
218
selenium_manager.set_os(cli.os.unwrap_or_default());
219
selenium_manager.set_arch(cli.arch.unwrap_or_default());
220
selenium_manager.set_ttl(cli.ttl);
221
selenium_manager.set_force_browser_download(cli.force_browser_download);
222
selenium_manager.set_avoid_browser_download(cli.avoid_browser_download);
223
selenium_manager.set_cache_path(cache_path.clone());
224
selenium_manager.set_offline(cli.offline);
225
selenium_manager.set_language_binding(cli.language_binding.unwrap_or_default());
226
let sm_version = clap::crate_version!();
227
let selenium_version = sm_version.strip_prefix(SM_BETA_LABEL).unwrap_or(sm_version);
228
selenium_manager.set_selenium_version(selenium_version.to_string());
229
selenium_manager.set_avoid_stats(cli.avoid_stats);
230
selenium_manager.set_skip_driver_in_path(cli.skip_driver_in_path);
231
selenium_manager.set_skip_browser_in_path(cli.skip_browser_in_path);
232
233
if cli.clear_cache || BooleanKey("clear-cache", false).get_value() {
234
clear_cache(selenium_manager.get_logger(), &cache_path);
235
}
236
if cli.clear_metadata || BooleanKey("clear-metadata", false).get_value() {
237
clear_metadata(selenium_manager.get_logger(), &cache_path);
238
}
239
240
selenium_manager
241
.set_timeout(cli.timeout)
242
.and_then(|_| selenium_manager.set_proxy(cli.proxy.unwrap_or_default()))
243
.and_then(|_| selenium_manager.stats())
244
.and_then(|_| selenium_manager.setup())
245
.map(|driver_path| {
246
let log = selenium_manager.get_logger();
247
log_driver_and_browser_path(
248
log,
249
&driver_path,
250
&selenium_manager.get_browser_path_or_latest_from_cache(),
251
selenium_manager.get_receiver(),
252
);
253
flush_and_exit(OK, log, None);
254
})
255
.unwrap_or_else(|err| {
256
let log = selenium_manager.get_logger();
257
if selenium_manager.is_fallback_driver_from_cache() {
258
if let Some(best_driver_from_cache) =
259
selenium_manager.find_best_driver_from_cache().unwrap()
260
{
261
log.debug_or_warn(
262
format!(
263
"There was an error managing {} ({}); using driver found in the cache",
264
selenium_manager.get_driver_name(),
265
err
266
),
267
selenium_manager.is_offline(),
268
);
269
log_driver_and_browser_path(
270
log,
271
&best_driver_from_cache,
272
&selenium_manager.get_browser_path_or_latest_from_cache(),
273
selenium_manager.get_receiver(),
274
);
275
flush_and_exit(OK, log, Some(err));
276
}
277
}
278
if selenium_manager.is_offline() {
279
log.warn(&err);
280
flush_and_exit(OK, log, Some(err));
281
} else {
282
let error_msg = log
283
.is_debug_enabled()
284
.then(|| format!("{:?}", err))
285
.unwrap_or_else(|| err.to_string());
286
log.error(error_msg);
287
flush_and_exit(DATAERR, log, Some(err));
288
}
289
});
290
}
291
292
fn log_driver_and_browser_path(
293
log: &Logger,
294
driver_path: &Path,
295
browser_path: &str,
296
receiver: &Receiver<String>,
297
) {
298
if let Ok(err) = receiver.try_recv() {
299
log.warn(err);
300
}
301
if driver_path.exists() {
302
log.info(format!("{}{}", DRIVER_PATH, driver_path.display()));
303
} else {
304
log.error(format!("Driver unavailable: {}", driver_path.display()));
305
flush_and_exit(UNAVAILABLE, log, None);
306
}
307
if !browser_path.is_empty() {
308
log.info(format!("{}{}", BROWSER_PATH, browser_path));
309
}
310
}
311
312
fn flush_and_exit(code: i32, log: &Logger, err: Option<Error>) -> ! {
313
if let Some(error) = err {
314
let backtrace = Backtrace::capture();
315
let backtrace_status = backtrace.status();
316
if backtrace_status == BacktraceStatus::Captured {
317
log.debug(format!("Backtrace:\n{}", error.backtrace()));
318
}
319
}
320
log.set_code(code);
321
log.flush();
322
clear_lock_if_required();
323
exit(code);
324
}
325
326