diff --git a/CHANGES.md b/CHANGES.md index ed684cf..ddf9073 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,8 +3,58 @@ Change log All notable changes to this program are documented in this file. +0.29.1 (2021-04-09), `970ef713fe58`) +------------------------------------- + +### 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. + +- _Android:_ + + Marionette will only be enabled in GeckoView based applications when the + Firefox preference `devtools.debugger.remote-enabled` is set to `True` via + [`moz:firefoxOptions`]. This will be fixed in one of the upcoming Firefox + for Android releases. + +### Added + +- When testing GeckoView based applications on Android it's now enough to + specify the `androidPackage` capability. The appropriate activity name, + and required intent arguments will now automatically be used for + applications released by Mozilla. + +- Native AArch64 (M1) builds of geckodriver for MacOS are now available. These + are currently shipped as Tier2 due to missing test infrastructure. Please let + us know if you experience issues. + +### Fixed + +- Fixed a stack overflow crash in thread 'webdriver dispatcher' when + handling certain device errors. + +- Fixed an application crash due to missing permissions on unrooted devices + by changing the location of the test related files, e.g the profile folder. + Therefore the deprecated --android-storage command line argument + now defaults to the `sdcard` option, which changed its location to + `$EXTERNAL_STORAGE/Android/data/%androidPackage%/files/`. With this change + proper support for unrooted devices running Android 10+ has been added. + + _Note_: Do not use the --android-storage command line argument + anymore unless there is a strong reason. It will be removed in a future + release. + + 0.29.0 (2021-01-14, `cf6956a5ec8e`) --------------------- +------------------------------------ ### Known problems @@ -18,6 +68,20 @@ All notable changes to this program are documented in this file. find more details on how to work around this issue in the [macOS notarization] section of the documentation. +- _Android:_ + + Marionette will only be enabled in GeckoView based applications when the + Firefox preference `devtools.debugger.remote-enabled` is set to `True` via + [`moz:firefoxOptions`]. This will be fixed in one of the upcoming Firefox + for Android releases. + + In some cases geckodriver could crash due to a stack overflow when handling + certain device errors. + + On unrooted Android 10+ devices startup crashes of the application can be + experienced due to an inappropriate location of test related files, e.g the + profile folder. + ### Added - Introduced the new boolean capability `moz:debuggerAddress` that can be used @@ -30,7 +94,7 @@ All notable changes to this program are documented in this file. Firefox aka [Fission] will be not available. 0.28.0 (2020-11-03, `c00d2b6acd3f`) --------------------- +------------------------------------ ### Known problems @@ -44,6 +108,20 @@ All notable changes to this program are documented in this file. find more details on how to work around this issue in the [macOS notarization] section of the documentation. +- _Android:_ + + Marionette will only be enabled in GeckoView based applications when the + Firefox preference `devtools.debugger.remote-enabled` is set to `True` via + [`moz:firefoxOptions`]. This will be fixed in one of the upcoming Firefox + for Android releases. + + In some cases geckodriver could crash due to a stack overflow when handling + certain device errors. + + On unrooted Android 10+ devices startup crashes of the application can be + experienced due to an inappropriate location of test related files, e.g the + profile folder. + ### Added - The command line flag `--android-storage` has been added, to allow geckodriver @@ -66,7 +144,7 @@ All notable changes to this program are documented in this file. and querying its attributes are no longer needed, and have been removed. 0.27.0 (2020-07-27, `7b8c4f32cdde`) --------------------- +------------------------------------ ### Security Fixes @@ -92,10 +170,20 @@ All notable changes to this program are documented in this file. find more details on how to work around this issue in the [macOS notarization] section of the documentation. +- _Android:_ + + Marionette will only be enabled in GeckoView based applications when the + Firefox preference `devtools.debugger.remote-enabled` is set to `True` via + [`moz:firefoxOptions`]. This will be fixed in one of the upcoming Firefox + for Android releases. + + In some cases geckodriver could crash due to a stack overflow when handling + certain device errors. + ### Added - To set environment variables for the launched Firefox for Android, - it is now possible to add an `env` object on `moz:firefoxOptions` + it is now possible to add an `env` object on [`moz:firefoxOptions`] (note: this is not supported for Firefox Desktop) - Support for print-to-PDF @@ -126,7 +214,7 @@ All notable changes to this program are documented in this file. - Windows and Linux binaries are again statically linked. 0.26.0 (2019-10-12, `e9783a644016'`) ------------------------------------- +------------------------------------- Note that with this release the minimum recommended Firefox version has changed to Firefox ≥60. @@ -149,6 +237,16 @@ has changed to Firefox ≥60. runtime] installed on your system for the binary to run. This is a known bug which we weren't able fix for this release. +- _Android:_ + + Marionette will only be enabled in GeckoView based applications when the + Firefox preference `devtools.debugger.remote-enabled` is set to `True` via + [`moz:firefoxOptions`]. This will be fixed in one of the upcoming Firefox + for Android releases. + + In some cases geckodriver could crash due to a stack overflow when handling + certain device errors. + ### Added - Support for Firefox on Android @@ -522,7 +620,7 @@ Firefox and Selenium versions have changed: - Firefox will now be started with the `-foreground` and `-no-remote` flags if they have not already been specified by the user in - `moz:firefoxOptions`. + [`moz:firefoxOptions`]. `-foreground` will ensure the application window gets focus when Firefox is started, and `-no-remote` will prevent remote commands @@ -732,7 +830,7 @@ Note that with geckodriver 0.19.0 the following versions are recommended: - To pick up a prepared profile from the filesystem, it is now possible to pass `["-profile", "/path/to/profile"]` in the `args` array on - `moz:firefoxOptions` + [`moz:firefoxOptions`] - geckodriver now recommends Firefox 53 and greater @@ -882,7 +980,7 @@ and greater. - Fix broken unmarshaling of _Get Timeouts_ response format from Firefox 52 and earlier (fixed by [Jason Juang]) -- Allow preferences in `moz:firefoxOptions` to be both positive- and +- Allow preferences in [`moz:firefoxOptions`] to be both positive- and negative integers (fixed by [Jason Juang]) - Allow IPv6 hostnames in the proxy configuration object @@ -1035,7 +1133,7 @@ and greater. `/session/{sessionId}/moz/xbl/{elementId}/anonymous_by_attribute` to return an anonymous element by a name and attribute query -- Introduced a `moz:firefoxOptions` capability to customise a Firefox +- Introduced a [`moz:firefoxOptions`] capability to customise a Firefox session: - The `binary`, `args`, and `profile` entries on this dictionary @@ -1054,7 +1152,7 @@ and greater. ### Changed - `firefox_binary`, `firefox_args`, and `firefox_profile` capabilities - removed in favour of the `moz:firefoxOptions` dictionary detailed above + removed in favour of the [`moz:firefoxOptions`] dictionary detailed above and in the [README] - Removed `--no-e10s` flag, and geckodriver will from now rely on the diff --git a/Cargo.toml b/Cargo.toml index 06ea304..395604e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "geckodriver" -version = "0.29.0" +version = "0.29.1" 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,8 +17,8 @@ hyper = "0.13" lazy_static = "1.0" log = { version = "0.4", features = ["std"] } marionette = { path = "./marionette" } -mozdevice = "0.3.1" -mozprofile = "0.7.1" +mozdevice = "0.3.2" +mozprofile = "0.7.2" mozrunner = "0.12.1" mozversion = "0.4.1" regex = { version="1.0", default-features = false, features = ["perf", "std"] } @@ -27,7 +27,7 @@ serde_derive = "1.0" serde_json = "1.0" serde_yaml = "0.8" uuid = { version = "0.8", features = ["v4"] } -webdriver = "0.43.0" +webdriver = "0.43.1" zip = { version = "0.4", default-features = false, features = ["deflate"] } [[bin]] diff --git a/doc/Flags.md b/doc/Flags.md index 1fc6285..e0eda54 100644 --- a/doc/Flags.md +++ b/doc/Flags.md @@ -3,6 +3,11 @@ Flags #### --android-storage ANDROID_STORAGE +**Deprecation warning**: This argument is deprecated and planned to be removed +with the 0.31.0 release of geckodriver. As such it shouldn't be used with version +0.30.0 or later anymore. By default the automatic detection will now use the +external storage location, which is always readable and writeable. + Selects the test data location on the Android device, eg. the Firefox profile. By default `auto` is used. @@ -40,10 +45,9 @@ By default `auto` is used. shell user. Commands will be executed via su. sdcard -

Location: /mnt/sdcard/test_root

- This location is not supported on Android 11+ due to the - - changes related to scoped storage. +

Location: $EXTERNAL_STORAGE/Android/data/%androidPackage%/files/test_root

+ This location is supported by all versions of Android whether if the device + is rooted or not. @@ -135,6 +139,33 @@ system to atomically assign a free port. Attach [browser toolbox] debugger when Firefox starts. This is useful for debugging [Marionette] internals. +To be prompted at the start of the test run or between tests, +you can set the `marionette.debugging.clicktostart` preference to +`true`. + +For reference, below is the list of preferences that enables the +chrome debugger. These are all set implicitly when the +argument is passed to geckodriver. + + * `devtools.browsertoolbox.panel` -> `jsdebugger` + + Selects the Debugger panel by default. + + * `devtools.chrome.enabled` → true + + Enables debugging of chrome code. + + * `devtools.debugger.prompt-connection` → false + + Controls the remote connection prompt. Note that this will + automatically expose your Firefox instance to localhost. + + * `devtools.debugger.remote-enabled` → true + + Allows a remote debugger to connect, which is necessary for + debugging chrome code. + + [browser toolbox]: https://developer.mozilla.org/en-US/docs/Tools/Browser_Toolbox diff --git a/doc/Releasing.md b/doc/Releasing.md index 3918580..7cf8b33 100644 --- a/doc/Releasing.md +++ b/doc/Releasing.md @@ -60,9 +60,9 @@ If a feature was added but removed before release, there is no reason to list it as a change. It is good practice to also include relevant information from the -[webdriver] and [rust-mozrunner] crates, since these are the two most -important dependencies of geckodriver and a lot of its functionality -is implemented there. +[webdriver], [rust-mozrunner], and [rust-mozdevice] crates, since these +are the most important dependencies of geckodriver and a lot of its +functionality is implemented there. We follow the writing style of the existing change log, with one section per version (with a release date), with subsections @@ -73,8 +73,9 @@ to make the file readable in a text editor as well as rendered HTML. fmt(1) does a splendid job at text formatting. [CHANGES.md]: https://searchfox.org/mozilla-central/source/testing/geckodriver/CHANGES.md +[webdriver]: https://searchfox.org/mozilla-central/source/testing/webdriver [rust-mozrunner]: https://searchfox.org/mozilla-central/source/testing/mozbase/rust/mozrunner - +[rust-mozdevice]: https://searchfox.org/mozilla-central/source/testing/mozbase/rust/mozdevice Update libraries ---------------- diff --git a/doc/Support.md b/doc/Support.md index 94afa54..741aab8 100644 --- a/doc/Support.md +++ b/doc/Support.md @@ -22,7 +22,11 @@ and required versions of Selenium and Firefox: max - + + 0.29.1 + ≥ 3.11 (3.14 Python) + 60 + n/a 0.29.0 ≥ 3.11 (3.14 Python) diff --git a/src/android.rs b/src/android.rs index 6aaf58e..e21c146 100644 --- a/src/android.rs +++ b/src/android.rs @@ -171,7 +171,18 @@ impl AndroidHandler { buf } AndroidStorage::Internal => PathBuf::from("/data/local/tmp/test_root"), - AndroidStorage::Sdcard => PathBuf::from("/mnt/sdcard/test_root"), + AndroidStorage::Sdcard => { + // We need to push the profile to a location on the device that can also + // be read and write by the application, and works for unrooted devices. + // The only location that meets this criteria is under: + // $EXTERNAL_STORAGE/Android/data/%options.package%/files + let response = device.execute_host_shell_command("echo $EXTERNAL_STORAGE")?; + let mut buf = PathBuf::from(response.trim_end_matches('\n')); + buf.push("Android/data"); + buf.push(&options.package); + buf.push("files/test_root"); + buf + } }; debug!( @@ -431,7 +442,19 @@ mod test { buf } AndroidStorage::Internal => PathBuf::from("/data/local/tmp/test_root"), - AndroidStorage::Sdcard => PathBuf::from("/mnt/sdcard/test_root"), + AndroidStorage::Sdcard => { + let response = handler + .process + .device + .execute_host_shell_command("echo $EXTERNAL_STORAGE") + .unwrap(); + + let mut buf = PathBuf::from(response.trim_end_matches('\n')); + buf.push("Android/data/"); + buf.push(&package); + buf.push("files/test_root"); + buf + } }; assert_eq!(handler.test_root, test_root); diff --git a/src/capabilities.rs b/src/capabilities.rs index e21651e..a409cfe 100644 --- a/src/capabilities.rs +++ b/src/capabilities.rs @@ -583,7 +583,7 @@ impl FirefoxOptions { )); } - let mut android = AndroidOptions::new(package, storage); + let mut android = AndroidOptions::new(package.clone(), storage); android.activity = match options.get("androidActivity") { Some(json) => { @@ -606,7 +606,25 @@ impl FirefoxOptions { Some(activity) } - None => None, + 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") { @@ -644,7 +662,16 @@ impl FirefoxOptions { Some(args) } - None => None, + 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)) @@ -871,13 +898,7 @@ 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(), - AndroidStorageInput::Auto - )) - ); + assert_eq!(opts.android.unwrap().package, value.to_string()); } } @@ -899,20 +920,63 @@ mod tests { } #[test] - fn fx_options_android_activity_valid_value() { - for value in ["cheese", "Cheese_9"].iter() { + 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!("foo.bar")); - firefox_opts.insert("androidActivity".into(), json!(value)); + firefox_opts.insert("androidPackage".into(), json!(package)); let opts = make_options(firefox_opts).expect("valid firefox options"); - let android_opts = AndroidOptions { - package: "foo.bar".to_owned(), - activity: Some(value.to_string()), - ..Default::default() - }; - assert_eq!(opts.android, Some(android_opts)); + 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).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).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).expect("valid firefox options"); + assert_eq!(opts.android.unwrap().activity, Some("foo".to_string())); } #[test] @@ -940,16 +1004,14 @@ mod tests { firefox_opts.insert("androidDeviceSerial".into(), json!("cheese")); let opts = make_options(firefox_opts).expect("valid firefox options"); - let android_opts = AndroidOptions { - package: "foo.bar".to_owned(), - device_serial: Some("cheese".to_owned()), - ..Default::default() - }; - assert_eq!(opts.android, Some(android_opts)); + assert_eq!( + opts.android.unwrap().device_serial, + Some("cheese".to_string()) + ); } #[test] - fn fx_options_android_serial_invalid() { + 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)); @@ -958,18 +1020,45 @@ mod tests { } #[test] - fn fx_options_android_intent_arguments() { + 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).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).expect("valid firefox options"); - let android_opts = AndroidOptions { - package: "foo.bar".to_owned(), - intent_arguments: Some(vec!["lorem".to_owned(), "ipsum".to_owned()]), - ..Default::default() - }; - assert_eq!(opts.android, Some(android_opts)); + assert_eq!( + opts.android.unwrap().intent_arguments, + Some(vec!["lorem".to_string(), "ipsum".to_string()]) + ); } #[test] diff --git a/src/command.rs b/src/command.rs index f5bc27a..b3ccdb7 100644 --- a/src/command.rs +++ b/src/command.rs @@ -123,21 +123,21 @@ impl<'de> Deserialize<'de> for AddonInstallParameters { struct Base64 { addon: String, temporary: Option, - }; + } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] struct Path { path: String, temporary: Option, - }; + } #[derive(Debug, Deserialize)] #[serde(untagged)] enum Helper { Base64(Base64), Path(Path), - }; + } let params = match Helper::deserialize(deserializer)? { Helper::Path(ref mut data) => AddonInstallParameters { diff --git a/src/main.rs b/src/main.rs index 4dd6c66..ca894e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -130,6 +130,7 @@ enum Operation { log_level: Option, address: SocketAddr, settings: MarionetteSettings, + deprecated_storage_arg: bool, }, } @@ -159,7 +160,8 @@ fn parse_args(app: &mut App) -> ProgramResult { Err(e) => usage!("{}: {}:{}", e, host, port), }; - let android_storage = value_t!(matches, "android_storage", AndroidStorageInput)?; + let android_storage = value_t!(matches, "android_storage", AndroidStorageInput) + .unwrap_or(AndroidStorageInput::Auto); let binary = matches.value_of("binary").map(PathBuf::from); @@ -189,6 +191,7 @@ fn parse_args(app: &mut App) -> ProgramResult { log_level, address, settings, + deprecated_storage_arg: matches.is_present("android_storage"), } }; @@ -204,6 +207,7 @@ fn inner_main(app: &mut App) -> ProgramResult<()> { log_level, address, settings, + deprecated_storage_arg, } => { if let Some(ref level) = log_level { logging::init_with_level(*level).unwrap(); @@ -211,6 +215,10 @@ fn inner_main(app: &mut App) -> ProgramResult<()> { logging::init().unwrap(); } + if deprecated_storage_arg { + warn!("--android-storage argument is deprecated and will be removed soon."); + }; + let handler = MarionetteHandler::new(settings); let listening = webdriver::server::start(address, handler, extension_routes())?; info!("Listening on {}", listening.socket); @@ -325,9 +333,8 @@ fn make_app<'a, 'b>() -> App<'a, 'b> { 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."), + .help("Selects storage location to be used for test data (deprecated)."), ) } diff --git a/src/marionette.rs b/src/marionette.rs index 99a82f3..87657ba 100644 --- a/src/marionette.rs +++ b/src/marionette.rs @@ -309,7 +309,6 @@ impl MarionetteHandler { prefs.insert("devtools.debugger.remote-enabled", Pref::new(true)); prefs.insert("devtools.chrome.enabled", Pref::new(true)); prefs.insert("devtools.debugger.prompt-connection", Pref::new(false)); - prefs.insert("marionette.debugging.clicktostart", Pref::new(true)); } prefs.insert("marionette.log.level", logging::max_level().into()); diff --git a/src/prefs.rs b/src/prefs.rs index 075d0c6..33da0e5 100644 --- a/src/prefs.rs +++ b/src/prefs.rs @@ -120,9 +120,6 @@ lazy_static! { // that may cause unexpected test timeouts. ("idle.lastDailyNotification", Pref::new(-1)), - // Show chrome errors and warnings in the error console - ("javascript.options.showInConsole", Pref::new(true)), - // Disable download and usage of OpenH264, and Widevine plugins ("media.gmp-manager.updateEnabled", Pref::new(false)),