Repository: wix/Detox
Pull request #3784 (2022-12-27 20:41:55)

fix(Android): java.lang.AbstractMethodError coming from status inquiry
## 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 removed the default implementation of `DescriptiveIdlingResource.getBusyHint()`, introduced within that kotlin interface itself; As I suspected, the implementation is not well honored across Java derivatives, at runtime, hence causing errors such as this one:

```
 @Thread detox.secondary(665):
    java.lang.AbstractMethodError: abstract method "java.util.Map com.wix.detox.espresso.idlingresources.DescriptiveIdlingResource.getBusyHint()"
    	at com.wix.detox.inquiry.DetoxBusyResource$BusyIdlingResource.getIRDescription(DetoxBusyResource.kt:26)
    	at com.wix.detox.inquiry.DetoxBusyResource$BusyIdlingResource.getDescription(DetoxBusyResource.kt:13)
    	at com.wix.detox.adapters.server.QueryStatusActionHandler.formatStatus(QueryStatusActionHandler.kt:25)
    	at com.wix.detox.adapters.server.QueryStatusActionHandler.handle(QueryStatusActionHandler.kt:14)
    	at com.wix.detox.DetoxMain$initActionHandlers$2$1.handle(DetoxMain.kt:87)
    	at com.wix.detox.adapters.server.ActionsExecutor.executeAction$lambda-2$lambda-1(DetoxActionsDispatcher.kt:64)
    	at com.wix.detox.adapters.server.ActionsExecutor.$r8$lambda$nk3cF8ntnfFqbJOcVqd04vjb5nY(Unknown Source:0)
    	at com.wix.detox.adapters.server.ActionsExecutor$$ExternalSyntheticLambda1.run(Unknown Source:8)
    	at android.os.Handler.handleCallback(Handler.java:873)
    	at android.os.Handler.dispatchMessage(Handler.java:99)
    	at android.os.Looper.loop(Looper.java:193)
    	at com.wix.detox.adapters.server.ActionsExecutor._init_$lambda-0(DetoxActionsDispatcher.kt:50)
    	at com.wix.detox.adapters.server.ActionsExecutor.$r8$lambda$Hi5vyewqQiQqk57AkJy_cHqNGKY(Unknown Source:0)
    	at com.wix.detox.adapters.server.ActionsExecutor$$ExternalSyntheticLambda0.run(Unknown Source:4)
    	at java.lang.Thread.run(Thread.java:764)
```

<!--
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/android/detox/src/full/java/com/wix/detox/espresso/idlingresources/DescriptiveIdlingResource.kt b/detox/android/detox/src/full/java/com/wix/detox/espresso/idlingresources/DescriptiveIdlingResource.kt
index 605d3ef0f..87b861fd6 100644
--- a/detox/android/detox/src/full/java/com/wix/detox/espresso/idlingresources/DescriptiveIdlingResource.kt
+++ b/detox/android/detox/src/full/java/com/wix/detox/espresso/idlingresources/DescriptiveIdlingResource.kt
@@ -4,5 +4,5 @@ import androidx.test.espresso.IdlingResource
 
 interface DescriptiveIdlingResource: IdlingResource {
     fun getDebugName(): String
-    fun getBusyHint(): Map<String, Any>? = null
+    fun getBusyHint(): Map<String, Any>?
 }
diff --git a/detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/AsyncStorageIdlingResource.kt b/detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/AsyncStorageIdlingResource.kt
index a05183072..5a00637dc 100644
--- a/detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/AsyncStorageIdlingResource.kt
+++ b/detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/AsyncStorageIdlingResource.kt
@@ -52,6 +52,7 @@ open class AsyncStorageIdlingResource
 
     override fun getName(): String = javaClass.name
     override fun getDebugName() = "io"
+    override fun getBusyHint(): Map<String, Any>? = null
 
     override fun isIdleNow(): Boolean =
         checkIdle().also { idle ->
diff --git a/detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/BridgeIdlingResource.java b/detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/BridgeIdlingResource.java
index cbb097931..61bea96ed 100644
--- a/detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/BridgeIdlingResource.java
+++ b/detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/BridgeIdlingResource.java
@@ -48,6 +48,12 @@ public class BridgeIdlingResource extends DetoxBaseIdlingResource implements Not
         return "bridge";
     }
 
+    @Nullable
+    @Override
+    public Map<String, Object> getBusyHint() {
+        return null;
+    }
+
     @Override
     protected boolean checkIdle() {
         boolean ret = idleNow.get();
@@ -85,10 +91,4 @@ public class BridgeIdlingResource extends DetoxBaseIdlingResource implements Not
             callback.onTransitionToIdle();
         }
     }
