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