Repository: wix/Detox
Pull request #1770 (2019-11-18 11:51:04)

Make Android's adb-reverse a public device API
- [x] This is a small change 
- [ ] This change has been discussed in issue #0 and the solution has been agreed upon with maintainers.

---

**Description:**

Introduce the common `adb reverse` command in Detox' `device` API.

@viktorijasujetaite 


Patch:
diff --git a/detox/android/build.gradle b/detox/android/build.gradle
index 02827eb8a..405e99443 100644
--- a/detox/android/build.gradle
+++ b/detox/android/build.gradle
@@ -1,26 +1,21 @@
 buildscript {
-    ext {
-        isOfficialDetoxLib = true
-        kotlinVersion = '1.3.41'
-        dokkaVersion = '0.9.18'
-        buildToolsVersion = '29.0.0'
-        compileSdkVersion = 29
-        targetSdkVersion = 29
-        minSdkVersion = 18
-    }
+    ext.isOfficialDetoxLib = true
+    ext.kotlinVersion = '1.3.41'
     ext.detoxKotlinVerion = ext.kotlinVersion
+    ext.dokkaVersion = '0.9.18'
+    ext.buildToolsVersion = '28.0.3'
 
     repositories {
         jcenter()
         google()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.4.2'
+        classpath 'com.android.tools.build:gradle:3.4.1'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
         classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion"
 
         // Needed by Spek (https://spekframework.org/setup-android)
-        classpath 'de.mannodermaus.gradle.plugins:android-junit5:1.4.2.1'
+        classpath "de.mannodermaus.gradle.plugins:android-junit5:1.4.2.1"
     }
 }
 
diff --git a/detox/android/detox/build.gradle b/detox/android/detox/build.gradle
index d4b41fc96..145b20e94 100644
--- a/detox/android/detox/build.gradle
+++ b/detox/android/detox/build.gradle
@@ -3,6 +3,7 @@ apply plugin: 'kotlin-android'
 apply plugin: 'kotlin-android-extensions'
 
 def _ext = rootProject.ext
+
 def _compileSdkVersion = _ext.has('compileSdkVersion') ? _ext.compileSdkVersion : 28
 def _buildToolsVersion = _ext.has('buildToolsVersion') ? _ext.buildToolsVersion : '28.0.3'
 def _minSdkVersion = _ext.has('minSdkVersion') ? _ext.minSdkVersion : 18
@@ -17,7 +18,7 @@ android {
         minSdkVersion _minSdkVersion
         targetSdkVersion _targetSdkVersion
         versionCode 1
-        versionName '1.0'
+        versionName "1.0"
 
         consumerProguardFiles 'proguard-rules.pro'
     }
