You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1416 lines
51 KiB
1416 lines
51 KiB
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
use crate::command::LogOptions;
|
|
use crate::logging::Level;
|
|
use crate::marionette::MarionetteSettings;
|
|
use mozdevice::AndroidStorageInput;
|
|
use mozprofile::preferences::Pref;
|
|
use mozprofile::profile::Profile;
|
|
use mozrunner::firefox_args::{get_arg_value, parse_args, Arg};
|
|
use mozrunner::runner::platform::firefox_default_path;
|
|
use mozversion::{self, firefox_binary_version, firefox_version, Version};
|
|
use regex::bytes::Regex;
|
|
use serde_json::{Map, Value};
|
|
use std::collections::BTreeMap;
|
|
use std::default::Default;
|
|
use std::ffi::OsString;
|
|
use std::fmt::{self, Display};
|
|
use std::fs;
|
|
use std::io;
|
|
use std::io::BufWriter;
|
|
use std::io::Cursor;
|
|
use std::path::{Path, PathBuf};
|
|
use std::str::{self, FromStr};
|
|
use webdriver::capabilities::{BrowserCapabilities, Capabilities};
|
|
use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
|
|
|
|
#[derive(Clone, Debug)]
|
|
enum VersionError {
|
|
VersionError(mozversion::Error),
|
|
MissingBinary,
|
|
}
|
|
|
|
impl From<mozversion::Error> for VersionError {
|
|
fn from(err: mozversion::Error) -> VersionError {
|
|
VersionError::VersionError(err)
|
|
}
|
|
}
|
|
|
|
impl Display for VersionError {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match *self {
|
|
VersionError::VersionError(ref x) => x.fmt(f),
|
|
VersionError::MissingBinary => "No binary provided".fmt(f),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<VersionError> for WebDriverError {
|
|
fn from(err: VersionError) -> WebDriverError {
|
|
WebDriverError::new(ErrorStatus::SessionNotCreated, err.to_string())
|
|
}
|
|
}
|
|
|
|
/// Provides matching of `moz:firefoxOptions` and resolutionnized of which Firefox
|
|
/// binary to use.
|
|
///
|
|
/// `FirefoxCapabilities` is constructed with the fallback binary, should
|
|
/// `moz:firefoxOptions` not contain a binary entry. This may either be the
|
|
/// system Firefox installation or an override, for example given to the
|
|
/// `--binary` flag of geckodriver.
|
|
pub struct FirefoxCapabilities<'a> {
|
|
pub chosen_binary: Option<PathBuf>,
|
|
fallback_binary: Option<&'a PathBuf>,
|
|
version_cache: BTreeMap<PathBuf, Result<Version, VersionError>>,
|
|
}
|
|
|
|
impl<'a> FirefoxCapabilities<'a> {
|
|
pub fn new(fallback_binary: Option<&'a PathBuf>) -> FirefoxCapabilities<'a> {
|
|
FirefoxCapabilities {
|
|
chosen_binary: None,
|
|
fallback_binary,
|
|
version_cache: BTreeMap::new(),
|
|
}
|
|
}
|
|
|
|
fn set_binary(&mut self, capabilities: &Map<String, Value>) {
|
|
self.chosen_binary = capabilities
|
|
.get("moz:firefoxOptions")
|
|
.and_then(|x| x.get("binary"))
|
|
.and_then(|x| x.as_str())
|
|
.map(PathBuf::from)
|
|
.or_else(|| self.fallback_binary.cloned())
|
|
.or_else(firefox_default_path);
|
|
}
|
|
|
|
fn version(&mut self, binary: Option<&Path>) -> Result<Version, VersionError> {
|
|
if let Some(binary) = binary {
|
|
if let Some(cache_value) = self.version_cache.get(binary) {
|
|
return cache_value.clone();
|
|
}
|
|
let rv = self
|
|
.version_from_ini(binary)
|
|
.or_else(|_| self.version_from_binary(binary));
|
|
if let Ok(ref version) = rv {
|
|
debug!("Found version {}", version);
|
|
} else {
|
|
debug!("Failed to get binary version");
|
|
}
|
|
self.version_cache.insert(binary.to_path_buf(), rv.clone());
|
|
rv
|
|
} else {
|
|
Err(VersionError::MissingBinary)
|
|
}
|
|
}
|
|
|
|
fn version_from_ini(&self, binary: &Path) -> Result<Version, VersionError> {
|
|
debug!("Trying to read firefox version from ini files");
|
|
let version = firefox_version(binary)?;
|
|
if let Some(version_string) = version.version_string {
|
|
Version::from_str(&version_string).map_err(|err| err.into())
|
|
} else {
|
|
Err(VersionError::VersionError(
|
|
mozversion::Error::MetadataError("Missing version string".into()),
|
|
))
|
|
}
|
|
}
|
|
|
|
fn version_from_binary(&self, binary: &Path) -> Result<Version, VersionError> {
|
|
debug!("Trying to read firefox version from binary");
|
|
Ok(firefox_binary_version(binary)?)
|
|
}
|
|
}
|
|
|
|
impl<'a> BrowserCapabilities for FirefoxCapabilities<'a> {
|
|
fn init(&mut self, capabilities: &Capabilities) {
|
|
self.set_binary(capabilities);
|
|
}
|
|
|
|
fn browser_name(&mut self, _: &Capabilities) -> WebDriverResult<Option<String>> {
|
|
Ok(Some("firefox".into()))
|
|
}
|
|
|
|
fn browser_version(&mut self, _: &Capabilities) -> WebDriverResult<Option<String>> {
|
|
let binary = self.chosen_binary.clone();
|
|
self.version(binary.as_ref().map(|x| x.as_ref()))
|
|
.map_err(|err| err.into())
|
|
.map(|x| Some(x.to_string()))
|
|
}
|
|
|
|
fn platform_name(&mut self, _: &Capabilities) -> WebDriverResult<Option<String>> {
|
|
Ok(if cfg!(target_os = "windows") {
|
|
Some("windows".into())
|
|
} else if cfg!(target_os = "macos") {
|
|
Some("mac".into())
|
|
} else if cfg!(target_os = "linux") {
|
|
Some("linux".into())
|
|
} else {
|
|
None
|
|
})
|
|
}
|
|
|
|
fn accept_insecure_certs(&mut self, _: &Capabilities) -> WebDriverResult<bool> {
|
|
Ok(true)
|
|
}
|
|
|
|
fn accept_proxy(&mut self, _: &Capabilities, _: &Capabilities) -> WebDriverResult<bool> {
|
|
Ok(true)
|
|
}
|
|
|
|
fn set_window_rect(&mut self, _: &Capabilities) -> WebDriverResult<bool> {
|
|
Ok(true)
|
|
}
|
|
|
|
fn compare_browser_version(
|
|
&mut self,
|
|
version: &str,
|
|
comparison: &str,
|
|
) -> WebDriverResult<bool> {
|
|
Version::from_str(version)
|
|
.map_err(VersionError::from)?
|
|
.matches(comparison)
|
|
.map_err(|err| VersionError::from(err).into())
|
|
}
|
|
|
|
fn strict_file_interactability(&mut self, _: &Capabilities) -> WebDriverResult<bool> {
|
|
Ok(true)
|
|
}
|
|
|
|
fn web_socket_url(&mut self, _: &Capabilities) -> WebDriverResult<bool> {
|
|
Ok(true)
|
|
}
|
|
|
|
fn validate_custom(&mut self, name: &str, value: &Value) -> WebDriverResult<()> {
|
|
if !name.starts_with("moz:") {
|
|
return Ok(());
|
|
}
|
|
match name {
|
|
"moz:firefoxOptions" => {
|
|
let data = try_opt!(
|
|
value.as_object(),
|
|
ErrorStatus::InvalidArgument,
|
|
"moz:firefoxOptions is not an object"
|
|
);
|
|
for (key, value) in data.iter() {
|
|
match &**key {
|
|
"androidActivity"
|
|
| "androidDeviceSerial"
|
|
| "androidPackage"
|
|
| "profile" => {
|
|
if !value.is_string() {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
format!("{} is not a string", &**key),
|
|
));
|
|
}
|
|
}
|
|
"androidIntentArguments" | "args" => {
|
|
if !try_opt!(
|
|
value.as_array(),
|
|
ErrorStatus::InvalidArgument,
|
|
format!("{} is not an array", &**key)
|
|
)
|
|
.iter()
|
|
.all(|value| value.is_string())
|
|
{
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
format!("{} entry is not a string", &**key),
|
|
));
|
|
}
|
|
}
|
|
"binary" => {
|
|
if let Some(binary) = value.as_str() {
|
|
if !data.contains_key("androidPackage")
|
|
&& self.version(Some(Path::new(binary))).is_err()
|
|
{
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
format!("{} is not a Firefox executable", &**key),
|
|
));
|
|
}
|
|
} else {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
format!("{} is not a string", &**key),
|
|
));
|
|
}
|
|
}
|
|
"env" => {
|
|
let env_data = try_opt!(
|
|
value.as_object(),
|
|
ErrorStatus::InvalidArgument,
|
|
"env value is not an object"
|
|
);
|
|
if !env_data.values().all(Value::is_string) {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"Environment values were not all strings",
|
|
));
|
|
}
|
|
}
|
|
"log" => {
|
|
let log_data = try_opt!(
|
|
value.as_object(),
|
|
ErrorStatus::InvalidArgument,
|
|
"log value is not an object"
|
|
);
|
|
for (log_key, log_value) in log_data.iter() {
|
|
match &**log_key {
|
|
"level" => {
|
|
let level = try_opt!(
|
|
log_value.as_str(),
|
|
ErrorStatus::InvalidArgument,
|
|
"log level is not a string"
|
|
);
|
|
if Level::from_str(level).is_err() {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
format!("Not a valid log level: {}", level),
|
|
));
|
|
}
|
|
}
|
|
x => {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
format!("Invalid log field {}", x),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"prefs" => {
|
|
let prefs_data = try_opt!(
|
|
value.as_object(),
|
|
ErrorStatus::InvalidArgument,
|
|
"prefs value is not an object"
|
|
);
|
|
let is_pref_value_type = |x: &Value| {
|
|
x.is_string() || x.is_i64() || x.is_u64() || x.is_boolean()
|
|
};
|
|
if !prefs_data.values().all(is_pref_value_type) {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"Preference values not all string or integer or boolean",
|
|
));
|
|
}
|
|
}
|
|
x => {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
format!("Invalid moz:firefoxOptions field {}", x),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"moz:useNonSpecCompliantPointerOrigin" => {
|
|
warn!("You are using the deprecated vendor specific capability 'moz:useNonSpecCompliantPointerOrigin', which will be removed in Firefox 116.");
|
|
if !value.is_boolean() {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"moz:useNonSpecCompliantPointerOrigin is not a boolean",
|
|
));
|
|
}
|
|
}
|
|
"moz:webdriverClick" => {
|
|
if !value.is_boolean() {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"moz:webdriverClick is not a boolean",
|
|
));
|
|
}
|
|
}
|
|
"moz:debuggerAddress" => {
|
|
if !value.is_boolean() {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"moz:debuggerAddress is not a boolean",
|
|
));
|
|
}
|
|
}
|
|
_ => {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
format!("Unrecognised option {}", name),
|
|
))
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn accept_custom(&mut self, _: &str, _: &Value, _: &Capabilities) -> WebDriverResult<bool> {
|
|
Ok(true)
|
|
}
|
|
}
|
|
|
|
/// Android-specific options in the `moz:firefoxOptions` struct.
|
|
/// These map to "androidCamelCase", following [chromedriver's Android-specific
|
|
/// Capabilities](http://chromedriver.chromium.org/getting-started/getting-started---android).
|
|
#[derive(Default, Clone, Debug, PartialEq)]
|
|
pub struct AndroidOptions {
|
|
pub activity: Option<String>,
|
|
pub device_serial: Option<String>,
|
|
pub intent_arguments: Option<Vec<String>>,
|
|
pub package: String,
|
|
pub storage: AndroidStorageInput,
|
|
}
|
|
|
|
impl AndroidOptions {
|
|
pub fn new(package: String, storage: AndroidStorageInput) -> AndroidOptions {
|
|
AndroidOptions {
|
|
package,
|
|
storage,
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum ProfileType {
|
|
Path(Profile),
|
|
Named,
|
|
Temporary,
|
|
}
|
|
|
|
impl Default for ProfileType {
|
|
fn default() -> Self {
|
|
ProfileType::Temporary
|
|
}
|
|
}
|
|
|
|
/// Rust representation of `moz:firefoxOptions`.
|
|
///
|
|
/// Calling `FirefoxOptions::from_capabilities(binary, capabilities)` causes
|
|
/// the encoded profile, the binary arguments, log settings, and additional
|
|
/// preferences to be checked and unmarshaled from the `moz:firefoxOptions`
|
|
/// JSON Object into a Rust representation.
|
|
#[derive(Default, Debug)]
|
|
pub struct FirefoxOptions {
|
|
pub binary: Option<PathBuf>,
|
|
pub profile: ProfileType,
|
|
pub args: Option<Vec<String>>,
|
|
pub env: Option<Vec<(String, String)>>,
|
|
pub log: LogOptions,
|
|
pub prefs: Vec<(String, Pref)>,
|
|
pub android: Option<AndroidOptions>,
|
|
pub use_websocket: bool,
|
|
}
|
|
|
|
impl FirefoxOptions {
|
|
pub fn new() -> FirefoxOptions {
|
|
Default::default()
|
|
}
|
|
|
|
pub(crate) fn from_capabilities(
|
|
binary_path: Option<PathBuf>,
|
|
settings: &MarionetteSettings,
|
|
matched: &mut Capabilities,
|
|
) -> WebDriverResult<FirefoxOptions> {
|
|
let mut rv = FirefoxOptions::new();
|
|
rv.binary = binary_path;
|
|
|
|
if let Some(json) = matched.remove("moz:firefoxOptions") {
|
|
let options = json.as_object().ok_or_else(|| {
|
|
WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"'moz:firefoxOptions' \
|
|
capability is not an object",
|
|
)
|
|
})?;
|
|
|
|
if options.get("androidPackage").is_some() && options.get("binary").is_some() {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"androidPackage and binary are mutual exclusive",
|
|
));
|
|
}
|
|
|
|
rv.android = FirefoxOptions::load_android(settings.android_storage, options)?;
|
|
rv.args = FirefoxOptions::load_args(options)?;
|
|
rv.env = FirefoxOptions::load_env(options)?;
|
|
rv.log = FirefoxOptions::load_log(options)?;
|
|
rv.prefs = FirefoxOptions::load_prefs(options)?;
|
|
if let Some(profile) =
|
|
FirefoxOptions::load_profile(settings.profile_root.as_deref(), options)?
|
|
{
|
|
rv.profile = ProfileType::Path(profile);
|
|
}
|
|
}
|
|
|
|
if let Some(args) = rv.args.as_ref() {
|
|
let os_args = parse_args(args.iter().map(OsString::from).collect::<Vec<_>>().iter());
|
|
|
|
if let Some(path) = get_arg_value(os_args.iter(), Arg::Profile) {
|
|
if let ProfileType::Path(_) = rv.profile {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"Can't provide both a --profile argument and a profile",
|
|
));
|
|
}
|
|
let path_buf = PathBuf::from(path);
|
|
rv.profile = ProfileType::Path(Profile::new_from_path(&path_buf)?);
|
|
}
|
|
|
|
if get_arg_value(os_args.iter(), Arg::NamedProfile).is_some() {
|
|
if let ProfileType::Path(_) = rv.profile {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"Can't provide both a -P argument and a profile",
|
|
));
|
|
}
|
|
// See bug 1757720
|
|
warn!("Firefox was configured to use a named profile (`-P <name>`). \
|
|
Support for named profiles will be removed in a future geckodriver release. \
|
|
Please instead use the `--profile <path>` Firefox argument to start with an existing profile");
|
|
rv.profile = ProfileType::Named;
|
|
}
|
|
|
|
// Block these Firefox command line arguments that should not be settable
|
|
// via session capabilities.
|
|
if let Some(arg) = os_args
|
|
.iter()
|
|
.filter_map(|(opt_arg, _)| opt_arg.as_ref())
|
|
.find(|arg| {
|
|
matches!(
|
|
arg,
|
|
Arg::Marionette
|
|
| Arg::RemoteAllowHosts
|
|
| Arg::RemoteAllowOrigins
|
|
| Arg::RemoteDebuggingPort
|
|
)
|
|
})
|
|
{
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
format!("Argument {} can't be set via capabilities", arg),
|
|
));
|
|
};
|
|
}
|
|
|
|
let has_web_socket_url = matched
|
|
.get("webSocketUrl")
|
|
.and_then(|x| x.as_bool())
|
|
.unwrap_or(false);
|
|
|
|
let has_debugger_address = matched
|
|
.remove("moz:debuggerAddress")
|
|
.and_then(|x| x.as_bool())
|
|
.unwrap_or(false);
|
|
|
|
// Set a command line provided port for the Remote Agent for now.
|
|
// It needs to be the same on the host and the Android device.
|
|
if has_web_socket_url || has_debugger_address {
|
|
rv.use_websocket = true;
|
|
|
|
// Bug 1722863: Setting of command line arguments would be
|
|
// better suited in the individual Browser implementations.
|
|
let mut remote_args = Vec::new();
|
|
remote_args.push("--remote-debugging-port".to_owned());
|
|
remote_args.push(settings.websocket_port.to_string());
|
|
|
|
// Handle additional hosts for WebDriver BiDi WebSocket connections
|
|
if !settings.allow_hosts.is_empty() {
|
|
remote_args.push("--remote-allow-hosts".to_owned());
|
|
remote_args.push(
|
|
settings
|
|
.allow_hosts
|
|
.iter()
|
|
.map(|host| host.to_string())
|
|
.collect::<Vec<String>>()
|
|
.join(","),
|
|
);
|
|
}
|
|
|
|
// Handle additional origins for WebDriver BiDi WebSocket connections
|
|
if !settings.allow_origins.is_empty() {
|
|
remote_args.push("--remote-allow-origins".to_owned());
|
|
remote_args.push(
|
|
settings
|
|
.allow_origins
|
|
.iter()
|
|
.map(|origin| origin.to_string())
|
|
.collect::<Vec<String>>()
|
|
.join(","),
|
|
);
|
|
}
|
|
|
|
if let Some(ref mut args) = rv.args {
|
|
args.append(&mut remote_args);
|
|
} else {
|
|
rv.args = Some(remote_args);
|
|
}
|
|
}
|
|
|
|
Ok(rv)
|
|
}
|
|
|
|
fn load_profile(
|
|
profile_root: Option<&Path>,
|
|
options: &Capabilities,
|
|
) -> WebDriverResult<Option<Profile>> {
|
|
if let Some(profile_json) = options.get("profile") {
|
|
let profile_base64 = profile_json.as_str().ok_or_else(|| {
|
|
WebDriverError::new(ErrorStatus::InvalidArgument, "Profile is not a string")
|
|
})?;
|
|
let profile_zip = &*base64::decode(profile_base64)?;
|
|
|
|
// Create an emtpy profile directory
|
|
let profile = Profile::new(profile_root)?;
|
|
unzip_buffer(
|
|
profile_zip,
|
|
profile
|
|
.temp_dir
|
|
.as_ref()
|
|
.expect("Profile doesn't have a path")
|
|
.path(),
|
|
)?;
|
|
|
|
Ok(Some(profile))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
fn load_args(options: &Capabilities) -> WebDriverResult<Option<Vec<String>>> {
|
|
if let Some(args_json) = options.get("args") {
|
|
let args_array = args_json.as_array().ok_or_else(|| {
|
|
WebDriverError::new(ErrorStatus::InvalidArgument, "Arguments were not an array")
|
|
})?;
|
|
let args = args_array
|
|
.iter()
|
|
.map(|x| x.as_str().map(|x| x.to_owned()))
|
|
.collect::<Option<Vec<String>>>()
|
|
.ok_or_else(|| {
|
|
WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"Arguments entries were not all strings",
|
|
)
|
|
})?;
|
|
|
|
Ok(Some(args))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
pub fn load_env(options: &Capabilities) -> WebDriverResult<Option<Vec<(String, String)>>> {
|
|
if let Some(env_data) = options.get("env") {
|
|
let env = env_data.as_object().ok_or_else(|| {
|
|
WebDriverError::new(ErrorStatus::InvalidArgument, "Env was not an object")
|
|
})?;
|
|
let mut rv = Vec::with_capacity(env.len());
|
|
for (key, value) in env.iter() {
|
|
rv.push((
|
|
key.clone(),
|
|
value
|
|
.as_str()
|
|
.ok_or_else(|| {
|
|
WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"Env value is not a string",
|
|
)
|
|
})?
|
|
.to_string(),
|
|
));
|
|
}
|
|
Ok(Some(rv))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
fn load_log(options: &Capabilities) -> WebDriverResult<LogOptions> {
|
|
if let Some(json) = options.get("log") {
|
|
let log = json.as_object().ok_or_else(|| {
|
|
WebDriverError::new(ErrorStatus::InvalidArgument, "Log section is not an object")
|
|
})?;
|
|
|
|
let level = match log.get("level") {
|
|
Some(json) => {
|
|
let s = json.as_str().ok_or_else(|| {
|
|
WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"Log level is not a string",
|
|
)
|
|
})?;
|
|
Some(Level::from_str(s).ok().ok_or_else(|| {
|
|
WebDriverError::new(ErrorStatus::InvalidArgument, "Log level is unknown")
|
|
})?)
|
|
}
|
|
None => None,
|
|
};
|
|
|
|
Ok(LogOptions { level })
|
|
} else {
|
|
Ok(Default::default())
|
|
}
|
|
}
|
|
|
|
pub fn load_prefs(options: &Capabilities) -> WebDriverResult<Vec<(String, Pref)>> {
|
|
if let Some(prefs_data) = options.get("prefs") {
|
|
let prefs = prefs_data.as_object().ok_or_else(|| {
|
|
WebDriverError::new(ErrorStatus::InvalidArgument, "Prefs were not an object")
|
|
})?;
|
|
let mut rv = Vec::with_capacity(prefs.len());
|
|
for (key, value) in prefs.iter() {
|
|
rv.push((key.clone(), pref_from_json(value)?));
|
|
}
|
|
Ok(rv)
|
|
} else {
|
|
Ok(vec![])
|
|
}
|
|
}
|
|
|
|
pub fn load_android(
|
|
storage: AndroidStorageInput,
|
|
options: &Capabilities,
|
|
) -> WebDriverResult<Option<AndroidOptions>> {
|
|
if let Some(package_json) = options.get("androidPackage") {
|
|
let package = package_json
|
|
.as_str()
|
|
.ok_or_else(|| {
|
|
WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"androidPackage is not a string",
|
|
)
|
|
})?
|
|
.to_owned();
|
|
|
|
// https://developer.android.com/studio/build/application-id
|
|
let package_regexp =
|
|
Regex::new(r#"^([a-zA-Z][a-zA-Z0-9_]*\.){1,}([a-zA-Z][a-zA-Z0-9_]*)$"#).unwrap();
|
|
if !package_regexp.is_match(package.as_bytes()) {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"Not a valid androidPackage name",
|
|
));
|
|
}
|
|
|
|
let mut android = AndroidOptions::new(package.clone(), storage);
|
|
|
|
android.activity = match options.get("androidActivity") {
|
|
Some(json) => {
|
|
let activity = json
|
|
.as_str()
|
|
.ok_or_else(|| {
|
|
WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"androidActivity is not a string",
|
|
)
|
|
})?
|
|
.to_owned();
|
|
|
|
if activity.contains('/') {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"androidActivity should not contain '/",
|
|
));
|
|
}
|
|
|
|
Some(activity)
|
|
}
|
|
None => {
|
|
match package.as_str() {
|
|
"org.mozilla.firefox"
|
|
| "org.mozilla.firefox_beta"
|
|
| "org.mozilla.fenix"
|
|
| "org.mozilla.fenix.debug"
|
|
| "org.mozilla.reference.browser" => {
|
|
Some("org.mozilla.fenix.IntentReceiverActivity".to_string())
|
|
}
|
|
"org.mozilla.focus"
|
|
| "org.mozilla.focus.debug"
|
|
| "org.mozilla.klar"
|
|
| "org.mozilla.klar.debug" => {
|
|
Some("org.mozilla.focus.activity.IntentReceiverActivity".to_string())
|
|
}
|
|
// For all other applications fallback to auto-detection.
|
|
_ => None,
|
|
}
|
|
}
|
|
};
|
|
|
|
android.device_serial = match options.get("androidDeviceSerial") {
|
|
Some(json) => Some(
|
|
json.as_str()
|
|
.ok_or_else(|| {
|
|
WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"androidDeviceSerial is not a string",
|
|
)
|
|
})?
|
|
.to_owned(),
|
|
),
|
|
None => None,
|
|
};
|
|
|
|
android.intent_arguments = match options.get("androidIntentArguments") {
|
|
Some(json) => {
|
|
let args_array = json.as_array().ok_or_else(|| {
|
|
WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"androidIntentArguments is not an array",
|
|
)
|
|
})?;
|
|
let args = args_array
|
|
.iter()
|
|
.map(|x| x.as_str().map(|x| x.to_owned()))
|
|
.collect::<Option<Vec<String>>>()
|
|
.ok_or_else(|| {
|
|
WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"androidIntentArguments entries are not all strings",
|
|
)
|
|
})?;
|
|
|
|
Some(args)
|
|
}
|
|
None => {
|
|
// All GeckoView based applications support this view,
|
|
// and allow to open a blank page in a Gecko window.
|
|
Some(vec![
|
|
"-a".to_string(),
|
|
"android.intent.action.VIEW".to_string(),
|
|
"-d".to_string(),
|
|
"about:blank".to_string(),
|
|
])
|
|
}
|
|
};
|
|
|
|
Ok(Some(android))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn pref_from_json(value: &Value) -> WebDriverResult<Pref> {
|
|
match *value {
|
|
Value::String(ref x) => Ok(Pref::new(x.clone())),
|
|
Value::Number(ref x) => Ok(Pref::new(x.as_i64().unwrap())),
|
|
Value::Bool(x) => Ok(Pref::new(x)),
|
|
_ => Err(WebDriverError::new(
|
|
ErrorStatus::UnknownError,
|
|
"Could not convert pref value to string, boolean, or integer",
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn unzip_buffer(buf: &[u8], dest_dir: &Path) -> WebDriverResult<()> {
|
|
let reader = Cursor::new(buf);
|
|
let mut zip = zip::ZipArchive::new(reader)
|
|
.map_err(|_| WebDriverError::new(ErrorStatus::UnknownError, "Failed to unzip profile"))?;
|
|
|
|
for i in 0..zip.len() {
|
|
let mut file = zip.by_index(i).map_err(|_| {
|
|
WebDriverError::new(
|
|
ErrorStatus::UnknownError,
|
|
"Processing profile zip file failed",
|
|
)
|
|
})?;
|
|
let unzip_path = {
|
|
let name = file.name();
|
|
let is_dir = name.ends_with('/');
|
|
let rel_path = Path::new(name);
|
|
let dest_path = dest_dir.join(rel_path);
|
|
|
|
{
|
|
let create_dir = if is_dir {
|
|
Some(dest_path.as_path())
|
|
} else {
|
|
dest_path.parent()
|
|
};
|
|
if let Some(dir) = create_dir {
|
|
if !dir.exists() {
|
|
debug!("Creating profile directory tree {}", dir.to_string_lossy());
|
|
fs::create_dir_all(dir)?;
|
|
}
|
|
}
|
|
}
|
|
|
|
if is_dir {
|
|
None
|
|
} else {
|
|
Some(dest_path)
|
|
}
|
|
};
|
|
|
|
if let Some(unzip_path) = unzip_path {
|
|
debug!("Extracting profile to {}", unzip_path.to_string_lossy());
|
|
let dest = fs::File::create(unzip_path)?;
|
|
if file.size() > 0 {
|
|
let mut writer = BufWriter::new(dest);
|
|
io::copy(&mut file, &mut writer)?;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
extern crate mozprofile;
|
|
|
|
use self::mozprofile::preferences::Pref;
|
|
use super::*;
|
|
use serde_json::{json, Map, Value};
|
|
use std::fs::File;
|
|
use std::io::Read;
|
|
use url::{Host, Url};
|
|
use webdriver::capabilities::Capabilities;
|
|
|
|
fn example_profile() -> Value {
|
|
let mut profile_data = Vec::with_capacity(1024);
|
|
let mut profile = File::open("src/tests/profile.zip").unwrap();
|
|
profile.read_to_end(&mut profile_data).unwrap();
|
|
Value::String(base64::encode(&profile_data))
|
|
}
|
|
|
|
fn make_options(
|
|
firefox_opts: Capabilities,
|
|
marionette_settings: Option<MarionetteSettings>,
|
|
) -> WebDriverResult<FirefoxOptions> {
|
|
let mut caps = Capabilities::new();
|
|
caps.insert("moz:firefoxOptions".into(), Value::Object(firefox_opts));
|
|
|
|
FirefoxOptions::from_capabilities(None, &marionette_settings.unwrap_or_default(), &mut caps)
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_default() {
|
|
let opts: FirefoxOptions = Default::default();
|
|
assert_eq!(opts.android, None);
|
|
assert_eq!(opts.args, None);
|
|
assert_eq!(opts.binary, None);
|
|
assert_eq!(opts.log, LogOptions { level: None });
|
|
assert_eq!(opts.prefs, vec![]);
|
|
// Profile doesn't support PartialEq
|
|
// assert_eq!(opts.profile, None);
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_from_capabilities_no_binary_and_empty_caps() {
|
|
let mut caps = Capabilities::new();
|
|
|
|
let marionette_settings = Default::default();
|
|
let opts = FirefoxOptions::from_capabilities(None, &marionette_settings, &mut caps)
|
|
.expect("valid firefox options");
|
|
assert_eq!(opts.android, None);
|
|
assert_eq!(opts.args, None);
|
|
assert_eq!(opts.binary, None);
|
|
assert_eq!(opts.log, LogOptions { level: None });
|
|
assert_eq!(opts.prefs, vec![]);
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_from_capabilities_with_binary_and_caps() {
|
|
let mut caps = Capabilities::new();
|
|
caps.insert(
|
|
"moz:firefoxOptions".into(),
|
|
Value::Object(Capabilities::new()),
|
|
);
|
|
|
|
let binary = PathBuf::from("foo");
|
|
let marionette_settings = Default::default();
|
|
|
|
let opts = FirefoxOptions::from_capabilities(
|
|
Some(binary.clone()),
|
|
&marionette_settings,
|
|
&mut caps,
|
|
)
|
|
.expect("valid firefox options");
|
|
assert_eq!(opts.android, None);
|
|
assert_eq!(opts.args, None);
|
|
assert_eq!(opts.binary, Some(binary));
|
|
assert_eq!(opts.log, LogOptions { level: None });
|
|
assert_eq!(opts.prefs, vec![]);
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_from_capabilities_with_blocked_firefox_arguments() {
|
|
let blocked_args = vec![
|
|
"--marionette",
|
|
"--remote-allow-hosts",
|
|
"--remote-allow-origins",
|
|
"--remote-debugging-port",
|
|
];
|
|
|
|
for arg in blocked_args {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("args".into(), json!([arg]));
|
|
|
|
make_options(firefox_opts, None).expect_err("invalid firefox options");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_from_capabilities_with_websocket_url_not_set() {
|
|
let mut caps = Capabilities::new();
|
|
|
|
let marionette_settings = Default::default();
|
|
let opts = FirefoxOptions::from_capabilities(None, &marionette_settings, &mut caps)
|
|
.expect("Valid Firefox options");
|
|
|
|
assert!(
|
|
opts.args.is_none(),
|
|
"CLI arguments for Firefox unexpectedly found"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_from_capabilities_with_websocket_url_false() {
|
|
let mut caps = Capabilities::new();
|
|
caps.insert("webSocketUrl".into(), json!(false));
|
|
|
|
let marionette_settings = Default::default();
|
|
let opts = FirefoxOptions::from_capabilities(None, &marionette_settings, &mut caps)
|
|
.expect("Valid Firefox options");
|
|
|
|
assert!(
|
|
opts.args.is_none(),
|
|
"CLI arguments for Firefox unexpectedly found"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_from_capabilities_with_websocket_url_true() {
|
|
let mut caps = Capabilities::new();
|
|
caps.insert("webSocketUrl".into(), json!(true));
|
|
|
|
let settings = MarionetteSettings {
|
|
websocket_port: 1234,
|
|
..Default::default()
|
|
};
|
|
let opts = FirefoxOptions::from_capabilities(None, &settings, &mut caps)
|
|
.expect("Valid Firefox options");
|
|
|
|
if let Some(args) = opts.args {
|
|
let mut iter = args.iter();
|
|
assert!(iter.any(|arg| arg == &"--remote-debugging-port".to_owned()));
|
|
assert_eq!(iter.next(), Some(&"1234".to_owned()));
|
|
} else {
|
|
panic!("CLI arguments for Firefox not found");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_from_capabilities_with_websocket_and_allow_hosts() {
|
|
let mut caps = Capabilities::new();
|
|
caps.insert("webSocketUrl".into(), json!(true));
|
|
|
|
let mut marionette_settings: MarionetteSettings = Default::default();
|
|
marionette_settings.allow_hosts = vec![
|
|
Host::parse("foo").expect("host"),
|
|
Host::parse("bar").expect("host"),
|
|
];
|
|
let opts = FirefoxOptions::from_capabilities(None, &marionette_settings, &mut caps)
|
|
.expect("Valid Firefox options");
|
|
|
|
if let Some(args) = opts.args {
|
|
let mut iter = args.iter();
|
|
assert!(iter.any(|arg| arg == &"--remote-allow-hosts".to_owned()));
|
|
assert_eq!(iter.next(), Some(&"foo,bar".to_owned()));
|
|
assert!(!iter.any(|arg| arg == &"--remote-allow-origins".to_owned()));
|
|
} else {
|
|
panic!("CLI arguments for Firefox not found");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_from_capabilities_with_websocket_and_allow_origins() {
|
|
let mut caps = Capabilities::new();
|
|
caps.insert("webSocketUrl".into(), json!(true));
|
|
|
|
let mut marionette_settings: MarionetteSettings = Default::default();
|
|
marionette_settings.allow_origins = vec![
|
|
Url::parse("http://foo/").expect("url"),
|
|
Url::parse("http://bar/").expect("url"),
|
|
];
|
|
let opts = FirefoxOptions::from_capabilities(None, &marionette_settings, &mut caps)
|
|
.expect("Valid Firefox options");
|
|
|
|
if let Some(args) = opts.args {
|
|
let mut iter = args.iter();
|
|
assert!(iter.any(|arg| arg == &"--remote-allow-origins".to_owned()));
|
|
assert_eq!(iter.next(), Some(&"http://foo/,http://bar/".to_owned()));
|
|
assert!(!iter.any(|arg| arg == &"--remote-allow-hosts".to_owned()));
|
|
} else {
|
|
panic!("CLI arguments for Firefox not found");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_from_capabilities_with_debugger_address_not_set() {
|
|
let caps = Capabilities::new();
|
|
|
|
let opts = make_options(caps, None).expect("valid firefox options");
|
|
assert!(
|
|
opts.args.is_none(),
|
|
"CLI arguments for Firefox unexpectedly found"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_from_capabilities_with_debugger_address_false() {
|
|
let mut caps = Capabilities::new();
|
|
caps.insert("moz:debuggerAddress".into(), json!(false));
|
|
|
|
let opts = make_options(caps, None).expect("valid firefox options");
|
|
assert!(
|
|
opts.args.is_none(),
|
|
"CLI arguments for Firefox unexpectedly found"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_from_capabilities_with_debugger_address_true() {
|
|
let mut caps = Capabilities::new();
|
|
caps.insert("moz:debuggerAddress".into(), json!(true));
|
|
|
|
let settings = MarionetteSettings {
|
|
websocket_port: 1234,
|
|
..Default::default()
|
|
};
|
|
let opts = FirefoxOptions::from_capabilities(None, &settings, &mut caps)
|
|
.expect("Valid Firefox options");
|
|
|
|
if let Some(args) = opts.args {
|
|
let mut iter = args.iter();
|
|
assert!(iter.any(|arg| arg == &"--remote-debugging-port".to_owned()));
|
|
assert_eq!(iter.next(), Some(&"1234".to_owned()));
|
|
} else {
|
|
panic!("CLI arguments for Firefox not found");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_from_capabilities_with_invalid_caps() {
|
|
let mut caps = Capabilities::new();
|
|
caps.insert("moz:firefoxOptions".into(), json!(42));
|
|
|
|
let marionette_settings = Default::default();
|
|
FirefoxOptions::from_capabilities(None, &marionette_settings, &mut caps)
|
|
.expect_err("Firefox options need to be of type object");
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_package_and_binary() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidPackage".into(), json!("foo"));
|
|
firefox_opts.insert("binary".into(), json!("bar"));
|
|
|
|
make_options(firefox_opts, None)
|
|
.expect_err("androidPackage and binary are mutual exclusive");
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_no_package() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidAvtivity".into(), json!("foo"));
|
|
|
|
let opts = make_options(firefox_opts, None).expect("valid firefox options");
|
|
assert_eq!(opts.android, None);
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_package_valid_value() {
|
|
for value in ["foo.bar", "foo.bar.cheese.is.good", "Foo.Bar_9"].iter() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidPackage".into(), json!(value));
|
|
|
|
let opts = make_options(firefox_opts, None).expect("valid firefox options");
|
|
assert_eq!(opts.android.unwrap().package, value.to_string());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_package_invalid_type() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidPackage".into(), json!(42));
|
|
|
|
make_options(firefox_opts, None).expect_err("invalid firefox options");
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_package_invalid_value() {
|
|
for value in ["../foo", "\\foo\n", "foo", "_foo", "0foo"].iter() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidPackage".into(), json!(value));
|
|
make_options(firefox_opts, None).expect_err("invalid firefox options");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_activity_default_known_apps() {
|
|
let packages = vec![
|
|
"org.mozilla.firefox",
|
|
"org.mozilla.firefox_beta",
|
|
"org.mozilla.fenix",
|
|
"org.mozilla.fenix.debug",
|
|
"org.mozilla.focus",
|
|
"org.mozilla.focus.debug",
|
|
"org.mozilla.klar",
|
|
"org.mozilla.klar.debug",
|
|
"org.mozilla.reference.browser",
|
|
];
|
|
|
|
for package in packages {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidPackage".into(), json!(package));
|
|
|
|
let opts = make_options(firefox_opts, None).expect("valid firefox options");
|
|
assert!(opts
|
|
.android
|
|
.unwrap()
|
|
.activity
|
|
.unwrap()
|
|
.contains("IntentReceiverActivity"));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_activity_default_unknown_apps() {
|
|
let packages = vec!["org.mozilla.geckoview_example", "com.some.other.app"];
|
|
|
|
for package in packages {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidPackage".into(), json!(package));
|
|
|
|
let opts = make_options(firefox_opts, None).expect("valid firefox options");
|
|
assert_eq!(opts.android.unwrap().activity, None);
|
|
}
|
|
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert(
|
|
"androidPackage".into(),
|
|
json!("org.mozilla.geckoview_example"),
|
|
);
|
|
|
|
let opts = make_options(firefox_opts, None).expect("valid firefox options");
|
|
assert_eq!(opts.android.unwrap().activity, None);
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_activity_override() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidPackage".into(), json!("foo.bar"));
|
|
firefox_opts.insert("androidActivity".into(), json!("foo"));
|
|
|
|
let opts = make_options(firefox_opts, None).expect("valid firefox options");
|
|
assert_eq!(opts.android.unwrap().activity, Some("foo".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_activity_invalid_type() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidPackage".into(), json!("foo.bar"));
|
|
firefox_opts.insert("androidActivity".into(), json!(42));
|
|
|
|
make_options(firefox_opts, None).expect_err("invalid firefox options");
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_activity_invalid_value() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidPackage".into(), json!("foo.bar"));
|
|
firefox_opts.insert("androidActivity".into(), json!("foo.bar/cheese"));
|
|
|
|
make_options(firefox_opts, None).expect_err("invalid firefox options");
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_device_serial() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidPackage".into(), json!("foo.bar"));
|
|
firefox_opts.insert("androidDeviceSerial".into(), json!("cheese"));
|
|
|
|
let opts = make_options(firefox_opts, None).expect("valid firefox options");
|
|
assert_eq!(
|
|
opts.android.unwrap().device_serial,
|
|
Some("cheese".to_string())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_device_serial_invalid() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidPackage".into(), json!("foo.bar"));
|
|
firefox_opts.insert("androidDeviceSerial".into(), json!(42));
|
|
|
|
make_options(firefox_opts, None).expect_err("invalid firefox options");
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_intent_arguments_defaults() {
|
|
let packages = vec![
|
|
"org.mozilla.firefox",
|
|
"org.mozilla.firefox_beta",
|
|
"org.mozilla.fenix",
|
|
"org.mozilla.fenix.debug",
|
|
"org.mozilla.geckoview_example",
|
|
"org.mozilla.reference.browser",
|
|
"com.some.other.app",
|
|
];
|
|
|
|
for package in packages {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidPackage".into(), json!(package));
|
|
|
|
let opts = make_options(firefox_opts, None).expect("valid firefox options");
|
|
assert_eq!(
|
|
opts.android.unwrap().intent_arguments,
|
|
Some(vec![
|
|
"-a".to_string(),
|
|
"android.intent.action.VIEW".to_string(),
|
|
"-d".to_string(),
|
|
"about:blank".to_string(),
|
|
])
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_intent_arguments_override() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidPackage".into(), json!("foo.bar"));
|
|
firefox_opts.insert("androidIntentArguments".into(), json!(["lorem", "ipsum"]));
|
|
|
|
let opts = make_options(firefox_opts, None).expect("valid firefox options");
|
|
assert_eq!(
|
|
opts.android.unwrap().intent_arguments,
|
|
Some(vec!["lorem".to_string(), "ipsum".to_string()])
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_intent_arguments_no_array() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidPackage".into(), json!("foo.bar"));
|
|
firefox_opts.insert("androidIntentArguments".into(), json!(42));
|
|
|
|
make_options(firefox_opts, None).expect_err("invalid firefox options");
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_android_intent_arguments_invalid_value() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("androidPackage".into(), json!("foo.bar"));
|
|
firefox_opts.insert("androidIntentArguments".into(), json!(["lorem", 42]));
|
|
|
|
make_options(firefox_opts, None).expect_err("invalid firefox options");
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_env() {
|
|
let mut env: Map<String, Value> = Map::new();
|
|
env.insert("TEST_KEY_A".into(), Value::String("test_value_a".into()));
|
|
env.insert("TEST_KEY_B".into(), Value::String("test_value_b".into()));
|
|
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("env".into(), env.into());
|
|
|
|
let mut opts = make_options(firefox_opts, None).expect("valid firefox options");
|
|
for sorted in opts.env.iter_mut() {
|
|
sorted.sort()
|
|
}
|
|
assert_eq!(
|
|
opts.env,
|
|
Some(vec![
|
|
("TEST_KEY_A".into(), "test_value_a".into()),
|
|
("TEST_KEY_B".into(), "test_value_b".into()),
|
|
])
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_env_invalid_container() {
|
|
let env = Value::Number(1.into());
|
|
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("env".into(), env);
|
|
|
|
make_options(firefox_opts, None).expect_err("invalid firefox options");
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_env_invalid_value() {
|
|
let mut env: Map<String, Value> = Map::new();
|
|
env.insert("TEST_KEY".into(), Value::Number(1.into()));
|
|
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("env".into(), env.into());
|
|
|
|
make_options(firefox_opts, None).expect_err("invalid firefox options");
|
|
}
|
|
|
|
#[test]
|
|
fn test_profile() {
|
|
let encoded_profile = example_profile();
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("profile".into(), encoded_profile);
|
|
|
|
let opts = make_options(firefox_opts, None).expect("valid firefox options");
|
|
let mut profile = match opts.profile {
|
|
ProfileType::Path(profile) => profile,
|
|
_ => panic!("Expected ProfileType::Path"),
|
|
};
|
|
let prefs = profile.user_prefs().expect("valid preferences");
|
|
|
|
println!("{:#?}", prefs.prefs);
|
|
|
|
assert_eq!(
|
|
prefs.get("startup.homepage_welcome_url"),
|
|
Some(&Pref::new("data:text/html,PASS"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_args_profile() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("args".into(), json!(["--profile", "foo"]));
|
|
|
|
let options = make_options(firefox_opts, None).expect("Valid args");
|
|
assert!(matches!(options.profile, ProfileType::Path(_)));
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_args_named_profile() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("args".into(), json!(["-P", "foo"]));
|
|
|
|
let options = make_options(firefox_opts, None).expect("Valid args");
|
|
assert!(matches!(options.profile, ProfileType::Named));
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_args_no_profile() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("args".into(), json!(["--headless"]));
|
|
|
|
let options = make_options(firefox_opts, None).expect("Valid args");
|
|
assert!(matches!(options.profile, ProfileType::Temporary));
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_args_profile_and_profile() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("args".into(), json!(["--profile", "foo"]));
|
|
firefox_opts.insert("profile".into(), json!("foo"));
|
|
|
|
make_options(firefox_opts, None).expect_err("Invalid args");
|
|
}
|
|
|
|
#[test]
|
|
fn fx_options_args_p_and_profile() {
|
|
let mut firefox_opts = Capabilities::new();
|
|
firefox_opts.insert("args".into(), json!(["-P"]));
|
|
firefox_opts.insert("profile".into(), json!("foo"));
|
|
|
|
make_options(firefox_opts, None).expect_err("Invalid args");
|
|
}
|
|
}
|