--- a/testing/geckodriver/src/capabilities.rs
+++ b/testing/geckodriver/src/capabilities.rs
@@ -6,19 +6,19 @@ use mozprofile::profile::Profile;
use mozrunner::runner::platform::firefox_default_path;
use mozversion::{self, firefox_version, Version};
use regex::bytes::Regex;
use serde_json::{Map, Value};
use std::collections::BTreeMap;
use std::default::Default;
use std::error::Error;
use std::fs;
+use std::io;
use std::io::BufWriter;
use std::io::Cursor;
-use std::io;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::str::{self, FromStr};
use webdriver::capabilities::{BrowserCapabilities, Capabilities};
use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
use zip;
/// Provides matching of `moz:firefoxOptions` and resolution of which Firefox
@@ -29,17 +29,16 @@ use zip;
/// 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, String>,
}
-
impl<'a> FirefoxCapabilities<'a> {
pub fn new(fallback_binary: Option<&'a PathBuf>) -> FirefoxCapabilities<'a> {
FirefoxCapabilities {
chosen_binary: None,
fallback_binary: fallback_binary,
version_cache: BTreeMap::new(),
}
}
@@ -64,52 +63,54 @@ impl<'a> FirefoxCapabilities<'a> {
.ok()
.and_then(|x| x.version_string)
.or_else(|| {
debug!("Trying to read firefox version from binary");
self.version_from_binary(binary)
});
if let Some(ref version) = rv {
debug!("Found version {}", version);
- self.version_cache
- .insert(binary.clone(), version.clone());
+ self.version_cache.insert(binary.clone(), version.clone());
} else {
debug!("Failed to get binary version");
}
rv
} else {
None
}
}
fn version_from_binary(&self, binary: &PathBuf) -> Option<String> {
- let version_regexp = Regex::new(r#"\d+\.\d+(?:[a-z]\d+)?"#).expect("Error parsing version regexp");
+ let version_regexp =
+ Regex::new(r#"\d+\.\d+(?:[a-z]\d+)?"#).expect("Error parsing version regexp");
let output = Command::new(binary)
.args(&["-version"])
.stdout(Stdio::piped())
.spawn()
.and_then(|child| child.wait_with_output())
.ok();
if let Some(x) = output {
- version_regexp.captures(&*x.stdout)
+ version_regexp
+ .captures(&*x.stdout)
.and_then(|captures| captures.get(0))
.and_then(|m| str::from_utf8(m.as_bytes()).ok())
.map(|x| x.into())
} else {
None
}
}
}
// TODO: put this in webdriver-rust
fn convert_version_error(err: mozversion::Error) -> WebDriverError {
WebDriverError::new(
ErrorStatus::SessionNotCreated,
- err.description().to_string())
+ err.description().to_string(),
+ )
}
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>> {
@@ -117,18 +118,18 @@ impl<'a> BrowserCapabilities for Firefox
}
fn browser_version(&mut self, _: &Capabilities) -> WebDriverResult<Option<String>> {
Ok(self.version())
}
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("windows".into())
+ } else if cfg!(target_os = "macos") {
Some("mac".into())
} else if cfg!(target_os = "linux") {
Some("linux".into())
} else {
None
})
}
@@ -140,120 +141,149 @@ impl<'a> BrowserCapabilities for Firefox
Ok(false)
}
}
fn set_window_rect(&mut self, _: &Capabilities) -> WebDriverResult<bool> {
Ok(true)
}
- fn compare_browser_version(&mut self,
- version: &str,
- comparison: &str)
- -> WebDriverResult<bool> {
+ fn compare_browser_version(
+ &mut self,
+ version: &str,
+ comparison: &str,
+ ) -> WebDriverResult<bool> {
try!(Version::from_str(version).or_else(|x| Err(convert_version_error(x))))
.matches(comparison)
.or_else(|x| Err(convert_version_error(x)))
}
fn accept_proxy(&mut self, _: &Capabilities, _: &Capabilities) -> WebDriverResult<bool> {
Ok(true)
}
- fn validate_custom(&self, name: &str, value: &Value) -> WebDriverResult<()> {
+ fn validate_custom(&self, name: &str, value: &Value) -> WebDriverResult<()> {
if !name.starts_with("moz:") {
- return Ok(())
+ return Ok(());
}
match name {
"moz:firefoxOptions" => {
- let data = try_opt!(value.as_object(),
- ErrorStatus::InvalidArgument,
- "moz:firefoxOptions is not an object");
+ let data = try_opt!(
+ value.as_object(),
+ ErrorStatus::InvalidArgument,
+ "moz:firefoxOptions is not an object"
+ );
for (key, value) in data.iter() {
match &**key {
"binary" => {
if !value.is_string() {
return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
- "binary path is not a string"));
+ "binary path is not a string",
+ ));
}
- },
+ }
"args" => {
- if !try_opt!(value.as_array(),
- ErrorStatus::InvalidArgument,
- "args is not an array")
- .iter()
- .all(|value| value.is_string()) {
+ if !try_opt!(
+ value.as_array(),
+ ErrorStatus::InvalidArgument,
+ "args is not an array"
+ ).iter()
+ .all(|value| value.is_string())
+ {
return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
- "args entry is not a string"));
- }
- },
+ "args entry is not a string",
+ ));
+ }
+ }
"profile" => {
if !value.is_string() {
return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
- "profile is not a string"));
+ "profile is not a string",
+ ));
}
- },
+ }
"log" => {
- let log_data = try_opt!(value.as_object(),
- ErrorStatus::InvalidArgument,
- "log value is not an object");
+ 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");
+ 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)))
+ format!("Not a valid log level: {}", level),
+ ));
}
}
- x => return Err(WebDriverError::new(
- ErrorStatus::InvalidArgument,
- format!("Invalid log field {}", x)))
+ 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");
- if !prefs_data.values()
- .all(|x| x.is_string() || x.is_i64() || x.is_u64() || x.is_boolean()) {
- return Err(WebDriverError::new(
- ErrorStatus::InvalidArgument,
- "Preference values not all string or integer or boolean"));
- }
+ let prefs_data = try_opt!(
+ value.as_object(),
+ ErrorStatus::InvalidArgument,
+ "prefs value is not an object"
+ );
+ if !prefs_data.values().all(|x| {
+ x.is_string() || x.is_i64() || x.is_u64() || x.is_boolean()
+ }) {
+ 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)))
+ x => {
+ return Err(WebDriverError::new(
+ ErrorStatus::InvalidArgument,
+ format!("Invalid moz:firefoxOptions field {}", x),
+ ))
+ }
}
}
}
"moz:useNonSpecCompliantPointerOrigin" => {
if !value.is_boolean() {
return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
- "moz:useNonSpecCompliantPointerOrigin is not a boolean"));
+ "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:webdriverClick is not a boolean",
+ ));
}
}
- _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
- format!("Unrecognised option {}", name)))
+ _ => {
+ return Err(WebDriverError::new(
+ ErrorStatus::InvalidArgument,
+ format!("Unrecognised option {}", name),
+ ))
+ }
}
Ok(())
}
fn accept_custom(&mut self, _: &str, _: &Value, _: &Capabilities) -> WebDriverResult<bool> {
Ok(true)
}
}
@@ -273,75 +303,82 @@ pub struct FirefoxOptions {
pub prefs: Vec<(String, Pref)>,
}
impl FirefoxOptions {
pub fn new() -> FirefoxOptions {
Default::default()
}
- pub fn from_capabilities(binary_path: Option<PathBuf>,
- matched: &mut Capabilities)
- -> WebDriverResult<FirefoxOptions> {
+ pub fn from_capabilities(
+ binary_path: Option<PathBuf>,
+ matched: &mut Capabilities,
+ ) -> WebDriverResult<FirefoxOptions> {
let mut rv = FirefoxOptions::new();
rv.binary = binary_path;
if let Some(json) = matched.remove("moz:firefoxOptions") {
- let options = try!(json.as_object()
- .ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,
- "'moz:firefoxOptions' \
- capability is not an object")));
+ let options = try!(json.as_object().ok_or(WebDriverError::new(
+ ErrorStatus::InvalidArgument,
+ "'moz:firefoxOptions' \
+ capability is not an object"
+ )));
rv.profile = try!(FirefoxOptions::load_profile(&options));
rv.args = try!(FirefoxOptions::load_args(&options));
rv.log = try!(FirefoxOptions::load_log(&options));
rv.prefs = try!(FirefoxOptions::load_prefs(&options));
}
Ok(rv)
}
fn load_profile(options: &Capabilities) -> WebDriverResult<Option<Profile>> {
if let Some(profile_json) = options.get("profile") {
- let profile_base64 =
- try!(profile_json
- .as_str()
- .ok_or(WebDriverError::new(ErrorStatus::UnknownError,
- "Profile is not a string")));
+ let profile_base64 = try!(profile_json.as_str().ok_or(WebDriverError::new(
+ ErrorStatus::UnknownError,
+ "Profile is not a string"
+ )));
let profile_zip = &*try!(base64::decode(profile_base64));
// Create an emtpy profile directory
let profile = try!(Profile::new(None));
- try!(unzip_buffer(profile_zip,
- profile
- .temp_dir
- .as_ref()
- .expect("Profile doesn't have a path")
- .path()));
+ try!(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 = try!(args_json
- .as_array()
- .ok_or(WebDriverError::new(ErrorStatus::UnknownError,
- "Arguments were not an \
- array")));
- let args = try!(args_array
- .iter()
- .map(|x| x.as_str().map(|x| x.to_owned()))
- .collect::<Option<Vec<String>>>()
- .ok_or(WebDriverError::new(ErrorStatus::UnknownError,
- "Arguments entries were not all \
- strings")));
+ let args_array = try!(args_json.as_array().ok_or(WebDriverError::new(
+ ErrorStatus::UnknownError,
+ "Arguments were not an \
+ array"
+ )));
+ let args = try!(
+ args_array
+ .iter()
+ .map(|x| x.as_str().map(|x| x.to_owned()))
+ .collect::<Option<Vec<String>>>()
+ .ok_or(WebDriverError::new(
+ ErrorStatus::UnknownError,
+ "Arguments entries were not all \
+ strings"
+ ))
+ );
Ok(Some(args))
} else {
Ok(None)
}
}
fn load_log(options: &Capabilities) -> WebDriverResult<LogOptions> {
if let Some(json) = options.get("log") {
@@ -367,51 +404,55 @@ impl FirefoxOptions {
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 = try!(prefs_data
- .as_object()
- .ok_or(WebDriverError::new(ErrorStatus::UnknownError,
- "Prefs were not an object")));
+ let prefs = try!(prefs_data.as_object().ok_or(WebDriverError::new(
+ ErrorStatus::UnknownError,
+ "Prefs were not an object"
+ )));
let mut rv = Vec::with_capacity(prefs.len());
for (key, value) in prefs.iter() {
rv.push((key.clone(), try!(pref_from_json(value))));
}
Ok(rv)
} else {
Ok(vec![])
}
}
}
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"))
+ _ => 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 = try!(zip::ZipArchive::new(reader).map_err(|_| {
- WebDriverError::new(ErrorStatus::UnknownError, "Failed to unzip profile")
- }));
+ let mut zip = try!(
+ zip::ZipArchive::new(reader)
+ .map_err(|_| WebDriverError::new(ErrorStatus::UnknownError, "Failed to unzip profile"))
+ );
for i in 0..zip.len() {
- let mut file = try!(zip.by_index(i).map_err(|_| {
- WebDriverError::new(ErrorStatus::UnknownError, "Processing profile zip file failed")
- }));
+ let mut file = try!(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 {
@@ -481,18 +522,20 @@ mod tests {
firefox_opts.insert("profile".into(), encoded_profile);
let opts = make_options(firefox_opts);
let mut profile = opts.profile.unwrap();
let prefs = profile.user_prefs().unwrap();
println!("{:#?}", prefs.prefs);
- assert_eq!(prefs.get("startup.homepage_welcome_url"),
- Some(&Pref::new("data:text/html,PASS")));
+ assert_eq!(
+ prefs.get("startup.homepage_welcome_url"),
+ Some(&Pref::new("data:text/html,PASS"))
+ );
}
#[test]
fn test_prefs() {
let encoded_profile = example_profile();
let mut prefs: Map<String, Value> = Map::new();
prefs.insert(
"browser.display.background_color".into(),
--- a/testing/geckodriver/src/main.rs
+++ b/testing/geckodriver/src/main.rs
@@ -9,49 +9,49 @@ extern crate mozprofile;
extern crate mozrunner;
extern crate mozversion;
extern crate regex;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate uuid;
+extern crate webdriver;
extern crate zip;
-extern crate webdriver;
#[macro_use]
extern crate log;
use std::io::Write;
use std::net::{IpAddr, SocketAddr};
use std::path::PathBuf;
use std::str::FromStr;
use clap::{App, Arg};
macro_rules! try_opt {
- ($expr:expr, $err_type:expr, $err_msg:expr) => ({
+ ($expr:expr, $err_type:expr, $err_msg:expr) => {{
match $expr {
Some(x) => x,
- None => return Err(WebDriverError::new($err_type, $err_msg))
+ None => return Err(WebDriverError::new($err_type, $err_msg)),
}
- })
+ }};
}
mod build;
+mod capabilities;
mod logging;
+mod marionette;
mod prefs;
-mod marionette;
-mod capabilities;
#[cfg(test)]
pub mod test;
use build::BuildInfo;
-use marionette::{MarionetteHandler, MarionetteSettings, extension_routes};
+use marionette::{extension_routes, MarionetteHandler, MarionetteSettings};
type ProgramResult = std::result::Result<(), (ExitCode, String)>;
enum ExitCode {
Ok = 0,
Usage = 64,
Unavailable = 69,
}
@@ -64,62 +64,80 @@ fn print_version() {
println!("");
println!("This program is subject to the terms of the Mozilla Public License 2.0.");
println!("You can obtain a copy of the license at https://mozilla.org/MPL/2.0/.");
}
fn app<'a, 'b>() -> App<'a, 'b> {
App::new(format!("geckodriver {}", crate_version!()))
.about("WebDriver implementation for Firefox.")
- .arg(Arg::with_name("webdriver_host")
- .long("host")
- .value_name("HOST")
- .help("Host ip to use for WebDriver server (default: 127.0.0.1)")
- .takes_value(true))
- .arg(Arg::with_name("webdriver_port")
- .short("p")
- .long("port")
- .value_name("PORT")
- .help("Port to use for WebDriver server (default: 4444)")
- .takes_value(true)
- .alias("webdriver-port"))
- .arg(Arg::with_name("binary")
- .short("b")
- .long("binary")
- .value_name("BINARY")
- .help("Path to the Firefox binary")
- .takes_value(true))
- .arg(Arg::with_name("marionette_port")
- .long("marionette-port")
- .value_name("PORT")
- .help("Port to use to connect to Gecko (default: random free port)")
- .takes_value(true))
- .arg(Arg::with_name("connect_existing")
- .long("connect-existing")
- .requires("marionette_port")
- .help("Connect to an existing Firefox instance"))
- .arg(Arg::with_name("jsdebugger")
- .long("jsdebugger")
- .takes_value(false)
- .help("Attach browser toolbox debugger for Firefox"))
- .arg(Arg::with_name("verbosity")
- .short("v")
- .multiple(true)
- .conflicts_with("log_level")
- .help("Log level verbosity (-v for debug and -vv for trace level)"))
- .arg(Arg::with_name("log_level")
- .long("log")
- .takes_value(true)
- .value_name("LEVEL")
- .possible_values(&["fatal", "error", "warn", "info", "config", "debug", "trace"])
- .help("Set Gecko log level"))
- .arg(Arg::with_name("version")
- .short("V")
- .long("version")
- .help("Prints version and copying information"))
+ .arg(
+ Arg::with_name("webdriver_host")
+ .long("host")
+ .value_name("HOST")
+ .help("Host ip to use for WebDriver server (default: 127.0.0.1)")
+ .takes_value(true),
+ )
+ .arg(
+ Arg::with_name("webdriver_port")
+ .short("p")
+ .long("port")
+ .value_name("PORT")
+ .help("Port to use for WebDriver server (default: 4444)")
+ .takes_value(true)
+ .alias("webdriver-port"),
+ )
+ .arg(
+ Arg::with_name("binary")
+ .short("b")
+ .long("binary")
+ .value_name("BINARY")
+ .help("Path to the Firefox binary")
+ .takes_value(true),
+ )
+ .arg(
+ Arg::with_name("marionette_port")
+ .long("marionette-port")
+ .value_name("PORT")
+ .help("Port to use to connect to Gecko (default: random free port)")
+ .takes_value(true),
+ )
+ .arg(
+ Arg::with_name("connect_existing")
+ .long("connect-existing")
+ .requires("marionette_port")
+ .help("Connect to an existing Firefox instance"),
+ )
+ .arg(
+ Arg::with_name("jsdebugger")
+ .long("jsdebugger")
+ .takes_value(false)
+ .help("Attach browser toolbox debugger for Firefox"),
+ )
+ .arg(
+ Arg::with_name("verbosity")
+ .short("v")
+ .multiple(true)
+ .conflicts_with("log_level")
+ .help("Log level verbosity (-v for debug and -vv for trace level)"),
+ )
+ .arg(
+ Arg::with_name("log_level")
+ .long("log")
+ .takes_value(true)
+ .value_name("LEVEL")
+ .possible_values(&["fatal", "error", "warn", "info", "config", "debug", "trace"])
+ .help("Set Gecko log level"),
+ )
+ .arg(
+ Arg::with_name("version")
+ .short("V")
+ .long("version")
+ .help("Prints version and copying information"),
+ )
}
fn run() -> ProgramResult {
let matches = app().get_matches();
if matches.is_present("version") {
print_version();
return Ok(());
@@ -138,22 +156,20 @@ fn run() -> ProgramResult {
let addr = match IpAddr::from_str(host) {
Ok(addr) => SocketAddr::new(addr, port),
Err(_) => return Err((ExitCode::Usage, "invalid host address".into())),
};
let binary = matches.value_of("binary").map(|x| PathBuf::from(x));
let marionette_port = match matches.value_of("marionette_port") {
- Some(x) => {
- match u16::from_str(x) {
- Ok(x) => Some(x),
- Err(_) => return Err((ExitCode::Usage, "invalid Marionette port".into())),
- }
- }
+ Some(x) => match u16::from_str(x) {
+ Ok(x) => Some(x),
+ Err(_) => return Err((ExitCode::Usage, "invalid Marionette port".into())),
+ },
None => None,
};
let log_level = if matches.is_present("log_level") {
logging::Level::from_str(matches.value_of("log_level").unwrap()).ok()
} else {
match matches.occurrences_of("verbosity") {
0 => Some(logging::Level::Info),