Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/rust/src/lib.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 crate::chrome::{ChromeManager, CHROMEDRIVER_NAME, CHROME_NAME};
19
use crate::config::ARCH::{ARM64, ARMV7, X32, X64};
20
use crate::config::OS::{MACOS, WINDOWS};
21
use crate::config::{str_to_os, ManagerConfig};
22
use crate::downloads::download_to_tmp_folder;
23
use crate::edge::{EdgeManager, EDGEDRIVER_NAME, EDGE_NAMES, WEBVIEW2_NAME};
24
use crate::electron::{ElectronManager, ELECTRON_NAME};
25
use crate::files::get_win_file_version;
26
use crate::files::{
27
capitalize, collect_files_from_cache, create_path_if_not_exists, default_cache_folder,
28
find_latest_from_cache, get_binary_extension, path_to_string,
29
};
30
use crate::files::{parse_version, uncompress, BrowserPath};
31
use crate::firefox::{FirefoxManager, FIREFOX_NAME, GECKODRIVER_NAME};
32
use crate::grid::GRID_NAME;
33
use crate::iexplorer::{IExplorerManager, IEDRIVER_NAME, IE_NAMES};
34
use crate::lock::Lock;
35
use crate::logger::Logger;
36
use crate::metadata::{
37
create_browser_metadata, create_stats_metadata, get_browser_version_from_metadata,
38
get_metadata, is_stats_in_metadata, write_metadata,
39
};
40
use crate::safari::{SafariManager, SAFARIDRIVER_NAME, SAFARI_NAME};
41
use crate::safaritp::{SafariTPManager, SAFARITP_NAMES};
42
use crate::shell::{
43
run_shell_command, run_shell_command_by_os, run_shell_command_with_log, Command,
44
};
45
use crate::stats::{send_stats_to_plausible, Props};
46
use anyhow::anyhow;
47
use anyhow::Error;
48
use reqwest::{Client, Proxy};
49
use std::collections::HashMap;
50
use std::path::{Path, PathBuf};
51
use std::sync::mpsc::{Receiver, Sender};
52
use std::time::Duration;
53
use std::{env, fs, thread};
54
use walkdir::DirEntry;
55
use which::which;
56
57
pub mod chrome;
58
pub mod config;
59
pub mod downloads;
60
pub mod edge;
61
pub mod electron;
62
pub mod files;
63
pub mod firefox;
64
pub mod grid;
65
pub mod iexplorer;
66
pub mod lock;
67
pub mod logger;
68
pub mod metadata;
69
pub mod mirror;
70
pub mod safari;
71
pub mod safaritp;
72
pub mod shell;
73
pub mod stats;
74
75
pub const REQUEST_TIMEOUT_SEC: u64 = 300; // The timeout is applied from when the request starts connecting until the response body has finished
76
pub const STABLE: &str = "stable";
77
pub const BETA: &str = "beta";
78
pub const DEV: &str = "dev";
79
pub const CANARY: &str = "canary";
80
pub const NIGHTLY: &str = "nightly";
81
pub const ESR: &str = "esr";
82
pub const REG_VERSION_ARG: &str = "version";
83
pub const REG_CURRENT_VERSION_ARG: &str = "CurrentVersion";
84
pub const REG_PV_ARG: &str = "pv";
85
pub const PLIST_COMMAND: &str =
86
r#"/usr/libexec/PlistBuddy -c "print :CFBundleShortVersionString" {}/Contents/Info.plist"#;
87
pub const HDIUTIL_ATTACH_COMMAND: &str = "hdiutil attach {}";
88
pub const HDIUTIL_DETACH_COMMAND: &str = "hdiutil detach /Volumes/{}";
89
pub const CP_VOLUME_COMMAND: &str = "cp -R /Volumes/{}/{}.app {}";
90
pub const MSIEXEC_INSTALL_COMMAND: &str = "start /wait msiexec /i {} /qn ALLOWDOWNGRADE=1";
91
pub const WINDOWS_CHECK_ADMIN_COMMAND: &str = "net session";
92
pub const DASH_VERSION: &str = "{}{}{} -v";
93
pub const DASH_DASH_VERSION: &str = "{}{}{} --version";
94
pub const DOUBLE_QUOTE: &str = r#"""#;
95
pub const SINGLE_QUOTE: &str = "'";
96
pub const ENV_PROGRAM_FILES: &str = "PROGRAMFILES";
97
pub const ENV_PROGRAM_FILES_X86: &str = "PROGRAMFILES(X86)";
98
pub const ENV_LOCALAPPDATA: &str = "LOCALAPPDATA";
99
pub const ENV_PROCESSOR_ARCHITECTURE: &str = "PROCESSOR_ARCHITECTURE";
100
pub const ENV_X86: &str = " (x86)";
101
pub const ARCH_X86: &str = "x86";
102
pub const ARCH_X64: &str = "x86_64";
103
pub const ARCH_ARM64: &str = "arm64";
104
pub const ARCH_ARM7L: &str = "arm7l";
105
pub const ARCH_OTHER: &str = "other";
106
pub const TTL_SEC: u64 = 3600;
107
pub const UNAME_COMMAND: &str = "uname -{}";
108
pub const ESCAPE_COMMAND: &str = r#"printf %q "{}""#;
109
pub const SNAPSHOT: &str = "SNAPSHOT";
110
pub const OFFLINE_REQUEST_ERR_MSG: &str = "Unable to discover proper {} version in offline mode";
111
pub const OFFLINE_DOWNLOAD_ERR_MSG: &str = "Unable to download {} in offline mode";
112
pub const UNAVAILABLE_DOWNLOAD_ERR_MSG: &str = "{}{} not available for download";
113
pub const UNAVAILABLE_DOWNLOAD_WITH_MIN_VERSION_ERR_MSG: &str =
114
"{} {} not available for download (minimum version: {})";
115
pub const NOT_ADMIN_FOR_EDGE_INSTALLER_ERR_MSG: &str =
116
"{} can only be installed in Windows with administrator permissions";
117
pub const ONLINE_DISCOVERY_ERROR_MESSAGE: &str = "Unable to discover {}{} in online repository";
118
pub const UNC_PREFIX: &str = r"\\?\";
119
pub const SM_BETA_LABEL: &str = "0.";
120
pub const LATEST_RELEASE: &str = "latest";
121
122
pub trait SeleniumManager {
123
// ----------------------------------------------------------
124
// Browser-specific functions
125
// ----------------------------------------------------------
126
127
fn get_browser_name(&self) -> &str;
128
129
fn get_browser_names_in_path(&self) -> Vec<&str>;
130
131
fn get_http_client(&self) -> &Client;
132
133
fn set_http_client(&mut self, http_client: Client);
134
135
fn get_browser_path_map(&self) -> HashMap<BrowserPath, &str>;
136
137
fn discover_browser_version(&mut self) -> Result<Option<String>, Error>;
138
139
fn get_driver_name(&self) -> &str;
140
141
fn request_driver_version(&mut self) -> Result<String, Error>;
142
143
fn request_browser_version(&mut self) -> Result<Option<String>, Error>;
144
145
fn get_driver_url(&mut self) -> Result<String, Error>;
146
147
fn get_driver_path_in_cache(&self) -> Result<PathBuf, Error>;
148
149
fn get_config(&self) -> &ManagerConfig;
150
151
fn get_config_mut(&mut self) -> &mut ManagerConfig;
152
153
fn set_config(&mut self, config: ManagerConfig);
154
155
fn get_logger(&self) -> &Logger;
156
157
fn set_logger(&mut self, log: Logger);
158
159
fn get_sender(&self) -> &Sender<String>;
160
161
fn get_receiver(&self) -> &Receiver<String>;
162
163
fn get_platform_label(&self) -> &str;
164
165
fn request_latest_browser_version_from_online(
166
&mut self,
167
browser_version: &str,
168
) -> Result<String, Error>;
169
170
fn request_fixed_browser_version_from_online(
171
&mut self,
172
browser_version: &str,
173
) -> Result<String, Error>;
174
175
fn get_min_browser_version_for_download(&self) -> Result<i32, Error>;
176
177
fn get_browser_binary_path(&mut self, browser_version: &str) -> Result<PathBuf, Error>;
178
179
fn get_browser_url_for_download(&mut self, browser_version: &str) -> Result<String, Error>;
180
181
fn get_browser_label_for_download(&self, _browser_version: &str)
182
-> Result<Option<&str>, Error>;
183
184
fn is_download_browser(&self) -> bool;
185
186
fn set_download_browser(&mut self, download_browser: bool);
187
188
fn is_snap(&self, browser_path: &str) -> bool;
189
190
fn get_snap_path(&self) -> Option<PathBuf>;
191
192
// ----------------------------------------------------------
193
// Shared functions
194
// ----------------------------------------------------------
195
196
fn download_driver(&mut self) -> Result<(), Error> {
197
let driver_path_in_cache = self.get_driver_path_in_cache()?;
198
let driver_name_with_extension = self.get_driver_name_with_extension();
199
200
let mut lock = Lock::acquire(
201
self.get_logger(),
202
&driver_path_in_cache,
203
Some(driver_name_with_extension.clone()),
204
)?;
205
if !lock.exists() && driver_path_in_cache.exists() {
206
self.get_logger().debug(format!(
207
"Driver already in cache: {}",
208
driver_path_in_cache.display()
209
));
210
return Ok(());
211
}
212
213
let driver_url = self.get_driver_url()?;
214
self.get_logger().debug(format!(
215
"Downloading {} {} from {}",
216
self.get_driver_name(),
217
self.get_driver_version(),
218
driver_url
219
));
220
let (_tmp_folder, driver_zip_file) =
221
download_to_tmp_folder(self.get_http_client(), driver_url, self.get_logger())?;
222
223
if self.is_grid() {
224
let driver_path_in_cache = self.get_driver_path_in_cache()?;
225
fs::rename(driver_zip_file, driver_path_in_cache)?;
226
} else {
227
uncompress(
228
&driver_zip_file,
229
&driver_path_in_cache,
230
self.get_logger(),
231
self.get_os(),
232
Some(driver_name_with_extension),
233
None,
234
)?;
235
}
236
237
lock.release();
238
Ok(())
239
}
240
241
fn download_browser(
242
&mut self,
243
original_browser_version: &str,
244
) -> Result<Option<PathBuf>, Error> {
245
let browser_version;
246
let cache_path = self.get_cache_path()?;
247
let mut metadata = get_metadata(self.get_logger(), &cache_path);
248
let major_browser_version = self.get_major_browser_version();
249
let major_browser_version_int = major_browser_version.parse::<i32>().unwrap_or_default();
250
251
// Browser version should be available for download
252
let min_browser_version_for_download = self.get_min_browser_version_for_download()?;
253
if !self.is_browser_version_unstable()
254
&& !self.is_browser_version_stable()
255
&& !self.is_browser_version_empty()
256
&& major_browser_version_int < min_browser_version_for_download
257
{
258
return Err(anyhow!(format_three_args(
259
UNAVAILABLE_DOWNLOAD_WITH_MIN_VERSION_ERR_MSG,
260
self.get_browser_name(),
261
&major_browser_version,
262
&min_browser_version_for_download.to_string(),
263
)));
264
}
265
266
if self.is_version_specific(original_browser_version) {
267
browser_version = original_browser_version.to_string();
268
} else {
269
// Browser version is checked in the local metadata
270
match get_browser_version_from_metadata(
271
&metadata.browsers,
272
self.get_browser_name(),
273
&major_browser_version,
274
) {
275
Some(version) => {
276
self.get_logger().trace(format!(
277
"Browser with valid TTL. Getting {} version from metadata",
278
self.get_browser_name()
279
));
280
browser_version = version;
281
self.set_browser_version(browser_version.clone());
282
}
283
_ => {
284
// If not in metadata, discover version using online metadata
285
if self.is_browser_version_stable() || self.is_browser_version_empty() {
286
browser_version = self
287
.request_latest_browser_version_from_online(original_browser_version)?;
288
} else {
289
browser_version = self
290
.request_fixed_browser_version_from_online(original_browser_version)?;
291
}
292
self.set_browser_version(browser_version.clone());
293
294
let browser_ttl = self.get_ttl();
295
if browser_ttl > 0
296
&& !self.is_browser_version_empty()
297
&& !self.is_browser_version_stable()
298
{
299
metadata.browsers.push(create_browser_metadata(
300
self.get_browser_name(),
301
&major_browser_version,
302
&browser_version,
303
browser_ttl,
304
));
305
write_metadata(&metadata, self.get_logger(), cache_path);
306
}
307
}
308
}
309
}
310
self.get_logger().debug(format!(
311
"Required browser: {} {}",
312
self.get_browser_name(),
313
browser_version
314
));
315
316
// Checking if browser version is in the cache
317
let browser_binary_path = self.get_browser_binary_path(original_browser_version)?;
318
if browser_binary_path.exists() {
319
self.get_logger().debug(format!(
320
"{} {} already exists",
321
self.get_browser_name(),
322
browser_version
323
));
324
} else {
325
// If browser is not available, download it
326
if WINDOWS.is(self.get_os()) && self.is_edge() && !self.is_windows_admin() {
327
return Err(anyhow!(format_one_arg(
328
NOT_ADMIN_FOR_EDGE_INSTALLER_ERR_MSG,
329
self.get_browser_name(),
330
)));
331
}
332
333
let browser_path_in_cache = self.get_browser_path_in_cache()?;
334
let mut lock = Lock::acquire(self.get_logger(), &browser_path_in_cache, None)?;
335
if !lock.exists() && browser_binary_path.exists() {
336
self.get_logger().debug(format!(
337
"Browser already in cache: {}",
338
browser_binary_path.display()
339
));
340
self.set_browser_path(path_to_string(&browser_binary_path));
341
return Ok(Some(browser_binary_path.clone()));
342
}
343
344
let browser_url = self.get_browser_url_for_download(original_browser_version)?;
345
self.get_logger().debug(format!(
346
"Downloading {} {} from {}",
347
self.get_browser_name(),
348
self.get_browser_version(),
349
browser_url
350
));
351
let (_tmp_folder, driver_zip_file) =
352
download_to_tmp_folder(self.get_http_client(), browser_url, self.get_logger())?;
353
354
let browser_label_for_download =
355
self.get_browser_label_for_download(original_browser_version)?;
356
uncompress(
357
&driver_zip_file,
358
&browser_path_in_cache,
359
self.get_logger(),
360
self.get_os(),
361
None,
362
browser_label_for_download,
363
)?;
364
lock.release();
365
}
366
if browser_binary_path.exists() {
367
self.set_browser_path(path_to_string(&browser_binary_path));
368
Ok(Some(browser_binary_path))
369
} else {
370
self.get_logger().warn(format!(
371
"Expected {} path does not exist: {}",
372
self.get_browser_name(),
373
browser_binary_path.display()
374
));
375
Ok(None)
376
}
377
}
378
379
fn get_browser_path_from_version(&self, mut browser_version: &str) -> &str {
380
if browser_version.eq_ignore_ascii_case(CANARY) {
381
browser_version = NIGHTLY;
382
} else if !self.is_unstable(browser_version) {
383
browser_version = STABLE;
384
}
385
self.get_browser_path_map()
386
.get(&BrowserPath::new(
387
str_to_os(self.get_os()).unwrap(),
388
browser_version,
389
))
390
.cloned()
391
.unwrap_or_default()
392
}
393
394
fn detect_browser_path(&mut self) -> Option<PathBuf> {
395
let browser_version = self.get_browser_version();
396
let browser_path = self.get_browser_path_from_version(browser_version);
397
398
let mut full_browser_path = Path::new(browser_path).to_path_buf();
399
if WINDOWS.is(self.get_os()) {
400
let envs = vec![ENV_PROGRAM_FILES, ENV_PROGRAM_FILES_X86, ENV_LOCALAPPDATA];
401
402
for env in envs {
403
let mut env_value = env::var(env).unwrap_or_default();
404
if env.eq(ENV_PROGRAM_FILES) && env_value.contains(ENV_X86) {
405
// This special case is required to keep compliance between x32 and x64
406
// architectures (since the selenium-manager in Windows is compiled as x32 binary)
407
env_value = env_value.replace(ENV_X86, "");
408
}
409
let parent_path = Path::new(&env_value);
410
full_browser_path = parent_path.join(browser_path);
411
if full_browser_path.exists() {
412
break;
413
}
414
}
415
}
416
417
if full_browser_path.exists() {
418
let canon_browser_path = self.canonicalize_path(full_browser_path);
419
self.get_logger().debug(format!(
420
"{} detected at {}",
421
self.get_browser_name(),
422
canon_browser_path
423
));
424
self.set_browser_path(canon_browser_path.clone());
425
426
Some(Path::new(&canon_browser_path).to_path_buf())
427
} else {
428
// Check browser in PATH
429
let browser_in_path = self.find_browser_in_path();
430
if let Some(path) = &browser_in_path {
431
if self.is_skip_browser_in_path() {
432
self.get_logger().debug(format!(
433
"Skipping {} in path: {}",
434
self.get_browser_name(),
435
path.display()
436
));
437
return None;
438
} else {
439
self.set_browser_path(path_to_string(path));
440
}
441
}
442
browser_in_path
443
}
444
}
445
446
fn detect_browser_version(&self, commands: Vec<Command>) -> Option<String> {
447
let browser_name = &self.get_browser_name();
448
449
self.get_logger().trace(format!(
450
"Using shell command to find out {} version",
451
browser_name
452
));
453
let mut browser_version: Option<String> = None;
454
for driver_version_command in commands.into_iter() {
455
let output = match run_shell_command_with_log(
456
self.get_logger(),
457
self.get_os(),
458
driver_version_command,
459
) {
460
Ok(out) => out,
461
Err(_) => continue,
462
};
463
464
let full_browser_version = parse_version(output, self.get_logger()).unwrap_or_default();
465
if full_browser_version.is_empty() {
466
continue;
467
}
468
self.get_logger().trace(format!(
469
"The version of {} is {}",
470
browser_name, full_browser_version
471
));
472
473
browser_version = Some(full_browser_version);
474
break;
475
}
476
477
browser_version
478
}
479
480
fn discover_local_browser(&mut self) -> Result<(), Error> {
481
let mut download_browser = self.is_force_browser_download();
482
if !download_browser && !self.is_electron() {
483
let major_browser_version = self.get_major_browser_version();
484
match self.discover_browser_version()? {
485
Some(discovered_version) => {
486
if !self.is_safari() {
487
self.get_logger().debug(format!(
488
"Detected browser: {} {}",
489
self.get_browser_name(),
490
discovered_version
491
));
492
}
493
let discovered_major_browser_version = self
494
.get_major_version(&discovered_version)
495
.unwrap_or_default();
496
497
if self.is_browser_version_stable() || self.is_browser_version_unstable() {
498
let online_browser_version = self.request_browser_version()?;
499
if online_browser_version.is_some() {
500
let major_online_browser_version =
501
self.get_major_version(&online_browser_version.unwrap())?;
502
if discovered_major_browser_version.eq(&major_online_browser_version) {
503
self.get_logger().debug(format!(
504
"Discovered online {} version ({}) is the same as the detected local {} version",
505
self.get_browser_name(),
506
discovered_major_browser_version,
507
self.get_browser_name(),
508
));
509
self.set_browser_version(discovered_version);
510
} else {
511
self.get_logger().debug(format!(
512
"Discovered online {} version ({}) is different to the detected local {} version ({})",
513
self.get_browser_name(),
514
major_online_browser_version,
515
self.get_browser_name(),
516
discovered_major_browser_version,
517
));
518
download_browser = true;
519
}
520
} else {
521
self.set_browser_version(discovered_version);
522
}
523
} else if !major_browser_version.is_empty()
524
&& !self.is_browser_version_unstable()
525
&& !major_browser_version.eq(&discovered_major_browser_version)
526
{
527
self.get_logger().debug(format!(
528
"Discovered {} version ({}) different to specified browser version ({})",
529
self.get_browser_name(),
530
discovered_major_browser_version,
531
major_browser_version,
532
));
533
download_browser = true;
534
} else {
535
self.set_browser_version(discovered_version);
536
}
537
if self.is_webview2() && PathBuf::from(self.get_browser_path()).is_dir() {
538
let browser_path = format!(
539
r"{}\{}\msedge{}",
540
self.get_browser_path(),
541
&self.get_browser_version(),
542
get_binary_extension(self.get_os())
543
);
544
self.set_browser_path(browser_path);
545
}
546
}
547
None => {
548
self.get_logger().debug(format!(
549
"{}{} not found in the system",
550
self.get_browser_name(),
551
self.get_browser_version_label()
552
));
553
download_browser = true;
554
}
555
}
556
}
557
self.set_download_browser(download_browser);
558
559
Ok(())
560
}
561
562
fn download_browser_if_necessary(
563
&mut self,
564
original_browser_version: &str,
565
) -> Result<(), Error> {
566
if self.is_download_browser()
567
&& !self.is_avoid_browser_download()
568
&& !self.is_iexplorer()
569
&& !self.is_grid()
570
&& !self.is_safari()
571
&& !self.is_webview2()
572
&& !self.is_electron()
573
{
574
let browser_path = self.download_browser(original_browser_version)?;
575
if browser_path.is_some() {
576
self.get_logger().debug(format!(
577
"{} {} is available at {}",
578
self.get_browser_name(),
579
self.get_browser_version(),
580
browser_path.unwrap().display()
581
));
582
} else if !self.is_iexplorer() && !self.is_grid() && !self.is_safari() {
583
return Err(anyhow!(format!(
584
"{}{} cannot be downloaded",
585
self.get_browser_name(),
586
self.get_browser_version_label()
587
)));
588
}
589
}
590
Ok(())
591
}
592
593
fn discover_driver_version(&mut self) -> Result<String, Error> {
594
// We request the driver version using online endpoints
595
let driver_version = self.request_driver_version()?;
596
if driver_version.is_empty() {
597
Err(anyhow!(format!(
598
"The {} version cannot be discovered",
599
self.get_driver_name()
600
)))
601
} else {
602
self.get_logger().debug(format!(
603
"Required driver: {} {}",
604
self.get_driver_name(),
605
driver_version
606
));
607
Ok(driver_version)
608
}
609
}
610
611
fn find_browser_in_path(&self) -> Option<PathBuf> {
612
for browser_name in self.get_browser_names_in_path().iter() {
613
self.get_logger()
614
.trace(format!("Checking {} in PATH", browser_name));
615
let browser_path = self.execute_which_in_shell(browser_name);
616
if let Some(path) = browser_path {
617
self.get_logger()
618
.debug(format!("Found {} in PATH: {}", browser_name, &path));
619
if self.is_snap(&path) {
620
if let Some(snap_path) = self.get_snap_path() {
621
if snap_path.exists() {
622
self.get_logger().debug(format!(
623
"Using {} snap: {}",
624
browser_name,
625
path_to_string(snap_path.as_path())
626
));
627
return Some(snap_path);
628
}
629
}
630
}
631
return Some(Path::new(&path).to_path_buf());
632
}
633
}
634
self.get_logger()
635
.debug(format!("{} not found in PATH", self.get_browser_name()));
636
None
637
}
638
639
fn find_driver_in_path(&self) -> (Option<String>, Option<String>) {
640
let driver_version_command = Command::new_single(format_three_args(
641
DASH_DASH_VERSION,
642
self.get_driver_name(),
643
"",
644
"",
645
));
646
match run_shell_command_by_os(self.get_os(), driver_version_command) {
647
Ok(output) => {
648
let parsed_version = parse_version(output, self.get_logger()).unwrap_or_default();
649
if !parsed_version.is_empty() {
650
let driver_path = self.execute_which_in_shell(self.get_driver_name());
651
return (Some(parsed_version), driver_path);
652
}
653
(None, None)
654
}
655
Err(_) => (None, None),
656
}
657
}
658
659
fn execute_which_in_shell(&self, arg: &str) -> Option<String> {
660
match which(arg) {
661
Ok(path) => Some(path_to_string(&path)),
662
Err(_) => None,
663
}
664
}
665
666
fn get_first_in_vector(&self, vector: Vec<&str>) -> Option<String> {
667
if vector.is_empty() {
668
return None;
669
}
670
let first = vector.first().unwrap().to_string();
671
if first.is_empty() {
672
None
673
} else {
674
Some(first)
675
}
676
}
677
678
fn is_windows_admin(&self) -> bool {
679
let os = self.get_os();
680
if WINDOWS.is(os) {
681
let command = Command::new_single(WINDOWS_CHECK_ADMIN_COMMAND.to_string());
682
let output = run_shell_command_by_os(os, command).unwrap_or_default();
683
!output.is_empty() && !output.contains("error") && !output.contains("not recognized")
684
} else {
685
false
686
}
687
}
688
689
fn is_safari(&self) -> bool {
690
self.get_browser_name().contains(SAFARI_NAME)
691
}
692
693
fn is_iexplorer(&self) -> bool {
694
self.get_browser_name().eq(IE_NAMES[0])
695
}
696
697
fn is_grid(&self) -> bool {
698
self.get_browser_name().eq(GRID_NAME)
699
}
700
701
fn is_electron(&self) -> bool {
702
self.get_browser_name().eq_ignore_ascii_case(ELECTRON_NAME)
703
}
704
705
fn is_firefox(&self) -> bool {
706
self.get_browser_name().contains(FIREFOX_NAME)
707
}
708
709
fn is_edge(&self) -> bool {
710
self.get_browser_name().eq(EDGE_NAMES[0])
711
}
712
713
fn is_webview2(&self) -> bool {
714
self.get_browser_name().eq(WEBVIEW2_NAME)
715
}
716
717
fn is_browser_version_beta(&self) -> bool {
718
self.is_beta(self.get_browser_version())
719
}
720
721
fn is_beta(&self, browser_version: &str) -> bool {
722
browser_version.eq_ignore_ascii_case(BETA)
723
}
724
725
fn is_browser_version_dev(&self) -> bool {
726
self.is_dev(self.get_browser_version())
727
}
728
729
fn is_dev(&self, browser_version: &str) -> bool {
730
browser_version.eq_ignore_ascii_case(DEV)
731
}
732
733
fn is_browser_version_nightly(&self) -> bool {
734
self.is_nightly(self.get_browser_version())
735
}
736
737
fn is_nightly(&self, browser_version: &str) -> bool {
738
browser_version.eq_ignore_ascii_case(NIGHTLY)
739
|| browser_version.eq_ignore_ascii_case(CANARY)
740
}
741
742
fn is_browser_version_esr(&self) -> bool {
743
self.is_esr(self.get_browser_version())
744
}
745
746
fn is_esr(&self, browser_version: &str) -> bool {
747
browser_version.eq_ignore_ascii_case(ESR)
748
}
749
750
fn is_unstable(&self, browser_version: &str) -> bool {
751
browser_version.eq_ignore_ascii_case(BETA)
752
|| browser_version.eq_ignore_ascii_case(DEV)
753
|| browser_version.eq_ignore_ascii_case(NIGHTLY)
754
|| browser_version.eq_ignore_ascii_case(CANARY)
755
|| browser_version.eq_ignore_ascii_case(ESR)
756
}
757
758
fn is_browser_version_unstable(&self) -> bool {
759
self.is_unstable(self.get_browser_version())
760
}
761
762
fn is_empty(&self, browser_version: &str) -> bool {
763
browser_version.is_empty()
764
}
765
766
fn is_browser_version_empty(&self) -> bool {
767
self.is_empty(self.get_browser_version())
768
}
769
770
fn is_stable(&self, browser_version: &str) -> bool {
771
browser_version.eq_ignore_ascii_case(STABLE)
772
}
773
774
fn is_browser_version_stable(&self) -> bool {
775
self.is_stable(self.get_browser_version())
776
}
777
778
fn is_version_specific(&self, version: &str) -> bool {
779
version.contains(".")
780
}
781
782
fn is_browser_version_specific(&self) -> bool {
783
self.is_version_specific(self.get_browser_version())
784
}
785
786
fn setup(&mut self) -> Result<PathBuf, Error> {
787
let mut driver_in_path = None;
788
let mut driver_in_path_version = None;
789
let original_browser_version = self.get_config().browser_version.clone();
790
791
// Try to find driver in PATH
792
if !self.is_safari() && !self.is_grid() && !self.is_electron() {
793
self.get_logger()
794
.trace(format!("Checking {} in PATH", self.get_driver_name()));
795
(driver_in_path_version, driver_in_path) = self.find_driver_in_path();
796
if let (Some(version), Some(path)) = (&driver_in_path_version, &driver_in_path) {
797
self.get_logger().debug(format!(
798
"Found {} {} in PATH: {}",
799
self.get_driver_name(),
800
version,
801
path
802
));
803
} else {
804
self.get_logger()
805
.debug(format!("{} not found in PATH", self.get_driver_name()));
806
}
807
}
808
let use_driver_in_path = driver_in_path_version.is_some()
809
&& driver_in_path.is_some()
810
&& original_browser_version.is_empty();
811
812
// Discover browser version (or the need to download it, if not available and possible)
813
match self.discover_local_browser() {
814
Ok(_) => {}
815
Err(err) => self.check_error_with_driver_in_path(&use_driver_in_path, err)?,
816
}
817
818
// Download browser if necessary
819
match self.download_browser_if_necessary(&original_browser_version) {
820
Ok(_) => {}
821
Err(err) => {
822
self.set_fallback_driver_from_cache(false);
823
self.check_error_with_driver_in_path(&use_driver_in_path, err)?
824
}
825
}
826
827
// With the discovered browser version, discover the proper driver version using online endpoints
828
if self.get_driver_version().is_empty()
829
|| (self.is_grid() && self.is_nightly(self.get_driver_version()))
830
{
831
match self.discover_driver_version() {
832
Ok(driver_version) => {
833
self.set_driver_version(driver_version);
834
}
835
Err(err) => self.check_error_with_driver_in_path(&use_driver_in_path, err)?,
836
}
837
}
838
839
// Use driver in PATH when the user has not specified any browser version
840
if use_driver_in_path {
841
let path = driver_in_path.unwrap();
842
843
if self.is_skip_driver_in_path() {
844
self.get_logger().debug(format!(
845
"Skipping {} in path: {}",
846
self.get_driver_name(),
847
path
848
));
849
} else {
850
let version = driver_in_path_version.unwrap();
851
let major_version = self.get_major_version(&version)?;
852
853
// Display warning if the discovered driver version is not the same as the driver in PATH
854
if !self.get_driver_version().is_empty()
855
&& !self.is_snap(self.get_browser_path())
856
&& (self.is_firefox() && !version.eq(self.get_driver_version()))
857
|| (!self.is_firefox() && !major_version.eq(&self.get_major_browser_version()))
858
{
859
self.get_logger().warn(format!(
860
"The {} version ({}) detected in PATH at {} might not be compatible with \
861
the detected {} version ({}); currently, {} {} is recommended for {} {}.*, \
862
so it is advised to delete the driver in PATH and retry",
863
self.get_driver_name(),
864
&version,
865
path,
866
self.get_browser_name(),
867
self.get_browser_version(),
868
self.get_driver_name(),
869
self.get_driver_version(),
870
self.get_browser_name(),
871
self.get_major_browser_version()
872
));
873
}
874
self.set_driver_version(version.to_string());
875
return Ok(PathBuf::from(path));
876
}
877
}
878
879
// If driver was not in the PATH, try to find it in the cache
880
let driver_path = self.get_driver_path_in_cache()?;
881
if driver_path.exists() {
882
if !self.is_safari() {
883
self.get_logger().debug(format!(
884
"{} {} already in the cache",
885
self.get_driver_name(),
886
self.get_driver_version()
887
));
888
}
889
} else if !self.is_safari() {
890
// If driver is not in the cache, download it
891
self.assert_online_or_err(OFFLINE_DOWNLOAD_ERR_MSG)?;
892
self.download_driver()?;
893
}
894
Ok(driver_path)
895
}
896
897
fn stats(&self) -> Result<(), Error> {
898
if !self.is_avoid_stats() && !self.is_offline() {
899
let props = Props {
900
browser: self.get_browser_name().to_ascii_lowercase(),
901
browser_version: self.get_browser_version().to_ascii_lowercase(),
902
os: self.get_os().to_ascii_lowercase(),
903
arch: self
904
.get_normalized_arch()
905
.unwrap_or(ARCH_OTHER)
906
.to_ascii_lowercase(),
907
lang: self.get_language_binding().to_ascii_lowercase(),
908
selenium_version: self.get_selenium_version().to_ascii_lowercase(),
909
};
910
let http_client = self.get_http_client().to_owned();
911
let sender = self.get_sender().to_owned();
912
let cache_path = self.get_cache_path()?;
913
let mut metadata = get_metadata(self.get_logger(), &cache_path);
914
if !is_stats_in_metadata(&metadata.stats, &props) {
915
self.get_logger()
916
.debug(format!("Sending stats to Plausible: {:?}", props,));
917
let stats_ttl = self.get_ttl();
918
if stats_ttl > 0 {
919
metadata
920
.stats
921
.push(create_stats_metadata(&props, stats_ttl));
922
write_metadata(&metadata, self.get_logger(), cache_path);
923
}
924
thread::spawn(move || {
925
send_stats_to_plausible(http_client, props, sender);
926
});
927
}
928
}
929
Ok(())
930
}
931
932
fn check_error_with_driver_in_path(
933
&mut self,
934
is_driver_in_path: &bool,
935
err: Error,
936
) -> Result<(), Error> {
937
if *is_driver_in_path {
938
self.get_logger().warn(format!(
939
"Exception managing {}: {}",
940
self.get_browser_name(),
941
err
942
));
943
Ok(())
944
} else {
945
Err(err)
946
}
947
}
948
949
fn is_browser(&self, entry: &DirEntry) -> bool {
950
if MACOS.is(self.get_os()) && !self.is_firefox() {
951
let entry_path = path_to_string(entry.path());
952
self.is_in_cache(entry, &capitalize(self.get_browser_name()))
953
&& entry_path.contains(".app/Contents/MacOS")
954
&& !entry_path.contains("Framework")
955
} else {
956
self.is_in_cache(entry, &self.get_browser_name_with_extension())
957
}
958
}
959
960
fn is_driver(&self, entry: &DirEntry) -> bool {
961
self.is_in_cache(entry, &self.get_driver_name_with_extension())
962
}
963
964
fn is_in_cache(&self, entry: &DirEntry, file_name: &str) -> bool {
965
let is_file = entry.path().is_file();
966
967
let is_file_name = entry
968
.file_name()
969
.to_str()
970
.map(|s| {
971
if MACOS.is(self.get_os()) && !self.is_firefox() {
972
s.contains(file_name)
973
} else {
974
s.ends_with(file_name)
975
}
976
})
977
.unwrap_or(false);
978
979
let match_os = entry
980
.path()
981
.to_str()
982
.map(|s| s.contains(self.get_platform_label()))
983
.unwrap_or(false);
984
985
is_file && is_file_name && match_os
986
}
987
988
fn is_driver_and_matches_browser_version(&self, entry: &DirEntry) -> bool {
989
let match_driver_version = entry
990
.path()
991
.parent()
992
.unwrap_or(entry.path())
993
.file_name()
994
.map(|s| {
995
s.to_str()
996
.unwrap_or_default()
997
.starts_with(&self.get_major_browser_version())
998
})
999
.unwrap_or(false);
1000
1001
self.is_driver(entry) && match_driver_version
1002
}
1003
1004
fn get_browser_path_or_latest_from_cache(&self) -> String {
1005
let mut browser_path = self.get_browser_path().to_string();
1006
if browser_path.is_empty() {
1007
let best_browser_from_cache = &self
1008
.find_best_browser_from_cache()
1009
.unwrap_or_default()
1010
.unwrap_or_default();
1011
if best_browser_from_cache.exists() {
1012
self.get_logger().debug_or_warn(
1013
format!(
1014
"There was an error managing {}; using browser found in the cache",
1015
self.get_browser_name()
1016
),
1017
self.is_offline(),
1018
);
1019
browser_path = path_to_string(best_browser_from_cache);
1020
}
1021
}
1022
browser_path
1023
}
1024
1025
fn find_best_browser_from_cache(&self) -> Result<Option<PathBuf>, Error> {
1026
let cache_path = self.get_cache_path()?.unwrap_or_default();
1027
find_latest_from_cache(&cache_path, |entry| self.is_browser(entry))
1028
}
1029
1030
fn find_best_driver_from_cache(&self) -> Result<Option<PathBuf>, Error> {
1031
let cache_path = self.get_cache_path()?.unwrap_or_default();
1032
let drivers_in_cache_matching_version = collect_files_from_cache(&cache_path, |entry| {
1033
self.is_driver_and_matches_browser_version(entry)
1034
});
1035
1036
// First we look for drivers in cache that matches browser version (should work for Chrome and Edge)
1037
if !drivers_in_cache_matching_version.is_empty() {
1038
Ok(Some(
1039
drivers_in_cache_matching_version
1040
.iter()
1041
.last()
1042
.unwrap()
1043
.to_owned(),
1044
))
1045
} else {
1046
// If not available, we look for the latest available driver in the cache
1047
find_latest_from_cache(&cache_path, |entry| self.is_driver(entry))
1048
}
1049
}
1050
1051
fn get_major_version(&self, full_version: &str) -> Result<String, Error> {
1052
get_index_version(full_version, 0)
1053
}
1054
1055
fn get_minor_version(&self, full_version: &str) -> Result<String, Error> {
1056
get_index_version(full_version, 1)
1057
}
1058
1059
fn get_selenium_release_version(&self) -> Result<String, Error> {
1060
let driver_version = self.get_driver_version();
1061
if driver_version.contains(SNAPSHOT) {
1062
return Ok(NIGHTLY.to_string());
1063
}
1064
1065
let mut release_version = driver_version.to_string();
1066
if !driver_version.ends_with('0') && !self.is_nightly(driver_version) {
1067
// E.g.: version 4.8.1 is shipped within release 4.8.0
1068
let error_message = format!(
1069
"Wrong {} version: '{}'",
1070
self.get_driver_name(),
1071
driver_version
1072
);
1073
let index = release_version.rfind('.').ok_or(anyhow!(error_message))? + 1;
1074
release_version = release_version[..index].to_string();
1075
release_version.push('0');
1076
}
1077
Ok(format!("selenium-{release_version}"))
1078
}
1079
1080
fn assert_online_or_err(&self, message: &str) -> Result<(), Error> {
1081
if self.is_offline() {
1082
return Err(anyhow!(format_one_arg(message, self.get_driver_name())));
1083
}
1084
Ok(())
1085
}
1086
1087
fn get_driver_name_with_extension(&self) -> String {
1088
format!(
1089
"{}{}",
1090
self.get_driver_name(),
1091
get_binary_extension(self.get_os())
1092
)
1093
}
1094
1095
fn get_browser_name_with_extension(&self) -> String {
1096
format!(
1097
"{}{}",
1098
self.get_browser_name(),
1099
get_binary_extension(self.get_os())
1100
)
1101
}
1102
1103
fn general_request_browser_version(
1104
&mut self,
1105
browser_name: &str,
1106
) -> Result<Option<String>, Error> {
1107
let browser_version;
1108
let original_browser_version = self.get_config().browser_version.clone();
1109
let major_browser_version = self.get_major_browser_version();
1110
let cache_path = self.get_cache_path()?;
1111
let mut metadata = get_metadata(self.get_logger(), &cache_path);
1112
1113
// Browser version is checked in the local metadata
1114
match get_browser_version_from_metadata(
1115
&metadata.browsers,
1116
browser_name,
1117
&major_browser_version,
1118
) {
1119
Some(version) => {
1120
self.get_logger().trace(format!(
1121
"Browser with valid TTL. Getting {} version from metadata",
1122
browser_name
1123
));
1124
browser_version = version;
1125
self.set_browser_version(browser_version.clone());
1126
}
1127
_ => {
1128
// If not in metadata, discover version using online endpoints
1129
browser_version =
1130
if major_browser_version.is_empty() || self.is_browser_version_stable() {
1131
self.request_latest_browser_version_from_online(&original_browser_version)?
1132
} else {
1133
self.request_fixed_browser_version_from_online(&original_browser_version)?
1134
};
1135
1136
let browser_ttl = self.get_ttl();
1137
if browser_ttl > 0 {
1138
metadata.browsers.push(create_browser_metadata(
1139
browser_name,
1140
&major_browser_version,
1141
&browser_version,
1142
browser_ttl,
1143
));
1144
write_metadata(&metadata, self.get_logger(), cache_path);
1145
}
1146
}
1147
}
1148
1149
Ok(Some(browser_version))
1150
}
1151
1152
fn general_discover_browser_version(
1153
&mut self,
1154
reg_key: &'static str,
1155
reg_version_arg: &'static str,
1156
cmd_version_arg: &str,
1157
) -> Result<Option<String>, Error> {
1158
let mut browser_path = self.get_browser_path().to_string();
1159
if browser_path.is_empty() {
1160
if let Some(path) = self.detect_browser_path() {
1161
browser_path = path_to_string(&path);
1162
}
1163
} else if !Path::new(&browser_path).exists() {
1164
self.set_fallback_driver_from_cache(false);
1165
return Err(anyhow!(format_one_arg(
1166
"Browser path does not exist: {}",
1167
&browser_path,
1168
)));
1169
}
1170
let escaped_browser_path = self.get_escaped_path(browser_path.to_string());
1171
1172
let mut commands = Vec::new();
1173
if WINDOWS.is(self.get_os()) {
1174
if !escaped_browser_path.is_empty() {
1175
return Ok(get_win_file_version(&escaped_browser_path));
1176
}
1177
if !self.is_browser_version_unstable() {
1178
let reg_command =
1179
Command::new_multiple(vec!["REG", "QUERY", reg_key, "/v", reg_version_arg]);
1180
commands.push(reg_command);
1181
}
1182
} else if !escaped_browser_path.is_empty() {
1183
commands.push(Command::new_single(format_three_args(
1184
cmd_version_arg,
1185
"",
1186
&escaped_browser_path,
1187
"",
1188
)));
1189
commands.push(Command::new_single(format_three_args(
1190
cmd_version_arg,
1191
DOUBLE_QUOTE,
1192
&browser_path,
1193
DOUBLE_QUOTE,
1194
)));
1195
commands.push(Command::new_single(format_three_args(
1196
cmd_version_arg,
1197
SINGLE_QUOTE,
1198
&browser_path,
1199
SINGLE_QUOTE,
1200
)));
1201
commands.push(Command::new_single(format_three_args(
1202
cmd_version_arg,
1203
"",
1204
&browser_path,
1205
"",
1206
)));
1207
}
1208
1209
Ok(self.detect_browser_version(commands))
1210
}
1211
1212
fn discover_safari_version(&mut self, safari_path: String) -> Result<Option<String>, Error> {
1213
let mut browser_path = self.get_browser_path().to_string();
1214
let mut commands = Vec::new();
1215
if browser_path.is_empty() {
1216
match self.detect_browser_path() {
1217
Some(path) => {
1218
browser_path = self.get_escaped_path(path_to_string(&path));
1219
}
1220
_ => return Ok(None),
1221
}
1222
}
1223
if MACOS.is(self.get_os()) {
1224
let plist_command = Command::new_single(format_one_arg(PLIST_COMMAND, &browser_path));
1225
commands.push(plist_command);
1226
} else {
1227
return Ok(None);
1228
}
1229
self.set_browser_path(safari_path);
1230
Ok(self.detect_browser_version(commands))
1231
}
1232
1233
fn get_browser_path_in_cache(&self) -> Result<PathBuf, Error> {
1234
Ok(self
1235
.get_cache_path()?
1236
.unwrap_or_default()
1237
.join(self.get_browser_name())
1238
.join(self.get_platform_label())
1239
.join(self.get_browser_version()))
1240
}
1241
1242
fn get_browser_version_label(&self) -> String {
1243
let major_browser_version = self.get_major_browser_version();
1244
if major_browser_version.is_empty() {
1245
"".to_string()
1246
} else {
1247
format!(" {}", major_browser_version)
1248
}
1249
}
1250
1251
fn unavailable_download<T>(&self) -> Result<T, Error>
1252
where
1253
Self: Sized,
1254
{
1255
self.throw_error_message(UNAVAILABLE_DOWNLOAD_ERR_MSG)
1256
}
1257
1258
fn unavailable_discovery<T>(&self) -> Result<T, Error>
1259
where
1260
Self: Sized,
1261
{
1262
self.throw_error_message(ONLINE_DISCOVERY_ERROR_MESSAGE)
1263
}
1264
1265
fn throw_error_message<T>(&self, error_message: &str) -> Result<T, Error>
1266
where
1267
Self: Sized,
1268
{
1269
let browser_version = self.get_browser_version();
1270
let browser_version_label = if browser_version.is_empty() {
1271
"".to_string()
1272
} else {
1273
format!(" {}", browser_version)
1274
};
1275
Err(anyhow!(format_two_args(
1276
error_message,
1277
self.get_browser_name(),
1278
&browser_version_label,
1279
)))
1280
}
1281
1282
// ----------------------------------------------------------
1283
// Getters and setters for configuration parameters
1284
// ----------------------------------------------------------
1285
1286
fn get_os(&self) -> &str {
1287
self.get_config().os.as_str()
1288
}
1289
1290
fn set_os(&mut self, os: String) {
1291
if !os.is_empty() {
1292
self.get_config_mut().os = os;
1293
}
1294
}
1295
1296
fn get_arch(&self) -> &str {
1297
self.get_config().arch.as_str()
1298
}
1299
1300
fn get_normalized_arch(&self) -> Result<&str, Error> {
1301
let arch = self.get_arch();
1302
if X32.is(arch) {
1303
Ok(ARCH_X86)
1304
} else if X64.is(arch) {
1305
Ok(ARCH_X64)
1306
} else if ARM64.is(arch) {
1307
Ok(ARCH_ARM64)
1308
} else if ARMV7.is(arch) {
1309
Ok(ARCH_ARM7L)
1310
} else {
1311
let err_msg = format!("Unsupported architecture: {}", arch);
1312
self.get_logger().warn(err_msg.clone());
1313
Err(anyhow!(err_msg))
1314
}
1315
}
1316
1317
fn set_arch(&mut self, arch: String) {
1318
if !arch.is_empty() {
1319
self.get_config_mut().arch = arch;
1320
}
1321
}
1322
1323
fn get_browser_version(&self) -> &str {
1324
self.get_config().browser_version.as_str()
1325
}
1326
1327
fn get_major_browser_version(&self) -> String {
1328
if self.is_browser_version_stable() {
1329
STABLE.to_string()
1330
} else if self.is_browser_version_unstable() {
1331
self.get_browser_version().to_string()
1332
} else {
1333
self.get_major_version(self.get_browser_version())
1334
.unwrap_or_default()
1335
}
1336
}
1337
1338
fn set_browser_version(&mut self, browser_version: String) {
1339
if !browser_version.is_empty() {
1340
self.get_config_mut().browser_version = browser_version;
1341
}
1342
}
1343
1344
fn get_driver_version(&self) -> &str {
1345
self.get_config().driver_version.as_str()
1346
}
1347
1348
fn get_major_driver_version(&self) -> String {
1349
self.get_major_version(self.get_driver_version())
1350
.unwrap_or_default()
1351
}
1352
1353
fn set_driver_version(&mut self, driver_version: String) {
1354
if !driver_version.is_empty() {
1355
self.get_config_mut().driver_version = driver_version;
1356
}
1357
}
1358
1359
fn get_browser_path(&self) -> &str {
1360
self.get_config().browser_path.as_str()
1361
}
1362
1363
fn set_browser_path(&mut self, browser_path: String) {
1364
if !browser_path.is_empty() {
1365
self.get_config_mut().browser_path = browser_path;
1366
}
1367
}
1368
1369
fn get_driver_mirror_url(&self) -> &str {
1370
self.get_config().driver_mirror_url.as_str()
1371
}
1372
1373
fn set_driver_mirror_url(&mut self, mirror_url: String) {
1374
if !mirror_url.is_empty() {
1375
self.get_config_mut().driver_mirror_url = mirror_url;
1376
}
1377
}
1378
1379
fn get_browser_mirror_url(&self) -> &str {
1380
self.get_config().browser_mirror_url.as_str()
1381
}
1382
1383
fn set_browser_mirror_url(&mut self, mirror_url: String) {
1384
if !mirror_url.is_empty() {
1385
self.get_config_mut().browser_mirror_url = mirror_url;
1386
}
1387
}
1388
1389
fn get_driver_mirror_versions_url_or_default<'a>(&'a self, default_url: &'a str) -> String {
1390
let driver_mirror_url = self.get_driver_mirror_url();
1391
if !driver_mirror_url.is_empty() {
1392
let driver_versions_path = default_url.rfind('/').map(|i| &default_url[i + 1..]);
1393
if let Some(path) = driver_versions_path {
1394
let driver_mirror_versions_url = if driver_mirror_url.ends_with('/') {
1395
format!("{}{}", driver_mirror_url, path)
1396
} else {
1397
format!("{}/{}", driver_mirror_url, path)
1398
};
1399
self.get_logger().debug(format!(
1400
"Using mirror URL to discover driver versions: {}",
1401
driver_mirror_versions_url
1402
));
1403
return driver_mirror_versions_url;
1404
}
1405
}
1406
default_url.to_string()
1407
}
1408
1409
fn get_driver_mirror_url_or_default<'a>(&'a self, default_url: &'a str) -> String {
1410
self.get_url_or_default(self.get_driver_mirror_url(), default_url)
1411
}
1412
1413
fn get_browser_mirror_url_or_default<'a>(&'a self, default_url: &'a str) -> String {
1414
self.get_url_or_default(self.get_browser_mirror_url(), default_url)
1415
}
1416
1417
fn get_url_or_default<'a>(&'a self, value_url: &'a str, default_url: &'a str) -> String {
1418
let url = if value_url.is_empty() {
1419
default_url
1420
} else {
1421
value_url
1422
};
1423
if !url.ends_with('/') {
1424
format!("{}/", url)
1425
} else {
1426
url.to_string()
1427
}
1428
}
1429
1430
fn canonicalize_path(&self, path_buf: PathBuf) -> String {
1431
let mut canon_path = path_to_string(&path_buf);
1432
if WINDOWS.is(self.get_os()) || canon_path.starts_with(UNC_PREFIX) {
1433
canon_path = path_to_string(
1434
&path_buf
1435
.as_path()
1436
.canonicalize()
1437
.unwrap_or(path_buf.clone()),
1438
)
1439
.replace(UNC_PREFIX, "")
1440
}
1441
if !path_to_string(&path_buf).eq(&canon_path) {
1442
self.get_logger().trace(format!(
1443
"Path {} has been canonicalized to {}",
1444
path_buf.display(),
1445
canon_path
1446
));
1447
}
1448
canon_path
1449
}
1450
1451
fn get_escaped_path(&self, string_path: String) -> String {
1452
let mut escaped_path = string_path.clone();
1453
let path = Path::new(&string_path);
1454
1455
if path.exists() {
1456
escaped_path = self.canonicalize_path(path.to_path_buf());
1457
if WINDOWS.is(self.get_os()) {
1458
escaped_path = escaped_path.replace('\\', "\\\\");
1459
} else {
1460
let escape_command =
1461
Command::new_single(format_one_arg(ESCAPE_COMMAND, escaped_path.as_str()));
1462
escaped_path = run_shell_command("bash", "-c", escape_command).unwrap_or_default();
1463
if escaped_path.is_empty() {
1464
escaped_path = string_path.clone();
1465
}
1466
}
1467
}
1468
if !string_path.eq(&escaped_path) {
1469
self.get_logger().trace(format!(
1470
"Path {} has been escaped to {}",
1471
string_path, escaped_path
1472
));
1473
}
1474
escaped_path
1475
}
1476
1477
fn get_proxy(&self) -> &str {
1478
self.get_config().proxy.as_str()
1479
}
1480
1481
fn set_proxy(&mut self, proxy: String) -> Result<(), Error> {
1482
if !proxy.is_empty() && !self.is_offline() {
1483
self.get_logger().debug(format!("Using proxy: {}", &proxy));
1484
self.get_config_mut().proxy = proxy;
1485
self.update_http_client()?;
1486
}
1487
Ok(())
1488
}
1489
1490
fn get_timeout(&self) -> u64 {
1491
self.get_config().timeout
1492
}
1493
1494
fn set_timeout(&mut self, timeout: u64) -> Result<(), Error> {
1495
if timeout != REQUEST_TIMEOUT_SEC {
1496
self.get_config_mut().timeout = timeout;
1497
self.get_logger()
1498
.debug(format!("Using timeout of {} seconds", timeout));
1499
self.update_http_client()?;
1500
}
1501
Ok(())
1502
}
1503
1504
fn update_http_client(&mut self) -> Result<(), Error> {
1505
let proxy = self.get_proxy();
1506
let timeout = self.get_timeout();
1507
let http_client = create_http_client(timeout, proxy)?;
1508
self.set_http_client(http_client);
1509
Ok(())
1510
}
1511
1512
fn get_ttl(&self) -> u64 {
1513
self.get_config().ttl
1514
}
1515
1516
fn set_ttl(&mut self, ttl: u64) {
1517
self.get_config_mut().ttl = ttl;
1518
}
1519
1520
fn is_offline(&self) -> bool {
1521
self.get_config().offline
1522
}
1523
1524
fn set_offline(&mut self, offline: bool) {
1525
if offline {
1526
self.get_logger()
1527
.debug("Using Selenium Manager in offline mode");
1528
self.get_config_mut().offline = true;
1529
}
1530
}
1531
1532
fn is_force_browser_download(&self) -> bool {
1533
self.get_config().force_browser_download
1534
}
1535
1536
fn set_force_browser_download(&mut self, force_browser_download: bool) {
1537
if force_browser_download {
1538
self.get_config_mut().force_browser_download = true;
1539
}
1540
}
1541
1542
fn is_avoid_browser_download(&self) -> bool {
1543
self.get_config().avoid_browser_download
1544
}
1545
1546
fn set_avoid_browser_download(&mut self, avoid_browser_download: bool) {
1547
if avoid_browser_download {
1548
self.get_config_mut().avoid_browser_download = true;
1549
}
1550
}
1551
1552
fn is_skip_driver_in_path(&self) -> bool {
1553
self.get_config().skip_driver_in_path
1554
}
1555
1556
fn set_skip_driver_in_path(&mut self, skip_driver_in_path: bool) {
1557
if skip_driver_in_path {
1558
self.get_config_mut().skip_driver_in_path = true;
1559
}
1560
}
1561
1562
fn is_skip_browser_in_path(&self) -> bool {
1563
self.get_config().skip_browser_in_path
1564
}
1565
1566
fn set_skip_browser_in_path(&mut self, skip_browser_in_path: bool) {
1567
if skip_browser_in_path {
1568
self.get_config_mut().skip_browser_in_path = true;
1569
}
1570
}
1571
1572
fn get_cache_path(&self) -> Result<Option<PathBuf>, Error> {
1573
let path = Path::new(&self.get_config().cache_path);
1574
match create_path_if_not_exists(path) {
1575
Ok(_) => {
1576
let canon_path = self.canonicalize_path(path.to_path_buf());
1577
Ok(Some(Path::new(&canon_path).to_path_buf()))
1578
}
1579
Err(err) => {
1580
self.get_logger().warn(format!(
1581
"Cache folder ({}) cannot be created: {}",
1582
path.display(),
1583
err
1584
));
1585
Ok(None)
1586
}
1587
}
1588
}
1589
1590
fn set_cache_path(&mut self, cache_path: String) {
1591
if !cache_path.is_empty() {
1592
self.get_config_mut().cache_path = cache_path;
1593
}
1594
}
1595
1596
fn get_language_binding(&self) -> &str {
1597
self.get_config().language_binding.as_str()
1598
}
1599
1600
fn set_language_binding(&mut self, language_binding: String) {
1601
if !language_binding.is_empty() {
1602
self.get_config_mut().language_binding = language_binding;
1603
}
1604
}
1605
1606
fn get_selenium_version(&self) -> &str {
1607
self.get_config().selenium_version.as_str()
1608
}
1609
1610
fn set_selenium_version(&mut self, selenium_version: String) {
1611
if !selenium_version.is_empty() {
1612
self.get_config_mut().selenium_version = selenium_version;
1613
}
1614
}
1615
1616
fn is_avoid_stats(&self) -> bool {
1617
self.get_config().avoid_stats
1618
}
1619
1620
fn set_avoid_stats(&mut self, avoid_stats: bool) {
1621
if avoid_stats {
1622
self.get_config_mut().avoid_stats = true;
1623
}
1624
}
1625
1626
fn is_fallback_driver_from_cache(&self) -> bool {
1627
self.get_config().fallback_driver_from_cache
1628
}
1629
1630
fn set_fallback_driver_from_cache(&mut self, fallback_driver_from_cache: bool) {
1631
self.get_config_mut().fallback_driver_from_cache = fallback_driver_from_cache;
1632
}
1633
}
1634
1635
// ----------------------------------------------------------
1636
// Public functions
1637
// ----------------------------------------------------------
1638
1639
pub fn get_manager_by_browser(browser_name: String) -> Result<Box<dyn SeleniumManager>, Error> {
1640
let browser_name_lower_case = browser_name.to_ascii_lowercase();
1641
if browser_name_lower_case.eq(CHROME_NAME) {
1642
Ok(ChromeManager::new()?)
1643
} else if browser_name_lower_case.eq(FIREFOX_NAME) {
1644
Ok(FirefoxManager::new()?)
1645
} else if EDGE_NAMES.contains(&browser_name_lower_case.as_str()) {
1646
Ok(EdgeManager::new_with_name(browser_name)?)
1647
} else if IE_NAMES.contains(&browser_name_lower_case.as_str()) {
1648
Ok(IExplorerManager::new()?)
1649
} else if browser_name_lower_case.eq(SAFARI_NAME) {
1650
Ok(SafariManager::new()?)
1651
} else if SAFARITP_NAMES.contains(&browser_name_lower_case.as_str()) {
1652
Ok(SafariTPManager::new()?)
1653
} else if browser_name_lower_case.eq(ELECTRON_NAME) {
1654
Ok(ElectronManager::new()?)
1655
} else {
1656
Err(anyhow!(format!("Invalid browser name: {browser_name}")))
1657
}
1658
}
1659
1660
pub fn get_manager_by_driver(driver_name: String) -> Result<Box<dyn SeleniumManager>, Error> {
1661
if driver_name.eq_ignore_ascii_case(CHROMEDRIVER_NAME) {
1662
Ok(ChromeManager::new()?)
1663
} else if driver_name.eq_ignore_ascii_case(GECKODRIVER_NAME) {
1664
Ok(FirefoxManager::new()?)
1665
} else if driver_name.eq_ignore_ascii_case(EDGEDRIVER_NAME) {
1666
Ok(EdgeManager::new()?)
1667
} else if driver_name.eq_ignore_ascii_case(IEDRIVER_NAME) {
1668
Ok(IExplorerManager::new()?)
1669
} else if driver_name.eq_ignore_ascii_case(SAFARIDRIVER_NAME) {
1670
Ok(SafariManager::new()?)
1671
} else {
1672
Err(anyhow!(format!("Invalid driver name: {driver_name}")))
1673
}
1674
}
1675
1676
pub fn clear_cache(log: &Logger, path: &str) {
1677
let cache_path = Path::new(path).to_path_buf();
1678
if cache_path.exists() {
1679
log.debug(format!("Clearing cache at: {}", cache_path.display()));
1680
fs::remove_dir_all(&cache_path).unwrap_or_else(|err| {
1681
log.warn(format!(
1682
"The cache {} cannot be cleared: {}",
1683
cache_path.display(),
1684
err
1685
))
1686
});
1687
}
1688
}
1689
1690
pub fn create_http_client(timeout: u64, proxy: &str) -> Result<Client, Error> {
1691
let mut client_builder = Client::builder()
1692
.danger_accept_invalid_certs(true)
1693
.use_rustls_tls()
1694
.timeout(Duration::from_secs(timeout));
1695
if !proxy.is_empty() {
1696
client_builder = client_builder.proxy(Proxy::all(proxy)?);
1697
}
1698
Ok(client_builder.build().unwrap_or_default())
1699
}
1700
1701
pub fn format_one_arg(string: &str, arg1: &str) -> String {
1702
string.replacen("{}", arg1, 1)
1703
}
1704
1705
pub fn format_two_args(string: &str, arg1: &str, arg2: &str) -> String {
1706
string.replacen("{}", arg1, 1).replacen("{}", arg2, 1)
1707
}
1708
1709
pub fn format_three_args(string: &str, arg1: &str, arg2: &str, arg3: &str) -> String {
1710
string
1711
.replacen("{}", arg1, 1)
1712
.replacen("{}", arg2, 1)
1713
.replacen("{}", arg3, 1)
1714
}
1715
1716
// ----------------------------------------------------------
1717
// Private functions
1718
// ----------------------------------------------------------
1719
1720
fn get_index_version(full_version: &str, index: usize) -> Result<String, Error> {
1721
let version_vec: Vec<&str> = full_version.split('.').collect();
1722
Ok(version_vec
1723
.get(index)
1724
.ok_or(anyhow!(format!("Wrong version: {}", full_version)))?
1725
.to_string())
1726
}
1727
1728