Repository: wix/Detox
Pull request #3526 (2022-08-02 14:39:12)

[post-workshop] Fix and improve error messages in aapt resolution
## Description

<!--
Thank you for contributing!

Step 1: Before submitting a pull request that introduces a new functionality or fixes a bug,
please open an issue where we could discuss the suggestion, problem - and potential ways to fix.
-->

- This pull request addresses the issue described here: #<?>

<!--
Step 2: Provide an overview of how your fix / enhancement works.
If possible, provide screenshots of the before and after states (even for simple command line options - show the terminal).
-->

In this pull request, I have fixed error messages coming from failed `aapt` resolutions, of the following form:

```
12:05:47.946 detox[12345] ERROR: There was no "aapt" executable file in directory /Users/d4vidi/Library/Android/sdk/Users/d4vidi/Library/Android/sdk/build-tools/32.0.0.
Check integrity of your Android SDK.
```

As one can easily notice - the SDK path is duplicated!

Also, error was not descriptive enough for the (realistic?) case where the are no build-tools effectively installed under `build-tools/` (e.g. under `build-tools/32.0.0/`).

<!--
Step 3: Please review the checklist below.
-->

---


> _For features/enhancements:_
 - [ ] I have added/updated the relevant references in the [documentation](https://github.com/wix/Detox/tree/master/docs) files.

> _For API changes:_
 - [ ] I have made the necessary changes in the [types index](https://github.com/wix/Detox/blob/master/detox/index.d.ts) file.



Patch:
diff --git a/detox/package.json b/detox/package.json
index ce663c5cd..e0f399a90 100644
--- a/detox/package.json
+++ b/detox/package.json
@@ -1,7 +1,7 @@
 {
   "name": "detox",
   "description": "E2E tests and automation for mobile",
-  "version": "19.8.4",
+  "version": "19.9.0",
   "bin": {
     "detox": "local-cli/cli.js"
   },
diff --git a/detox/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.js b/detox/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.js
index c34343f5f..c8fba7980 100644
--- a/detox/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.js
+++ b/detox/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.js
@@ -40,6 +40,7 @@ class GenyAllocDriver extends AllocationDriverBase {
     const { adbName } = instance;
 
     await this._adb.disableAndroidAnimations(adbName);
+    await this._adb.setWiFiToggle(adbName, true);
     await this._adb.apiLevel(adbName);
     return new GenycloudEmulatorCookie(instance);
   }
diff --git a/detox/src/devices/common/drivers/android/exec/ADB.js b/detox/src/devices/common/drivers/android/exec/ADB.js
index 6f7b6925c..9b0b346a5 100644
--- a/detox/src/devices/common/drivers/android/exec/ADB.js
+++ b/detox/src/devices/common/drivers/android/exec/ADB.js
@@ -206,6 +206,11 @@ class ADB {
     await this.shell(deviceId, `settings put global transition_animation_scale 0`);
   }
 
+  async setWiFiToggle(deviceId, state) {
+    const value = (state === true ? 'enable' : 'disable');
+    await this.shell(deviceId, `svc wifi ${value}`);
+  }
+
   async screencap(deviceId, path) {
     await this.shell(deviceId, `screencap ${path}`);
   }
diff --git a/detox/src/utils/environment.js b/detox/src/utils/environment.js
index f44a88e8b..e7e4ce71f 100644
--- a/detox/src/utils/environment.js
+++ b/detox/src/utils/environment.js
@@ -78,7 +78,7 @@ function getAndroidEmulatorPath() {
     return legacyPath;
   }
 
-  throwSdkIntegrityError(sdkRoot, 'emulator/emulator');
+  throwSdkBinIntegrityError(sdkRoot, 'emulator/emulator');
 }
 
 async function getAaptPath() {
@@ -88,23 +88,33 @@ async function getAaptPath() {
   }
 
   const latestBuildTools = await getLatestBuildToolsPath(sdkRoot);
-  const defaultPath = latestBuildTools && which('aapt', latestBuildTools);
+  if (!latestBuildTools) {
+    throwSdkIntegrityError('Failed to find the "aapt" tool under the Android SDK: No build-tools are installed!');
+  }
+
+  const defaultPath = which('aapt', latestBuildTools);
   if (defaultPath) {
     return defaultPath;
   }
 
-  throwSdkIntegrityError(sdkRoot, `${latestBuildTools}/aapt`);
+  throwSdkToolPathError(`${latestBuildTools}/aapt`);
 }
 
 async function getLatestBuildToolsPath(sdkRoot) {
-  if (!sdkRoot) return '';
+  if (!sdkRoot) {
+    return '';
+  }
 
   const buildToolsDir = path.join(sdkRoot, 'build-tools');
-  if (!fs.existsSync(buildToolsDir)) return '';
+  if (!fs.existsSync(buildToolsDir)) {
+    return '';
+  }
 
   const buildToolsVersions = await fsext.getDirectories(buildToolsDir);
   const latestBuildToolsVersion = _.last(buildToolsVersions);
-  if (!latestBuildToolsVersion) return '';
+  if (!latestBuildToolsVersion) {
+    return '';
+  }
 
   return path.join(buildToolsDir, latestBuildToolsVersion);
 }
@@ -120,7 +130,7 @@ function getAdbPath() {
     return defaultPath;
   }
 
-  throwSdkIntegrityError(sdkRoot, 'platform-tools/adb');
+  throwSdkBinIntegrityError(sdkRoot, 'platform-tools/adb');
 }
 
 function getGmsaasPath() {
@@ -142,15 +152,20 @@ function throwMissingAvdError(avdName, avdPath, avdIniPath) {
   );
 }
 
-function throwSdkIntegrityError(sdkRoot, relativeExecutablePath) {
-  const executablePath = path.join(sdkRoot, relativeExecutablePath);
-  const name = path.basename(executablePath);
-  const dir = path.dirname(executablePath);
+function throwSdkBinIntegrityError(sdkRoot, relativeBinPath) {
+  const executablePath = path.join(sdkRoot, relativeBinPath);
+  throwSdkToolPathError(executablePath);
+}
 
-  throw new DetoxRuntimeError(
-    `There was no "${name}" executable file in directory: ${dir}.\n` +
-    `Check integrity of your Android SDK.`
-  );
+function throwSdkToolPathError(sdkToolPath) {
+  const name = path.basename(sdkToolPath);
+  const dir = path.dirname(sdkToolPath);
+
+  throwSdkIntegrityError(`There was no "${name}" executable file in directory: ${dir}`);
+}
+
+function throwSdkIntegrityError(errMessage) {
+  throw new DetoxRuntimeError(`${errMessage}\nCheck the integrity of your Android SDK.`);
 }
 
 function throwMissingGmsaasError() {
diff --git a/examples/demo-native-android/package.json b/examples/demo-native-android/package.json
index 02a43c412..86ce81a75 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": "19.8.4",
+  "version": "19.9.0",
   "private": true,
   "scripts": {
     "packager": "react-native start",
@@ -8,7 +8,7 @@
     "e2e": "mocha e2e --opts ./e2e/mocha.opts"
   },
   "devDependencies": {
-    "detox": "^19.8.4",
+    "detox": "^19.9.0",
     "mocha": "^6.1.3"
   },
   "detox": {}
diff --git a/examples/demo-native-ios/package.json b/examples/demo-native-ios/package.json
index 1f4ba06a4..7a68fae5e 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": "19.8.4",
+  "version": "19.9.0",
   "private": true,
   "devDependencies": {
-    "detox": "^19.8.4",
+    "detox": "^19.9.0",
     "mocha": "^6.1.3"
   },
   "detox": {
diff --git a/examples/demo-plugin/package.json b/examples/demo-plugin/package.json
index b3dda0cbc..d4d43ef2d 100644
--- a/examples/demo-plugin/package.json
+++ b/examples/demo-plugin/package.json
@@ -1,12 +1,12 @@
 {
   "name": "demo-plugin",
-  "version": "19.8.4",
+  "version": "19.9.0",
   "private": true,
   "scripts": {
     "test:plugin": "detox test --configuration plugin -l verbose"
   },
   "devDependencies": {
-    "detox": "^19.8.4",
+    "detox": "^19.9.0",
     "jest": "^28.0.0"
   },
   "detox": {
diff --git a/examples/demo-react-native-detox-instruments/package.json b/examples/demo-react-native-detox-instruments/package.json
index 0dd730976..8d0708cc9 100644
--- a/examples/demo-react-native-detox-instruments/package.json
+++ b/examples/demo-react-native-detox-instruments/package.json
@@ -1,10 +1,10 @@
 {
   "name": "demo-react-native-detox-instruments",
-  "version": "19.8.4",
+  "version": "19.9.0",
   "private": true,
   "scripts": {},
   "devDependencies": {
-    "detox": "^19.8.4",
+    "detox": "^19.9.0",
     "mocha": "6.x.x"
   },
   "detox": {
diff --git a/examples/demo-react-native-jest/package.json b/examples/demo-react-native-jest/package.json
index fd5311630..677e38920 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": "19.8.4",
+  "version": "19.9.0",
   "private": true,
   "scripts": {
     "test:ios-release": "detox test --configuration ios.sim.release -l verbose",
@@ -12,7 +12,7 @@
   },
   "devDependencies": {
     "@types/jest": "^27.0.3",
-    "detox": "^19.8.4",
+    "detox": "^19.9.0",
     "jest": "^28.0.0",
     "sanitize-filename": "^1.6.1",
     "ts-jest": "^28.0.0",
diff --git a/examples/demo-react-native/package.json b/examples/demo-react-native/package.json
index cdc1ec0a9..f7a7ba133 100644
--- a/examples/demo-react-native/package.json
+++ b/examples/demo-react-native/package.json
@@ -1,6 +1,6 @@
 {
   "name": "example",
-  "version": "19.8.4",
+  "version": "19.9.0",
   "private": true,
   "scripts": {
     "start": "react-native start",
@@ -31,7 +31,7 @@
   },
   "devDependencies": {
     "@types/mocha": "^8.2.0",
-    "detox": "^19.8.4",
+    "detox": "^19.9.0",
     "mocha": "^6.1.3",
     "ts-node": "^9.1.1",
     "typescript": "^4.1.3"
diff --git a/lerna.json b/lerna.json
index fb565dca0..de5dc2e5a 100644
--- a/lerna.json
+++ b/lerna.json
@@ -13,7 +13,7 @@
     "generation",
     "."
   ],
-  "version": "19.8.4",
+  "version": "19.9.0",
   "npmClient": "npm",
   "command": {
     "publish": {
diff --git a/package.json b/package.json
index f63bc1bdd..8d2198610 100644
--- a/package.json
+++ b/package.json
@@ -54,5 +54,5 @@
     "shell-utils": "1.x.x",
     "unified": "^10.1.0"
   },
-  "version": "19.8.4"
+  "version": "19.9.0"
 }


Test patch:
diff --git a/detox/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.test.js b/detox/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.test.js
index 120d86ed0..d0fc14a5b 100644
--- a/detox/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.test.js
+++ b/detox/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.test.js
@@ -171,6 +171,7 @@ describe('Allocation driver for Genymotion cloud emulators', () => {
       await allocDriver.allocate(deviceConfig);
 
       expect(adb.disableAndroidAnimations).toHaveBeenCalledWith(instance.adbName);
+      expect(adb.setWiFiToggle).toHaveBeenCalledWith(instance.adbName, true);
     });
 
     it('should inquire the API level', async () => {
diff --git a/detox/src/devices/common/drivers/android/exec/ADB.test.js b/detox/src/devices/common/drivers/android/exec/ADB.test.js
index bf9628dbe..805480028 100644
--- a/detox/src/devices/common/drivers/android/exec/ADB.test.js
+++ b/detox/src/devices/common/drivers/android/exec/ADB.test.js
@@ -326,18 +326,30 @@ describe('ADB', () => {
 
   describe('animation disabling', () => {
     it('should disable animator (e.g. ObjectAnimator) animations', async () => {
-      await adb.disableAndroidAnimations();
-      expect(execWithRetriesAndLogs).toHaveBeenCalledWith(`"${adbBinPath}"  shell "settings put global animator_duration_scale 0"`, { retries: 1 });
+      await adb.disableAndroidAnimations(deviceId);
+      expect(execWithRetriesAndLogs).toHaveBeenCalledWith(`"${adbBinPath}" -s ${deviceId} shell "settings put global animator_duration_scale 0"`, { retries: 1 });
     });
 
     it('should disable window animations', async () => {
-      await adb.disableAndroidAnimations();
-      expect(execWithRetriesAndLogs).toHaveBeenCalledWith(`"${adbBinPath}"  shell "settings put global window_animation_scale 0"`, { retries: 1 });
+      await adb.disableAndroidAnimations(deviceId);
+      expect(execWithRetriesAndLogs).toHaveBeenCalledWith(`"${adbBinPath}" -s ${deviceId} shell "settings put global window_animation_scale 0"`, { retries: 1 });
     });
 
     it('should disable transition (e.g. activity launch) animations', async () => {
-      await adb.disableAndroidAnimations();
-      expect(execWithRetriesAndLogs).toHaveBeenCalledWith(`"${adbBinPath}"  shell "settings put global transition_animation_scale 0"`, { retries: 1 });
+      await adb.disableAndroidAnimations(deviceId);
+      expect(execWithRetriesAndLogs).toHaveBeenCalledWith(`"${adbBinPath}" -s ${deviceId} shell "settings put global transition_animation_scale 0"`, { retries: 1 });
+    });
+  });
+
+  describe('WiFi toggle', () => {
+    it('should enable wifi', async () => {
+      await adb.setWiFiToggle(deviceId, true);
+      expect(execWithRetriesAndLogs).toHaveBeenCalledWith(`"${adbBinPath}" -s ${deviceId} shell "svc wifi enable"`, { retries: 1 });
+    });
+
+    it('should disable wifi', async () => {
+      await adb.setWiFiToggle(deviceId, false);
+      expect(execWithRetriesAndLogs).toHaveBeenCalledWith(`"${adbBinPath}" -s ${deviceId} shell "svc wifi disable"`, { retries: 1 });
     });
   });
 });
diff --git a/detox/src/utils/environment.test.js b/detox/src/utils/environment.test.js
index d227c3dd0..d4b3875c6 100644
--- a/detox/src/utils/environment.test.js
+++ b/detox/src/utils/environment.test.js
@@ -202,9 +202,17 @@ describe('Environment', () => {
           expect(await Environment.getAaptPath()).toMatch(/.build-tools.20.0.0.aapt/);
         });
 
-        it('should throw error if aapt is not found inside build-tools/<someDir>', async () => {
-          await genExec('build-tools/aapt');
-          await expect(Environment.getAaptPath()).rejects.toThrow(/There was no.*file in directory:/);
+        it('should throw error if aapt is missing inside (a valid) build-tools/<sdk-version> directory', async () => {
+          const validBuildToolsPath = 'build-tools/20.0.0';
+          const expectedErrPath = path.join(tempSdkPath, validBuildToolsPath);
+
+          await genExec(`${validBuildToolsPath}/not-aapt`);
+          await expect(Environment.getAaptPath()).rejects.toThrow(`There was no "aapt" executable file in directory: ${expectedErrPath}`);
+        });
+
+        it('should throw error if there are no inner <sdk-version> directories where aapt could reside', async () => {
+          await genExec('build-tools/dummy');
+          await expect(Environment.getAaptPath()).rejects.toThrow('Failed to find the "aapt" tool under the Android SDK: No build-tools are installed!');
         });
       });
 
@@ -226,7 +234,9 @@ describe('Environment', () => {
         });
 
         it('should throw error if adb is not found in platform-tools/', async () => {
-          await expect(Environment.getAaptPath()).rejects.toThrow(/There was no.*file in directory:/);
+          const expectedErrPath = path.join(tempSdkPath, 'platform-tools');
+
+          expect(() => Environment.getAdbPath()).toThrow(`There was no "adb" executable file in directory: ${expectedErrPath}`);
         });
       });
 
diff --git a/detox/test/package.json b/detox/test/package.json
index bda19d411..34a1d6b9c 100644
--- a/detox/test/package.json
+++ b/detox/test/package.json
@@ -1,6 +1,6 @@
 {
   "name": "detox-test",
-  "version": "19.8.4",
+  "version": "19.9.0",
   "private": true,
   "engines": {
     "node": ">=8.3.0"
@@ -50,7 +50,7 @@
     "@types/react-native": "^0.64.19",
     "@typescript-eslint/eslint-plugin": "^5.4.0",
     "@typescript-eslint/parser": "^5.4.0",
-    "detox": "^19.8.4",
+    "detox": "^19.9.0",
     "eslint": "^7.32.0",
     "eslint-plugin-unicorn": "^39.0.0",
     "express": "^4.15.3",