Repository: wix/Detox
Pull request #1333 (2019-04-25 08:30:54)

feat: record network in Detox Instruments artifacts
- [x] This is a small change 

---

**Description:**

Adds network requests to `*.dtxrec` recordings produced by Detox artifacts subsystem.

LeoNatan: You wanted to add the JS events too no?

LeoNatan: `@property(nonatomic, readonly) BOOL recordInternalReactNativeEvents;`
LeoNatan: Add that property and set it to `YES`.
LeoNatan: @noomorph  ^
noomorph: @LeoNatan , thanks for the reminder! 👍 

Patch:
diff --git a/detox/ios/Detox/DetoxInstrumentsManager.m b/detox/ios/Detox/DetoxInstrumentsManager.m
index 491785ab2..c59cd75f5 100644
--- a/detox/ios/Detox/DetoxInstrumentsManager.m
+++ b/detox/ios/Detox/DetoxInstrumentsManager.m
@@ -16,7 +16,11 @@ DTX_CREATE_LOG_PREFIX(DetoxInstrumentsManager, @"🥶")
 
 @property (class, nonatomic, strong, readonly) id defaultProfilingConfiguration;
 @property (nonatomic, readwrite) NSTimeInterval samplingInterval;
+@property (nonatomic, readwrite) BOOL recordEvents;
 @property (nonatomic, readwrite) BOOL recordNetwork;
+@property (nonatomic, readwrite) BOOL recordLocalhostNetwork;
+@property (nonatomic, readwrite) BOOL profileReactNative;
+@property (nonatomic, readwrite) BOOL recordInternalReactNativeEvents;
 @property (nonatomic, readwrite) BOOL recordThreadInformation;
 @property (nonatomic, readwrite) BOOL collectStackTraces;
 @property (nonatomic, readwrite) BOOL symbolicateStackTraces;
