Repository: wix/Detox
Pull request #2681 (2021-03-10 08:13:06)

fix(logger): consider also -l, the alias for --loglevel
- [x] This is a small change 

---

**Description:**

It appears that non-`detox test` commands do not really react to `-l` shorthand alias for `--loglevel`.

For example, `detox run-server` – it is confusing that you cannot start it in a standalone mode with `--loglevel trace` when required.

It is easy to show that on the example with INFO/WARN levels.

Before (incorrect, because WARN level is higher than INFO):

![image](https://user-images.githubusercontent.com/1962469/110597266-fae85e80-8188-11eb-9e9f-fca01019dc1a.png)

After (correct, the log level now allows only ERROR and WARN messages):

![image](https://user-images.githubusercontent.com/1962469/110597153-da200900-8188-11eb-9cbd-223af2136180.png)


Patch:
diff --git a/detox/local-cli/cli.js b/detox/local-cli/cli.js
index 0e9f2c129..86b016790 100755
--- a/detox/local-cli/cli.js
+++ b/detox/local-cli/cli.js
@@ -1,6 +1,7 @@
 #!/usr/bin/env node
 global.DETOX_CLI = true;
 const yargs = require('yargs');
+const DetoxRuntimeError = require('../src/errors/DetoxRuntimeError');
 const logger = require('../src/utils/logger').child({ __filename });
 
 yargs
@@ -24,7 +25,7 @@ yargs
   .wrap(yargs.terminalWidth() * 0.9)
   .fail(function(msg, err, program) {
     if (err) {
-      const lines = err.toString().split("\n");
+      const lines = DetoxRuntimeError.format(err).split("\n");
       for (const line of lines) {
         logger.error(line);
       }
diff --git a/detox/runners/jest-circus/environment.js b/detox/runners/jest-circus/environment.js
index 554542c3c..258a35434 100644
--- a/detox/runners/jest-circus/environment.js
+++ b/detox/runners/jest-circus/environment.js
@@ -5,6 +5,7 @@ const DetoxInitErrorListener = require('./listeners/DetoxInitErrorListener');
 const assertJestCircus26 = require('./utils/assertJestCircus26');
 const assertExistingContext = require('./utils/assertExistingContext');
 const wrapErrorWithNoopLifecycle = require('./utils/wrapErrorWithNoopLifecycle');
+const DetoxRuntimeError = require('../../src/errors/DetoxRuntimeError');
 const Timer = require('../../src/utils/Timer');
 
 const SYNC_CIRCUS_EVENTS = new Set([
@@ -71,7 +72,7 @@ class DetoxCircusEnvironment extends NodeEnvironment {
           try {
             await this._timer.run(() => listener[name](event, state));
           } catch (listenerError) {
-            this._logger.error(`${listenerError}`);
+            this._logError(listenerError);
           }
         }
       }
@@ -104,7 +105,7 @@ class DetoxCircusEnvironment extends NodeEnvironment {
           return await this.initDetox();
         } catch (actualError) {
           state.unhandledErrors.push(actualError);
-          this._logger.error(`${actualError}`);
+          this._logError(actualError);
           throw actualError;
         }
       });
@@ -112,7 +113,7 @@ class DetoxCircusEnvironment extends NodeEnvironment {
       if (!state.unhandledErrors.includes(maybeActualError)) {
         const timeoutError = maybeActualError;
         state.unhandledErrors.push(timeoutError);
-        this._logger.error(`${timeoutError}`);
+        this._logError(timeoutError);
       }
 
       detox = wrapErrorWithNoopLifecycle(maybeActualError);
@@ -137,7 +138,7 @@ class DetoxCircusEnvironment extends NodeEnvironment {
       await this._timer.run(() => this.cleanupDetox());
     } catch (cleanupError) {
       state.unhandledErrors.push(cleanupError);
-      this._logger.error(`${cleanupError}`);
+      this._logError(cleanupError);
     }
   }
 
@@ -146,6 +147,11 @@ class DetoxCircusEnvironment extends NodeEnvironment {
     return require('../../src/utils/logger');
   }
 
+  /** @private */
+  _logError(e) {
+    this._logger.error(DetoxRuntimeError.format(e));
+  }
+
   /** @protected */
   async initDetox() {
     return this.detox.init();
diff --git a/detox/src/errors/DetoxRuntimeError.js b/detox/src/errors/DetoxRuntimeError.js
index ecfe1e85f..0c2b0fb0d 100644
--- a/detox/src/errors/DetoxRuntimeError.js
+++ b/detox/src/errors/DetoxRuntimeError.js
@@ -6,7 +6,7 @@ class DetoxRuntimeError extends Error {
     message = '',
     hint = '',
     debugInfo = '',
-    inspectOptions,
+    inspectOptions = null,
   } = {}) {
     const formattedMessage = _.compact([
       message,
@@ -30,6 +30,25 @@ class DetoxRuntimeError extends Error {
       ...options,
     });
   }
+
+  /**
+   * @param {*} err
+   */
+  static format(err) {
+    if (err instanceof DetoxRuntimeError) {
+      return err.message;
+    }
+
+    if (_.isError(err) && /^Command failed:/.test(err.message)) {
+      return err.message;
+    }
+
+    if (_.isError(err) && (err.stack || err.message)) {
+      return String(err.stack || err.message);
+    }
+
+    return this.inspectObj(err, { depth: 1 })
+  }
 }
 
 module.exports = DetoxRuntimeError;
diff --git a/detox/src/utils/argparse.js b/detox/src/utils/argparse.js
index 49bfe0da1..af15cd6f0 100644
--- a/detox/src/utils/argparse.js
+++ b/detox/src/utils/argparse.js
@@ -2,12 +2,10 @@ const _ = require('lodash');
 const argv = require('minimist')(process.argv.slice(2));
 const {escape} = require('./pipeCommands');
 
-function getArgValue(key) {
-  let value;
+function getArgValue(key, alias) {
+  let value = _getArgvValue(key, alias);
 
-  if (argv && argv[key]) {
-    value = argv[key];
-  } else {
+  if (value === undefined) {
     const envKey = _.findKey(process.env, matchesKey(
       `DETOX_${_.snakeCase(key)}`.toUpperCase()
     ));
@@ -21,6 +19,18 @@ function getArgValue(key) {
   return value;
 }
 
+function _getArgvValue(...aliases) {
+  if (!argv) {
+    return;
+  }
+
+  for (const alias of aliases) {
+    if (alias && argv[alias]) {
+      return argv[alias];
+    }
+  }
+}
+
 function matchesKey(key) {
   return /* istanbul ignore next */ process.platform === 'win32'
     ? (value, envKey) => envKey.toUpperCase() === key
diff --git a/detox/src/utils/logger.js b/detox/src/utils/logger.js
index 0d4f6132d..2576a7a55 100644
--- a/detox/src/utils/logger.js
+++ b/detox/src/utils/logger.js
@@ -73,7 +73,7 @@ function createPlainBunyanStream({ logPath, level }) {
 }
 
 function init() {
-  const levelFromArg = argparse.getArgValue('loglevel');
+  const levelFromArg = argparse.getArgValue('loglevel', 'l');
   const level = adaptLogLevelName(levelFromArg);
   const bunyanStreams = [createPlainBunyanStream({ level })];
 


Test patch:
diff --git a/detox/src/errors/DetoxRuntimeError.test.js b/detox/src/errors/DetoxRuntimeError.test.js
index 5fe1946f4..44136b882 100644
--- a/detox/src/errors/DetoxRuntimeError.test.js
+++ b/detox/src/errors/DetoxRuntimeError.test.js
@@ -9,6 +9,29 @@ describe(DetoxRuntimeError, () => {
     });
   });
 
+  it('should format any object to an error message', () => {
+    expect(DetoxRuntimeError.format({})).toBe("{}");
+
+    const err = new Error('Command failed: echo Hello world');
+    expect(DetoxRuntimeError.format(err)).toBe(err.message);
+
+    err.message = 'Other error message';
+    expect(DetoxRuntimeError.format(err)).toBe(err.stack);
+
+    delete err.stack;
+    expect(DetoxRuntimeError.format(err)).toBe(err.message);
+
+    delete err.message;
+    expect(DetoxRuntimeError.format(err)).toBe('[Error]');
+
+    const runtimeError = new DetoxRuntimeError({
+      message: 'msg',
+      hint: 'hint',
+    });
+
+    expect(DetoxRuntimeError.format(runtimeError)).toBe(runtimeError.message);
+  });
+
   function varietiesOfInstantiation() {
     return {
       'no args': new DetoxRuntimeError(),
diff --git a/detox/src/utils/argparse.test.js b/detox/src/utils/argparse.test.js
index 77046028c..bac587927 100644
--- a/detox/src/utils/argparse.test.js
+++ b/detox/src/utils/argparse.test.js
@@ -1,6 +1,13 @@
 jest.unmock('process');
 
 describe('argparse', () => {
+  let minimist;
+
+  beforeEach(() => {
+    jest.mock('minimist');
+    minimist = require('minimist');
+  });
+
   describe('getArgValue()', () => {
     describe('using env variables', () => {
       let _env;
@@ -14,6 +21,7 @@ describe('argparse', () => {
           DETOX_FOO_BAR: 'value',
         };
 
+        minimist.mockReturnValue(undefined);
         argparse = require('./argparse');
       });
 
@@ -43,9 +51,7 @@ describe('argparse', () => {
       let argparse;
 
       beforeEach(() => {
-        jest.mock('minimist');
-        const minimist = require('minimist');
-        minimist.mockReturnValue({'kebab-case-key': 'a value'});
+        minimist.mockReturnValue({'kebab-case-key': 'a value', 'b': 'shortened'});
         argparse = require('./argparse');
       });
 
@@ -53,6 +59,10 @@ describe('argparse', () => {
         expect(argparse.getArgValue('blah')).not.toBeDefined();
       });
 
+      it(`alias alternative should fiind the result`, () => {
+        expect(argparse.getArgValue('blah', 'b')).toBe('shortened');
+      });
+
       it(`existing key should return a result`, () => {
         expect(argparse.getArgValue('kebab-case-key')).toBe('a value');
       });
@@ -63,8 +73,6 @@ describe('argparse', () => {
     let argparse;
 
     beforeEach(() => {
-      jest.mock('minimist');
-      const minimist = require('minimist');
       minimist.mockReturnValue({'flag-true': 1, 'flag-false': 0});
       argparse = require('./argparse');
     });