Repository: wix/Detox
Pull request #1276 (2019-04-13 20:45:08)

fix(android): install APK with a file path that needs escaping
<!---
Thank you for contributing!
Before submitting a pull request that implements a new functionality or fixing a major bug,
please open an issue first and propose your solution. This way we can discuss before time is 
spent on a solution that may not work for us.

Please check the correct option in the below list and delete the irrelevant one.
For the second option, please fill the issue where the solution has been discussed.
-->

- [x] This is a small change 

---

**Description:**

Windows users with spaces in their APK path face an issue where the path is broken by spaces. I faced this issue and tried escaping the space, but it turned out the solution was to remove the backslash escapes I added and use quotation marks, like "path". Apostrophes like 'path' don't work.

For reference,
https://android.stackexchange.com/questions/185951/script-to-pull-from-directory-containing-spaces

This fixed the problem for me and I'm now able to run `detox build`.

I considered other places in the code where the path could be wrapped in quotes, but this seemed like the simplest and only place necessary.


noomorph: Can you elaborate about the backslash escapes? Do you mean Detox tries somewhere to escape spaces with backslashes on Windows?

And, in any case, please use `escape` utility methods to work around the edge cases with quotes inside the directory names. You will see the reference in the very top of the file you edited. 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab2b7c19a..498443b72 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,89 @@
 # Change Log
 