-
-    @Nullable
-    @Override
-    public Map<String, Object> getBusyHint() {
-        return null;
-    }
 }
diff --git a/detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/TimersIdlingResource.kt b/detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/TimersIdlingResource.kt
index 824bb8d57..126725a4a 100644
--- a/detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/TimersIdlingResource.kt
+++ b/detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/TimersIdlingResource.kt
@@ -13,6 +13,7 @@ class TimersIdlingResource @JvmOverloads constructor(
 
     override fun getName(): String = this.javaClass.name
     override fun getDebugName(): String = "timers"
+    override fun getBusyHint(): Map<String, Any>? = null
 
     override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
         this.callback = callback
diff --git a/detox/index.js b/detox/index.js
index 959ee5ed0..89ef2ccfe 100644
--- a/detox/index.js
+++ b/detox/index.js
@@ -1,1 +1,12 @@
-module.exports = require('./src/realms');
+function create() {
+  if (process.env.DETOX_CONFIG_SNAPSHOT_PATH) {
+    return require('./src/realms/secondary');
+  } else {
+    return require('./src/realms/primary');
+  }
+}
+
+/** @type {Detox.DetoxExportWrapper} */
+module.exports = global['__detox__']
+  ? global['__detox__'].clientApi
+  : create();
diff --git a/detox/internals.js b/detox/internals.js
index bce68781f..63cf35484 100644
--- a/detox/internals.js
+++ b/detox/internals.js
@@ -1,4 +1,11 @@
-const realm = require('./src/realms');
-const DetoxInternalsFacade = require('./src/realms/DetoxInternalsFacade');
+function create() {
+  const context = require('./index');
+  const DetoxInternalsFacade = require('./src/realms/DetoxInternalsFacade');
 
-module.exports = new DetoxInternalsFacade(realm);
+  return new DetoxInternalsFacade(context);
+}
+
+/** @type {DetoxInternals.Facade} */
+module.exports = global['__detox__']
+  ? global['__detox__'].internalsApi
+  : create();
diff --git a/detox/package.json b/detox/package.json
index 037193971..4bfd11617 100644
--- a/detox/package.json
+++ b/detox/package.json
@@ -123,6 +123,8 @@
       "__tests__",
       ".test.js$",
       ".mock.js$",
+      "index.js",
+      "internals.js",
       "local-cli/utils",
       "src/environmentFactory",
       "src/android/espressoapi",
diff --git a/detox/src/DetoxWorker.js b/detox/src/DetoxWorker.js
index d31cd05bb..ce8ead075 100644
--- a/detox/src/DetoxWorker.js
+++ b/detox/src/DetoxWorker.js
@@ -5,7 +5,7 @@ const Client = require('./client/Client');
 const environmentFactory = require('./environmentFactory');
 const { DetoxRuntimeErrorComposer } = require('./errors');
 const { InvocationManager } = require('./invoke');
-const symbols = require('./symbols');
+const symbols = require('./realms/symbols');
 const AsyncEmitter = require('./utils/AsyncEmitter');
 const uuid = require('./utils/uuid');
 
diff --git a/detox/src/realms/DetoxContext.js b/detox/src/realms/DetoxContext.js
index c0ec43a76..786550d6a 100644
--- a/detox/src/realms/DetoxContext.js
+++ b/detox/src/realms/DetoxContext.js
@@ -3,9 +3,9 @@ const funpermaproxy = require('funpermaproxy');
 const temporary = require('../artifacts/utils/temporaryPath');
 const { DetoxRuntimeError } = require('../errors');
 const { DetoxLogger, DetoxLogFinalizer, installLegacyTracerInterface } = require('../logger');
-const symbols = require('../symbols');
 
 const DetoxConstants = require('./DetoxConstants');
+const symbols = require('./symbols');
 
 //#region Protected symbols
 const $cleanup = Symbol('cleanup');
@@ -135,7 +135,10 @@ class DetoxContext {
    */
   async [symbols.installWorker](opts) {
     if (opts.global) {
-      opts.global['__detox__'] = this;
+      opts.global['__detox__'] = {
+        clientApi: require('../../index'),
+        internalsApi: require('../../internals'),
+      };
       this.log.overrideConsole(opts.global);
     }
 
diff --git a/detox/src/realms/DetoxInternalsFacade.js b/detox/src/realms/DetoxInternalsFacade.js
index adaae1334..2dd784406 100644
--- a/detox/src/realms/DetoxInternalsFacade.js
+++ b/detox/src/realms/DetoxInternalsFacade.js
@@ -1,6 +1,6 @@
 const funpermaproxy = require('funpermaproxy');
 
-const symbols = require('../symbols');
+const symbols = require('./symbols');
 
 class DetoxInternalsFacade {
   /**
diff --git a/detox/src/realms/DetoxPrimaryContext.js b/detox/src/realms/DetoxPrimaryContext.js
index edb4c475b..ad1e59590 100644
--- a/detox/src/realms/DetoxPrimaryContext.js
+++ b/detox/src/realms/DetoxPrimaryContext.js
@@ -6,11 +6,11 @@ const onSignalExit = require('signal-exit');
 const temporary = require('../artifacts/utils/temporaryPath');
 const { DetoxRuntimeError } = require('../errors');
 const SessionState = require('../ipc/SessionState');
-const symbols = require('../symbols');
 const { getCurrentCommand } = require('../utils/argparse');
 const uuid = require('../utils/uuid');
 
 const DetoxContext = require('./DetoxContext');
+const symbols = require('./symbols');
 
 // Protected symbols
 const { $logFinalizer, $restoreSessionState, $sessionState, $worker } = DetoxContext.protected;
diff --git a/detox/src/realms/DetoxSecondaryContext.js b/detox/src/realms/DetoxSecondaryContext.js
index dc0cc60dd..8946f0d06 100644
--- a/detox/src/realms/DetoxSecondaryContext.js
+++ b/detox/src/realms/DetoxSecondaryContext.js
@@ -2,9 +2,9 @@ const fs = require('fs-extra');
 
 const { DetoxInternalError } = require('../errors');
 const SessionState = require('../ipc/SessionState');
-const symbols = require('../symbols');
 
 const DetoxContext = require('./DetoxContext');
+const symbols = require('./symbols');
 
 // Protected symbols
 const { $restoreSessionState, $sessionState, $worker } = DetoxContext.protected;
diff --git a/detox/src/realms/index.js b/detox/src/realms/index.js
deleted file mode 100644
index ed63281f6..000000000
--- a/detox/src/realms/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-function current() {
-  if (process.env.DETOX_CONFIG_SNAPSHOT_PATH) {
-    return require('./secondary');
-  } else {
-    return require('./primary');
-  }
-}
-
-/** @type {DetoxInternals.Facade} */
-module.exports = global['__detox__'] || current();


Test patch:
diff --git a/detox/src/DetoxWorker.test.js b/detox/src/DetoxWorker.test.js
index aab7fbcf2..799ff2f17 100644
--- a/detox/src/DetoxWorker.test.js
+++ b/detox/src/DetoxWorker.test.js
@@ -77,7 +77,7 @@ describe('DetoxWorker', () => {
     AsyncEmitter = require('./utils/AsyncEmitter');
 
     Detox = require('./DetoxWorker');
-    const symbols = require('./symbols');
+    const symbols = require('./realms/symbols');
     detoxContext = { log: logger, [symbols.config]: detoxConfig };
   });
 
diff --git a/detox/src/symbols.js b/detox/src/realms/symbols.js
similarity index 100%
rename from detox/src/symbols.js
rename to detox/src/realms/symbols.js
diff --git a/detox/test/e2e/27.retries.test.js b/detox/test/e2e/27.retries.test.js
index fd38bde19..de683385f 100644
--- a/detox/test/e2e/27.retries.test.js
+++ b/detox/test/e2e/27.retries.test.js
@@ -1,5 +1,8 @@
 jest.retryTimes(3);
 
+const { session } = require('detox/internals');
+const jestExpect = require('expect').default;
+
 const {
   assertArtifactExists,
   waitUntilArtifactsManagerIsIdle,
@@ -9,6 +12,10 @@ describe('jest.retryTimes() support', () => {
   let counter = 3;
 
   beforeAll(async () => {
+    // This test won't work if you retry it via -R, --retries.
+    // Here we also assert that the session object is accessible from the sandbox.
+    jestExpect(session.testSessionIndex).toBe(0);
+
     await device.launchApp({ newInstance: true });
   });
 
diff --git a/detox/test/integration/__snapshots__/timeline-artifact.test.js.snap b/detox/test/integration/__snapshots__/timeline-artifact.test.js.snap
index 5a1fb014e..8a13b891b 100644
--- a/detox/test/integration/__snapshots__/timeline-artifact.test.js.snap
+++ b/detox/test/integration/__snapshots__/timeline-artifact.test.js.snap
@@ -10,7 +10,7 @@ exports[`Timeline integration test should deterministically produce a timeline a
       "v": 0,
     },
     "cat": "lifecycle",
-    "name": "node_modules/.bin/detox test -c stub --config integration/e2e/config.js -a integration_artifacts/ -R 1 stub1",
+    "name": "node_modules/.bin/detox test -c stub --config integration/e2e/config.js -a integration_artifacts/ -R 1 flaky",
     "ph": "B",
     "pid": 0,
     "tid": 0,
@@ -35,7 +35,7 @@ exports[`Timeline integration test should deterministically produce a timeline a
       "v": 0,
     },
     "cat": "lifecycle,cli",
-    "name": "nyc jest --config integration/e2e/config.js stub1",
+    "name": "nyc jest --config integration/e2e/config.js flaky",
     "ph": "B",
     "pid": 0,
     "tid": 0,
@@ -47,7 +47,7 @@ exports[`Timeline integration test should deterministically produce a timeline a
       "v": 0,
     },
     "cat": "lifecycle,jest-environment",
-    "name": "integration/e2e/stub1.test.js",
+    "name": "integration/e2e/flaky.test.js",
     "ph": "B",
     "pid": 1,
     "tid": 1,
@@ -83,7 +83,7 @@ exports[`Timeline integration test should deterministically produce a timeline a
       "v": 0,
     },
     "cat": "lifecycle",
-    "name": "stub1.test.js is assigned to Stub #StubDevice#1",
+    "name": "flaky.test.js is assigned to Stub #StubDevice#1",
     "ph": "i",
     "pid": 1,
     "tid": 1,
@@ -107,7 +107,7 @@ exports[`Timeline integration test should deterministically produce a timeline a
       "v": 0,
     },
     "cat": "lifecycle,jest-environment",
-    "name": "Stub1",
+    "name": "Flaky",
     "ph": "B",
     "pid": 1,
     "tid": 1,
@@ -116,7 +116,7 @@ exports[`Timeline integration test should deterministically produce a timeline a
   {
     "args": {
       "context": "test",
-      "fullName": "Stub1 should have welcome screen",
+      "fullName": "Flaky should have welcome screen",
       "invocations": 1,
       "level": 10,
       "status": "running",
@@ -135,7 +135,7 @@ exports[`Timeline integration test should deterministically produce a timeline a
       "v": 0,
     },
     "cat": "lifecycle",
-    "name": "Stub1: should have welcome screen",
+    "name": "Flaky: should have welcome screen",
     "ph": "i",
     "pid": 1,
     "tid": 1,
@@ -225,7 +225,9 @@ exports[`Timeline integration test should deterministically produce a timeline a
     } finally {
       detox.trace.endSection('Asserting various texts');
     }
-    throw new Error(\`I'm only here to make things interesting!\`);
+    if (session.testSessionIndex === 0) {
+      throw new Error(\`I'm only here to make things interesting!\`);
+    }
   }",
       "level": 10,
       "v": 0,
@@ -269,80 +271,12 @@ exports[`Timeline integration test should deterministically produce a timeline a
       "v": 0,
     },
     "cat": "lifecycle",
-    "name": "Stub1: should have welcome screen [FAIL]",
+    "name": "Flaky: should have welcome screen [FAIL]",
     "ph": "i",
     "pid": 1,
     "tid": 1,
     "ts": 18,
   },
-  {
-    "args": {
-      "context": "test",
-      "fullName": "Stub1 Skipped test",
-      "invocations": 1,
-      "level": 10,
-      "status": "skip",
-      "v": 0,
-    },
-    "cat": "lifecycle,jest-environment",
-    "name": "Skipped test",
-    "ph": "i",
-    "pid": 1,
-    "tid": 1,
-    "ts": 19,
-  },
-  {
-    "args": {
-      "level": 30,
-      "v": 0,
-    },
-    "cat": "lifecycle",
-    "name": "Stub1: Skipped test",
-    "ph": "i",
-    "pid": 1,
-    "tid": 1,
-    "ts": 20,
-  },
-  {
-    "args": {
-      "level": 30,
-      "v": 0,
-    },
-    "cat": "lifecycle",
-    "name": "Stub1: Skipped test [SKIPPED]",
-    "ph": "i",
-    "pid": 1,
-    "tid": 1,
-    "ts": 21,
-  },
-  {
-    "args": {
-      "context": "test",
-      "fullName": "Stub1 Check that todo tests are also traced",
-      "invocations": 1,
-      "level": 10,
-      "status": "todo",
-      "v": 0,
-    },
-    "cat": "lifecycle,jest-environment",
-    "name": "Check that todo tests are also traced",
-    "ph": "i",
-    "pid": 1,
-    "tid": 1,
-    "ts": 22,
-  },
-  {
-    "args": {
-      "level": 30,
-      "v": 0,
-    },
-    "cat": "lifecycle",
-    "name": "Stub1: Check that todo tests are also traced",
-    "ph": "i",
-    "pid": 1,
-    "tid": 1,
-    "ts": 23,
-  },
   {
     "args": {
       "level": 10,
@@ -352,7 +286,7 @@ exports[`Timeline integration test should deterministically produce a timeline a
     "ph": "E",
     "pid": 1,
     "tid": 1,
-    "ts": 24,
+    "ts": 19,
   },
   {
     "args": {
@@ -363,7 +297,7 @@ exports[`Timeline integration test should deterministically produce a timeline a
     "ph": "E",
     "pid": 1,
     "tid": 1,
-    "ts": 25,
+    "ts": 20,
   },
   {
     "args": {
@@ -375,7 +309,7 @@ exports[`Timeline integration test should deterministically produce a timeline a
     "ph": "B",
     "pid": 1,
     "tid": 1,
-    "ts": 26,
+    "ts": 21,
   },
   {
     "args": {
@@ -386,7 +320,7 @@ exports[`Timeline integration test should deterministically produce a timeline a
     "ph": "E",
     "pid": 1,
     "tid": 1,
-    "ts": 27,
+    "ts": 22,
   },
   {
     "args": {
@@ -398,7 +332,7 @@ exports[`Timeline integration test should deterministically produce a timeline a
     "ph": "E",
     "pid": 1,
     "tid": 1,
-    "ts": 28,
+    "ts": 23,
   },
   {
     "args": {
@@ -412,7 +346,7 @@ exports[`Timeline integration test should deterministically produce a timeline a
     "ph": "E",
     "pid": 0,
     "tid": 0,
-    "ts": 29,
+    "ts": 24,
   },
   {
     "args": {
@@ -421,14 +355,14 @@ exports[`Timeline integration test should deterministically produce a timeline a
     },
     "cat": "lifecycle,cli",
     "name": "There were failing tests in the following files:
-  1. integration/e2e/stub1.test.js
+  1. integration/e2e/flaky.test.js
 
 Detox CLI is going to restart the test runner with those files...
 ",
     "ph": "i",
     "pid": 0,
     "tid": 0,
-    "ts": 30,
+    "ts": 25,
   },
   {
     "args": {
@@ -437,11 +371,11 @@ Detox CLI is going to restart the test runner with those files...
       "v": 0,
     },
     "cat": "lifecycle,cli",
-    "name": "nyc jest --config integration/e2e/config.js $CWD/integration/e2e/stub1.test.js",
+    "name": "nyc jest --config integration/e2e/config.js $CWD/integration/e2e/flaky.test.js",
     "ph": "B",
     "pid": 0,
     "tid": 0,
-    "ts": 31,
+    "ts": 26,
   },
   {
     "args": {
@@ -449,11 +383,11 @@ Detox CLI is going to restart the test runner with those files...
       "v": 0,
     },
     "cat": "lifecycle,jest-environment",
-    "name": "integration/e2e/stub1.test.js",
+    "name": "integration/e2e/flaky.test.js",
     "ph": "B",
     "pid": 2,
     "tid": 2,
-    "ts": 32,
+    "ts": 27,
   },
   {
     "args": {
@@ -465,7 +399,7 @@ Detox CLI is going to restart the test runner with those files...
     "ph": "B",
     "pid": 2,
     "tid": 2,
-    "ts": 33,
+    "ts": 28,
   },
   {
     "args": {
@@ -477,7 +411,7 @@ Detox CLI is going to restart the test runner with those files...
     "ph": "E",
     "pid": 2,
     "tid": 2,
-    "ts": 34,
+    "ts": 29,
   },
   {
     "args": {
@@ -485,11 +419,11 @@ Detox CLI is going to restart the test runner with those files...
       "v": 0,
     },
     "cat": "lifecycle",
-    "name": "stub1.test.js is assigned to Stub #StubDevice#1",
+    "name": "flaky.test.js is assigned to Stub #StubDevice#1",
     "ph": "i",
     "pid": 2,
     "tid": 2,
-    "ts": 35,
+    "ts": 30,
   },
   {
     "args": {
@@ -501,7 +435,7 @@ Detox CLI is going to restart the test runner with those files...
     "ph": "B",
     "pid": 2,
     "tid": 2,
-    "ts": 36,
+    "ts": 31,
   },
   {
     "args": {
@@ -509,16 +443,16 @@ Detox CLI is going to restart the test runner with those files...
       "v": 0,
     },
     "cat": "lifecycle,jest-environment",
-    "name": "Stub1",
+    "name": "Flaky",
     "ph": "B",
     "pid": 2,
     "tid": 2,
-    "ts": 37,
+    "ts": 32,
   },
   {
     "args": {
       "context": "test",
-      "fullName": "Stub1 should have welcome screen",
+      "fullName": "Flaky should have welcome screen",
       "invocations": 2,
       "level": 10,
       "status": "running",
@@ -529,7 +463,7 @@ Detox CLI is going to restart the test runner with those files...
     "ph": "B",
     "pid": 2,
     "tid": 2,
-    "ts": 38,
+    "ts": 33,
   },
   {
     "args": {
@@ -537,11 +471,11 @@ Detox CLI is going to restart the test runner with those files...
       "v": 0,
     },
     "cat": "lifecycle",
-    "name": "Stub1: should have welcome screen",
+    "name": "Flaky: should have welcome screen",
     "ph": "i",
     "pid": 2,
     "tid": 2,
-    "ts": 39,
+    "ts": 34,
   },
   {
     "args": {
@@ -574,7 +508,7 @@ Detox CLI is going to restart the test runner with those files...
     "ph": "B",
     "pid": 2,
     "tid": 2,
-    "ts": 40,
+    "ts": 35,
   },
   {
     "args": {
@@ -586,7 +520,7 @@ Detox CLI is going to restart the test runner with those files...
     "ph": "E",
     "pid": 2,
     "tid": 2,
-    "ts": 41,
+    "ts": 36,
   },
   {
     "args": {
@@ -602,7 +536,7 @@ Detox CLI is going to restart the test runner with those files...
     "ph": "B",
     "pid": 2,
     "tid": 2,
-    "ts": 42,
+    "ts": 37,
   },
   {
     "args": {
@@ -614,7 +548,7 @@ Detox CLI is going to restart the test runner with those files...
     "ph": "E",
     "pid": 2,
     "tid": 2,
-    "ts": 43,
+    "ts": 38,
   },
   {
     "args": {
@@ -627,7 +561,9 @@ Detox CLI is going to restart the test runner with those files...
     } finally {
       detox.trace.endSection('Asserting various texts');
     }
-    throw new Error(\`I'm only here to make things interesting!\`);
+    if (session.testSessionIndex === 0) {
+      throw new Error(\`I'm only here to make things interesting!\`);
+    }
   }",
       "level": 10,
       "v": 0,
@@ -637,101 +573,31 @@ Detox CLI is going to restart the test runner with those files...
     "ph": "B",
     "pid": 2,
     "tid": 2,
-    "ts": 44,
+    "ts": 39,
   },
   {
     "args": {
-      "error": "Error: I'm only here to make things interesting!",
       "level": 10,
-      "success": false,
+      "success": true,
       "v": 0,
     },
     "cat": "lifecycle,jest-environment",
     "ph": "E",
     "pid": 2,
     "tid": 2,
-    "ts": 45,
+    "ts": 40,
   },
   {
     "args": {
       "level": 10,
-      "status": "failed",
-      "timedOut": false,
+      "status": "passed",
       "v": 0,
     },
     "cat": "lifecycle,jest-environment",
     "ph": "E",
     "pid": 2,
     "tid": 2,
-    "ts": 46,
-  },
-  {
-    "args": {
-      "level": 30,
-      "v": 0,
-    },
-    "cat": "lifecycle",
-    "name": "Stub1: should have welcome screen [FAIL]",
-    "ph": "i",
-    "pid": 2,
-    "tid": 2,
-    "ts": 47,
-  },
-  {
-    "args": {
-      "context": "test",
-      "fullName": "Stub1 Skipped test",
-      "invocations": 2,
-      "level": 10,
-      "status": "skip",
-      "v": 0,
-    },
-    "cat": "lifecycle,jest-environment",
-    "name": "Skipped test",
-    "ph": "i",
-    "pid": 2,
-    "tid": 2,
-    "ts": 48,
-  },
-  {
-    "args": {
-      "level": 30,
-      "v": 0,
-    },
-    "cat": "lifecycle",
-    "name": "Stub1: Skipped test",
-    "ph": "i",
-    "pid": 2,
-    "tid": 2,
-    "ts": 49,
-  },
-  {
-    "args": {
-      "level": 30,
-      "v": 0,
-    },
-    "cat": "lifecycle",
-    "name": "Stub1: Skipped test [SKIPPED]",
-    "ph": "i",
-    "pid": 2,
-    "tid": 2,
-    "ts": 50,
-  },
-  {
-    "args": {
-      "context": "test",
-      "fullName": "Stub1 Check that todo tests are also traced",
-      "invocations": 2,
-      "level": 10,
-      "status": "todo",
-      "v": 0,
-    },
-    "cat": "lifecycle,jest-environment",
-    "name": "Check that todo tests are also traced",
-    "ph": "i",
-    "pid": 2,
-    "tid": 2,
-    "ts": 51,
+    "ts": 41,
   },
   {
     "args": {
@@ -739,11 +605,11 @@ Detox CLI is going to restart the test runner with those files...
       "v": 0,
     },
     "cat": "lifecycle",
-    "name": "Stub1: Check that todo tests are also traced",
+    "name": "Flaky: should have welcome screen [OK]",
     "ph": "i",
     "pid": 2,
     "tid": 2,
-    "ts": 52,
+    "ts": 42,
   },
   {
     "args": {
@@ -754,7 +620,7 @@ Detox CLI is going to restart the test runner with those files...
     "ph": "E",
     "pid": 2,
     "tid": 2,
-    "ts": 53,
+    "ts": 43,
   },
   {
     "args": {
@@ -765,7 +631,7 @@ Detox CLI is going to restart the test runner with those files...
     "ph": "E",
     "pid": 2,
     "tid": 2,
-    "ts": 54,
+    "ts": 44,
   },
   {
     "args": {
@@ -777,7 +643,7 @@ Detox CLI is going to restart the test runner with those files...
     "ph": "B",
     "pid": 2,
     "tid": 2,
-    "ts": 55,
+    "ts": 45,
   },
   {
     "args": {
@@ -788,7 +654,7 @@ Detox CLI is going to restart the test runner with those files...
     "ph": "E",
     "pid": 2,
     "tid": 2,
-    "ts": 56,
+    "ts": 46,
   },
   {
     "args": {
@@ -800,21 +666,19 @@ Detox CLI is going to restart the test runner with those files...
     "ph": "E",
     "pid": 2,
     "tid": 2,
-    "ts": 57,
+    "ts": 47,
   },
   {
     "args": {
-      "code": 1,
-      "level": 50,
-      "signal": null,
-      "success": false,
+      "level": 10,
+      "success": true,
       "v": 0,
     },
     "cat": "lifecycle,cli",
     "ph": "E",
     "pid": 0,
     "tid": 0,
-    "ts": 58,
+    "ts": 48,
   },
   {
     "args": {
@@ -825,7 +689,7 @@ Detox CLI is going to restart the test runner with those files...
     "ph": "E",
     "pid": 0,
     "tid": 0,
-    "ts": 59,
+    "ts": 49,
   },
 ]
 `;
diff --git a/detox/test/integration/e2e/stub1.test.js b/detox/test/integration/e2e/flaky.test.js
similarity index 69%
rename from detox/test/integration/e2e/stub1.test.js
rename to detox/test/integration/e2e/flaky.test.js
index 520bac123..efb336684 100644
--- a/detox/test/integration/e2e/stub1.test.js
+++ b/detox/test/integration/e2e/flaky.test.js
@@ -1,4 +1,6 @@
-describe('Stub1', () => {
+const { session } = require('detox/internals');
+
+describe('Flaky', () => {
   beforeEach(async () => {
     await device.reloadReactNative();
     await detox.traceCall('Navigate to sanity', element(by.text('Sanity')).tap());
@@ -13,12 +15,10 @@ describe('Stub1', () => {
     } finally {
       detox.trace.endSection('Asserting various texts');
     }
-    throw new Error(`I'm only here to make things interesting!`);
-  });
 
-  it.skip('Skipped test', async () => {
-    // Checking that skipped tests are also traced
+    if (session.testSessionIndex === 0) {
+      throw new Error(`I'm only here to make things interesting!`);
+    }
   });
 
-  it.todo('Check that todo tests are also traced');
 });
diff --git a/detox/test/integration/e2e/passing-sandbox.js b/detox/test/integration/e2e/passing-sandbox.js
new file mode 100644
index 000000000..770a83815
--- /dev/null
+++ b/detox/test/integration/e2e/passing-sandbox.js
@@ -0,0 +1,9 @@
+const { config, session } = require('detox/internals');
+const { default: expect } = require('expect');
+
+describe('Sandbox', () => {
+  test('detox/internals should be accessible from the sandbox', () => {
+    expect(session.testSessionIndex).toBe(0);
+    expect(config.configurationName).toBe('stub');
+  });
+});
diff --git a/detox/test/integration/e2e/stub2.test.js b/detox/test/integration/e2e/passing-simple.test.js
similarity index 100%
rename from detox/test/integration/e2e/stub2.test.js
rename to detox/test/integration/e2e/passing-simple.test.js
diff --git a/detox/test/integration/e2e/passing-skipped.js b/detox/test/integration/e2e/passing-skipped.js
new file mode 100644
index 000000000..71b844cfb
--- /dev/null
+++ b/detox/test/integration/e2e/passing-skipped.js
@@ -0,0 +1,7 @@
+describe('Skipped', () => {
+  it.skip('Skipped test', async () => {
+    // Checking that skipped tests are also traced
+  });
+
+  it.todo('Check that todo tests are also traced');
+});
diff --git a/detox/test/integration/e2e/stub3.test.js b/detox/test/integration/e2e/stub3.test.js
deleted file mode 100644
index 4dc2f7ef6..000000000
--- a/detox/test/integration/e2e/stub3.test.js
+++ /dev/null
@@ -1,22 +0,0 @@
-describe('Stub3', () => {
-  beforeEach(async () => {
-    await device.reloadReactNative();
-    await element(by.text('Sanity')).tap();
-  });
-
-  it('should have welcome screen', async () => {
-    await expect(element(by.text('Welcome'))).toBeVisible();
-    await expect(element(by.text('Say Hello'))).toBeVisible();
-    await expect(element(by.text('Say World'))).toBeVisible();
-  });
-
-  it('should show hello screen after tap', async () => {
-    await element(by.text('Say Hello')).tap();
-    await expect(element(by.text('Hello!!!'))).toBeVisible();
-  });
-
-  it('should show world screen after tap', async () => {
-    await element(by.text('Say World')).tap();
-    await expect(element(by.text('World!!!'))).toBeVisible();
-  });
-});
diff --git a/detox/test/integration/initialization-test.test.js b/detox/test/integration/initialization-test.test.js
index 3fed7fe30..0d53cf11e 100644
--- a/detox/test/integration/initialization-test.test.js
+++ b/detox/test/integration/initialization-test.test.js
@@ -1,15 +1,16 @@
-const { execCommand } = require('./utils/exec');
+const execa = require('execa');
 
 describe('Initialization (context) tests', () => {
   test.each([
     `cross-env DETOX_CONFIGURATION=stub node integration/utils/simplistic-runner.js`,
 
-    `detox test -c stub --config integration/e2e/config.js --runInBand --retries 1 stub1 stub2`,
-    `cross-env DETOX_CONFIGURATION=stub jest --config integration/e2e/config.js --runInBand stub2 stub3`,
+    `detox test -c stub --config integration/e2e/config.js --runInBand --retries 1 flaky passing`,
+    `cross-env DETOX_CONFIGURATION=stub jest --config integration/e2e/config.js --runInBand passing`,
 
-    `detox test -c stub --config integration/e2e/config.js --maxWorkers 2 --retries 1`,
-    `cross-env DETOX_CONFIGURATION=stub jest --config integration/e2e/config.js --maxWorkers 2`,
+    `detox test -c stub --config integration/e2e/config.js --maxWorkers 2 --retries 1 flaky passing`,
+    `cross-env DETOX_CONFIGURATION=stub jest --config integration/e2e/config.js --maxWorkers 2 passing`,
   ])('should run: %s', async (cmd) => {
-    await execCommand(cmd);
+    const handle = execa.commandSync(cmd, { stdio: 'inherit', shell: true });
+    expect(handle.exitCode).toBe(0);
   });
 });
diff --git a/detox/test/integration/timeline-artifact.test.js b/detox/test/integration/timeline-artifact.test.js
index a02355589..22ca32145 100644
--- a/detox/test/integration/timeline-artifact.test.js
+++ b/detox/test/integration/timeline-artifact.test.js
@@ -27,7 +27,7 @@ describe('Timeline integration test', () => {
   beforeAll(clearAllArtifacts);
 
   beforeAll(async () => {
-    await execCommand(`detox test -c stub --config integration/e2e/config.js -a ${artifactsDirectory} -R 1 stub1`);
+    await execCommand(`detox test -c stub --config integration/e2e/config.js -a ${artifactsDirectory} -R 1 flaky`);
     tac = JSON.parse(await readFile(timelineArtifactPath, 'utf8'));
   });
 
diff --git a/detox/test/integration/utils/simplistic-runner.js b/detox/test/integration/utils/simplistic-runner.js
index fd8a645cc..2d10357d6 100644
--- a/detox/test/integration/utils/simplistic-runner.js
+++ b/detox/test/integration/utils/simplistic-runner.js
@@ -3,8 +3,8 @@ const detox = require('detox/internals');
 async function main() {
   try {
     await detox.init();
-    await test1().catch(e => console.error(e));
-    await test2().catch(e => console.error(e));
+    await test1();
+    await test2();
   } finally {
     await detox.cleanup();
   }
diff --git a/detox/test/package.json b/detox/test/package.json
index 5cceec8b0..ec8c58e12 100644
--- a/detox/test/package.json
+++ b/detox/test/package.json
@@ -57,6 +57,7 @@
     "detox": "^20.1.0",
     "eslint": "^7.32.0",
     "eslint-plugin-unicorn": "^39.0.0",
+    "execa": "^5.1.1",
     "express": "^4.15.3",
     "glob": "^7.2.0",
     "jest": "^29.2.0",