@@ -146,7 +150,15 @@ static void (*__DTXProfilerMarkEvent)(NSString* category, NSString* name, __DTXE
 	[config setRecordingFileURL:URL];
 	
 	//TODO: Finalize the actual config for Detox perf recording.
-	[config setRecordNetwork:NO];
+	[config setRecordEvents:YES];
+	[config setProfileReactNative:YES];
+	if ([config respondsToSelector:@selector(setRecordInternalReactNativeEvents:)])
+	{
+		[config setRecordInternalReactNativeEvents:YES];
+	}
+
+	[config setRecordNetwork:YES];
+	[config setRecordLocalhostNetwork:YES];
 	[config setRecordThreadInformation:YES];
 	[config setCollectStackTraces:YES];
 	[config setSymbolicateStackTraces:YES];
diff --git a/detox/package.json b/detox/package.json
index 157440a06..69e31e774 100644
--- a/detox/package.json
+++ b/detox/package.json
@@ -1,7 +1,7 @@
 {
   "name": "detox",
   "description": "E2E tests and automation for mobile",
-  "version": "12.4.1",
+  "version": "12.6.0",
   "bin": {
     "detox": "local-cli/cli.js"
   },
@@ -23,6 +23,7 @@
     "unit": "jest --coverage --verbose",
     "pretest": "npm run lint",
     "test": "npm run unit",
+    "posttest": "cp coverage/lcov.info coverage/unit.lcov",
     "unit:watch": "jest --watch",
     "prepublish": "npm run build",
     "postinstall": "node scripts/postinstall.js"
diff --git a/detox/src/artifacts/instruments/SimulatorInstrumentsPlugin.js b/detox/src/artifacts/instruments/SimulatorInstrumentsPlugin.js
index 5f9c5e987..2f0f59c40 100644
--- a/detox/src/artifacts/instruments/SimulatorInstrumentsPlugin.js
+++ b/detox/src/artifacts/instruments/SimulatorInstrumentsPlugin.js
@@ -10,8 +10,7 @@ class SimulatorInstrumentsPlugin extends WholeTestRecorderPlugin {
     super(config);
 
     this.client = config.client;
-    this.enabled = false;
-    this.shouldRecord = argparse.getArgValue('record-performance') === 'all';
+    this.enabled = argparse.getArgValue('record-performance') === 'all';
   }
 
   async onBeforeUninstallApp(event) {
@@ -38,16 +37,15 @@ class SimulatorInstrumentsPlugin extends WholeTestRecorderPlugin {
   async onBeforeLaunchApp(event) {
     await super.onBeforeLaunchApp(event);
 
-    if (!this.enabled && this.shouldRecord) {
-      const isInstalled = await this._assertDetoxInstrumentsInstalled(event.launchArgs['-instrumentsPath']);
-      this.enabled = this.shouldRecord = isInstalled;
-    }
-
     const isInsideRunningTest = !!this.context.testSummary;
     if (this.enabled && isInsideRunningTest && !this.testRecording) {
       this.testRecording = this.createTrackedTestRecording();
     }
 
+    if (process.env.DETOX_INSTRUMENTS_PATH) {
+      event.launchArgs['-instrumentsPath'] = process.env.DETOX_INSTRUMENTS_PATH;
+    }
+
     if (this.testRecording) {
       event.launchArgs['-recordingPath'] = this.testRecording.temporaryRecordingPath;
     }
@@ -61,22 +59,6 @@ class SimulatorInstrumentsPlugin extends WholeTestRecorderPlugin {
     }
   }
 
-  async _assertDetoxInstrumentsInstalled(customInstrumentsPath) {
-    const instrumentsPath = customInstrumentsPath || SimulatorInstrumentsPlugin.DEFAULT_INSTRUMENTS_PATH;
-    if (await fs.exists(instrumentsPath)) {
-      return true;
-    }
-
-    const hint = customInstrumentsPath
-      ? `Please verify that -instrumentsPath argument points to the existing Detox Instruments installation.`
-      : `To enable recording performance profiles, please follow: https://github.com/wix/DetoxInstruments#installation`;
-
-    log.warn({ event: 'INSTRUMENTS_NOT_FOUND' },
-      `Failed to find Detox Instruments app at path: ${instrumentsPath}\n${hint}`);
-
-    return false;
-  }
-
   createTestRecording() {
     return new SimulatorInstrumentsRecording({
       client: this.client,
@@ -89,6 +71,4 @@ class SimulatorInstrumentsPlugin extends WholeTestRecorderPlugin {
   }
 }
 
-SimulatorInstrumentsPlugin.DEFAULT_INSTRUMENTS_PATH = process.env.DETOX_INSTRUMENTS_PATH || '/Applications/Detox Instruments.app';
-
 module.exports = SimulatorInstrumentsPlugin;
diff --git a/detox/src/artifacts/instruments/SimulatorInstrumentsRecording.js b/detox/src/artifacts/instruments/SimulatorInstrumentsRecording.js
index 9cf45cd49..3558de042 100644
--- a/detox/src/artifacts/instruments/SimulatorInstrumentsRecording.js
+++ b/detox/src/artifacts/instruments/SimulatorInstrumentsRecording.js
@@ -1,3 +1,4 @@
+const _ = require('lodash');
 const log = require('../../utils/logger').child({ __filename });
 const Artifact = require('../templates/artifact/Artifact');
 
@@ -33,7 +34,10 @@ class SimulatorInstrumentsRecording extends Artifact {
   }
 
   async doSave(artifactPath) {
-    await Artifact.moveTemporaryFile(log, this.temporaryRecordingPath, artifactPath);
+    const success = await Artifact.moveTemporaryFile(log, this.temporaryRecordingPath, artifactPath);
+    if (!success) {
+      SimulatorInstrumentsRecording.hintAboutDetoxInstruments();
+    }
   }
 
   async doDiscard() {
@@ -45,4 +49,14 @@ class SimulatorInstrumentsRecording extends Artifact {
   }
 }
 
+SimulatorInstrumentsRecording.hintAboutDetoxInstruments = _.once(() => {
+  log.warn(`Make sure either:
+1. You have installed Detox Instruments:
+   https://github.com/wix/DetoxInstruments#installation 
+2. You have integrated Detox Instruments in your app:
+   https://github.com/wix/DetoxInstruments/blob/master/Documentation/XcodeIntegrationGuide.md 
+3. You have set the environment variable with your custom Detox Instruments location:
+   export DETOX_INSTRUMENTS_PATH="/path/to/Detox Instruments.app"`);
+});
+
 module.exports = SimulatorInstrumentsRecording;
diff --git a/detox/src/artifacts/templates/artifact/Artifact.js b/detox/src/artifacts/templates/artifact/Artifact.js
index ace87db75..30d4a30fd 100644
--- a/detox/src/artifacts/templates/artifact/Artifact.js
+++ b/detox/src/artifacts/templates/artifact/Artifact.js
@@ -106,8 +106,10 @@ class Artifact {
     if (await fs.exists(source)) {
       logger.debug({ event: 'MOVE_FILE' }, `moving "${source}" to ${destination}`);
       await fs.move(source, destination);
+      return true;
     } else {
-      logger.error({ event: 'MOVE_FILE_ERROR'} , `did not find temporary file: ${source}`);
+      logger.warn({ event: 'MOVE_FILE_MISSING'} , `did not find temporary file: ${source}`);
+      return false;
     }
   }
 }
diff --git a/detox/src/client/Client.js b/detox/src/client/Client.js
index e00bad085..60ea30217 100644
--- a/detox/src/client/Client.js
+++ b/detox/src/client/Client.js
@@ -1,3 +1,4 @@
+const _ = require('lodash');
 const AsyncWebSocket = require('./AsyncWebSocket');
 const actions = require('./actions/actions');
 const argparse = require('../utils/argparse');
@@ -93,7 +94,8 @@ class Client {
       await this.sendAction(new actions.Invoke(invocation));
     } catch (err) {
       this.successfulTestRun = false;
-      potentialError.message = err;
+
+      potentialError.message = _.isError(err) ? err.message : String(err);
       throw potentialError;
     }
     clearTimeout(this.slowInvocationStatusHandler);
diff --git a/docs/APIRef.Artifacts.md b/docs/APIRef.Artifacts.md
index 6561f6ea6..c27a33acd 100644
--- a/docs/APIRef.Artifacts.md
+++ b/docs/APIRef.Artifacts.md
@@ -79,19 +79,17 @@ There might be a similar issue on Android when the screenrecording process exits
 
 ### Detox Instruments is installed in a custom location
 
-If you see such warnings in log as:
+If you have to use [Detox Instruments](https://github.com/wix/DetoxInstruments)
+installed in a custom location (e.g., inside `node_modules`), you can point Detox
+to it with the `DETOX_INSTRUMENTS_PATH` environment variable, as shown below:
 
-```
-detox[46413] WARN:  [SimulatorInstrumentsPlugin.js/INSTRUMENTS_NOT_FOUND] Failed to find Detox Instruments app at path: /Applications/Detox Instruments.app
-To enable recording performance profiles, please follow: https://github.com/wix/DetoxInstruments#installation
+```bash
+DETOX_INSTRUMENTS_PATH="/path/to/Detox Instruments.app" detox test ...
 ```
 
-and you can't fix that by installing [Detox Instruments](https://github.com/wix/DetoxInstruments)
-in a default location, then you can override the path by setting an environment variable, e.g.:
+Please mind that if **Detox Instruments** had been [integrated into
+your app](https://github.com/wix/DetoxInstruments/blob/master/Documentation/XcodeIntegrationGuide.md) (usually, that is in development builds), then the built-in version of [Detox Profiler framework](https://github.com/wix/DetoxInstruments/tree/master/Profiler) will always take priority over any custom path to Detox Instruments installation.
 
-```
-DETOX_INSTRUMENTS_PATH="/path/to/Detox Instruments.app" detox test ...
-```
 
 ### Ctrl+C does not terminate Detox+Jest tests correctly
 
diff --git a/docs/Guide.Migration.md b/docs/Guide.Migration.md
index 237021c93..701dde8a0 100644
--- a/docs/Guide.Migration.md
+++ b/docs/Guide.Migration.md
@@ -5,6 +5,63 @@ title: Migration Guide
 
 We are improving detox API as we go along, sometimes these changes require us to break the API in order for it to make more sense. These migration guides refer to breaking changes.
 
+## Migrating from Detox 12.4.x to 12.5.0 (nonbreaking)
+
+Starting Detox `12.5.0`, we ship Android with precompiled sources under a  `.aar` file. The complete configuration process is thoroughly described in the [Android setup guide](Introduction.Android.md#2-add-detox-dependency-to-an-android-project) - but it mostly fits **new** projects. For existing projects, migrating is strongly recommended; Here's the diff:
+
+Root `settings.gradle` file:
+
+```diff
+-include ':detox'
+-project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox')
+```
+
+
+
+Root buildscript (i.e. `build.gradle`):
+
+```diff
+allprojects {
+    repositories {
+         // ...
++        maven {
++            url "$rootDir/../node_modules/detox/Detox-android"
++        }
+     }
+ }
+```
+
+
+
+App buildscript (i.e. `app/build.gradle`):
+
+```diff
+ dependencies {
+-    androidTestImplementation(project(path: ":detox"))
++    androidTestImplementation('com.wix:detox:+') { transitive = true }
+ }
+```
+
+
+
+#### Proguard Configuration
+
+If you have Detox Proguard rules integrated into the `app/build.gradle`, be sure to switch to an explicit search path:
+
+```diff
+     buildTypes {
+         release {
+         
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+-            proguardFile "${project(':detox').projectDir}/proguard-rules-app.pro"
++            proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro"
+
+         }
+     }
+```
+
+
+
 ## Migrating from Detox 12.3.x to 12.4.0
 
 The deprecation of `"specs"` (in `package.json`) introduced in 12.1.0 is **no longer relevant**.
diff --git a/docs/Introduction.Android.md b/docs/Introduction.Android.md
index c5ea02608..8d4c5e2b1 100644
--- a/docs/Introduction.Android.md
+++ b/docs/Introduction.Android.md
@@ -21,55 +21,67 @@ title: Detox for Android
 
   **Note: As a rule of thumb, we consider all old major versions discontinued; We only support the latest Detox major version.**
 
+
+
 ## Setup :gear:
+
 ### 1. Run through the initial _Getting Started_ Guide
 
 - [Getting Started](Introduction.GettingStarted.md)
 
-### 2. Add Detox dependency to an Android project
 
-In `android/settings.gradle` add:
 
-```groovy
-include ':detox'
-project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox')
-```
+### 2. Add Detox dependency to an Android project
 
-In `android/app/build.gradle` add this to `defaultConfig` section:
+> **Starting Detox 12.5.0, Detox is shipped as a precompiled `.aar`.**
+> To configure Detox as a _compiling dependency_, nevertheless -- refer to the _Setting Detox up as a compiling dependency_ section at the bottom.
+
+In your *root* buildscript (i.e. `build.gradle`), register both `google()` _and_ detox as repository lookup points in all projects:
 
 ```groovy
-  defaultConfig {
-      ...
-      testBuildType System.getProperty('testBuildType', 'debug')  //this will later be used to control the test apk build type
-      testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-      ...
-  }
+// Note: add the 'allproject' section if it doesn't exist
+allprojects {
+    repositories {
+        // ...
+        google()
+        maven {
+            // All of Detox' artifacts are provided via the npm module
+            url "$rootDir/../node_modules/detox/Detox-android"
+        }
+    }
+}
 ```
-Please be aware that the `minSdkVersion` needs to be at least 18.
 
 
 
-
-In `android/app/build.gradle` add this in `dependencies` section:
+In your app's buildscript (i.e. `app/build.gradle`) add this in `dependencies` section:
 
 ```groovy
 dependencies {
-	// ...
-    androidTestImplementation(project(path: ":detox"))
+	  // ...
+    androidTestImplementation('com.wix:detox:+') { transitive = true } 
     androidTestImplementation 'junit:junit:4.12'
 }
 ```
 
-And in `android/build.gradle` you need to add this under `allprojects > repositories`:
+
+
+In your app's buildscript (i.e. `app/build.gradle`)  add this to the `defaultConfig` subsection:
 
 ```groovy
-buildscript {
-    repositories {
-	    // ...
-        google()
-    }
+android {
+  // ...
+  
+  defaultConfig {
+      // ...
+      testBuildType System.getProperty('testBuildType', 'debug')  // This will later be used to control the test apk build type
+      testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+  }
 }
 ```
+Please be aware that the `minSdkVersion` needs to be at least 18.
+
+
 
 ### 3. Add Kotlin
 
@@ -101,10 +113,14 @@ buildscript {
 
 ***Note that Detox has been tested for version 1.1.0 of Kotlin, and higher!***
 
+
+
 ### 4. Create Android Test class
 
 Add the file `android/app/src/androidTest/java/com/[your.package]/DetoxTest.java` and fill as in [the detox example app for NR](../examples/demo-react-native/android/app/src/androidTest/java/com/example/DetoxTest.java). **Don't forget to change the package name to your project's**.
 
+
+
 ### 5. Add Android configuration
 
 Add this part to your `package.json`:
@@ -147,6 +163,8 @@ Using the `android.emu.debug` configuration from above, you can invoke it in the
 detox test -c android.emu.debug
 ```
 
+
+
 ## Proguard (Minification)
 
 In apps running [minification using Proguard](https://developer.android.com/studio/build/shrink-code), in order for Detox to work well on release builds, please enable some Detox proguard-configuration rules by applying the custom configuration file on top of your own. Typically, this is defined using the `proguardFiles` statement in the minification-enabled build-type in your `app/build.gradle`:
@@ -156,11 +174,11 @@ In apps running [minification using Proguard](https://developer.android.com/stud
         // 'release' is typically the default proguard-enabled build-type
         release {
             minifyEnabled true
-          
-            // The last expression results in a path to Detox' custom rules file
-            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro', "${project(':detox').projectDir}/proguard-rules-app.pro"
 
-            // ...
+            // Typical pro-guard definitions
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+            // Detox-specific additions to pro-guard
+            proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro"
         }
     }
 
@@ -186,3 +204,63 @@ packagingOptions {
     exclude 'META-INF/LICENSE'
 }
 ```
+
+
+
+## Setting Detox up as a compiling dependency
+
+This is an **alternative** to the setup process described under the previous section, on adding Detox as a dependency.
+
+
+
+In your project's `settings.gradle` add:
+
+```groovy
+include ':detox'
+project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox')
+```
+
+
+
+In your *root* buildscript (i.e. `build.gradle`), register `google()` as a repository lookup point in all projects:
+
+```groovy
+// Note: add the 'allproject' section if it doesn't exist
+allprojects {
+    repositories {
+        // ...
+        google()
+    }
+}
+```
+
+
+
+In your app's buildscript (i.e. `app/build.gradle`) add this in `dependencies` section:
+
+```groovy
+dependencies {
+  	// ...
+    androidTestImplementation(project(path: ":detox"))
+    androidTestImplementation 'junit:junit:4.12'
+}
+```
+
+
+
+In your app's buildscript (i.e. `app/build.gradle`) add this to the `defaultConfig` subsection:
+
+```groovy
+android {
+  // ...
+  
+  defaultConfig {
+      // ...
+      testBuildType System.getProperty('testBuildType', 'debug')  // This will later be used to control the test apk build type
+      testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+  }
+}
+```
+
+Please be aware that the `minSdkVersion` needs to be at least 18.
+
diff --git a/examples/demo-native-android/package.json b/examples/demo-native-android/package.json
index e14a3460b..42adf67ee 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.4.1",
+  "version": "12.6.0",
   "private": true,
   "scripts": {
     "packager": "react-native start",
@@ -8,7 +8,7 @@
     "e2e": "mocha e2e --opts ./e2e/mocha.opts"
   },
   "devDependencies": {
-    "detox": "^12.4.1",
+    "detox": "^12.6.0",
     "mocha": "^6.1.3"
   },
   "detox": {}
diff --git a/examples/demo-native-ios/package.json b/examples/demo-native-ios/package.json
index 362459483..c182e80d4 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.4.1",
+  "version": "12.6.0",
   "private": true,
   "devDependencies": {
-    "detox": "^12.4.1",
+    "detox": "^12.6.0",
     "mocha": "^6.1.3"
   },
   "detox": {
diff --git a/examples/demo-react-native-jest/package.json b/examples/demo-react-native-jest/package.json
index a2e9855d6..8b18d7512 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.4.1",
+  "version": "12.6.0",
   "private": true,
   "scripts": {
     "start": "node node_modules/react-native/local-cli/cli.js start",
@@ -19,7 +19,7 @@
     "react-native": "0.57.x"
   },
   "devDependencies": {
-    "detox": "^12.4.1",
+    "detox": "^12.6.0",
     "jest": "24.x.x"
   },
   "detox": {
diff --git a/examples/demo-react-native/package.json b/examples/demo-react-native/package.json
index 2c01d8e55..38744891c 100644
--- a/examples/demo-react-native/package.json
+++ b/examples/demo-react-native/package.json
@@ -1,6 +1,6 @@
 {
   "name": "example",
-  "version": "12.4.1",
+  "version": "12.6.0",
   "private": true,
   "scripts": {
     "start": "react-native start",
@@ -20,7 +20,7 @@
     "react-native": "0.56.0"
   },
   "devDependencies": {
-    "detox": "^12.4.1",
+    "detox": "^12.6.0",
     "mocha": "^6.1.3"
   },
   "detox": {
diff --git a/lerna.json b/lerna.json
index 6217b8f90..f5981b85a 100644
--- a/lerna.json
+++ b/lerna.json
@@ -10,7 +10,7 @@
     "examples/demo-react-native-jest",
     "generation"
   ],
-  "version": "12.4.1",
+  "version": "12.6.0",
   "npmClient": "npm",
   "command": {
     "publish": {
diff --git a/scripts/aggregate_coverage.sh b/scripts/aggregate_coverage.sh
new file mode 100755
index 000000000..468095ecf
--- /dev/null
+++ b/scripts/aggregate_coverage.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+
+if [ -d aggregated-coverage ]; then
+  npm install --no-save --no-package-lock "lcov-result-merger@3.x.x" "coveralls@3.x.x"
+
+  rm -f "aggregated-coverage/merged.lcov"
+  {
+    for lcov in aggregated-coverage/**/*.lcov; do
+      echo "Forcing relative paths in: $lcov"
+      sed -i 's/^SF:.*\(detox-[a-z]\+-[0-9]\+-[a-z]\+\)\//SF:/g' $lcov
+    done
+
+    echo "Merging LCOV files"
+    node_modules/.bin/lcov-result-merger 'aggregated-coverage/**/*.lcov' "aggregated-coverage/merged.lcov"
+  } && {
+    echo "Uploading to coveralls"
+    cat "aggregated-coverage/merged.lcov" | node_modules/.bin/coveralls
+  } || {
+    echo "Failed to aggregate the test coverage, skipping."
+  }
+else
+  echo "Aggregated coverage directory was not found, skipping."
+fi
diff --git a/scripts/ci.android.sh b/scripts/ci.android.sh
index ea65d9a57..6620e88d2 100755
--- a/scripts/ci.android.sh
+++ b/scripts/ci.android.sh
@@ -13,5 +13,6 @@ cp extras/release.gradle node_modules/react-native/ReactAndroid/
 
 run_f "npm run build:android"
 run_f "npm run e2e:android -- --headless --loglevel verbose"
+cp coverage/lcov.info coverage/e2e.lcov
 # run_f "npm run verify-artifacts:android"
 popd
diff --git a/scripts/ci.ios.sh b/scripts/ci.ios.sh
index 5d0d7573c..158dc0c50 100755
--- a/scripts/ci.ios.sh
+++ b/scripts/ci.ios.sh
@@ -7,6 +7,6 @@ run_f "$(dirname "$0")/unit.ios.sh"
 pushd detox/test
 run_f "npm run build:ios"
 run_f "npm run e2e:ios"
+cp coverage/lcov.info coverage/e2e.lcov
 # run_f "npm run verify-artifacts:ios"
 popd
-


Test patch:
diff --git a/detox/local-cli/test.js b/detox/local-cli/test.js
index 89acdac0d..497a26c2b 100644
--- a/detox/local-cli/test.js
+++ b/detox/local-cli/test.js
@@ -159,16 +159,15 @@ module.exports.handler = async function test(program) {
   const platform = currentConfiguration.type.split('.')[0];
 
   function run() {
-    switch (runner) {
-      case 'mocha':
-        runMocha();
-        break;
-      case 'jest':
-        runJest();
-        break;
-      default:
-        throw new Error(`${runner} is not supported in detox cli tools. You can still run your tests with the runner's own cli tool`);
+    if (runner.includes('jest')) {
+      return runJest();
     }
+
+    if (runner.includes('mocha')) {
+      return runMocha();
+    }
+
+    throw new Error(`${runner} is not supported in detox cli tools. You can still run your tests with the runner's own cli tool`);
   }
 
   function getConfigFor(...keys) {
@@ -206,7 +205,7 @@ module.exports.handler = async function test(program) {
     }
 
     const command = _.compact([
-      (path.join('node_modules', '.bin', 'mocha')),
+      (path.join('node_modules', '.bin', runner)),
       (runnerConfig ? `--opts ${runnerConfig}` : ''),
       (program.configuration ? `--configuration ${program.configuration}` : ''),
       (program.loglevel ? `--loglevel ${program.loglevel}` : ''),
@@ -237,7 +236,7 @@ module.exports.handler = async function test(program) {
     }
 
     const command = _.compact([
-      path.join('node_modules', '.bin', 'jest'),
+      path.join('node_modules', '.bin', runner),
       (runnerConfig ? `--config=${runnerConfig}` : ''),
       (program.noColor ? ' --no-color' : ''),
       `--maxWorkers=${program.workers}`,
@@ -282,13 +281,12 @@ module.exports.handler = async function test(program) {
   }
 
   function getDefaultRunnerConfig() {
-    switch (runner) {
-      case 'mocha':
-        return 'e2e/mocha.opts';
-      case 'jest':
-        return 'e2e/config.json';
-      default:
-        return undefined;
+    if (runner.includes('jest')) {
+      return 'e2e/config.json';
+    }
+
+    if (runner.includes('mocha')) {
+      return 'e2e/mocha.opts';
     }
   }
 
diff --git a/detox/src/artifacts/templates/artifact/Artifact.test.js b/detox/src/artifacts/templates/artifact/Artifact.test.js
index e9624b23e..607eae2cc 100644
--- a/detox/src/artifacts/templates/artifact/Artifact.test.js
+++ b/detox/src/artifacts/templates/artifact/Artifact.test.js
@@ -502,7 +502,7 @@ describe(Artifact.name, () => {
       it('should log error if source file does not exist', async () => {
         await Artifact.moveTemporaryFile(logger, source, destination = tempfile('.dest'));
         expect(await fs.exists(destination)).toBe(false);
-        expect(logger.error).toHaveBeenCalledWith({ event: 'MOVE_FILE_ERROR' }, expect.any(String));
+        expect(logger.warn).toHaveBeenCalledWith({ event: 'MOVE_FILE_MISSING' }, expect.any(String));
       });
     });
   });
diff --git a/detox/src/client/Client.test.js b/detox/src/client/Client.test.js
index b18a3da61..709e53109 100644
--- a/detox/src/client/Client.test.js
+++ b/detox/src/client/Client.test.js
@@ -196,6 +196,17 @@ describe('Client', () => {
     }
   });
 
+  it(`execute() - should throw if non-error is thrown`, async () => {
+    await connect();
+    client.ws.send.mockRejectedValueOnce("non-error");
+    const call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'matcherForAccessibilityLabel:', 'test');
+    try {
+      await client.execute(call);
+    } catch (ex) {
+      expect(ex.message).toBe("non-error");
+    }
+  });
+
   it(`execute() - unsupported result should throw`, async () => {
     await connect();
     client.ws.send.mockReturnValueOnce(Promise.resolve(`{"unsupported":"unsupported"}`));
diff --git a/detox/test/package.json b/detox/test/package.json
index 9f74d7274..57d075f0b 100644
--- a/detox/test/package.json
+++ b/detox/test/package.json
@@ -1,12 +1,12 @@
 {
   "name": "detox-test",
-  "version": "12.4.1",
+  "version": "12.6.0",
   "private": true,
   "scripts": {
     "test": ":",
     "packager": "react-native start",
     "detox-server": "detox run-server",
-    "e2e:ios": "detox test --configuration ios.sim.release --debug-synchronization --take-screenshots all --record-logs all --record-performance all",
+    "e2e:ios": "detox test --configuration ios.sim.release --debug-synchronization --take-screenshots all --record-logs all",
     "e2e:android": "detox test --configuration android.emu.release --take-screenshots all --record-logs all",
     "build:ios": "detox build --configuration ios.sim.release",
     "build:android": "detox build --configuration android.emu.release",
@@ -19,13 +19,14 @@
     "react-native": "0.56.0"
   },
   "devDependencies": {
-    "detox": "^12.4.1",
+    "detox": "^12.6.0",
     "express": "^4.15.3",
     "lodash": "^4.14.1",
-    "mocha": "^6.1.3"
+    "mocha": "^6.1.3",
+    "nyc": "^14.0.0"
   },
   "detox": {
-    "test-runner": "mocha",
+    "test-runner": "nyc mocha",
     "__session": {
       "server": "ws://localhost:8099",
       "sessionId": "test"
@@ -71,5 +72,23 @@
         "name": "Nexus_5X_API_26"
       }
     }
+  },
+  "nyc": {
+    "reporter": [
+      "lcov",
+      "html"
+    ],
+    "all": true,
+    "cwd": "..",
+    "include": [
+      "src/**/*.js"
+    ],
+    "report-dir": "test/coverage",
+    "exclude": [
+      "**/__test/**",
+      "**/__mocks__/**",
+      "**/*.mock.js",
+      "**/*.test.js"
+    ]
   }
 }