+## [12.2.0](https://github.com/wix/detox/tree/12.2.0) (2019-04-12)
+[Full Changelog](https://github.com/wix/detox/compare/12.1.4-screenshots-beta...12.2.0)
+
+**Enhancements**
+
+- Provide API for taking screenshots on demand [\#807](https://github.com/wix/Detox/issues/807)
+
+**Merged Pull Requests**
+
+- feat: device.takeScreenshot\(name\) [\#904](https://github.com/wix/Detox/pull/904) ([noomorph](https://github.com/noomorph))
+
+## [12.1.4-screenshots-beta](https://github.com/wix/detox/tree/12.1.4-screenshots-beta) (2019-04-11)
+[Full Changelog](https://github.com/wix/detox/compare/12.1.4-screenshot...12.1.4-screenshots-beta)
+
+## [12.1.4-screenshot](https://github.com/wix/detox/tree/12.1.4-screenshot) (2019-04-11)
+[Full Changelog](https://github.com/wix/detox/compare/12.1.4...12.1.4-screenshot)
+
+**Fixed Bugs**
+
+- Detox CLI does not error on unknown commands [\#149](https://github.com/wix/Detox/issues/149)
+
+## [12.1.4](https://github.com/wix/detox/tree/12.1.4) (2019-04-11)
+[Full Changelog](https://github.com/wix/detox/compare/12.1.3...12.1.4)
+
+**Enhancements**
+
+- View Hierarchy dump is an impediment to adoption [\#1227](https://github.com/wix/Detox/issues/1227)
+
+**Closed Issues**
+
+- Detox doesn't work with React-Native 0.59 on Android [\#1208](https://github.com/wix/Detox/issues/1208)
+
+**Merged Pull Requests**
+
+- fix: stabilize simulator log plugin [\#1273](https://github.com/wix/Detox/pull/1273) ([noomorph](https://github.com/noomorph))
+- code: lower status of artifact manager errors [\#1271](https://github.com/wix/Detox/pull/1271) ([noomorph](https://github.com/noomorph))
+- hotfix: redundant logs in the folder [\#1268](https://github.com/wix/Detox/pull/1268) ([noomorph](https://github.com/noomorph))
+- fix: e2e test on timezones different from LA [\#1267](https://github.com/wix/Detox/pull/1267) ([noomorph](https://github.com/noomorph))
+- fix: simulator log plugin issue [\#1261](https://github.com/wix/Detox/pull/1261) ([noomorph](https://github.com/noomorph))
+
+## [12.1.3](https://github.com/wix/detox/tree/12.1.3) (2019-04-04)
+[Full Changelog](https://github.com/wix/detox/compare/12.1.2...12.1.3)
+
+**Enhancements**
+
+- Migrate uiautomator to AndroidX [\#1235](https://github.com/wix/Detox/issues/1235)
+- Introduce proguard minification into test app [\#1216](https://github.com/wix/Detox/issues/1216)
+
+**Closed Issues**
+
+- CircleCI: Reports test failure but command exits with success code \(0\) [\#1252](https://github.com/wix/Detox/issues/1252)
+- Getting an error when running Detox - yargs [\#1250](https://github.com/wix/Detox/issues/1250)
+
+**Merged Pull Requests**
+
+- \(Android\) Migrate UIAutomator to AndroidX \(\#1235\) [\#1251](https://github.com/wix/Detox/pull/1251) ([d4vidi](https://github.com/d4vidi))
+- \(Android\) Introduce Proguard all-around [\#1249](https://github.com/wix/Detox/pull/1249) ([d4vidi](https://github.com/d4vidi))
+- Migrate uiautomator to AndroidX [\#1242](https://github.com/wix/Detox/pull/1242) ([leanmazzu](https://github.com/leanmazzu))
+
+## [12.1.2](https://github.com/wix/detox/tree/12.1.2) (2019-04-02)
+[Full Changelog](https://github.com/wix/detox/compare/12.1.1...12.1.2)
+
+**Enhancements**
+
+- feat: specify resuing existing app in Detox.init\(\) params [\#1245](https://github.com/wix/Detox/pull/1245) ([Ponyets](https://github.com/Ponyets))
+
+**Fixed Bugs**
+
+- `detox test` fails without status code [\#1247](https://github.com/wix/Detox/issues/1247)
+- iOS keyWindow.rootViewController would be null when app is driven by Detox. [\#1230](https://github.com/wix/Detox/issues/1230)
+- Taps do not register on iOS simulator [\#1224](https://github.com/wix/Detox/issues/1224)
+
+**Closed Issues**
+
+- How to pass -disableRNTestingOverride 1 to tests? [\#1243](https://github.com/wix/Detox/issues/1243)
+- Error: Can't find a simulator to match with "iPhone 7" [\#1241](https://github.com/wix/Detox/issues/1241)
+- React Navigation v3.3.1 SafeAreaView update breaks scroll\('down'\) \(iOS only\) [\#1196](https://github.com/wix/Detox/issues/1196)
+
+**Merged Pull Requests**
+
+- Exit process with error status code when a cli command fails [\#1248](https://github.com/wix/Detox/pull/1248) ([tancredi](https://github.com/tancredi))
+- Fix keyWindow.rootViewController nullability on iOS [\#1231](https://github.com/wix/Detox/pull/1231) ([Ponyets](https://github.com/Ponyets))
+- Fix main.jsbundle does not exist [\#1190](https://github.com/wix/Detox/pull/1190) ([msand](https://github.com/msand))
+
 ## [12.1.1](https://github.com/wix/detox/tree/12.1.1) (2019-03-27)
 [Full Changelog](https://github.com/wix/detox/compare/12.1.0...12.1.1)
 
@@ -244,17 +328,17 @@
 - Android: allow test packages \(built for emulator by Android Studio\) to be installed via adb [\#1015](https://github.com/wix/Detox/pull/1015) ([Annihil](https://github.com/Annihil))
 
 ## [9.1.2](https://github.com/wix/detox/tree/9.1.2) (2018-11-16)
-[Full Changelog](https://github.com/wix/detox/compare/v9.0.8...9.1.2)
+[Full Changelog](https://github.com/wix/detox/compare/9.0.8...9.1.2)
 
 **Enhancements**
 
 - New Actions: `tapBackspaceKey` and `tapReturnKey` [\#1044](https://github.com/wix/Detox/pull/1044) ([michaelgmcd](https://github.com/michaelgmcd))
 
-## [v9.0.8](https://github.com/wix/detox/tree/v9.0.8) (2018-11-15)
-[Full Changelog](https://github.com/wix/detox/compare/9.0.8...v9.0.8)
-
 ## [9.0.8](https://github.com/wix/detox/tree/9.0.8) (2018-11-15)
-[Full Changelog](https://github.com/wix/detox/compare/9.0.7...9.0.8)
+[Full Changelog](https://github.com/wix/detox/compare/v9.0.8...9.0.8)
+
+## [v9.0.8](https://github.com/wix/detox/tree/v9.0.8) (2018-11-15)
+[Full Changelog](https://github.com/wix/detox/compare/9.0.7...v9.0.8)
 
 **Enhancements**
 
@@ -387,7 +471,7 @@
 - fix: ADB.pidof integration issue with grep and CRLF edge case [\#927](https://github.com/wix/Detox/pull/927) ([noomorph](https://github.com/noomorph))
 
 ## [9.0.2](https://github.com/wix/detox/tree/9.0.2) (2018-09-24)
-[Full Changelog](https://github.com/wix/detox/compare/9.0.1...9.0.2)
+[Full Changelog](https://github.com/wix/detox/compare/9.0.0...9.0.2)
 
 **Enhancements**
 
@@ -412,11 +496,11 @@
 - Fix typo [\#928](https://github.com/wix/Detox/pull/928) ([tatane616](https://github.com/tatane616))
 - \[Android\] Remove @override annotation to support RN 0.53-0.55 on detox 9.0 [\#922](https://github.com/wix/Detox/pull/922) ([JamesWatling](https://github.com/JamesWatling))
 
-## [9.0.1](https://github.com/wix/detox/tree/9.0.1) (2018-08-31)
-[Full Changelog](https://github.com/wix/detox/compare/9.0.0...9.0.1)
-
 ## [9.0.0](https://github.com/wix/detox/tree/9.0.0) (2018-08-31)
-[Full Changelog](https://github.com/wix/detox/compare/8.2.3...9.0.0)
+[Full Changelog](https://github.com/wix/detox/compare/9.0.1...9.0.0)
+
+## [9.0.1](https://github.com/wix/detox/tree/9.0.1) (2018-08-31)
+[Full Changelog](https://github.com/wix/detox/compare/8.2.3...9.0.1)
 
 **Enhancements**
 
diff --git a/detox/ios/Detox/DTXMethodInvocation.m b/detox/ios/Detox/DTXMethodInvocation.m
index 7b5654949..fc617beeb 100644
--- a/detox/ios/Detox/DTXMethodInvocation.m
+++ b/detox/ios/Detox/DTXMethodInvocation.m
@@ -63,7 +63,7 @@
     }
     if ([type isEqualToString:@"CGFloat"])
     {
-        if (![value isKindOfClass:[NSNumber class]]) return nil;
+        if (![value isKindOfClass:[NSNumber class]]) return @(NAN);
         CGFloat v = [value doubleValue];
         return [NSNumber numberWithDouble:v];
     }
diff --git a/detox/package.json b/detox/package.json
index daf2bc483..dd1d2f6f6 100644
--- a/detox/package.json
+++ b/detox/package.json
@@ -1,7 +1,7 @@
 {
   "name": "detox",
   "description": "E2E tests and automation for mobile",
-  "version": "12.2.0",
+  "version": "12.2.1",
   "bin": {
     "detox": "local-cli/cli.js"
   },
diff --git a/detox/src/artifacts/screenshot/ScreenshotArtifactPlugin.js b/detox/src/artifacts/screenshot/ScreenshotArtifactPlugin.js
index f5db06c9b..4d03cb0b8 100644
--- a/detox/src/artifacts/screenshot/ScreenshotArtifactPlugin.js
+++ b/detox/src/artifacts/screenshot/ScreenshotArtifactPlugin.js
@@ -12,7 +12,8 @@ class ScreenshotArtifactPlugin extends TwoSnapshotsPerTestPlugin {
 
     const takeScreenshots = argparse.getArgValue('take-screenshots');
 
-    this.enabled = takeScreenshots && takeScreenshots !== 'none';
+    this.enabled = takeScreenshots === 'all' || takeScreenshots === 'failing';
+    this.allowManualScreenshots = !takeScreenshots || takeScreenshots === 'manual';
     this.keepOnlyFailedTestsArtifacts = takeScreenshots === 'failing';
     this._warnAboutNotImplementedMode = _.once(this._warnAboutNotImplementedMode.bind(this));
   }
@@ -39,11 +40,13 @@ class ScreenshotArtifactPlugin extends TwoSnapshotsPerTestPlugin {
   }
 
   async onUserAction({ type, options }) {
-    switch (type) {
-      case 'takeScreenshot':
-        return this.keepOnlyFailedTestsArtifacts
-          ? this._warnAboutNotImplementedMode()
-          : this.takeSnapshot(options.name);
+    if (this.enabled || this.allowManualScreenshots) {
+      switch (type) {
+        case 'takeScreenshot':
+          return this.keepOnlyFailedTestsArtifacts
+            ? this._warnAboutNotImplementedMode()
+            : this.takeSnapshot(options.name);
+      }
     }
   }
 
diff --git a/detox/src/devices/android/AAPT.js b/detox/src/devices/android/AAPT.js
index 86ad4730b..de1817623 100644
--- a/detox/src/devices/android/AAPT.js
+++ b/detox/src/devices/android/AAPT.js
@@ -2,6 +2,7 @@ const _ = require('lodash');
 const Environment = require('../../utils/environment');
 const path = require('path');
 const exec = require('../../utils/exec').execWithRetriesAndLogs;
+const escape = require('../../utils/pipeCommands').escape.inQuotedString;
 const egrep = require('../../utils/pipeCommands').search.fragment;
 const fsext = require('../../utils/fsext');
 
@@ -23,7 +24,7 @@ class AAPT {
   async getPackageName(apkPath) {
     await this._prepare();
     const process = await exec(
-      `${this.aaptBin} dump badging "${apkPath}" | ${egrep("package: name=")}`,
+      `${this.aaptBin} dump badging "${escape(apkPath)}" | ${egrep("package: name=")}`,
       undefined, undefined, 1
     );
 
diff --git a/detox/src/devices/android/ADB.js b/detox/src/devices/android/ADB.js
index 1ee245400..0e9393cf6 100644
--- a/detox/src/devices/android/ADB.js
+++ b/detox/src/devices/android/ADB.js
@@ -102,6 +102,8 @@ class ADB {
   }
 
   async install(deviceId, apkPath) {
+    apkPath = `"${escape.inQuotedString(apkPath)}"`;
+    
     const apiLvl = await this.apiLevel(deviceId);
 
     let childProcess;
diff --git a/detox/src/ios/expect.js b/detox/src/ios/expect.js
index 4c4304bb3..8cf39c000 100644
--- a/detox/src/ios/expect.js
+++ b/detox/src/ios/expect.js
@@ -121,9 +121,9 @@ class ClearTextAction extends Action {
 }
 
 class ScrollAmountAction extends Action {
-  constructor(direction, amount) {
+  constructor(direction, amount, startScrollX = NaN, startScrollY = NaN) {
     super();
-    this._call = invoke.callDirectly(GreyActions.actionForScrollInDirectionAmount(direction, amount));
+    this._call = invoke.callDirectly(GreyActions.actionForScrollInDirectionAmountXOriginStartPercentageYOriginStartPercentage(direction, amount, startScrollX, startScrollY));
   }
 }
 
@@ -268,10 +268,10 @@ class WaitForActionInteraction extends Interaction {
     this._call = GreyInteraction.assertWithMatcher(invoke.callDirectly(_interactionCall), callThunk(this._originalMatcher));
     await this.execute();
   }
-  async scroll(amount, direction = 'down') {
+  async scroll(amount, direction = 'down', startScrollX, startScrollY) {
     // override the user's element selection with an extended matcher that looks for UIScrollView children
     this._searchMatcher = this._searchMatcher._extendToDescendantScrollViews();
-    await this._execute(new ScrollAmountAction(direction, amount));
+    await this._execute(new ScrollAmountAction(direction, amount, startScrollX, startScrollY));
   }
 }
 
@@ -320,10 +320,10 @@ class Element {
   async pinchWithAngle(direction, speed = 'slow', angle = 0) {
     return await new ActionInteraction(this, new PinchAction(direction, speed, angle)).execute();
   }
-  async scroll(amount, direction = 'down') {
+  async scroll(amount, direction = 'down', startScrollX, startScrollY) {
     // override the user's element selection with an extended matcher that looks for UIScrollView children
     this._selectElementWithMatcher(this._originalMatcher._extendToDescendantScrollViews());
-    return await new ActionInteraction(this, new ScrollAmountAction(direction, amount)).execute();
+    return await new ActionInteraction(this, new ScrollAmountAction(direction, amount, startScrollX, startScrollY)).execute();
   }
   async scrollTo(edge) {
     // override the user's element selection with an extended matcher that looks for UIScrollView children
diff --git a/docs/APIRef.ActionsOnElement.md b/docs/APIRef.ActionsOnElement.md
index 9ef5eef32..8aefb1c2a 100644
--- a/docs/APIRef.ActionsOnElement.md
+++ b/docs/APIRef.ActionsOnElement.md
@@ -96,17 +96,21 @@ Clear text from a text field.
 await element(by.id('textField')).clearText();
 ```
 
-### `scroll(pixels, direction)`
+### `scroll(pixels, direction, startPositionX=NaN, startPositionY=NaN)`
+
 Scroll amount of pixels.<br>
-pixels - independent device pixels.<br>
-direction - left/right/top/bottom
+pixels - independent device pixels<br>
+direction - left/right/top/bottom<br>
+startPositionX - The X starting scroll position, in percentage; valid input: (0.0, 1.0), `NaN`; default: `NaN`—Choose the best value<br>
+startPositionX - The Y starting scroll position, in percentage; valid input: (0.0, 1.0), `NaN`; default: `NaN`—Choose the best value<br>
 
 ```js
-await element(by.id('scrollView')).scroll(100, 'down');
+await element(by.id('scrollView')).scroll(100, 'down', NaN, 0.85);
 await element(by.id('scrollView')).scroll(100, 'up');
 ```
 
 ### `scrollTo(edge)`
+
 Scroll to edge.
 
 edge - left/right/top/bottom
diff --git a/docs/APIRef.Artifacts.md b/docs/APIRef.Artifacts.md
index d664a435a..491788d87 100644
--- a/docs/APIRef.Artifacts.md
+++ b/docs/APIRef.Artifacts.md
@@ -16,7 +16,9 @@ Artifacts are disabled by default. Two things are required to enable them:
 
 * To record `.log` files, add `--record-logs all` (or `--record-logs failing`, if you want to keep logs only for failing tests).
 * To record `.mp4` test run videos, add `--record-videos all` (or `--record-videos failing`, if you want to keep video recordings only for failing tests).
-* To record `.png` screenshots before and after each test, add `--take-screenshots all` (or `--take-screenshots failing`, if you want to keep only screenshots of failing tests).
+* To take `.png` screenshots before and after each test, add `--take-screenshots all` (or `--take-screenshots failing`, if you want to keep only screenshots of failing tests).  
+Alternatively, you might leverage `await device.takeScreenshot('your screenshot name')` API to have manual control over taking the screenshots.  
+To disable screenshots subsystem forcibly, use `--take-screenshots none`.
 * To change artifacts root directory location (by default it is `./artifacts`), add `--artifacts-location <path>`.  
 **NOTE:** There is a slightly obscure convention. If you want to create automatically a subdirectory with timestamp and configuration name (to avoid file overwrites upon consquent re-runs), specify a path to directory that does not end with a slash. Otherwise, if you want to put artifacts straight to the specified directory (in a case where you make a single run only, e.g. on CI), add a slash (or a backslash) to the end.
 
diff --git a/docs/APIRef.DetoxCLI.md b/docs/APIRef.DetoxCLI.md
index eaeb7729c..ae5609b00 100644
--- a/docs/APIRef.DetoxCLI.md
+++ b/docs/APIRef.DetoxCLI.md
@@ -74,7 +74,7 @@ Initiating your test suite. <sup>[[1]](#notice-passthrough)</sup>
 | -d, --debug-synchronization \<value\>         | When an action/expectation takes a significant amount time use this option to print device synchronization status. The status will be printed if the action takes more than [value]ms to complete |
 | -a, --artifacts-location \<path\>             | Artifacts (logs, screenshots, etc) root directory.<sup>[[2]](#notice-artifacts)</sup> |
 | --record-logs [failing/all/none]              | Save logs during each test to artifacts directory. Pass "failing" to save logs of failing tests only. The default value is **none**. |
-| --take-screenshots [failing/all/none]         | Save screenshots before and after each test to artifacts directory. Pass "failing" to save screenshots of failing tests only. The default value is **none**. |
+| --take-screenshots [manual/failing/all/none]  | Save screenshots before and after each test to artifacts directory. Pass "failing" to save screenshots of failing tests only. The default value is **manual**. |
 | --record-videos [failing/all/none]            | Save screen recordings of each test to artifacts directory. Pass "failing" to save recordings of failing tests only. The default value is **none**. |
 | -r, --reuse                                   | Reuse existing installed app (do not delete + reinstall) for a faster run. |
 | -u, --cleanup                                 | Shutdown simulator when test is over, useful for CI scripts, to make sure detox exists cleanly with no residue |
diff --git a/docs/APIRef.DeviceObjectAPI.md b/docs/APIRef.DeviceObjectAPI.md
index c4a265b64..4d02e179a 100644
--- a/docs/APIRef.DeviceObjectAPI.md
+++ b/docs/APIRef.DeviceObjectAPI.md
@@ -327,7 +327,7 @@ describe('Menu items', () => {
 * If the test passes, the screenshot will be put to `<artifacts-location>/✓ Menu items should have Logout/tap on menu.png`.
 * If the test fails, the screenshot will be put to `<artifacts-location>/✗ Menu items should have Logout/tap on menu.png`.
 
-> NOTE: at the moment, taking screenshots on-demand is supported **only** in `--take-screenshots all` mode.
+> NOTE: At the moment, taking screenshots on-demand in `--take-screenshots failing` mode is not yet implemented.
 
 ### `device.pressBack()` **Android Only**
 Simulate press back button.
diff --git a/examples/demo-native-android/package.json b/examples/demo-native-android/package.json
index e67881911..cdda96c89 100644
--- a/examples/demo-native-android/package.json
+++ b/examples/demo-native-android/package.json
@@ -1,6 +1,6 @@
 {
   "name": "detox-demo-native-android",
-  "version": "12.2.0",
+  "version": "12.2.1",
   "private": true,
   "scripts": {
     "packager": "react-native start",
@@ -8,7 +8,7 @@
     "e2e": "mocha e2e --opts ./e2e/mocha.opts"
   },
   "devDependencies": {
-    "detox": "^12.2.0",
+    "detox": "^12.2.1",
     "mocha": "^4.0.0"
   },
   "detox": {}
diff --git a/examples/demo-native-ios/package.json b/examples/demo-native-ios/package.json
index 45631d054..cc01fa85b 100644
--- a/examples/demo-native-ios/package.json
+++ b/examples/demo-native-ios/package.json
@@ -1,9 +1,9 @@
 {
 	"name": "detox-demo-native-ios",
-	"version": "12.2.0",
+	"version": "12.2.1",
 	"private": true,
 	"devDependencies": {
-		"detox": "^12.2.0",
+		"detox": "^12.2.1",
 		"mocha": "^4.0.0"
 	},
 	"detox": {
diff --git a/examples/demo-react-native-jest/package.json b/examples/demo-react-native-jest/package.json
index 918a175f0..2bdb82c6b 100644
--- a/examples/demo-react-native-jest/package.json
+++ b/examples/demo-react-native-jest/package.json
@@ -1,6 +1,6 @@
 {
   "name": "demo-react-native-jest",
-  "version": "12.2.0",
+  "version": "12.2.1",
   "private": true,
   "scripts": {
     "start": "node node_modules/react-native/local-cli/cli.js start",
@@ -14,7 +14,7 @@
   "devDependencies": {
     "babel-jest": "21.2.0",
     "babel-preset-react-native": "4.0.0",
-    "detox": "^12.2.0",
+    "detox": "^12.2.1",
     "jest": "21.2.1",
     "react-test-renderer": "16.0.0-beta.5"
   },
diff --git a/examples/demo-react-native/package.json b/examples/demo-react-native/package.json
index 57de84fba..642fc84c1 100644
--- a/examples/demo-react-native/package.json
+++ b/examples/demo-react-native/package.json
@@ -1,6 +1,6 @@
 {
   "name": "example",
-  "version": "12.2.0",
+  "version": "12.2.1",
   "private": true,
   "scripts": {
     "start": "react-native start"
@@ -10,7 +10,7 @@
     "react-native": "0.56.0"
   },
   "devDependencies": {
-    "detox": "^12.2.0",
+    "detox": "^12.2.1",
     "mocha": "^4.0.1"
   },
   "detox": {
diff --git a/lerna.json b/lerna.json
index 922e45ebb..ffc3ad464 100644
--- a/lerna.json
+++ b/lerna.json
@@ -10,7 +10,7 @@
     "examples/demo-react-native-jest",
     "generation"
   ],
-  "version": "12.2.0",
+  "version": "12.2.1",
   "npmClient": "npm",
   "command": {
     "publish": {


Test patch:
diff --git a/detox/local-cli/test.js b/detox/local-cli/test.js
index d04b54d09..16f6d6ff8 100644
--- a/detox/local-cli/test.js
+++ b/detox/local-cli/test.js
@@ -92,8 +92,8 @@ module.exports.builder = {
   },
   'take-screenshots': {
     group: 'Debugging:',
-    choices: ['failing', 'all', 'none'],
-    default: 'none',
+    choices: ['manual', 'failing', 'all', 'none'],
+    default: 'manual',
     describe:
       'Save screenshots before and after each test to artifacts directory. Pass "failing" to save screenshots of failing tests only.'
   },
@@ -181,6 +181,13 @@ module.exports.handler = async function test(program) {
     return fallback;
   }
 
+  function hasCustomValue(key) {
+    const value = program[key];
+    const metadata = module.exports.builder[key];
+
+    return (value !== metadata.default);
+  }
+
   function runMocha() {
     if (program.workers !== 1) {
       log.warn('Can not use -w, --workers. Parallel test execution is only supported with iOS and Jest');
@@ -198,9 +205,9 @@ module.exports.handler = async function test(program) {
       (platform ? `--grep ${getPlatformSpecificString()} --invert` : ''),
       (program.headless ? `--headless` : ''),
       (program.gpu ? `--gpu ${program.gpu}` : ''),
-      (program.recordLogs ? `--record-logs ${program.recordLogs}` : ''),
-      (program.takeScreenshots ? `--take-screenshots ${program.takeScreenshots}` : ''),
-      (program.recordVideos ? `--record-videos ${program.recordVideos}` : ''),
+      (hasCustomValue('record-logs') ? `--record-logs ${program.recordLogs}` : ''),
+      (hasCustomValue('take-screenshots') ? `--take-screenshots ${program.takeScreenshots}` : ''),
+      (hasCustomValue('record-videos') ? `--record-videos ${program.recordVideos}` : ''),
       (program.artifactsLocation ? `--artifacts-location "${program.artifactsLocation}"` : ''),
       (program.deviceName ? `--device-name "${program.deviceName}"` : ''),
       testFolder,
diff --git a/detox/local-cli/test.test.js b/detox/local-cli/test.test.js
index 861b28b22..e659e19be 100644
--- a/detox/local-cli/test.test.js
+++ b/detox/local-cli/test.test.js
@@ -38,7 +38,7 @@ describe('test', () => {
       await callCli('./test', 'test');
 
       expect(mockExec).toHaveBeenCalledWith(
-        expect.stringContaining(`${normalize('node_modules/.bin/mocha')} --opts e2e/mocha.opts --configuration only --grep :ios: --invert --record-logs none --take-screenshots none --record-videos none --artifacts-location "${normalize('artifacts/only.')}`),
+        expect.stringContaining(`${normalize('node_modules/.bin/mocha')} --opts e2e/mocha.opts --configuration only --grep :ios: --invert --artifacts-location "${normalize('artifacts/only.')}`),
         expect.anything()
       );
 
@@ -88,7 +88,7 @@ describe('test', () => {
           env: expect.objectContaining({
             configuration: 'only',
             recordLogs: 'none',
-            takeScreenshots: 'none',
+            takeScreenshots: 'manual',
             recordVideos: 'none',
             artifactsLocation: expect.stringContaining(normalize('artifacts/only.')),
           }),
@@ -144,7 +144,7 @@ describe('test', () => {
     }
     expect(mockExec).toHaveBeenCalledWith(
       expect.stringContaining(
-        `${normalize('node_modules/.bin/mocha')} --opts e2e/mocha.opts --configuration only --debug-synchronization 3000 --grep :ios: --invert --record-logs none --take-screenshots none --record-videos none --artifacts-location "${normalize('artifacts/only.')}`
+        `${normalize('node_modules/.bin/mocha')} --opts e2e/mocha.opts --configuration only --debug-synchronization 3000 --grep :ios: --invert --artifacts-location "${normalize('artifacts/only.')}`
       ),
       expect.anything()
     );
diff --git a/detox/src/artifacts/templates/plugin/TwoSnapshotsPerTestPlugin.js b/detox/src/artifacts/templates/plugin/TwoSnapshotsPerTestPlugin.js
index c6e46c622..4052bf047 100644
--- a/detox/src/artifacts/templates/plugin/TwoSnapshotsPerTestPlugin.js
+++ b/detox/src/artifacts/templates/plugin/TwoSnapshotsPerTestPlugin.js
@@ -11,14 +11,14 @@ class TwoSnapshotsPerTestPlugin extends ArtifactPlugin {
 
   async onBeforeEach(testSummary) {
     await super.onBeforeEach(testSummary);
-    await this.takeSnapshot('beforeEach');
+    await this._takeAutomaticSnapshot('beforeEach');
   }
 
   async onAfterEach(testSummary) {
     await super.onAfterEach(testSummary);
 
     if (this.shouldKeepArtifactOfTest(testSummary)) {
-      await this.takeSnapshot('afterEach');
+      await this._takeAutomaticSnapshot('afterEach');
       this.startSavingSnapshot('beforeEach');
       this.startSavingSnapshot('afterEach');
     } else {
@@ -49,14 +49,16 @@ class TwoSnapshotsPerTestPlugin extends ArtifactPlugin {
     delete this.snapshots.afterEach;
   }
 
+  async _takeAutomaticSnapshot(name) {
+    if (this.enabled) {
+      await this.takeSnapshot(name);
+    }
+  }
+
   /***
    * @protected
    */
   async takeSnapshot(name) {
-    if (!this.enabled) {
-      return;
-    }
-
     const snapshot = this.snapshots[name] = this.createTestArtifact();
     await snapshot.start();
     await snapshot.stop();
diff --git a/detox/src/devices/android/ADB.test.js b/detox/src/devices/android/ADB.test.js
index abd7f230d..964244c6f 100644
--- a/detox/src/devices/android/ADB.test.js
+++ b/detox/src/devices/android/ADB.test.js
@@ -31,8 +31,16 @@ describe('ADB', () => {
   });
 
   it(`install`, async () => {
-    await adb.install('path/to/app');
-    expect(exec).toHaveBeenCalledTimes(2);
+    await adb.install('emulator-5556', 'path inside "quotes" to/app');
+
+    expect(exec).toHaveBeenCalledWith(
+      expect.stringContaining('adb -s emulator-5556 shell "getprop ro.build.version.sdk"'),
+      undefined, undefined, 1
+    );
+
+    expect(exec).toHaveBeenCalledWith(
+      expect.stringContaining('adb -s emulator-5556 install -rg "path inside \\"quotes\\" to/app"'),
+      undefined, undefined, 1);
   });
 
   it(`uninstall`, async () => {
diff --git a/detox/test/package.json b/detox/test/package.json
index cff3e9915..7bd3087f2 100644
--- a/detox/test/package.json
+++ b/detox/test/package.json
@@ -1,6 +1,6 @@
 {
   "name": "detox-test",
-  "version": "12.2.0",
+  "version": "12.2.1",
   "private": true,
   "scripts": {
     "test": ":",
@@ -19,14 +19,14 @@
     "react-native": "0.56.0"
   },
   "devDependencies": {
-    "detox": "^12.2.0",
+    "detox": "^12.2.1",
     "express": "^4.15.3",
     "jest": "^22.3.0",
     "lodash": "^4.14.1",
     "mocha": "^5.0.0"
   },
   "detox": {
-    "specs": "",
+    "specs": "e2e",
     "test-runner": "mocha",
     "__session": {
       "server": "ws://localhost:8099",