Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mozilla/geckodriver.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHenrik Skupin <mail@hskupin.info>2020-11-03 19:47:06 +0300
committerGitHub <noreply@github.com>2020-11-03 19:47:06 +0300
commit2acb733de5bda81ed30b6f4e90be27fee6f62ff7 (patch)
tree42e5480ca2ff5325c44f2c56ddc32318db2bbcd1
parent917daaccc78bb8cac3ab324861ada48d68746d13 (diff)
Import of v0.28.0 (#1803)v0.28.0
-rw-r--r--CHANGES.md61
-rw-r--r--Cargo.toml10
-rw-r--r--doc/Flags.md58
-rw-r--r--doc/Releasing.md8
-rw-r--r--doc/Support.md10
-rw-r--r--doc/Usage.md6
-rw-r--r--mach_commands.py115
-rw-r--r--moz.build3
-rw-r--r--src/android.rs386
-rw-r--r--src/capabilities.rs154
-rw-r--r--src/command.rs61
-rw-r--r--src/main.rs13
-rw-r--r--src/marionette.rs61
-rw-r--r--src/prefs.rs18
14 files changed, 561 insertions, 403 deletions
diff --git a/CHANGES.md b/CHANGES.md
index b1f76ce..eb7902c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,12 +1,59 @@
Change log
==========
-All notable changes to this program is documented in this file.
+All notable changes to this program are documented in this file.
+0.28.0 (2020-11-03, `c00d2b6acd3f`)
+--------------------
+
+### Known problems
+
+- _macOS 10.15 (Catalina):_
+
+ Due to the requirement from Apple that all programs must be
+ notarized, geckodriver will not work on Catalina if you manually
+ download it through another notarized program, such as Firefox.
+
+ Whilst we are working on a repackaging fix for this problem, you can
+ find more details on how to work around this issue in the [macOS
+ notarization] section of the documentation.
+
+### Added
+
+- The command line flag `--android-storage` has been added, to allow geckodriver
+to also control Firefox on root-less Android devices. See the [documentation][Flags]
+for available values.
+
+### Fixed
-0.27.0 (2020-07-27, `90ec81285ff6`)
+- Firefox can be started again via a shell script that is located outside of the
+ Firefox directory on Linux.
+
+- If Firefox cannot be started by geckodriver the real underlying error message is
+ now being reported.
+
+- Version numbers for minor and extended support releases of Firefox are now parsed correctly.
+
+### Removed
+
+- Since Firefox 72 extension commands for finding an element’s anonymous children
+ and querying its attributes are no longer needed, and have been removed.
+
+0.27.0 (2020-07-27, `7b8c4f32cdde`)
--------------------
+### Security Fixes
+
+- CVE-2020-15660
+
+ - Added additional checks on the `Content-Type` header for `POST`
+ requests to disallow `application/x-www-form-urlencoded`,
+ `multipart/form-data` and `text/plain`.
+
+ - Added checking of the `Origin` header for `POST` requests.
+
+ - The version number of Firefox is now checked when establishing a session.
+
### Known problems
- _macOS 10.15 (Catalina):_
@@ -15,9 +62,9 @@ All notable changes to this program is documented in this file.
notarized, geckodriver will not work on Catalina if you manually
download it through another notarized program, such as Firefox.
- Whilst we are working on a repackaging fix for this problem, you
- can find more details on how to work around this issue in the
- [macOS notarization] section of the documentation.
+ Whilst we are working on a repackaging fix for this problem, you can
+ find more details on how to work around this issue in the [macOS
+ notarization] section of the documentation.
### Added
@@ -42,8 +89,7 @@ All notable changes to this program is documented in this file.
- _Android:_
- * Firefox running on Android devices can now be controlled from a
- Windows host.
+ * Firefox running on Android devices can now be controlled from a Windows host.
* Setups with multiple connected Android devices are now supported.
@@ -1327,6 +1373,7 @@ and greater.
[Firefox Preview]: https://play.google.com/store/apps/details?id=org.mozilla.fenix
[Firefox Reality]: https://play.google.com/store/apps/details?id=org.mozilla.vrbrowser
[Capabilities]: https://firefox-source-docs.mozilla.org/testing/geckodriver/Capabilities.html
+[Flags]: https://firefox-source-docs.mozilla.org/testing/geckodriver/Flags.html
[enable remote debugging on the Android device]: https://developers.google.com/web/tools/chrome-devtools/remote-debugging
[macOS notarization]: https://firefox-source-docs.mozilla.org/testing/geckodriver/Notarization.html
diff --git a/Cargo.toml b/Cargo.toml
index 7a1ebdb..a5d4a27 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "geckodriver"
-version = "0.27.0"
+version = "0.28.0"
description = "Proxy for using WebDriver clients to interact with Gecko-based browsers."
keywords = ["webdriver", "w3c", "httpd", "mozilla", "firefox"]
repository = "https://hg.mozilla.org/mozilla-central/file/tip/testing/geckodriver"
@@ -17,17 +17,17 @@ hyper = "0.13"
lazy_static = "1.0"
log = { version = "0.4", features = ["std"] }
marionette = { path = "./marionette" }
-mozdevice = "0.2.0"
+mozdevice = "0.3.0"
mozprofile = "0.7.0"
-mozrunner = "0.11"
-mozversion = "0.3"
+mozrunner = "0.12.0"
+mozversion = "0.4.0"
regex = { version="1.0", default-features = false, features = ["perf", "std"] }
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
serde_yaml = "0.8"
uuid = { version = "0.8", features = ["v4"] }
-webdriver = "0.41"
+webdriver = "0.42.0"
zip = { version = "0.4", default-features = false, features = ["deflate"] }
[[bin]]
diff --git a/doc/Flags.md b/doc/Flags.md
index 209acf1..8889e8b 100644
--- a/doc/Flags.md
+++ b/doc/Flags.md
@@ -1,7 +1,53 @@
Flags
=====
-#### <code>-b <var>BINARY</var></code>/<code>--binary <var>BINARY</var></code>
+#### <code>--android-storage <var>ANDROID_STORAGE</var></code>
+
+Selects the test data location on the Android device, eg. the Firefox profile.
+By default `auto` is used.
+
+<style type="text/css">
+ table { width: 100%; margin-bottom: 2em; }
+ table, th, td { border: solid gray 1px; }
+ td, th { padding: 10px; text-align: left; vertical-align: middle; }
+ td:nth-child(1), th:nth-child(1) { width: 10em; text-align: center; }
+</style>
+
+<table>
+ <thead>
+ <tr>
+ <th>Value
+ <th>Description
+ </tr>
+ </thead>
+
+ <tr>
+ <td>auto
+ <td>Best suitable location based on whether the device is rooted.<br/>
+ If the device is rooted <code>internal</code> is used, otherwise <code>app</code>.
+ <tr>
+ <td>app
+ <td><p>Location: <code>/data/data/%androidPackage%/test_root</code></p>
+ Based on the <code>androidPackage</code> capability that is passed as part of
+ <code>moz:firefoxOptions</code> when creating a new session. Commands that
+ change data in the app's directory are executed using run-as. This requires
+ that the installed app is debuggable.
+ <tr>
+ <td>internal
+ <td><p>Location: <code>/data/local/tmp/test_root</code></p>
+ The device must be rooted since when the app runs, files that are created
+ in the profile, which is owned by the app user, cannot be changed by the
+ shell user. Commands will be executed via <code>su</code>.
+ <tr>
+ <td>sdcard
+ <td><p>Location: <code>/mnt/sdcard/test_root</code></p>
+ This location is not supported on Android 11+ due to the
+ <a href="https://developer.android.com/about/versions/11/privacy/storage">
+ changes related to scoped storage</a>.
+</table>
+
+
+#### <code>-b <var>BINARY</var></code> / <code>--binary <var>BINARY</var></code>
Path to the Firefox binary to use. By default geckodriver tries to
find and use the system installation of Firefox, but that behaviour
@@ -28,7 +74,7 @@ scanning the Windows registry.
[whereis(1)]: http://www.manpagez.com/man/1/whereis/
-#### `--connect-existing`
+#### <code>--connect-existing</code>
Connect geckodriver to an existing Firefox instance. This means
geckodriver will abstain from the default of starting a new Firefox
@@ -55,6 +101,12 @@ Set the Gecko and geckodriver log level. Possible values are `fatal`,
`error`, `warn`, `info`, `config`, `debug`, and `trace`.
+#### <code>--marionette-host <var>HOST</var></code>
+
+Selects the host for geckodriver’s connection to the [Marionette]
+remote protocol. Defaults to 127.0.0.1.
+
+
#### <code>--marionette-port <var>PORT</var></code>
Selects the port for geckodriver’s connection to the [Marionette]
@@ -70,7 +122,7 @@ under geckodriver’s control, it will simply connect to <var>PORT</var>.
[`--connect-existing`]: #connect-existing
-#### <code>-p <var>PORT</var></code>/<code>--port <var>PORT</var></code>
+#### <code>-p <var>PORT</var></code> / <code>--port <var>PORT</var></code>
Port to use for the WebDriver server. Defaults to 4444.
diff --git a/doc/Releasing.md b/doc/Releasing.md
index 9618045..3918580 100644
--- a/doc/Releasing.md
+++ b/doc/Releasing.md
@@ -124,10 +124,10 @@ repository, the changeset id for the release has to be added to the
change log. Therefore add a final place-holder commit to the patch
series, to already get review for.
-Once all previous revisions of the patch series have been reviewed and
-landed, it's known which commit id the version bump commit has, finalize the
-change log, and land that remaining revision.
-
+Once all previous revisions of the patch series have been landed, and got merged
+to `mozilla-central`, the changeset id from the merge commit has to picked for
+finalizing the change log. This specific id is needed because Taskcluster creates
+the final signed builds based on that merge.
Release new in-tree dependency crates
-------------------------------------
diff --git a/doc/Support.md b/doc/Support.md
index c3dcfe7..a7478d4 100644
--- a/doc/Support.md
+++ b/doc/Support.md
@@ -24,6 +24,16 @@ and required versions of Selenium and Firefox:
</thead>
<tr>
+ <td>0.28.0
+ <td>≥ 3.11 (3.14 Python)
+ <td>60
+ <td>n/a
+ <tr>
+ <td>0.27.0
+ <td>≥ 3.11 (3.14 Python)
+ <td>60
+ <td>n/a
+ <tr>
<td>0.26.0
<td>≥ 3.11 (3.14 Python)
<td>60
diff --git a/doc/Usage.md b/doc/Usage.md
index a5d915e..d3ce68a 100644
--- a/doc/Usage.md
+++ b/doc/Usage.md
@@ -68,9 +68,9 @@ Using [curl(1)]:
% geckodriver &
[1] 16010
% 1491834109194 geckodriver INFO Listening on 127.0.0.1:4444
- % curl -d '{"capabilities": {"alwaysMatch": {"acceptInsecureCerts": true}}}' http://localhost:4444/session
- {"sessionId":"d4605710-5a4e-4d64-a52a-778bb0c31e00","value":{"XULappId":"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}","acceptSslCerts":false,"appBuildId":"20160913030425","browserName":"firefox","browserVersion":"51.0a1","command_id":1,"platform":"LINUX","platformName":"linux","platformVersion":"4.9.0-1-amd64","processId":17474,"proxy":{},"raisesAccessibilityExceptions":false,"rotatable":false,"specificationLevel":0,"takesElementScreenshot":true,"takesScreenshot":true,"version":"51.0a1"}}
- % curl -d '{"url": "https://mozilla.org"}' http://localhost:4444/session/d4605710-5a4e-4d64-a52a-778bb0c31e00/url
+ % curl -H 'Content-Type: application/json' -d '{"capabilities": {"alwaysMatch": {"acceptInsecureCerts": true}}}' http://localhost:4444/session
+ {"value":{"sessionId":"d4605710-5a4e-4d64-a52a-778bb0c31e00","capabilities":{"acceptInsecureCerts":true,[...]}}}
+ % curl -H 'Content-Type: application/json' -d '{"url": "https://mozilla.org"}' http://localhost:4444/session/d4605710-5a4e-4d64-a52a-778bb0c31e00/url
{}
% curl http://localhost:4444/session/d4605710-5a4e-4d64-a52a-778bb0c31e00/url
{"value":"https://www.mozilla.org/en-US/"
diff --git a/mach_commands.py b/mach_commands.py
index efcf759..beb4b93 100644
--- a/mach_commands.py
+++ b/mach_commands.py
@@ -19,37 +19,56 @@ from mozbuild.base import MachCommandBase, BinaryNotFoundException
@CommandProvider
class GeckoDriver(MachCommandBase):
-
- @Command("geckodriver",
- category="post-build",
- description="Run the WebDriver implementation for Gecko.")
- @CommandArgument("--binary", type=str,
- help="Firefox binary (defaults to the local build).")
- @CommandArgument("params", nargs="...",
- help="Flags to be passed through to geckodriver.")
+ @Command(
+ "geckodriver",
+ category="post-build",
+ description="Run the WebDriver implementation for Gecko.",
+ )
+ @CommandArgument(
+ "--binary", type=str, help="Firefox binary (defaults to the local build)."
+ )
+ @CommandArgument(
+ "params", nargs="...", help="Flags to be passed through to geckodriver."
+ )
@CommandArgumentGroup("debugging")
- @CommandArgument("--debug", action="store_true", group="debugging",
- help="Enable the debugger. Not specifying a --debugger "
- "option will result in the default debugger "
- "being used.")
- @CommandArgument("--debugger", default=None, type=str, group="debugging",
- help="Name of debugger to use.")
- @CommandArgument("--debugger-args", default=None, metavar="params",
- type=str, group="debugging",
- help="Flags to pass to the debugger itself; "
- "split as the Bourne shell would.")
+ @CommandArgument(
+ "--debug",
+ action="store_true",
+ group="debugging",
+ help="Enable the debugger. Not specifying a --debugger "
+ "option will result in the default debugger "
+ "being used.",
+ )
+ @CommandArgument(
+ "--debugger",
+ default=None,
+ type=str,
+ group="debugging",
+ help="Name of debugger to use.",
+ )
+ @CommandArgument(
+ "--debugger-args",
+ default=None,
+ metavar="params",
+ type=str,
+ group="debugging",
+ help="Flags to pass to the debugger itself; "
+ "split as the Bourne shell would.",
+ )
def run(self, binary, params, debug, debugger, debugger_args):
try:
binpath = self.get_binary_path("geckodriver")
except BinaryNotFoundException as e:
- self.log(logging.ERROR, 'geckodriver',
- {'error': str(e)},
- 'ERROR: {error}')
- self.log(logging.INFO, 'geckodriver', {},
- "It looks like geckodriver isn't built. "
- "Add ac_add_options --enable-geckodriver to your "
- "mozconfig "
- "and run |./mach build| to build it.")
+ self.log(logging.ERROR, "geckodriver", {"error": str(e)}, "ERROR: {error}")
+ self.log(
+ logging.INFO,
+ "geckodriver",
+ {},
+ "It looks like geckodriver isn't built. "
+ "Add ac_add_options --enable-geckodriver to your "
+ "mozconfig "
+ "and run |./mach build| to build it.",
+ )
return 1
args = [binpath]
@@ -61,12 +80,10 @@ class GeckoDriver(MachCommandBase):
try:
binary = self.get_binary_path("app")
except BinaryNotFoundException as e:
- self.log(logging.ERROR, 'geckodriver',
- {'error': str(e)},
- 'ERROR: {error}')
- self.log(logging.INFO, 'geckodriver',
- {'help': e.help()},
- '{help}')
+ self.log(
+ logging.ERROR, "geckodriver", {"error": str(e)}, "ERROR: {error}"
+ )
+ self.log(logging.INFO, "geckodriver", {"help": e.help()}, "{help}")
return 1
args.extend(["--binary", binary])
@@ -76,10 +93,13 @@ class GeckoDriver(MachCommandBase):
self.log_manager.terminal_handler.setLevel(logging.WARNING)
import mozdebug
+
if not debugger:
# No debugger name was provided. Look for the default ones on
# current OS.
- debugger = mozdebug.get_default_debugger_name(mozdebug.DebuggerSearch.KeepLooking)
+ debugger = mozdebug.get_default_debugger_name(
+ mozdebug.DebuggerSearch.KeepLooking
+ )
if debugger:
self.debuggerInfo = mozdebug.get_debugger_info(debugger, debugger_args)
@@ -91,29 +111,35 @@ class GeckoDriver(MachCommandBase):
# their use.
if debugger_args:
from mozbuild import shellutil
+
try:
debugger_args = shellutil.split(debugger_args)
except shellutil.MetaCharacterException as e:
- print("The --debugger-args you passed require a real shell to parse them.")
+ print(
+ "The --debugger-args you passed require a real shell to parse them."
+ )
print("(We can't handle the %r character.)" % e.char)
return 1
# Prepend the debugger args.
args = [self.debuggerInfo.path] + self.debuggerInfo.args + args
- return self.run_process(args=args, ensure_exit_code=False,
- pass_thru=True)
+ return self.run_process(args=args, ensure_exit_code=False, pass_thru=True)
@CommandProvider
class GeckoDriverTest(MachCommandBase):
-
- @Command("geckodriver-test",
- category="post-build",
- description="Run geckodriver unit tests.")
- @CommandArgument("-v", "--verbose", action="store_true",
- help="Verbose output for what"
- " commands the build is running.")
+ @Command(
+ "geckodriver-test",
+ category="post-build",
+ description="Run geckodriver unit tests.",
+ )
+ @CommandArgument(
+ "-v",
+ "--verbose",
+ action="store_true",
+ help="Verbose output for what" " commands the build is running.",
+ )
def test(self, verbose=False, **kwargs):
from mozbuild.controller.building import BuildDriver
@@ -123,4 +149,5 @@ class GeckoDriverTest(MachCommandBase):
return driver.build(
what=["testing/geckodriver/check"],
verbose=verbose,
- mach_context=self._mach_context)
+ mach_context=self._mach_context,
+ )
diff --git a/moz.build b/moz.build
index 9f19159..9cda32b 100644
--- a/moz.build
+++ b/moz.build
@@ -10,10 +10,9 @@ RUST_TESTS = [
"geckodriver",
"webdriver",
"marionette",
-
# TODO: Move to mozbase/rust/moz.build once those crates can be
# tested separately.
- # "mozdevice", // Tests require adb, and cannot be run in CI
+ "mozdevice",
"mozprofile",
"mozrunner",
"mozversion",
diff --git a/src/android.rs b/src/android.rs
index 87cce8b..6aaf58e 100644
--- a/src/android.rs
+++ b/src/android.rs
@@ -1,5 +1,5 @@
use crate::capabilities::AndroidOptions;
-use mozdevice::{Device, Host};
+use mozdevice::{AndroidStorage, Device, Host};
use mozprofile::profile::Profile;
use serde::Serialize;
use serde_yaml::{Mapping, Value};
@@ -7,6 +7,7 @@ use std::fmt;
use std::io;
use std::path::PathBuf;
use std::time;
+use webdriver::error::{ErrorStatus, WebDriverError};
// TODO: avoid port clashes across GeckoView-vehicles.
// For now, we always use target port 2829, leading to issues like bug 1533704.
@@ -25,7 +26,6 @@ pub enum AndroidError {
ActivityNotFound(String),
Device(mozdevice::DeviceError),
IO(io::Error),
- NotConnected,
PackageNotFound(String),
Serde(serde_yaml::Error),
}
@@ -38,7 +38,6 @@ impl fmt::Display for AndroidError {
}
AndroidError::Device(ref message) => message.fmt(f),
AndroidError::IO(ref message) => message.fmt(f),
- AndroidError::NotConnected => write!(f, "Not connected to any Android device"),
AndroidError::PackageNotFound(ref package) => {
write!(f, "Package '{}' not found", package)
}
@@ -65,6 +64,12 @@ impl From<serde_yaml::Error> for AndroidError {
}
}
+impl From<AndroidError> for WebDriverError {
+ fn from(value: AndroidError) -> WebDriverError {
+ WebDriverError::new(ErrorStatus::UnknownError, value.to_string())
+ }
+}
+
/// A remote Gecko instance.
///
/// Host refers to the device running `geckodriver`. Target refers to the
@@ -90,12 +95,13 @@ impl AndroidProcess {
}
}
-#[derive(Debug, Default)]
+#[derive(Debug)]
pub struct AndroidHandler {
pub config: PathBuf,
pub options: AndroidOptions,
- pub process: Option<AndroidProcess>,
+ pub process: AndroidProcess,
pub profile: PathBuf,
+ pub test_root: PathBuf,
// For port forwarding host => target
pub host_port: u16,
@@ -105,59 +111,41 @@ pub struct AndroidHandler {
impl Drop for AndroidHandler {
fn drop(&mut self) {
// Try to clean up various settings
- if let Some(ref process) = self.process {
- let clear_command = format!("am clear-debug-app {}", process.package);
- match process.device.execute_host_shell_command(&clear_command) {
- Ok(_) => debug!("Disabled reading from configuration file"),
- Err(e) => error!("Failed disabling from configuration file: {}", e),
- }
+ let clear_command = format!("am clear-debug-app {}", self.process.package);
+ match self
+ .process
+ .device
+ .execute_host_shell_command(&clear_command)
+ {
+ Ok(_) => debug!("Disabled reading from configuration file"),
+ Err(e) => error!("Failed disabling from configuration file: {}", e),
+ }
- let remove_command = format!("rm -rf {}", self.config.display());
- match process.device.execute_host_shell_command(&remove_command) {
- Ok(_) => debug!("Deleted GeckoView configuration file"),
- Err(e) => error!("Failed deleting GeckoView configuration file: {}", e),
- }
+ match self.process.device.remove(&self.config) {
+ Ok(_) => debug!("Deleted GeckoView configuration file"),
+ Err(e) => error!("Failed deleting GeckoView configuration file: {}", e),
+ }
- match process.device.kill_forward_port(self.host_port) {
- Ok(_) => debug!(
- "Android port forward ({} -> {}) stopped",
- &self.host_port, &self.target_port
- ),
- Err(e) => error!(
- "Android port forward ({} -> {}) failed to stop: {}",
- &self.host_port, &self.target_port, e
- ),
- }
+ match self.process.device.kill_forward_port(self.host_port) {
+ Ok(_) => debug!(
+ "Android port forward ({} -> {}) stopped",
+ &self.host_port, &self.target_port
+ ),
+ Err(e) => error!(
+ "Android port forward ({} -> {}) failed to stop: {}",
+ &self.host_port, &self.target_port, e
+ ),
}
}
}
impl AndroidHandler {
- pub fn new(options: &AndroidOptions) -> AndroidHandler {
+ pub fn new(options: &AndroidOptions, host_port: u16) -> Result<AndroidHandler> {
// We need to push profile.pathbuf to a safe space on the device.
// Make it per-Android package to avoid clashes and confusion.
// This naming scheme follows GeckoView's configuration file naming scheme,
// see bug 1533385.
- let profile = PathBuf::from(format!(
- "/mnt/sdcard/{}-geckodriver-profile",
- &options.package
- ));
- let config = PathBuf::from(format!(
- "/data/local/tmp/{}-geckoview-config.yaml",
- &options.package
- ));
-
- AndroidHandler {
- options: options.clone(),
- profile,
- config,
- process: None,
- ..Default::default()
- }
- }
-
- pub fn connect(&mut self, host_port: u16) -> Result<()> {
let host = Host {
host: None,
port: None,
@@ -165,58 +153,88 @@ impl AndroidHandler {
write_timeout: Some(time::Duration::from_millis(5000)),
};
- let device = host.device_or_default(self.options.device_serial.as_ref())?;
-
- self.host_port = host_port;
- self.target_port = TARGET_PORT;
+ let mut device = host.device_or_default(options.device_serial.as_ref(), options.storage)?;
// Set up port forward. Port forwarding will be torn down, if possible,
- device.forward_port(self.host_port, self.target_port)?;
+ device.forward_port(host_port, TARGET_PORT)?;
debug!(
"Android port forward ({} -> {}) started",
- &self.host_port, &self.target_port
+ host_port, TARGET_PORT
+ );
+
+ let test_root = match device.storage {
+ AndroidStorage::App => {
+ device.run_as_package = Some(options.package.to_owned());
+ let mut buf = PathBuf::from("/data/data");
+ buf.push(&options.package);
+ buf.push("test_root");
+ buf
+ }
+ AndroidStorage::Internal => PathBuf::from("/data/local/tmp/test_root"),
+ AndroidStorage::Sdcard => PathBuf::from("/mnt/sdcard/test_root"),
+ };
+
+ debug!(
+ "Connecting: options={:?}, storage={:?}) test_root={}, run_as_package={:?}",
+ options,
+ device.storage,
+ test_root.display(),
+ device.run_as_package
);
+ let mut profile = test_root.clone();
+ profile.push(format!("{}-geckodriver-profile", &options.package));
+
// Check if the specified package is installed
- let response = device
- .execute_host_shell_command(&format!("pm list packages {}", &self.options.package))?;
+ let response =
+ device.execute_host_shell_command(&format!("pm list packages {}", &options.package))?;
let packages = response
+ .trim()
.split_terminator('\n')
.filter(|line| line.starts_with("package:"))
.map(|line| line.rsplit(':').next().expect("Package name found"))
.collect::<Vec<&str>>();
- if !packages.contains(&self.options.package.as_str()) {
- return Err(AndroidError::PackageNotFound(self.options.package.clone()));
+ if !packages.contains(&options.package.as_str()) {
+ return Err(AndroidError::PackageNotFound(options.package.clone()));
}
+ let config = PathBuf::from(format!(
+ "/data/local/tmp/{}-geckoview-config.yaml",
+ &options.package
+ ));
+
// If activity hasn't been specified default to the main activity of the package
- let activity = match self.options.activity {
+ let activity = match options.activity {
Some(ref activity) => activity.clone(),
None => {
let response = device.execute_host_shell_command(&format!(
"cmd package resolve-activity --brief {}",
- &self.options.package
+ &options.package
))?;
let activities = response
.split_terminator('\n')
- .filter(|line| line.starts_with(&self.options.package))
+ .filter(|line| line.starts_with(&options.package))
.map(|line| line.rsplit('/').next().unwrap())
.collect::<Vec<&str>>();
if activities.is_empty() {
- return Err(AndroidError::ActivityNotFound(self.options.package.clone()));
+ return Err(AndroidError::ActivityNotFound(options.package.clone()));
}
activities[0].to_owned()
}
};
- self.process = Some(AndroidProcess::new(
- device,
- self.options.package.clone(),
- activity,
- )?);
+ let process = AndroidProcess::new(device, options.package.clone(), activity)?;
- Ok(())
+ Ok(AndroidHandler {
+ options: options.clone(),
+ config,
+ process,
+ profile,
+ test_root,
+ host_port,
+ target_port: TARGET_PORT,
+ })
}
pub fn generate_config_file<I, K, V>(&self, envs: I) -> Result<String>
@@ -275,102 +293,178 @@ impl AndroidHandler {
K: ToString,
V: ToString,
{
- match self.process {
- Some(ref process) => {
- process.device.clear_app_data(&process.package)?;
-
- // These permissions, at least, are required to read profiles in /mnt/sdcard.
- for perm in &["READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE"] {
- process.device.execute_host_shell_command(&format!(
- "pm grant {} android.permission.{}",
- &process.package, perm
- ))?;
- }
-
- debug!("Deleting {}", self.profile.display());
- process
- .device
- .execute_host_shell_command(&format!("rm -rf {}", self.profile.display()))?;
+ self.process.device.clear_app_data(&self.process.package)?;
+
+ // These permissions, at least, are required to read profiles in /mnt/sdcard.
+ for perm in &["READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE"] {
+ self.process.device.execute_host_shell_command(&format!(
+ "pm grant {} android.permission.{}",
+ &self.process.package, perm
+ ))?;
+ }
- debug!(
- "Pushing {} to {}",
- profile.path.display(),
- self.profile.display()
- );
- process
- .device
- .push_dir(&profile.path, &self.profile, 0o777)?;
+ // Make sure to create the test root.
+ self.process.device.create_dir(&self.test_root)?;
+ self.process.device.chmod(&self.test_root, "777", true)?;
- let contents = self.generate_config_file(env)?;
- debug!("Content of generated GeckoView config file:\n{}", contents);
- let reader = &mut io::BufReader::new(contents.as_bytes());
+ // Replace the profile
+ self.process.device.remove(&self.profile)?;
+ self.process
+ .device
+ .push_dir(&profile.path, &self.profile, 0o777)?;
- debug!(
- "Pushing GeckoView configuration file to {}",
- self.config.display()
- );
- process.device.push(reader, &self.config, 0o777)?;
+ let contents = self.generate_config_file(env)?;
+ debug!("Content of generated GeckoView config file:\n{}", contents);
+ let reader = &mut io::BufReader::new(contents.as_bytes());
- // Bug 1584966: File permissions are not correctly set by push()
- process
- .device
- .execute_host_shell_command(&format!("chmod a+rw {}", self.config.display()))?;
+ debug!(
+ "Pushing GeckoView configuration file to {}",
+ self.config.display()
+ );
+ self.process.device.push(reader, &self.config, 0o777)?;
- // Tell GeckoView to read configuration even when `android:debuggable="false"`.
- process.device.execute_host_shell_command(&format!(
- "am set-debug-app --persistent {}",
- process.package
- ))?;
- }
- None => return Err(AndroidError::NotConnected),
- }
+ // Tell GeckoView to read configuration even when `android:debuggable="false"`.
+ self.process.device.execute_host_shell_command(&format!(
+ "am set-debug-app --persistent {}",
+ self.process.package
+ ))?;
Ok(())
}
pub fn launch(&self) -> Result<()> {
- match self.process {
- Some(ref process) => {
- // TODO: Remove the usage of intent arguments once Fennec is no longer
- // supported. Packages which are using GeckoView always read the arguments
- // via the YAML configuration file.
- let mut intent_arguments = self
- .options
- .intent_arguments
- .clone()
- .unwrap_or_else(|| Vec::with_capacity(3));
- intent_arguments.push("--es".to_owned());
- intent_arguments.push("args".to_owned());
- intent_arguments
- .push(format!("--marionette --profile {}", self.profile.display()).to_owned());
-
- debug!("Launching {}/{}", process.package, process.activity);
- process
- .device
- .launch(&process.package, &process.activity, &intent_arguments)
- .map_err(|e| {
- let message = format!(
- "Could not launch Android {}/{}: {}",
- process.package, process.activity, e
- );
- mozdevice::DeviceError::Adb(message)
- })?;
- }
- None => return Err(AndroidError::NotConnected),
- }
+ // TODO: Remove the usage of intent arguments once Fennec is no longer
+ // supported. Packages which are using GeckoView always read the arguments
+ // via the YAML configuration file.
+ let mut intent_arguments = self
+ .options
+ .intent_arguments
+ .clone()
+ .unwrap_or_else(|| Vec::with_capacity(3));
+ intent_arguments.push("--es".to_owned());
+ intent_arguments.push("args".to_owned());
+ intent_arguments.push(format!("--marionette --profile {}", self.profile.display()));
+
+ debug!(
+ "Launching {}/{}",
+ self.process.package, self.process.activity
+ );
+ self.process
+ .device
+ .launch(
+ &self.process.package,
+ &self.process.activity,
+ &intent_arguments,
+ )
+ .map_err(|e| {
+ let message = format!(
+ "Could not launch Android {}/{}: {}",
+ self.process.package, self.process.activity, e
+ );
+ mozdevice::DeviceError::Adb(message)
+ })?;
Ok(())
}
pub fn force_stop(&self) -> Result<()> {
- match &self.process {
- Some(process) => {
- debug!("Force stopping the Android package: {}", &process.package);
- process.device.force_stop(&process.package)?;
- }
- None => return Err(AndroidError::NotConnected),
- }
+ debug!(
+ "Force stopping the Android package: {}",
+ &self.process.package
+ );
+ self.process.device.force_stop(&self.process.package)?;
Ok(())
}
}
+
+#[cfg(test)]
+mod test {
+ // To successfully run those tests the geckoview_example package needs to
+ // be installed on the device or emulator. After setting up the build
+ // environment (https://mzl.la/3muLv5M), the following mach commands have to
+ // be executed:
+ //
+ // $ ./mach build && ./mach install
+ //
+ // Currently the mozdevice API is not safe for multiple requests at the same
+ // time. It is recommended to run each of the unit tests on its own. Also adb
+ // specific tests cannot be run in CI yet. To check those locally, also run
+ // the ignored tests.
+ //
+ // Use the following command to accomplish that:
+ //
+ // $ cargo test -- --ignored --test-threads=1
+
+ use crate::android::AndroidHandler;
+ use crate::capabilities::AndroidOptions;
+ use mozdevice::{AndroidStorage, AndroidStorageInput};
+ use std::path::PathBuf;
+
+ fn run_handler_storage_test(package: &str, storage: AndroidStorageInput) {
+ let options = AndroidOptions::new(package.to_owned(), storage);
+ let handler = AndroidHandler::new(&options, 4242).expect("has valid Android handler");
+
+ assert_eq!(handler.options, options);
+ assert_eq!(handler.process.package, package);
+
+ let expected_config_path = PathBuf::from(format!(
+ "/data/local/tmp/{}-geckoview-config.yaml",
+ &package
+ ));
+ assert_eq!(handler.config, expected_config_path);
+
+ if handler.process.device.storage == AndroidStorage::App {
+ assert_eq!(
+ handler.process.device.run_as_package,
+ Some(package.to_owned())
+ );
+ } else {
+ assert_eq!(handler.process.device.run_as_package, None);
+ }
+
+ let test_root = match handler.process.device.storage {
+ AndroidStorage::App => {
+ let mut buf = PathBuf::from("/data/data");
+ buf.push(&package);
+ buf.push("test_root");
+ buf
+ }
+ AndroidStorage::Internal => PathBuf::from("/data/local/tmp/test_root"),
+ AndroidStorage::Sdcard => PathBuf::from("/mnt/sdcard/test_root"),
+ };
+ assert_eq!(handler.test_root, test_root);
+
+ let mut profile = test_root.clone();
+ profile.push(format!("{}-geckodriver-profile", &package));
+ assert_eq!(handler.profile, profile);
+ }
+
+ #[test]
+ #[ignore]
+ fn android_handler_storage_as_app() {
+ let package = "org.mozilla.geckoview_example";
+ run_handler_storage_test(&package, AndroidStorageInput::App);
+ }
+
+ #[test]
+ #[ignore]
+ fn android_handler_storage_as_auto() {
+ let package = "org.mozilla.geckoview_example";
+ run_handler_storage_test(package, AndroidStorageInput::Auto);
+ }
+
+ #[test]
+ #[ignore]
+ fn android_handler_storage_as_internal() {
+ let package = "org.mozilla.geckoview_example";
+ run_handler_storage_test(package, AndroidStorageInput::Internal);
+ }
+
+ #[test]
+ #[ignore]
+ fn android_handler_storage_as_sdcard() {
+ let package = "org.mozilla.geckoview_example";
+ run_handler_storage_test(package, AndroidStorageInput::Sdcard);
+ }
+}
diff --git a/src/capabilities.rs b/src/capabilities.rs
index ef1b0d7..ab4ed68 100644
--- a/src/capabilities.rs
+++ b/src/capabilities.rs
@@ -1,26 +1,54 @@
use crate::command::LogOptions;
use crate::logging::Level;
use base64;
+use mozdevice::AndroidStorageInput;
use mozprofile::preferences::Pref;
use mozprofile::profile::Profile;
use mozrunner::runner::platform::firefox_default_path;
-use mozversion::{self, firefox_version, Version};
+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::fmt::{self, Display};
use std::fs;
use std::io;
use std::io::BufWriter;
use std::io::Cursor;
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
+#[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
@@ -30,7 +58,7 @@ use zip;
pub struct FirefoxCapabilities<'a> {
pub chosen_binary: Option<PathBuf>,
fallback_binary: Option<&'a PathBuf>,
- version_cache: BTreeMap<PathBuf, String>,
+ version_cache: BTreeMap<PathBuf, Result<Version, VersionError>>,
}
impl<'a> FirefoxCapabilities<'a> {
@@ -52,57 +80,42 @@ impl<'a> FirefoxCapabilities<'a> {
.or_else(firefox_default_path);
}
- fn version(&mut self, binary: Option<&Path>) -> Option<String> {
+ fn version(&mut self, binary: Option<&Path>) -> Result<Version, VersionError> {
if let Some(binary) = binary {
- if let Some(value) = self.version_cache.get(binary) {
- return Some((*value).clone());
+ if let Some(cache_value) = self.version_cache.get(binary) {
+ return cache_value.clone();
}
- debug!("Trying to read firefox version from ini files");
- let rv = firefox_version(&*binary)
- .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 {
+ let rv = self
+ .version_from_ini(binary)
+ .or_else(|_| self.version_from_binary(binary));
+ if let Ok(ref version) = rv {
debug!("Found version {}", version);
- self.version_cache
- .insert(binary.to_path_buf(), version.clone());
} else {
debug!("Failed to get binary version");
}
+ self.version_cache.insert(binary.to_path_buf(), rv.clone());
rv
} else {
- None
+ Err(VersionError::MissingBinary)
}
}
- fn version_from_binary(&self, binary: &Path) -> Option<String> {
- let version_regexp = Regex::new(r#"Mozilla Firefox [0-9]+\.[0-9]+(?:[a-z][0-9]+)?"#)
- .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)
- .and_then(|captures| captures.get(0))
- .and_then(|m| str::from_utf8(m.as_bytes()).ok())
- .map(|x| x.into())
+ 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 {
- None
+ Err(VersionError::VersionError(
+ mozversion::Error::MetadataError("Missing version string".into()),
+ ))
}
}
-}
-// TODO: put this in webdriver-rust
-fn convert_version_error(err: mozversion::Error) -> WebDriverError {
- WebDriverError::new(ErrorStatus::SessionNotCreated, err.to_string())
+ 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> {
@@ -116,7 +129,9 @@ impl<'a> BrowserCapabilities for FirefoxCapabilities<'a> {
fn browser_version(&mut self, _: &Capabilities) -> WebDriverResult<Option<String>> {
let binary = self.chosen_binary.clone();
- Ok(self.version(binary.as_ref().map(|x| x.as_ref())))
+ 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>> {
@@ -132,16 +147,7 @@ impl<'a> BrowserCapabilities for FirefoxCapabilities<'a> {
}
fn accept_insecure_certs(&mut self, _: &Capabilities) -> WebDriverResult<bool> {
- let binary = self.chosen_binary.clone();
- let version_str = self.version(binary.as_ref().map(|x| x.as_ref()));
- if let Some(x) = version_str {
- Ok(Version::from_str(&*x)
- .or_else(|x| Err(convert_version_error(x)))?
- .major
- >= 52)
- } else {
- Ok(false)
- }
+ Ok(true)
}
fn set_window_rect(&mut self, _: &Capabilities) -> WebDriverResult<bool> {
@@ -154,9 +160,9 @@ impl<'a> BrowserCapabilities for FirefoxCapabilities<'a> {
comparison: &str,
) -> WebDriverResult<bool> {
Version::from_str(version)
- .or_else(|x| Err(convert_version_error(x)))?
+ .map_err(|err| VersionError::from(err))?
.matches(comparison)
- .or_else(|x| Err(convert_version_error(x)))
+ .map_err(|err| VersionError::from(err).into())
}
fn strict_file_interactability(&mut self, _: &Capabilities) -> WebDriverResult<bool> {
@@ -209,7 +215,7 @@ impl<'a> BrowserCapabilities for FirefoxCapabilities<'a> {
"binary" => {
if let Some(binary) = value.as_str() {
if !data.contains_key("androidPackage")
- && self.version(Some(Path::new(binary))).is_none()
+ && self.version(Some(Path::new(binary))).is_err()
{
return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
@@ -331,12 +337,14 @@ pub struct AndroidOptions {
pub device_serial: Option<String>,
pub intent_arguments: Option<Vec<String>>,
pub package: String,
+ pub storage: AndroidStorageInput,
}
impl AndroidOptions {
- fn new(package: String) -> AndroidOptions {
+ pub fn new(package: String, storage: AndroidStorageInput) -> AndroidOptions {
AndroidOptions {
package,
+ storage,
..Default::default()
}
}
@@ -366,6 +374,7 @@ impl FirefoxOptions {
pub fn from_capabilities(
binary_path: Option<PathBuf>,
+ android_storage: AndroidStorageInput,
matched: &mut Capabilities,
) -> WebDriverResult<FirefoxOptions> {
let mut rv = FirefoxOptions::new();
@@ -380,7 +389,7 @@ impl FirefoxOptions {
)
})?;
- rv.android = FirefoxOptions::load_android(&options)?;
+ rv.android = FirefoxOptions::load_android(android_storage, &options)?;
rv.args = FirefoxOptions::load_args(&options)?;
rv.env = FirefoxOptions::load_env(&options)?;
rv.log = FirefoxOptions::load_log(&options)?;
@@ -508,7 +517,10 @@ impl FirefoxOptions {
}
}
- pub fn load_android(options: &Capabilities) -> WebDriverResult<Option<AndroidOptions>> {
+ 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()
@@ -530,7 +542,7 @@ impl FirefoxOptions {
));
}
- let mut android = AndroidOptions::new(package);
+ let mut android = AndroidOptions::new(package, storage);
android.activity = match options.get("androidActivity") {
Some(json) => {
@@ -669,10 +681,10 @@ fn unzip_buffer(buf: &[u8], dest_dir: &Path) -> WebDriverResult<()> {
mod tests {
extern crate mozprofile;
+ use self::mozprofile::preferences::Pref;
use super::*;
use crate::marionette::MarionetteHandler;
-
- use self::mozprofile::preferences::Pref;
+ use mozdevice::AndroidStorageInput;
use serde_json::json;
use std::default::Default;
use std::fs::File;
@@ -691,7 +703,7 @@ mod tests {
let mut caps = Capabilities::new();
caps.insert("moz:firefoxOptions".into(), Value::Object(firefox_opts));
- FirefoxOptions::from_capabilities(None, &mut caps)
+ FirefoxOptions::from_capabilities(None, AndroidStorageInput::Auto, &mut caps)
}
#[test]
@@ -710,7 +722,8 @@ mod tests {
fn fx_options_from_capabilities_no_binary_and_caps() {
let mut caps = Capabilities::new();
- let opts = FirefoxOptions::from_capabilities(None, &mut caps).unwrap();
+ let opts =
+ FirefoxOptions::from_capabilities(None, AndroidStorageInput::Auto, &mut caps).unwrap();
assert_eq!(opts.android, None);
assert_eq!(opts.args, None);
assert_eq!(opts.binary, None);
@@ -728,7 +741,12 @@ mod tests {
let binary = PathBuf::from("foo");
- let opts = FirefoxOptions::from_capabilities(Some(binary.clone()), &mut caps).unwrap();
+ let opts = FirefoxOptions::from_capabilities(
+ Some(binary.clone()),
+ AndroidStorageInput::Auto,
+ &mut caps,
+ )
+ .unwrap();
assert_eq!(opts.android, None);
assert_eq!(opts.args, None);
assert_eq!(opts.binary, Some(binary));
@@ -741,7 +759,7 @@ mod tests {
let mut caps = Capabilities::new();
caps.insert("moz:firefoxOptions".into(), json!(42));
- FirefoxOptions::from_capabilities(None, &mut caps)
+ FirefoxOptions::from_capabilities(None, AndroidStorageInput::Auto, &mut caps)
.expect_err("Firefox options need to be of type object");
}
@@ -761,7 +779,13 @@ mod tests {
firefox_opts.insert("androidPackage".into(), json!(value));
let opts = make_options(firefox_opts).expect("valid firefox options");
- assert_eq!(opts.android, Some(AndroidOptions::new(value.to_string())));
+ assert_eq!(
+ opts.android,
+ Some(AndroidOptions::new(
+ value.to_string(),
+ AndroidStorageInput::Auto
+ ))
+ );
}
}
diff --git a/src/command.rs b/src/command.rs
index 560598b..5e3a6f3 100644
--- a/src/command.rs
+++ b/src/command.rs
@@ -8,8 +8,7 @@ use std::fs::File;
use std::io::prelude::*;
use uuid::Uuid;
use webdriver::command::{WebDriverCommand, WebDriverExtensionCommand};
-use webdriver::common::WebElement;
-use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
+use webdriver::error::WebDriverResult;
use webdriver::httpapi::WebDriverExtensionRoute;
use webdriver::Parameters;
@@ -29,16 +28,6 @@ pub fn extension_routes() -> Vec<(Method, &'static str, GeckoExtensionRoute)> {
),
(
Method::POST,
- "/session/{sessionId}/moz/xbl/{elementId}/anonymous_children",
- GeckoExtensionRoute::XblAnonymousChildren,
- ),
- (
- Method::POST,
- "/session/{sessionId}/moz/xbl/{elementId}/anonymous_by_attribute",
- GeckoExtensionRoute::XblAnonymousByAttribute,
- ),
- (
- Method::POST,
"/session/{sessionId}/moz/addon/install",
GeckoExtensionRoute::InstallAddon,
),
@@ -59,8 +48,6 @@ pub fn extension_routes() -> Vec<(Method, &'static str, GeckoExtensionRoute)> {
pub enum GeckoExtensionRoute {
GetContext,
SetContext,
- XblAnonymousChildren,
- XblAnonymousByAttribute,
InstallAddon,
UninstallAddon,
TakeFullScreenshot,
@@ -71,7 +58,7 @@ impl WebDriverExtensionRoute for GeckoExtensionRoute {
fn command(
&self,
- params: &Parameters,
+ _params: &Parameters,
body_data: &Value,
) -> WebDriverResult<WebDriverCommand<GeckoExtensionCommand>> {
use self::GeckoExtensionRoute::*;
@@ -81,26 +68,6 @@ impl WebDriverExtensionRoute for GeckoExtensionRoute {
SetContext => {
GeckoExtensionCommand::SetContext(serde_json::from_value(body_data.clone())?)
}
- XblAnonymousChildren => {
- let element_id = try_opt!(
- params.get("elementId"),
- ErrorStatus::InvalidArgument,
- "Missing elementId parameter"
- );
- let element = WebElement(element_id.as_str().to_string());
- GeckoExtensionCommand::XblAnonymousChildren(element)
- }
- XblAnonymousByAttribute => {
- let element_id = try_opt!(
- params.get("elementId"),
- ErrorStatus::InvalidArgument,
- "Missing elementId parameter"
- );
- GeckoExtensionCommand::XblAnonymousByAttribute(
- WebElement(element_id.as_str().into()),
- serde_json::from_value(body_data.clone())?,
- )
- }
InstallAddon => {
GeckoExtensionCommand::InstallAddon(serde_json::from_value(body_data.clone())?)
}
@@ -114,12 +81,10 @@ impl WebDriverExtensionRoute for GeckoExtensionRoute {
}
}
-#[derive(Clone, PartialEq)]
+#[derive(Clone)]
pub enum GeckoExtensionCommand {
GetContext,
SetContext(GeckoContextParameters),
- XblAnonymousChildren(WebElement),
- XblAnonymousByAttribute(WebElement, XblLocatorParameters),
InstallAddon(AddonInstallParameters),
UninstallAddon(AddonUninstallParameters),
TakeFullScreenshot,
@@ -133,8 +98,6 @@ impl WebDriverExtensionCommand for GeckoExtensionCommand {
InstallAddon(x) => Some(serde_json::to_value(x).unwrap()),
SetContext(x) => Some(serde_json::to_value(x).unwrap()),
UninstallAddon(x) => Some(serde_json::to_value(x).unwrap()),
- XblAnonymousByAttribute(_, x) => Some(serde_json::to_value(x).unwrap()),
- XblAnonymousChildren(_) => None,
TakeFullScreenshot => None,
}
}
@@ -372,22 +335,4 @@ mod tests {
assert!(serde_json::from_value::<P>(json!({ "context": null })).is_err());
assert!(serde_json::from_value::<P>(json!({"context": "foo"})).is_err());
}
-
- #[test]
- fn test_json_xbl_anonymous_by_attribute() {
- let locator = XblLocatorParameters {
- name: "foo".to_string(),
- value: "bar".to_string(),
- };
- assert_de(&locator, json!({"name": "foo", "value": "bar"}));
- }
-
- #[test]
- fn test_json_xbl_anonymous_by_attribute_with_name_invalid() {
- type P = XblLocatorParameters;
- assert!(serde_json::from_value::<P>(json!({"value": "bar"})).is_err());
- assert!(serde_json::from_value::<P>(json!({"name": null, "value": "bar"})).is_err());
- assert!(serde_json::from_value::<P>(json!({"name": "foo"})).is_err());
- assert!(serde_json::from_value::<P>(json!({"name": "foo", "value": null})).is_err());
- }
}
diff --git a/src/main.rs b/src/main.rs
index 9ce17ef..4dd6c66 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,5 @@
#![forbid(unsafe_code)]
-extern crate base64;
extern crate chrono;
#[macro_use]
extern crate clap;
@@ -58,6 +57,7 @@ pub mod test;
use crate::command::extension_routes;
use crate::logging::Level;
use crate::marionette::{MarionetteHandler, MarionetteSettings};
+use mozdevice::AndroidStorageInput;
const EXIT_SUCCESS: i32 = 0;
const EXIT_USAGE: i32 = 64;
@@ -159,6 +159,8 @@ fn parse_args(app: &mut App) -> ProgramResult<Operation> {
Err(e) => usage!("{}: {}:{}", e, host, port),
};
+ let android_storage = value_t!(matches, "android_storage", AndroidStorageInput)?;
+
let binary = matches.value_of("binary").map(PathBuf::from);
let marionette_host = matches.value_of("marionette_host").unwrap();
@@ -181,6 +183,7 @@ fn parse_args(app: &mut App) -> ProgramResult<Operation> {
binary,
connect_existing: matches.is_present("connect_existing"),
jsdebugger: matches.is_present("jsdebugger"),
+ android_storage,
};
Operation::Server {
log_level,
@@ -318,6 +321,14 @@ fn make_app<'a, 'b>() -> App<'a, 'b> {
.long("version")
.help("Prints version and copying information"),
)
+ .arg(
+ Arg::with_name("android_storage")
+ .long("android-storage")
+ .possible_values(&["auto", "app", "internal", "sdcard"])
+ .default_value("auto")
+ .value_name("ANDROID_STORAGE")
+ .help("Selects storage location to be used for test data."),
+ )
}
fn get_program_name() -> String {
diff --git a/src/marionette.rs b/src/marionette.rs
index 666d56d..7074cf3 100644
--- a/src/marionette.rs
+++ b/src/marionette.rs
@@ -1,7 +1,7 @@
use crate::android::AndroidHandler;
use crate::command::{
AddonInstallParameters, AddonUninstallParameters, GeckoContextParameters,
- GeckoExtensionCommand, GeckoExtensionRoute, XblLocatorParameters, CHROME_ELEMENT_KEY,
+ GeckoExtensionCommand, GeckoExtensionRoute, CHROME_ELEMENT_KEY,
};
use marionette_rs::common::{
Cookie as MarionetteCookie, Date as MarionetteDate, Frame as MarionetteFrame,
@@ -17,6 +17,7 @@ use marionette_rs::webdriver::{
ScreenshotOptions, Script as MarionetteScript, Selector as MarionetteSelector,
Url as MarionetteUrl, WindowRect as MarionetteWindowRect,
};
+use mozdevice::AndroidStorageInput;
use mozprofile::preferences::Pref;
use mozprofile::profile::Profile;
use mozrunner::runner::{FirefoxProcess, FirefoxRunner, Runner, RunnerProcess};
@@ -95,6 +96,8 @@ pub struct MarionetteSettings {
/// Brings up the Browser Toolbox when starting Firefox,
/// letting you debug internals.
pub jsdebugger: bool,
+
+ pub android_storage: AndroidStorageInput,
}
#[derive(Default)]
@@ -131,6 +134,7 @@ impl MarionetteHandler {
let options = FirefoxOptions::from_capabilities(
fx_capabilities.chosen_binary,
+ self.settings.android_storage,
&mut capabilities,
)?;
(options, capabilities)
@@ -188,10 +192,7 @@ impl MarionetteHandler {
fn start_android(&mut self, port: u16, options: FirefoxOptions) -> WebDriverResult<()> {
let android_options = options.android.unwrap();
- let mut handler = AndroidHandler::new(&android_options);
- handler
- .connect(port)
- .map_err(|e| WebDriverError::new(ErrorStatus::UnknownError, e.to_string()))?;
+ let handler = AndroidHandler::new(&android_options, port)?;
// Profile management.
let is_custom_profile = options.profile.is_some();
@@ -330,7 +331,7 @@ impl WebDriverHandler<GeckoExtensionRoute> for MarionetteHandler {
let mut capabilities_options = None;
// First handle the status message which doesn't actually require a marionette
// connection or message
- if msg.command == Status {
+ if let Status = msg.command {
let (ready, message) = self
.connection
.lock()
@@ -862,27 +863,6 @@ impl MarionetteSession {
Extension(ref extension) => match extension {
GetContext => WebDriverResponse::Generic(resp.into_value_response(true)?),
SetContext(_) => WebDriverResponse::Void,
- XblAnonymousChildren(_) => {
- let els_vec = try_opt!(
- resp.result.as_array(),
- ErrorStatus::UnknownError,
- "Failed to interpret body as array"
- );
- let els = els_vec
- .iter()
- .map(|x| self.to_web_element(x))
- .collect::<Result<Vec<_>, _>>()?;
-
- WebDriverResponse::Generic(ValueResponse(serde_json::to_value(els)?))
- }
- XblAnonymousByAttribute(_, _) => {
- let el = self.to_web_element(try_opt!(
- resp.result.get("value"),
- ErrorStatus::UnknownError,
- "Failed to find value field"
- ))?;
- WebDriverResponse::Generic(ValueResponse(serde_json::to_value(el)?))
- }
InstallAddon(_) => WebDriverResponse::Generic(resp.into_value_response(true)?),
UninstallAddon(_) => WebDriverResponse::Void,
TakeFullScreenshot => WebDriverResponse::Generic(resp.into_value_response(true)?),
@@ -1165,18 +1145,6 @@ impl MarionetteCommand {
InstallAddon(x) => (Some("Addon:Install"), Some(x.to_marionette())),
SetContext(x) => (Some("Marionette:SetContext"), Some(x.to_marionette())),
UninstallAddon(x) => (Some("Addon:Uninstall"), Some(x.to_marionette())),
- XblAnonymousByAttribute(e, x) => {
- let mut data = x.to_marionette()?;
- data.insert("element".to_string(), Value::String(e.to_string()));
- (Some("WebDriver:FindElement"), Some(Ok(data)))
- }
- XblAnonymousChildren(e) => {
- let mut data = Map::new();
- data.insert("using".to_owned(), serde_json::to_value("anon")?);
- data.insert("value".to_owned(), Value::Null);
- data.insert("element".to_string(), serde_json::to_value(e.to_string())?);
- (Some("WebDriver:FindElements"), Some(Ok(data)))
- }
_ => (None, None),
},
_ => (None, None),
@@ -1581,21 +1549,6 @@ impl ToMarionette<MarionettePrintMargins> for PrintMargins {
}
}
-impl ToMarionette<Map<String, Value>> for XblLocatorParameters {
- fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
- let mut value = Map::new();
- value.insert(self.name.to_owned(), Value::String(self.value.clone()));
-
- let mut data = Map::new();
- data.insert(
- "using".to_owned(),
- Value::String("anon attribute".to_string()),
- );
- data.insert("value".to_owned(), Value::Object(value));
- Ok(data)
- }
-}
-
impl ToMarionette<Map<String, Value>> for ActionsParameters {
fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
Ok(try_opt!(
diff --git a/src/prefs.rs b/src/prefs.rs
index dfcaac2..17e882c 100644
--- a/src/prefs.rs
+++ b/src/prefs.rs
@@ -68,16 +68,16 @@ lazy_static! {
// Do not warn on quitting Firefox
("browser.warnOnQuit", Pref::new(false)),
- // Do not show datareporting policy notifications which can
- // interfere with tests
- ("datareporting.healthreport.about.reportUrl", Pref::new("http://%(server)s/dummy/abouthealthreport/")), // removed in Firefox 59
+ // Defensively disable data reporting systems
("datareporting.healthreport.documentServerURI", Pref::new("http://%(server)s/dummy/healthreport/")),
("datareporting.healthreport.logging.consoleEnabled", Pref::new(false)),
("datareporting.healthreport.service.enabled", Pref::new(false)),
("datareporting.healthreport.service.firstRun", Pref::new(false)),
("datareporting.healthreport.uploadEnabled", Pref::new(false)),
+
+ // Do not show datareporting policy notifications which can
+ // interfere with tests
("datareporting.policy.dataSubmissionEnabled", Pref::new(false)),
- ("datareporting.policy.dataSubmissionPolicyAccepted", Pref::new(false)),
("datareporting.policy.dataSubmissionPolicyBypassNotification", Pref::new(true)),
// Disable the ProcessHangMonitor
@@ -91,10 +91,6 @@ lazy_static! {
// Disable intalling any distribution extensions or add-ons
("extensions.installDistroAddons", Pref::new(false)),
- // Disable extensions compatibility dialogue.
- // TODO: Remove once minimum supported Firefox release is 61.
- ("extensions.showMismatchUI", Pref::new(false)),
-
// Turn off extension updates so they do not bother tests
("extensions.update.enabled", Pref::new(false)),
("extensions.update.notifyUser", Pref::new(false)),
@@ -126,6 +122,9 @@ lazy_static! {
// Disable download and usage of OpenH264, and Widevine plugins
("media.gmp-manager.updateEnabled", Pref::new(false)),
+ // Disable the GFX sanity window
+ ("media.sanity-test.disabled", Pref::new(true)),
+
// Do not prompt with long usernames or passwords in URLs
// TODO: Remove once minimum supported Firefox release is 61.
("network.http.phishy-userpass-length", Pref::new(255)),
@@ -153,8 +152,5 @@ lazy_static! {
// Prevent starting into safe mode after application crashes
("toolkit.startup.max_resumed_crashes", Pref::new(-1)),
-
- // We want to collect telemetry, but we don't want to send in the results
- ("toolkit.telemetry.server", Pref::new("https://%(server)s/dummy/telemetry/")),
];
}