@@ -68,12 +69,12 @@ dependencies {
     // Versions are in-sync with the 'androidx-test-1.1.0' release/tag of the android-test github repo,
     // used by the Detox generator. See https://github.com/android/android-test/releases/tag/androidx-test-1.1.0
     // Important: Should remain so when generator tag is replaced!
-    api('androidx.test.espresso:espresso-core:3.2.0') {
+    api('androidx.test.espresso:espresso-core:3.1.1') {
         exclude group: 'com.google.code.findbugs', module: 'jsr305'
     }
-    api 'androidx.test:runner:1.2.0'
-    api 'androidx.test:rules:1.2.0'
-    api 'androidx.test.ext:junit:1.1.1'
+    api 'androidx.test:runner:1.1.1'
+    api 'androidx.test:rules:1.1.1'
+    api 'androidx.test.ext:junit:1.1.0'
     api 'androidx.annotation:annotation:1.1.0'
 
     // Version is the latest; Cannot sync with the Github repo (e.g. android/android-test) because the androidx
diff --git a/detox/android/gradle/wrapper/gradle-wrapper.properties b/detox/android/gradle/wrapper/gradle-wrapper.properties
index f35855704..ae9b8300b 100644
--- a/detox/android/gradle/wrapper/gradle-wrapper.properties
+++ b/detox/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
diff --git a/detox/src/devices/Device.js b/detox/src/devices/Device.js
index cbfbfa4f9..de6ef26b6 100644
--- a/detox/src/devices/Device.js
+++ b/detox/src/devices/Device.js
@@ -140,7 +140,7 @@ class Device {
   }
 
   async setBiometricEnrollment(toggle) {
-    let yesOrNo = toggle ? 'YES' : 'NO'
+    const yesOrNo = toggle ? 'YES' : 'NO';
     await this.deviceDriver.setBiometricEnrollment(this._deviceId, yesOrNo);
   }
 
@@ -210,6 +210,14 @@ class Device {
     await this.deviceDriver.setLocation(this._deviceId, lat, lon);
   }
 
+  async reverseTcpPort(port) {
+    await this.deviceDriver.reverseTcpPort(this._deviceId, port);
+  }
+
+  async unreverseTcpPort(port) {
+    await this.deviceDriver.unreverseTcpPort(this._deviceId, port);
+  }
+
   async clearKeychain() {
     await this.deviceDriver.clearKeychain(this._deviceId);
   }
diff --git a/detox/src/devices/drivers/AndroidDriver.js b/detox/src/devices/drivers/AndroidDriver.js
index a3924802b..d0cad09a0 100644
--- a/detox/src/devices/drivers/AndroidDriver.js
+++ b/detox/src/devices/drivers/AndroidDriver.js
@@ -149,6 +149,14 @@ class AndroidDriver extends DeviceDriverBase {
     return this.uiDevice;
   }
 
+  async reverseTcpPort(deviceId, port) {
+    await this.adb.reverse(deviceId, port);
+  }
+
+  async unreverseTcpPort(deviceId, port) {
+    await this.adb.reverseRemove(deviceId, port);
+  }
+
   async setURLBlacklist(urlList) {
     const call = EspressoDetoxApi.setURLBlacklist(urlList);
     await this.invocationManager.execute(call);
diff --git a/detox/src/devices/drivers/DeviceDriverBase.js b/detox/src/devices/drivers/DeviceDriverBase.js
index 89f69fe01..7e5886354 100644
--- a/detox/src/devices/drivers/DeviceDriverBase.js
+++ b/detox/src/devices/drivers/DeviceDriverBase.js
@@ -100,6 +100,14 @@ class DeviceDriverBase {
     return await Promise.resolve('');
   }
 
+  async reverseTcpPort() {
+    return await Promise.resolve('');
+  }
+
+  async unreverseTcpPort() {
+    return await Promise.resolve('');
+  }
+
   async clearKeychain(_udid) {
     return await Promise.resolve('')
   }
diff --git a/docs/APIRef.DeviceObjectAPI.md b/docs/APIRef.DeviceObjectAPI.md
index f7656339e..2ca1e7fbc 100644
--- a/docs/APIRef.DeviceObjectAPI.md
+++ b/docs/APIRef.DeviceObjectAPI.md
@@ -31,6 +31,8 @@
 - [`device.clearKeychain()` **iOS Only**](#deviceclearkeychain-ios-only)
 - [`device.setStatusBar()` **iOS Only**](#devicesetstatusbar-ios-only)
 - [`device.resetStatusBar()` **iOS Only**](#deviceresetstatusbar-ios-only)
+- [`device.reverseTcpPort()` **Android Only**](#devicereversetcpport-android-only)
+- [`device.unreverseTcpPort()` **Android Only**](#deviceunreversetcpport-android-only)
 - [`device.pressBack()` **Android Only**](#devicepressback-android-only)
 - [`device.getUIDevice()` **Android Only**](#devicegetuidevice-android-only)
 
@@ -411,6 +413,14 @@ Override simulator's status bar. Available options:
 ### `device.resetStatusBar()` **iOS Only**
 Resets any override in simulator's status bar.
 
+### `device.reverseTcpPort()` **Android Only**
+
+Reverse a TCP port from the device (guest) back to the host-computer, as typically done with the `adb reverse` command. The end result would be that all network requests going from the device to the specified port will be forwarded to the computer.
+
+### `device.unreverseTcpPort()` **Android Only**
+
+Clear a _reversed_ TCP-port (e.g. previously set using `device.reverseTcpPort()`).
+
 ### `device.pressBack()` **Android Only**
 Simulate press back button.
 
diff --git a/examples/demo-react-native/android/app/build.gradle b/examples/demo-react-native/android/app/build.gradle
index 092a417b2..5498ba7b1 100644
--- a/examples/demo-react-native/android/app/build.gradle
+++ b/examples/demo-react-native/android/app/build.gradle
@@ -1,33 +1,32 @@
 import groovy.json.JsonSlurper
 import com.android.build.OutputFile
 
-apply plugin: 'com.android.application'
+apply plugin: "com.android.application"
 
 //project.ext.react = [:]
 project.ext.react = [
     enableHermes: false
 ]
-apply from: '../../node_modules/react-native/react.gradle'
+apply from: "../../node_modules/react-native/react.gradle"
 
 Boolean isRN60OrHigher = getRNVersion() >= 60
-Boolean isRN61OrHigher = getRNVersion() >= 61
 
 android {
-    compileSdkVersion 29
-    buildToolsVersion '29.0.0'
+    compileSdkVersion 28
+    buildToolsVersion '28.0.3'
 
     defaultConfig {
-        applicationId 'com.detox.rn.example'
+        applicationId "com.detox.rn.example"
         minSdkVersion 18
-        targetSdkVersion 29
+        targetSdkVersion 28
         versionCode 1
-        versionName '1.0'
+        versionName "1.0"
         ndk {
             abiFilters "armeabi-v7a", "x86"
         }
 
         testBuildType System.getProperty('testBuildType', 'debug')
-        testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     }
     splits {
         abi {
@@ -93,15 +92,14 @@ dependencies {
 }
 
 if (isRN60OrHigher) {
-    // refs: https://react-native-community.github.io/upgrade-helper/?from=0.59.9&to=0.60.6
-    //       https://react-native-community.github.io/upgrade-helper/?from=0.60.6&to=0.61.4
+    // ref: https://react-native-community.github.io/upgrade-helper/?from=0.59.9&to=0.60.6
     dependencies {
-        def enableHermes = project.ext.react.get('enableHermes', false)
+        def enableHermes = project.ext.react.get("enableHermes", false)
+
         if (enableHermes) {
-            def hermesModuleName = isRN61OrHigher ? 'hermes-engine' : 'hermesvm'
-            def hermesPath = "../../node_modules/$hermesModuleName/android/"
-            debugImplementation files(hermesPath + 'hermes-debug.aar')
-            releaseImplementation files(hermesPath + 'hermes-release.aar')
+            def hermesPath = "../../node_modules/hermesvm/android/"
+            debugImplementation files(hermesPath + "hermes-debug.aar")
+            releaseImplementation files(hermesPath + "hermes-release.aar")
         } else {
             implementation 'org.webkit:android-jsc:+'
         }
diff --git a/examples/demo-react-native/android/build.gradle b/examples/demo-react-native/android/build.gradle
index bc5b367cc..1f26b6be9 100644
--- a/examples/demo-react-native/android/build.gradle
+++ b/examples/demo-react-native/android/build.gradle
@@ -1,11 +1,5 @@
 buildscript {
-    ext {
-        kotlinVersion = '1.3.41'
-        buildToolsVersion = '29.0.0'
-        compileSdkVersion = 29
-        targetSdkVersion = 29
-        minSdkVersion = 18
-    }
+    ext.kotlinVersion = '1.3.41'
     ext.detoxKotlinVersion = ext.kotlinVersion
 
     repositories {
@@ -13,8 +7,7 @@ buildscript {
         google()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.4.2'
-        classpath 'de.undercouch:gradle-download-task:3.4.3'
+        classpath 'com.android.tools.build:gradle:3.4.1'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
     }
 }
diff --git a/examples/demo-react-native/android/gradle/wrapper/gradle-wrapper.properties b/examples/demo-react-native/android/gradle/wrapper/gradle-wrapper.properties
index 78e1f40b5..2b67919a6 100644
--- a/examples/demo-react-native/android/gradle/wrapper/gradle-wrapper.properties
+++ b/examples/demo-react-native/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip


Test patch:
diff --git a/detox/src/devices/Device.test.js b/detox/src/devices/Device.test.js
index dc9a3e990..811f3e3f7 100644
--- a/detox/src/devices/Device.test.js
+++ b/detox/src/devices/Device.test.js
@@ -71,6 +71,14 @@ describe('Device', () => {
     expectTerminateNotCalled() {
       expect(this.driver.terminate).not.toHaveBeenCalled();
     }
+
+    expectReverseTcpPortCalled(deviceId, port) {
+      expect(this.driver.reverseTcpPort).toHaveBeenCalledWith(deviceId, port);
+    }
+
+    expectUnreverseTcpPortCalled(deviceId, port) {
+      expect(this.driver.unreverseTcpPort).toHaveBeenCalledWith(deviceId, port);
+    }
   }
 
   function schemeDevice(scheme, configuration) {
@@ -533,7 +541,7 @@ describe('Device', () => {
 
     expect(driverMock.driver.setStatusBar).toHaveBeenCalledWith(device._deviceId, params);
   });
-  
+
   it(`resetStatusBar() should pass to device driver`, async () => {
     const device = validDevice();
     await device.resetStatusBar();
@@ -616,6 +624,20 @@ describe('Device', () => {
     expect(driverMock.driver.setLocation).toHaveBeenCalledWith(device._deviceId, '30.1', '30.2');
   });
 
+  it(`reverseTcpPort should pass to device driver`, async () => {
+    const device = validDevice();
+    await device.reverseTcpPort(666);
+
+    await driverMock.expectReverseTcpPortCalled(device._deviceId, 666);
+  });
+
+  it(`unreverseTcpPort should pass to device driver`, async () => {
+    const device = validDevice();
+    await device.unreverseTcpPort(777);
+
+    await driverMock.expectUnreverseTcpPortCalled(device._deviceId, 777);
+  });
+
   it(`setURLBlacklist() should pass to device driver`, async () => {
     const device = validDevice();
     await device.setURLBlacklist();
diff --git a/detox/src/devices/drivers/AndroidDriver.test.js b/detox/src/devices/drivers/AndroidDriver.test.js
index b7939864f..f1e5c2111 100644
--- a/detox/src/devices/drivers/AndroidDriver.test.js
+++ b/detox/src/devices/drivers/AndroidDriver.test.js
@@ -8,6 +8,7 @@ describe('Android driver', () => {
     jest.mock('../../utils/encoding', () => ({
       encodeBase64: (x) => `base64(${x})`,
     }));
+
     jest.mock('../android/ADB', () => mockADBClass);
     jest.mock('../../utils/AsyncEmitter', () => mockAsyncEmitter);
     jest.mock('../../utils/sleep', () => jest.fn().mockResolvedValue(''));
@@ -71,6 +72,21 @@ describe('Android driver', () => {
       });
     });
   });
+
+  describe('net-port reversing', () => {
+    const deviceId = 1010;
+    const port = 1337;
+
+    it(`should invoke ADB's reverse`, async () => {
+      await uut.reverseTcpPort(deviceId, port);
+      expect(uut.adb.reverse).toHaveBeenCalledWith(deviceId, port);
+    });
+
+    it(`should invoke ADB's reverse-remove`, async () => {
+      await uut.unreverseTcpPort(deviceId, port);
+      expect(uut.adb.reverseRemove).toHaveBeenCalledWith(deviceId, port);
+    });
+  });
 });
 
 class mockADBClass {
diff --git a/detox/test/android/app/build.gradle b/detox/test/android/app/build.gradle
index f3e0256e8..bedf5203d 100644
--- a/detox/test/android/app/build.gradle
+++ b/detox/test/android/app/build.gradle
@@ -6,13 +6,13 @@ apply plugin: 'com.android.application'
 project.ext.react = [
     enableHermes: false
 ]
-apply from: '../../node_modules/react-native/react.gradle'
+apply from: "../../node_modules/react-native/react.gradle"
 
 Boolean isRN60OrHigher = getRNVersion() >= 60
 Boolean isRN61OrHigher = getRNVersion() >= 61
 
 android {
-    compileSdkVersion rootProject.ext.compileSdkVersion
+    compileSdkVersion 28
 
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_8
@@ -20,16 +20,16 @@ android {
     }
 
     defaultConfig {
-        applicationId 'com.wix.detox.test'
-        minSdkVersion rootProject.ext.minSdkVersion
-        targetSdkVersion rootProject.ext.targetSdkVersion
+        applicationId "com.wix.detox.test"
+        minSdkVersion 18
+        targetSdkVersion 28
         versionCode 1
-        versionName '1.0'
+        versionName "1.0"
         ndk {
-            abiFilters 'armeabi-v7a', 'x86'
+            abiFilters "armeabi-v7a", "x86"
         }
         testBuildType System.getProperty('testBuildType', 'debug')
-        testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 
         /*
         testInstrumentationRunnerArguments = [
@@ -58,12 +58,12 @@ android {
     }
 
     productFlavors {
-        flavorDimensions 'compileRNFromSource'
+        flavorDimensions "compileRNFromSource"
         fromSource {
-            dimension 'compileRNFromSource'
+            dimension "compileRNFromSource"
         }
         fromBin {
-            dimension 'compileRNFromSource'
+            dimension "compileRNFromSource"
         }
     }
 
@@ -86,11 +86,11 @@ dependencies {
         implementation 'com.android.support:appcompat-v7:28.0.0'
     }
 
-    fromSourceImplementation(project(path: ':ReactAndroid'))
+    fromSourceImplementation(project(path: ":ReactAndroid"))
     // noinspection GradleDynamicVersion
-    fromBinImplementation 'com.facebook.react:react-native:+'
+    fromBinImplementation "com.facebook.react:react-native:+"
 
-    androidTestImplementation(project(path: ':detox'))
+    androidTestImplementation(project(path: ":detox"))
     androidTestImplementation 'junit:junit:4.12'
 }
 
@@ -98,12 +98,12 @@ if (isRN60OrHigher) {
     // refs: https://react-native-community.github.io/upgrade-helper/?from=0.59.9&to=0.60.6
     //       https://react-native-community.github.io/upgrade-helper/?from=0.60.6&to=0.61.4
     dependencies {
-        def enableHermes = project.ext.react.get('enableHermes', false)
+        def enableHermes = project.ext.react.get("enableHermes", false)
+
         if (enableHermes) {
-            def hermesModuleName = isRN61OrHigher ? 'hermes-engine' : 'hermesvm'
-            def hermesPath = "../../node_modules/$hermesModuleName/android/"
-            debugImplementation files(hermesPath + 'hermes-debug.aar')
-            releaseImplementation files(hermesPath + 'hermes-release.aar')
+            def hermesPath = (isRN61OrHigher ? "../../node_modules/hermes-engine/android/" : "../../node_modules/hermesvm/android/")
+            debugImplementation files(hermesPath + "hermes-debug.aar")
+            releaseImplementation files(hermesPath + "hermes-release.aar")
         } else {
             implementation 'org.webkit:android-jsc:+'
         }
diff --git a/detox/test/android/build.gradle b/detox/test/android/build.gradle
index f50563fee..0efa62d7b 100644
--- a/detox/test/android/build.gradle
+++ b/detox/test/android/build.gradle
@@ -1,13 +1,8 @@
 buildscript {
-    ext {
-        isOfficialDetoxApp = true
-        kotlinVersion = '1.3.41'
-        buildToolsVersion = '29.0.0'
-        compileSdkVersion = 29
-        targetSdkVersion = 29
-        minSdkVersion = 18
-    }
+    ext.isOfficialDetoxApp = true
+    ext.kotlinVersion = '1.3.41'
     ext.detoxKotlinVerion = ext.kotlinVersion
+    ext.buildToolsVersion = '28.0.3'
 
     repositories {
         google()
@@ -22,7 +17,7 @@ buildscript {
 
         // Needed by Spek (https://spekframework.org/setup-android)
         // Here in order to enable unit-tests run from Android Studio when working on the test app.
-        classpath 'de.mannodermaus.gradle.plugins:android-junit5:1.4.2.1'
+        classpath "de.mannodermaus.gradle.plugins:android-junit5:1.4.2.1"
     }
 }
 
diff --git a/detox/test/android/gradle/wrapper/gradle-wrapper.properties b/detox/test/android/gradle/wrapper/gradle-wrapper.properties
index f35855704..ae9b8300b 100644
--- a/detox/test/android/gradle/wrapper/gradle-wrapper.properties
+++ b/detox/test/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip