Repository: wix/Detox
Pull request #3400 (2022-05-16 17:06:23)

build: move jest-circus/ facade to jest/
## Description

In this PR, since there is only single integration with `jest` and `jest-circus`, and the latter is a default choice for Jest 27, and the only choice for Jest 28 (unless `jest-jasmine2` is installed and configured explicitly), I consider it justified to shorten the paths to the user-facing files.

### Required changes from the users

In the environment class file:

```diff
 const {
   DetoxCircusEnvironment,
   SpecReporter,
   WorkerAssignReporter,
-} = require('detox/runners/jest-circus');
+} = require('detox/runners/jest');
```

In Jest config:

```diff
   "testTimeout": 120000,
-  "reporters": ["detox/runners/jest/streamlineReporter"],
+  "reporters": ["detox/runners/jest/reporter"],
   "verbose": true
```

### Migration flow

As for the attempts to import the files at their old locations, the users will be seeing warnings:

```
[DEPRECATION] Detox detected an attempt to require a module from an outdated location, please change in your project:
- detox/runners/jest-circus/environment
+ detox/runners/jest/environment
```

That should enact the migration process smoothly enough.

> _For features/enhancements:_
 - [x] I have added/updated the relevant references in the [documentation](https://github.com/wix/Detox/tree/master/docs) files.

d4vidi: @noomorph kudos for finding those building blocks that allow moving forward with this in a step-by-step fashion ❤️ 

Patch:
diff --git a/detox/local-cli/templates/jest.js b/detox/local-cli/templates/jest.js
index 554a016e4..d15be4e56 100644
--- a/detox/local-cli/templates/jest.js
+++ b/detox/local-cli/templates/jest.js
@@ -5,7 +5,7 @@ const runnerConfig = `{
     "testEnvironment": "./environment",
     "testTimeout": 120000,
     "testMatch": ["<rootDir>/e2e/*.e2e.js"],
-    "reporters": ["detox/runners/jest-circus/reporter"],
+    "reporters": ["detox/runners/jest/reporter"],
     "verbose": true
 }
 `;
@@ -14,7 +14,7 @@ const environmentJsContent = `const {
   DetoxCircusEnvironment,
   SpecReporter,
   WorkerAssignReporter,
-} = require('detox/runners/jest-circus');
+} = require('detox/runners/jest');
 
 class CustomDetoxEnvironment extends DetoxCircusEnvironment {
   constructor(config, context) {
diff --git a/detox/package.json b/detox/package.json
index 4fa17db25..4eab08a2a 100644
--- a/detox/package.json
+++ b/detox/package.json
@@ -147,7 +147,7 @@
       "src/utils/pipeCommands.js",
       "src/utils/pressAnyKey.js",
       "src/utils/shellUtils.js",
-      "runners/jest-circus/utils/assertJestCircus27.js"
+      "runners/jest/.*.js"
     ],
     "resetMocks": true,
     "resetModules": true,
diff --git a/detox/runners/deprecation.js b/detox/runners/deprecation.js
index 4a15e1210..4decbb2dc 100644
--- a/detox/runners/deprecation.js
+++ b/detox/runners/deprecation.js
@@ -1,5 +1,6 @@
 const chalk = require('chalk');
 
+// @ts-ignore
 console.error(chalk.yellow(`
 =========================  THE NEW JOURNEY BEGINS  =============================
 
diff --git a/detox/runners/jest-circus/environment.js b/detox/runners/jest-circus/environment.js
index 5f4cd0268..16900a0fc 100644
--- a/detox/runners/jest-circus/environment.js
+++ b/detox/runners/jest-circus/environment.js
@@ -1,206 +1,1 @@
-// @ts-nocheck
-const maybeNodeEnvironment = require('jest-environment-node'); // eslint-disable-line node/no-extraneous-require
-const NodeEnvironment = maybeNodeEnvironment.default || maybeNodeEnvironment;
-
-const DetoxError = require('../../src/errors/DetoxError');
-const Timer = require('../../src/utils/Timer');
-
-const DetoxCoreListener = require('./listeners/DetoxCoreListener');
-const DetoxInitErrorListener = require('./listeners/DetoxInitErrorListener');
-const assertExistingContext = require('./utils/assertExistingContext');
-const { assertJestCircus27 } = require('./utils/assertJestCircus27');
-const wrapErrorWithNoopLifecycle = require('./utils/wrapErrorWithNoopLifecycle');
-
-const SYNC_CIRCUS_EVENTS = new Set([
-  'start_describe_definition',
-  'finish_describe_definition',
-  'add_hook',
-  'add_test',
-  'error',
-]);
-
-/**
- * @see https://www.npmjs.com/package/jest-circus#overview
- */
-class DetoxCircusEnvironment extends NodeEnvironment {
-  constructor(config, context) {
-    super(assertJestCircus27(config), assertExistingContext(context));
-
-    /** @private */
-    this._timer = null;
-    /** @private */
-    this._listenerFactories = {
-      DetoxInitErrorListener,
-      DetoxCoreListener,
-    };
-    /** @private */
-    this._calledDetoxInit = false;
-    /** @private */
-    this._calledDetoxCleanup = false;
-    /** @protected */
-    this.testPath = context.testPath;
-    /** @protected */
-    this.testEventListeners = [];
-    /** @protected */
-    this.initTimeout = 300000;
-  }
-
-  async setup() {
-    await super.setup();
-
-    this.global.detox = require('../../src')
-      ._setGlobal(this.global)
-      ._suppressLoggingInitErrors();
-  }
-
-  async teardown() {
-    try {
-      if (this._calledDetoxInit && !this._calledDetoxCleanup) {
-        await this._runEmergencyTeardown();
-      }
-    } finally {
-      await super.teardown();
-    }
-  }
-
-  get detox() {
-    return this.global.detox;
-  }
-
-  async handleTestEvent(event, state) {
-    const { name } = event;
-
-    if (SYNC_CIRCUS_EVENTS.has(name)) {
-      return this._handleTestEventSync(event, state);
-    }
-
-    this._timer = new Timer({
-      description: `handling jest-circus "${name}" event`,
-      timeout: name === 'setup' ? this.initTimeout : state.testTimeout,
-    });
-
-    try {
-      if (name === 'setup') {
-        await this._onSetup(state);
-      }
-
-      for (const listener of this.testEventListeners) {
-        if (typeof listener[name] === 'function') {
-          try {
-            await this._timer.run(() => listener[name](event, state));
-          } catch (listenerError) {
-            this._logError(listenerError);
-          }
-        }
-      }
-
-      if (name === 'teardown') {
-        await this._onTeardown(state);
-      }
-    } finally {
-      this._timer.dispose();
-      this._timer = null;
-    }
-  }
-
-  _handleTestEventSync(event, state) {
-    const { name } = event;
-
-    for (const listener of this.testEventListeners) {
-      if (typeof listener[name] === 'function') {
-        listener[name](event, state);
-      }
-    }
-  }
-
-  async _onSetup(state) {
-    let detox;
-
-    try {
-      detox = await this._timer.run(async () => {
-        try {
-          this._calledDetoxInit = true;
-          return await this.initDetox();
-        } catch (actualError) {
-          state.unhandledErrors.push(actualError);
-          this._logError(actualError);
-          throw actualError;
-        }
-      });
-    } catch (maybeActualError) {
-      if (!state.unhandledErrors.includes(maybeActualError)) {
-        const timeoutError = maybeActualError;
-        state.unhandledErrors.push(timeoutError);
-        this._logError(timeoutError);
-      }
-
-      detox = wrapErrorWithNoopLifecycle(maybeActualError);
-    } finally {
-      this._timer.reset(state.testTimeout);
-    }
-
-    this._instantiateListeners(detox);
-  }
-
-  _instantiateListeners(detoxInstance) {
-    for (const Listener of Object.values(this._listenerFactories)) {
-      this.testEventListeners.push(new Listener({
-        detox: detoxInstance,
-        env: this,
-      }));
-    }
-  }
-
-  async _onTeardown(state) {
-    try {
-      this._calledDetoxCleanup = true;
-      await this._timer.run(() => this.cleanupDetox());
-    } catch (cleanupError) {
-      state.unhandledErrors.push(cleanupError);
-      this._logError(cleanupError);
-    }
-  }
-
-  async _runEmergencyTeardown() {
-    this._timer = new Timer({
-      description: `handling environment teardown`,
-      timeout: this.initTimeout,
-    });
-
-    try {
-      await this._timer.run(() => this.cleanupDetox());
-    } catch (cleanupError) {
-      this._logError(cleanupError);
-    } finally {
-      this._timer.dispose();
-      this._timer = null;
-    }
-  }
-
-  /** @private */
-  get _logger() {
-    return require('../../src/utils/logger');
-  }
-
-  /** @private */
-  _logError(e) {
-    this._logger.error(DetoxError.format(e));
-  }
-
-  /** @protected */
-  async initDetox() {
-    return this.detox.init();
-  }
-
-  /** @protected */
-  async cleanupDetox() {
-    return this.detox.cleanup();
-  }
-
-  /** @protected */
-  registerListeners(map) {
-    Object.assign(this._listenerFactories, map);
-  }
-}
-
-module.exports = DetoxCircusEnvironment;
+module.exports = require('../migration')(__filename);
diff --git a/detox/runners/jest-circus/index.js b/detox/runners/jest-circus/index.js
index 950cce91e..16900a0fc 100644
--- a/detox/runners/jest-circus/index.js
+++ b/detox/runners/jest-circus/index.js
@@ -1,9 +1,1 @@
-const DetoxCircusEnvironment = require('./environment');
-const SpecReporter = require('./listeners/SpecReporter');
-const WorkerAssignReporter = require('./listeners/WorkerAssignReporter');
-
-module.exports = {
-  DetoxCircusEnvironment,
-  SpecReporter,
-  WorkerAssignReporter,
-};
+module.exports = require('../migration')(__filename);
diff --git a/detox/runners/jest-circus/reporter.js b/detox/runners/jest-circus/reporter.js
index a6f87d991..16900a0fc 100644
--- a/detox/runners/jest-circus/reporter.js
+++ b/detox/runners/jest-circus/reporter.js
@@ -1,1 +1,1 @@
-module.exports = require('./reporters/DetoxReporter');
+module.exports = require('../migration')(__filename);
diff --git a/detox/runners/jest/JestCircusEnvironment.js b/detox/runners/jest/JestCircusEnvironment.js
index aac212fbb..6bbde977e 100644
--- a/detox/runners/jest/JestCircusEnvironment.js
+++ b/detox/runners/jest/JestCircusEnvironment.js
@@ -1,1 +1,3 @@
-require('./deprecation');
+/** @deprecated */
+/** @type {never} */
+module.exports = require('../deprecation');
diff --git a/detox/runners/jest/adapter.js b/detox/runners/jest/adapter.js
index a3a1ad878..6bbde977e 100644
--- a/detox/runners/jest/adapter.js
+++ b/detox/runners/jest/adapter.js
@@ -1,1 +1,3 @@
-require('../deprecation');
+/** @deprecated */
+/** @type {never} */
+module.exports = require('../deprecation');
diff --git a/detox/runners/jest/assignReporter.js b/detox/runners/jest/assignReporter.js
index a3a1ad878..6bbde977e 100644
--- a/detox/runners/jest/assignReporter.js
+++ b/detox/runners/jest/assignReporter.js
@@ -1,1 +1,3 @@
-require('../deprecation');
+/** @deprecated */
+/** @type {never} */
+module.exports = require('../deprecation');
diff --git a/detox/runners/jest/environment.js b/detox/runners/jest/environment.js
new file mode 100644
index 000000000..5f4cd0268
--- /dev/null
+++ b/detox/runners/jest/environment.js
@@ -0,0 +1,206 @@
+// @ts-nocheck
+const maybeNodeEnvironment = require('jest-environment-node'); // eslint-disable-line node/no-extraneous-require
+const NodeEnvironment = maybeNodeEnvironment.default || maybeNodeEnvironment;
+
+const DetoxError = require('../../src/errors/DetoxError');
+const Timer = require('../../src/utils/Timer');
+
+const DetoxCoreListener = require('./listeners/DetoxCoreListener');
+const DetoxInitErrorListener = require('./listeners/DetoxInitErrorListener');
+const assertExistingContext = require('./utils/assertExistingContext');
+const { assertJestCircus27 } = require('./utils/assertJestCircus27');
+const wrapErrorWithNoopLifecycle = require('./utils/wrapErrorWithNoopLifecycle');
+
+const SYNC_CIRCUS_EVENTS = new Set([
+  'start_describe_definition',
+  'finish_describe_definition',
+  'add_hook',
+  'add_test',
+  'error',
+]);
+
+/**
+ * @see https://www.npmjs.com/package/jest-circus#overview
+ */
+class DetoxCircusEnvironment extends NodeEnvironment {
+  constructor(config, context) {
+    super(assertJestCircus27(config), assertExistingContext(context));
+
+    /** @private */
+    this._timer = null;
+    /** @private */
+    this._listenerFactories = {
+      DetoxInitErrorListener,
+      DetoxCoreListener,
+    };
+    /** @private */
+    this._calledDetoxInit = false;
+    /** @private */
+    this._calledDetoxCleanup = false;
+    /** @protected */
+    this.testPath = context.testPath;
+    /** @protected */
+    this.testEventListeners = [];
+    /** @protected */
+    this.initTimeout = 300000;
+  }
+
+  async setup() {
+    await super.setup();
+
+    this.global.detox = require('../../src')
+      ._setGlobal(this.global)
+      ._suppressLoggingInitErrors();
+  }
+
+  async teardown() {
+    try {
+      if (this._calledDetoxInit && !this._calledDetoxCleanup) {
+        await this._runEmergencyTeardown();
+      }
+    } finally {
+      await super.teardown();
+    }
+  }
+
+  get detox() {
+    return this.global.detox;
+  }
+
+  async handleTestEvent(event, state) {
+    const { name } = event;
+
+    if (SYNC_CIRCUS_EVENTS.has(name)) {
+      return this._handleTestEventSync(event, state);
+    }
+
+    this._timer = new Timer({
+      description: `handling jest-circus "${name}" event`,
+      timeout: name === 'setup' ? this.initTimeout : state.testTimeout,
+    });
+
+    try {
+      if (name === 'setup') {
+        await this._onSetup(state);
+      }
+
+      for (const listener of this.testEventListeners) {
+        if (typeof listener[name] === 'function') {
+          try {
+            await this._timer.run(() => listener[name](event, state));
+          } catch (listenerError) {
+            this._logError(listenerError);
+          }
+        }
+      }
+
+      if (name === 'teardown') {
+        await this._onTeardown(state);
+      }
+    } finally {
+      this._timer.dispose();
+      this._timer = null;
+    }
+  }
+
+  _handleTestEventSync(event, state) {
+    const { name } = event;
+
+    for (const listener of this.testEventListeners) {
+      if (typeof listener[name] === 'function') {
+        listener[name](event, state);
+      }
+    }
+  }
+
+  async _onSetup(state) {
+    let detox;
+
+    try {
+      detox = await this._timer.run(async () => {
+        try {
+          this._calledDetoxInit = true;
+          return await this.initDetox();
+        } catch (actualError) {
+          state.unhandledErrors.push(actualError);
+          this._logError(actualError);
+          throw actualError;
+        }
+      });
+    } catch (maybeActualError) {
+      if (!state.unhandledErrors.includes(maybeActualError)) {
+        const timeoutError = maybeActualError;
+        state.unhandledErrors.push(timeoutError);
+        this._logError(timeoutError);
+      }
+
+      detox = wrapErrorWithNoopLifecycle(maybeActualError);
+    } finally {
+      this._timer.reset(state.testTimeout);
+    }
+
+    this._instantiateListeners(detox);
+  }
+
+  _instantiateListeners(detoxInstance) {
+    for (const Listener of Object.values(this._listenerFactories)) {
+      this.testEventListeners.push(new Listener({
+        detox: detoxInstance,
+        env: this,
+      }));
+    }
+  }
+
+  async _onTeardown(state) {
+    try {
+      this._calledDetoxCleanup = true;
+      await this._timer.run(() => this.cleanupDetox());
+    } catch (cleanupError) {
+      state.unhandledErrors.push(cleanupError);
+      this._logError(cleanupError);
+    }
+  }
+
+  async _runEmergencyTeardown() {
+    this._timer = new Timer({
+      description: `handling environment teardown`,
+      timeout: this.initTimeout,
+    });
+
+    try {
+      await this._timer.run(() => this.cleanupDetox());
+    } catch (cleanupError) {
+      this._logError(cleanupError);
+    } finally {
+      this._timer.dispose();
+      this._timer = null;
+    }
+  }
+
+  /** @private */
+  get _logger() {
+    return require('../../src/utils/logger');
+  }
+
+  /** @private */
+  _logError(e) {
+    this._logger.error(DetoxError.format(e));
+  }
+
+  /** @protected */
+  async initDetox() {
+    return this.detox.init();
+  }
+
+  /** @protected */
+  async cleanupDetox() {
+    return this.detox.cleanup();
+  }
+
+  /** @protected */
+  registerListeners(map) {
+    Object.assign(this._listenerFactories, map);
+  }
+}
+
+module.exports = DetoxCircusEnvironment;
diff --git a/detox/runners/jest/index.js b/detox/runners/jest/index.js
new file mode 100644
index 000000000..950cce91e
--- /dev/null
+++ b/detox/runners/jest/index.js
@@ -0,0 +1,9 @@
+const DetoxCircusEnvironment = require('./environment');
+const SpecReporter = require('./listeners/SpecReporter');
+const WorkerAssignReporter = require('./listeners/WorkerAssignReporter');
+
+module.exports = {
+  DetoxCircusEnvironment,
+  SpecReporter,
+  WorkerAssignReporter,
+};
diff --git a/detox/runners/jest-circus/listeners/DetoxCoreListener.js b/detox/runners/jest/listeners/DetoxCoreListener.js
similarity index 100%
rename from detox/runners/jest-circus/listeners/DetoxCoreListener.js
rename to detox/runners/jest/listeners/DetoxCoreListener.js
diff --git a/detox/runners/jest-circus/listeners/DetoxInitErrorListener.js b/detox/runners/jest/listeners/DetoxInitErrorListener.js
similarity index 100%
rename from detox/runners/jest-circus/listeners/DetoxInitErrorListener.js
rename to detox/runners/jest/listeners/DetoxInitErrorListener.js
diff --git a/detox/runners/jest-circus/listeners/SpecReporter.js b/detox/runners/jest/listeners/SpecReporter.js
similarity index 100%
rename from detox/runners/jest-circus/listeners/SpecReporter.js
rename to detox/runners/jest/listeners/SpecReporter.js
diff --git a/detox/runners/jest-circus/listeners/WorkerAssignReporter.js b/detox/runners/jest/listeners/WorkerAssignReporter.js
similarity index 100%
rename from detox/runners/jest-circus/listeners/WorkerAssignReporter.js
rename to detox/runners/jest/listeners/WorkerAssignReporter.js
diff --git a/detox/runners/jest/reporter.js b/detox/runners/jest/reporter.js
new file mode 100644
index 000000000..a6f87d991
--- /dev/null
+++ b/detox/runners/jest/reporter.js
@@ -0,0 +1,1 @@
+module.exports = require('./reporters/DetoxReporter');
diff --git a/detox/runners/jest-circus/reporters/DetoxReporter.js b/detox/runners/jest/reporters/DetoxReporter.js
similarity index 100%
rename from detox/runners/jest-circus/reporters/DetoxReporter.js
rename to detox/runners/jest/reporters/DetoxReporter.js
diff --git a/detox/runners/jest-circus/reporters/FailingTestsReporter.js b/detox/runners/jest/reporters/FailingTestsReporter.js
similarity index 100%
rename from detox/runners/jest-circus/reporters/FailingTestsReporter.js
rename to detox/runners/jest/reporters/FailingTestsReporter.js
diff --git a/detox/runners/jest/specReporter.js b/detox/runners/jest/specReporter.js
index a3a1ad878..6bbde977e 100644
--- a/detox/runners/jest/specReporter.js
+++ b/detox/runners/jest/specReporter.js
@@ -1,1 +1,3 @@
-require('../deprecation');
+/** @deprecated */
+/** @type {never} */
+module.exports = require('../deprecation');
diff --git a/detox/runners/jest/streamlineReporter.js b/detox/runners/jest/streamlineReporter.js
index 196e6cc37..6bbde977e 100644
--- a/detox/runners/jest/streamlineReporter.js
+++ b/detox/runners/jest/streamlineReporter.js
@@ -1,1 +1,3 @@
-module.exports = require('../jest-circus/reporter');
+/** @deprecated */
+/** @type {never} */
+module.exports = require('../deprecation');
diff --git a/detox/runners/jest-circus/utils/assertExistingContext.js b/detox/runners/jest/utils/assertExistingContext.js
similarity index 100%
rename from detox/runners/jest-circus/utils/assertExistingContext.js
rename to detox/runners/jest/utils/assertExistingContext.js
diff --git a/detox/runners/jest-circus/utils/assertJestCircus27.js b/detox/runners/jest/utils/assertJestCircus27.js
similarity index 100%
rename from detox/runners/jest-circus/utils/assertJestCircus27.js
rename to detox/runners/jest/utils/assertJestCircus27.js
diff --git a/detox/runners/jest-circus/utils/assertJestCircus27.test.js b/detox/runners/jest/utils/assertJestCircus27.test.js
similarity index 100%
rename from detox/runners/jest-circus/utils/assertJestCircus27.test.js
rename to detox/runners/jest/utils/assertJestCircus27.test.js
diff --git a/detox/runners/jest-circus/utils/getFullTestName.js b/detox/runners/jest/utils/getFullTestName.js
similarity index 100%
rename from detox/runners/jest-circus/utils/getFullTestName.js
rename to detox/runners/jest/utils/getFullTestName.js
diff --git a/detox/runners/jest-circus/utils/hasTimedOut.js b/detox/runners/jest/utils/hasTimedOut.js
similarity index 100%
rename from detox/runners/jest-circus/utils/hasTimedOut.js
rename to detox/runners/jest/utils/hasTimedOut.js
diff --git a/detox/runners/jest-circus/utils/index.js b/detox/runners/jest/utils/index.js
similarity index 100%
rename from detox/runners/jest-circus/utils/index.js
rename to detox/runners/jest/utils/index.js
diff --git a/detox/runners/jest-circus/utils/stdout.js b/detox/runners/jest/utils/stdout.js
similarity index 100%
rename from detox/runners/jest-circus/utils/stdout.js
rename to detox/runners/jest/utils/stdout.js
diff --git a/detox/runners/jest-circus/utils/wrapErrorWithNoopLifecycle.js b/detox/runners/jest/utils/wrapErrorWithNoopLifecycle.js
similarity index 100%
rename from detox/runners/jest-circus/utils/wrapErrorWithNoopLifecycle.js
rename to detox/runners/jest/utils/wrapErrorWithNoopLifecycle.js
diff --git a/detox/runners/migration.js b/detox/runners/migration.js
new file mode 100644
index 000000000..2b3d93fdd
--- /dev/null
+++ b/detox/runners/migration.js
@@ -0,0 +1,36 @@
+const path = require('path');
+
+const chalk = require('chalk');
+const _ = require('lodash');
+
+const SEARCH_VALUE = ['', 'detox', 'runners', 'jest-circus', ''].join(path.sep);
+const REPLACE_VALUE = ['', 'detox', 'runners', 'jest', ''].join(path.sep);
+
+function jsify(pathToFile) {
+  return 'detox/' + path
+    .relative(
+      path.join(__dirname, '..'),
+      pathToFile
+    )
+    .replace(/\\/g, '/')
+    .replace(/(?:\/index)?\.js$/, '');
+}
+
+const redirectWithWarning = _.memoize((oldPath) => {
+  const newPath = oldPath.replace(SEARCH_VALUE, REPLACE_VALUE);
+
+  redirectWithWarning._log([
+    '[DEPRECATION] Detox detected an attempt to require a module from an outdated location, please change in your project:',
+    `- ${jsify(oldPath)}`,
+    `+ ${jsify(newPath)}`,
+    '',
+  ].join('\n'));
+
+  return require(newPath);
+});
+
+// @ts-ignore
+// istanbul ignore next
+redirectWithWarning._log = msg => process.stderr.write(chalk.yellow(msg) + '\n');
+
+module.exports = redirectWithWarning;
diff --git a/detox/runners/mocha/DetoxMochaAdapter.js b/detox/runners/mocha/DetoxMochaAdapter.js
index a3a1ad878..6bbde977e 100644
--- a/detox/runners/mocha/DetoxMochaAdapter.js
+++ b/detox/runners/mocha/DetoxMochaAdapter.js
@@ -1,1 +1,3 @@
-require('../deprecation');
+/** @deprecated */
+/** @type {never} */
+module.exports = require('../deprecation');
diff --git a/detox/runners/mocha/adapter.js b/detox/runners/mocha/adapter.js
index a3a1ad878..6bbde977e 100644
--- a/detox/runners/mocha/adapter.js
+++ b/detox/runners/mocha/adapter.js
@@ -1,1 +1,3 @@
-require('../deprecation');
+/** @deprecated */
+/** @type {never} */
+module.exports = require('../deprecation');
diff --git a/docs/Guide.Jest.md b/docs/Guide.Jest.md
index a661f588f..e499c9419 100644
--- a/docs/Guide.Jest.md
+++ b/docs/Guide.Jest.md
@@ -112,7 +112,7 @@ A typical `jest-circus` configuration in `e2e/config.json` file would look like:
   "testRunner": "jest-circus/runner",
   "testEnvironment": "./environment",
   "testTimeout": 120000,
-  "reporters": ["detox/runners/jest/streamlineReporter"],
+  "reporters": ["detox/runners/jest/reporter"],
   "verbose": true
 }
 ```
@@ -130,7 +130,7 @@ const {
   DetoxCircusEnvironment,
   SpecReporter,
   WorkerAssignReporter,
-} = require('detox/runners/jest-circus');
+} = require('detox/runners/jest');
 
 class CustomDetoxEnvironment extends DetoxCircusEnvironment {
   constructor(config, context) {


Test patch:
diff --git a/detox/runners/__tests__/__snapshots__/migration.test.js.snap b/detox/runners/__tests__/__snapshots__/migration.test.js.snap
new file mode 100644
index 000000000..f117d178e
--- /dev/null
+++ b/detox/runners/__tests__/__snapshots__/migration.test.js.snap
@@ -0,0 +1,22 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`file migration should work for: jest-circus/environment 1`] = `
+"[DEPRECATION] Detox detected an attempt to require a module from an outdated location, please change in your project:
+- detox/runners/jest-circus/environment
++ detox/runners/jest/environment
+"
+`;
+
+exports[`file migration should work for: jest-circus/index 1`] = `
+"[DEPRECATION] Detox detected an attempt to require a module from an outdated location, please change in your project:
+- detox/runners/jest-circus
++ detox/runners/jest
+"
+`;
+
+exports[`file migration should work for: jest-circus/reporter 1`] = `
+"[DEPRECATION] Detox detected an attempt to require a module from an outdated location, please change in your project:
+- detox/runners/jest-circus/reporter
++ detox/runners/jest/reporter
+"
+`;
diff --git a/detox/runners/__tests__/migration.test.js b/detox/runners/__tests__/migration.test.js
new file mode 100644
index 000000000..cb6424783
--- /dev/null
+++ b/detox/runners/__tests__/migration.test.js
@@ -0,0 +1,21 @@
+describe('file migration', () => {
+  let logger;
+
+  beforeEach(() => {
+    logger = jest.fn();
+    require('../migration')._log = logger;
+  });
+
+  test.each([
+    ['environment'],
+    ['index'],
+    ['reporter'],
+  ])('should work for: jest-circus/%s', (moduleName) => {
+    const newImpl = require(`../jest/${moduleName}`);
+    expect(logger).not.toHaveBeenCalled();
+    expect(require(`../jest-circus/${moduleName}`)).toBe(newImpl);
+    expect(logger.mock.calls[0][0]).toMatchSnapshot();
+    expect(require(`../jest-circus/${moduleName}`)).toBe(newImpl);
+    expect(logger).not.toHaveBeenCalledTimes(2);
+  });
+});
diff --git a/detox/test/e2e-unhappy/detox-init-timeout/environment.js b/detox/test/e2e-unhappy/detox-init-timeout/environment.js
index 71a9d9ee8..7b50f30b3 100644
--- a/detox/test/e2e-unhappy/detox-init-timeout/environment.js
+++ b/detox/test/e2e-unhappy/detox-init-timeout/environment.js
@@ -2,7 +2,7 @@ const {
   DetoxCircusEnvironment,
   SpecReporter,
   WorkerAssignReporter,
-} = require('detox/runners/jest-circus');
+} = require('detox/runners/jest');
 
 class CustomDetoxEnvironment extends DetoxCircusEnvironment {
   constructor(config, context) {
diff --git a/detox/test/e2e/config.js b/detox/test/e2e/config.js
index 1a3c31c90..f9ebe40f0 100644
--- a/detox/test/e2e/config.js
+++ b/detox/test/e2e/config.js
@@ -7,8 +7,8 @@ module.exports = {
   'globalTeardown': './test/e2e/global-teardown.js',
   'testTimeout': 120000,
   'reporters': process.env.DISABLE_JUNIT_REPORTER === '1'
-    ? ['<rootDir>/runners/jest-circus/reporter']
-    : ['<rootDir>/runners/jest-circus/reporter', '<rootDir>/test/node_modules/jest-junit'],
+    ? ['<rootDir>/runners/jest/reporter']
+    : ['<rootDir>/runners/jest/reporter', '<rootDir>/test/node_modules/jest-junit'],
   'verbose': true,
   'bail': false,
   'maxWorkers': Number(process.env.DEMO_MAX_WORKERS || '1'),
diff --git a/detox/test/e2e/environment.js b/detox/test/e2e/environment.js
index 0f7a09ff0..4827aae6d 100644
--- a/detox/test/e2e/environment.js
+++ b/detox/test/e2e/environment.js
@@ -2,7 +2,7 @@ const {
   DetoxCircusEnvironment,
   SpecReporter,
   WorkerAssignReporter,
-} = require('detox/runners/jest-circus');
+} = require('detox/runners/jest');
 
 class CustomDetoxEnvironment extends DetoxCircusEnvironment {
   constructor(config, context) {
diff --git a/examples/demo-plugin/e2e/config.json b/examples/demo-plugin/e2e/config.json
index 7ab11c70e..3d5a6dbf3 100644
--- a/examples/demo-plugin/e2e/config.json
+++ b/examples/demo-plugin/e2e/config.json
@@ -2,6 +2,6 @@
     "testEnvironment": "./environment",
     "testTimeout": 120000,
     "testRegex": "\\.test\\.js$",
-    "reporters": ["detox/runners/jest-circus/reporter"],
+    "reporters": ["detox/runners/jest/reporter"],
     "verbose": true
 }
diff --git a/examples/demo-plugin/e2e/environment.js b/examples/demo-plugin/e2e/environment.js
index 7f4fc942f..2aa79e005 100644
--- a/examples/demo-plugin/e2e/environment.js
+++ b/examples/demo-plugin/e2e/environment.js
@@ -2,7 +2,7 @@ const {
   DetoxCircusEnvironment,
   SpecReporter,
   WorkerAssignReporter,
-} = require('detox/runners/jest-circus');
+} = require('detox/runners/jest');
 
 class CustomDetoxEnvironment extends DetoxCircusEnvironment {
   constructor(config, context) {
diff --git a/examples/demo-react-native-jest/e2e/config.json b/examples/demo-react-native-jest/e2e/config.json
index 1fa68ffa9..57e540bb9 100644
--- a/examples/demo-react-native-jest/e2e/config.json
+++ b/examples/demo-react-native-jest/e2e/config.json
@@ -10,6 +10,6 @@
     "transform": {
       "\\.tsx?$": "ts-jest"
     },
-    "reporters": ["detox/runners/jest-circus/reporter"],
+    "reporters": ["detox/runners/jest/reporter"],
     "verbose": true
 }
diff --git a/examples/demo-react-native-jest/e2e/environment.js b/examples/demo-react-native-jest/e2e/environment.js
index 7f4fc942f..2aa79e005 100644
--- a/examples/demo-react-native-jest/e2e/environment.js
+++ b/examples/demo-react-native-jest/e2e/environment.js
@@ -2,7 +2,7 @@ const {
   DetoxCircusEnvironment,
   SpecReporter,
   WorkerAssignReporter,
-} = require('detox/runners/jest-circus');
+} = require('detox/runners/jest');
 
 class CustomDetoxEnvironment extends DetoxCircusEnvironment {
   constructor(config, context) {
diff --git a/examples/demo-react-native-jest/e2eExplicitRequire/config.json b/examples/demo-react-native-jest/e2eExplicitRequire/config.json
index de1a4c469..a9d802243 100644
--- a/examples/demo-react-native-jest/e2eExplicitRequire/config.json
+++ b/examples/demo-react-native-jest/e2eExplicitRequire/config.json
@@ -6,6 +6,6 @@
     "transform": {
       "\\.tsx?$": "ts-jest"
     },
-    "reporters": ["detox/runners/jest-circus/reporter"],
+    "reporters": ["detox/runners/jest/reporter"],
     "verbose": true
 }
diff --git a/examples/demo-react-native-jest/e2eExplicitRequire/environment.js b/examples/demo-react-native-jest/e2eExplicitRequire/environment.js
index 7f4fc942f..2aa79e005 100644
--- a/examples/demo-react-native-jest/e2eExplicitRequire/environment.js
+++ b/examples/demo-react-native-jest/e2eExplicitRequire/environment.js
@@ -2,7 +2,7 @@ const {
   DetoxCircusEnvironment,
   SpecReporter,
   WorkerAssignReporter,
-} = require('detox/runners/jest-circus');
+} = require('detox/runners/jest');
 
 class CustomDetoxEnvironment extends DetoxCircusEnvironment {
   constructor(config, context) {