Repository: wix/Detox
Pull request #4097 (2023-05-31 12:51:14)

React Native v.71 Compliance
This PR resolves https://github.com/wix/Detox/issues/4066, https://github.com/wix/Detox/issues/4067, https://github.com/wix/Detox/issues/4068, https://github.com/wix/Detox/issues/4069 and https://github.com/wix/Detox/issues/4070.


Patch:
diff --git a/.buildkite/jobs/pipeline.android_demo_app_rn_70.yml b/.buildkite/jobs/pipeline.android_demo_app_rn_70.yml
deleted file mode 100644
index dec0e9b0f..000000000
--- a/.buildkite/jobs/pipeline.android_demo_app_rn_70.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-  - label: ":android::react: RN .70 + Android: Demo app"
-    command:
-      - "nvm install"
-      - "./scripts/demo-projects.android.sh"
-    env:
-      REACT_NATIVE_VERSION: 0.70.7
-      DETOX_DISABLE_POD_INSTALL: true
-      DETOX_DISABLE_POSTINSTALL: true
-    artifact_paths:
-      - "/Users/builder/work/coverage/**/*.lcov"
-      - "/Users/builder/work/artifacts*.tar.gz"
diff --git a/.buildkite/jobs/pipeline.android_demo_app_rn_68.yml b/.buildkite/jobs/pipeline.android_demo_app_rn_71.yml
similarity index 60%
rename from .buildkite/jobs/pipeline.android_demo_app_rn_68.yml
rename to .buildkite/jobs/pipeline.android_demo_app_rn_71.yml
index 0a82faf7f..e6baf0522 100644
--- a/.buildkite/jobs/pipeline.android_demo_app_rn_68.yml
+++ b/.buildkite/jobs/pipeline.android_demo_app_rn_71.yml
@@ -1,10 +1,10 @@
-  - label: ":android::react: RN.68 + Android: Demo app"
+  - label: ":android::react: RN .71 + Android: Demo app"
     command:
       - "nvm install"
       - "./scripts/demo-projects.android.sh"
     env:
-      REACT_NATIVE_VERSION: 0.68.5
-      REACT_NATIVE_COMPAT_TEST: true
+      REACT_NATIVE_VERSION: 0.71.10
+      REACT_NATIVE_COMPAT_TEST: true # Only set 'true' in jobs with the latest supported RN
       DETOX_DISABLE_POD_INSTALL: true
       DETOX_DISABLE_POSTINSTALL: true
     artifact_paths:
diff --git a/.buildkite/jobs/pipeline.android_rn_68.yml b/.buildkite/jobs/pipeline.android_rn_71.yml
similarity index 77%
rename from .buildkite/jobs/pipeline.android_rn_68.yml
rename to .buildkite/jobs/pipeline.android_rn_71.yml
index 75b68ceb7..43861094f 100644
--- a/.buildkite/jobs/pipeline.android_rn_68.yml
+++ b/.buildkite/jobs/pipeline.android_rn_71.yml
@@ -1,9 +1,9 @@
-  - label: ":android::detox: RN.68 + Android: Tests app"
+  - label: ":android::detox: RN .71 + Android: Tests app"
     command:
       - "nvm install"
       - "./scripts/ci.android.sh"
     env:
-      REACT_NATIVE_VERSION: 0.68.5
+      REACT_NATIVE_VERSION: 0.71.10
       DETOX_DISABLE_POD_INSTALL: true
       DETOX_DISABLE_POSTINSTALL: true
     artifact_paths:
diff --git a/.buildkite/jobs/pipeline.ios_demo_app_rn_68.yml b/.buildkite/jobs/pipeline.ios_demo_app_rn_71.yml
similarity index 70%
rename from .buildkite/jobs/pipeline.ios_demo_app_rn_68.yml
rename to .buildkite/jobs/pipeline.ios_demo_app_rn_71.yml
index a2aebb2e2..83f272b4a 100644
--- a/.buildkite/jobs/pipeline.ios_demo_app_rn_68.yml
+++ b/.buildkite/jobs/pipeline.ios_demo_app_rn_71.yml
@@ -1,9 +1,9 @@
-  - label: ":ios::react: RN.68 + iOS: Demo app"
+  - label: ":ios::react: RN .71 + iOS: Demo app"
     command:
       - "nvm install"
       - "./scripts/demo-projects.ios.sh"
     env:
-      REACT_NATIVE_VERSION: 0.68.5
+      REACT_NATIVE_VERSION: 0.71.10
     artifact_paths:
       - "/Users/builder/work/coverage/**/*.lcov"
       - "/Users/builder/work/artifacts*.tar.gz"
diff --git a/.buildkite/jobs/pipeline.ios_rn_68.yml b/.buildkite/jobs/pipeline.ios_rn_71.yml
similarity index 74%
rename from .buildkite/jobs/pipeline.ios_rn_68.yml
rename to .buildkite/jobs/pipeline.ios_rn_71.yml
index 67178d109..854ef4ce2 100644
--- a/.buildkite/jobs/pipeline.ios_rn_68.yml
+++ b/.buildkite/jobs/pipeline.ios_rn_71.yml
@@ -1,9 +1,9 @@
-  - label: ":ios::detox: RN.68 + iOS: Tests app"
+  - label: ":ios::detox: RN .71 + iOS: Tests app"
     command:
       - "nvm install"
       - "./scripts/ci.ios.sh"
     env:
-      REACT_NATIVE_VERSION: 0.68.5
+      REACT_NATIVE_VERSION: 0.71.10
     artifact_paths:
       - "/Users/builder/work/coverage/**/*.lcov"
       - "/Users/builder/work/**/allure-report-*.html"
diff --git a/.buildkite/pipeline.debug.yml b/.buildkite/pipeline.debug.yml
index eab3359be..cbcdc58b5 100644
--- a/.buildkite/pipeline.debug.yml
+++ b/.buildkite/pipeline.debug.yml
@@ -8,20 +8,20 @@ steps:
         multiple: true
         hint: "hey Chef, what are we cooking today?"
         options:
-          - label: "iOS RN 68"
-            value: "ios_rn_68"
-          - label: "iOS RN 69"
-            value: "ios_rn_69"
-          - label: "Android RN 68"
-            value: "android_rn_68"
+          - label: "iOS RN 70"
+            value: "ios_rn_71"
+          - label: "iOS RN 70"
+            value: "ios_rn_71"
+          - label: "iOS Demo App RN 70"
+            value: "ios_demo_app_rn_70"
+          - label: "iOS Demo App RN 71"
+            value: "ios_demo_app_rn_71"
           - label: "Android RN 70"
             value: "android_rn_70"
-          - label: "Android Demo App RN 68"
-            value: "android_demo_app_rn_68"
-          - label: "Android Demo App RN 69"
-            value: "android_demo_app_rn_69"
-          - label: "iOS Demo App RN 69"
-            value: "ios_demo_app_rn_69"
+          - label: "Android RN 71"
+            value: "android_rn_71"
+          - label: "Android Demo App RN 71"
+            value: "android_demo_app_rn_71"
 
   - label: "Get Release Pipeline"
     command: '.buildkite/pipeline.sh debug | buildkite-agent pipeline upload'
diff --git a/.buildkite/pipeline_common.sh b/.buildkite/pipeline_common.sh
index 0b8507294..1d9e7afc9 100755
--- a/.buildkite/pipeline_common.sh
+++ b/.buildkite/pipeline_common.sh
@@ -2,12 +2,11 @@
 
 echo "steps:"
 
-cat .buildkite/jobs/pipeline.ios_rn_68.yml
+cat .buildkite/jobs/pipeline.ios_rn_71.yml
 cat .buildkite/jobs/pipeline.ios_rn_70.yml
-cat .buildkite/jobs/pipeline.android_rn_68.yml
+cat .buildkite/jobs/pipeline.android_rn_71.yml
 cat .buildkite/jobs/pipeline.android_rn_70.yml
-cat .buildkite/jobs/pipeline.android_demo_app_rn_68.yml
-cat .buildkite/jobs/pipeline.android_demo_app_rn_70.yml
-cat .buildkite/jobs/pipeline.ios_demo_app_rn_68.yml
+cat .buildkite/jobs/pipeline.android_demo_app_rn_71.yml
+cat .buildkite/jobs/pipeline.ios_demo_app_rn_71.yml
 cat .buildkite/jobs/pipeline.ios_demo_app_rn_70.yml
 cat .buildkite/pipeline.post_processing.yml
diff --git a/README.md b/README.md
index 2b252d679..66f18d264 100644
--- a/README.md
+++ b/README.md
@@ -45,39 +45,28 @@ High velocity native mobile development requires us to adopt continuous integrat
 
 The most difficult part of automated testing on mobile is the tip of the testing pyramid - E2E. The core problem with E2E tests is flakiness - tests are usually not deterministic. We believe the only way to tackle flakiness head on is by moving from black box testing to gray box testing. That’s where Detox comes into play.
 
-- **Cross Platform:** Write cross-platform end-to-end tests in JavaScript. Currently supports iOS and Android.
+- **Cross Platform:** Write end-to-end tests in JavaScript for React Native apps (Android & iOS).
 - **Debuggable:** Modern async-await API allows breakpoints in asynchronous tests to work as expected.
 - **Automatically Synchronized:** Stops flakiness at the core by monitoring asynchronous operations in your app.
 - **Made For CI:** Execute your E2E tests on CI platforms like Travis CI, Circle CI or Jenkins without grief.
 - **Runs on Devices:** Gain confidence to ship by testing your app on a device/simulator just like a real user (not yet supported on iOS).
 - **Test Runner Agnostic:** Detox provides a set of APIs to use with any test runner without it. It comes with [Jest](https://jestjs.io) integration out of the box.
 
-## Supported Versions
+## Supported React Native Versions
 
-### Environment
+Detox was built from the ground up to support React Native projects.
 
-**OS:**
+While Detox should work out of the box with almost any React Native version of the latest minor releases, official support is provided for React Native versions `0.70.x` and `0.71.x` without React Native's ["New Architecture"](https://reactnative.dev/docs/the-new-architecture/landing-page).
 
-- **macOS:** v10.15 (Catalina) or higher
-  - **Xcode**: v11.0 or higher
-  - **iOS Simulator Runtime**: iOS v13.0 or higher
-- **Ubuntu linux:** v20.04 or higher
-- **Windows 10:** Version 1803 or higher
+Newer versions, as well as React Native's "New Architecture", may work with Detox, but have not been tested out yet by the Detox team.
 
-### React Native
+Although we do not officially support older React Native versions, we do our best to keep Detox compatible with them.
 
-Detox is built from the ground up to support React Native projects as well as pure native ones.
+Also, in case of a problem with an unsupported version of React Native, please [submit an issue](https://github.com/wix/Detox/issues/new/choose) or write us in our [Discord server](https://discord.gg/CkD5QKheF5) and we will do our best to help out.
 
-Detox works, at least officially, with the following React Native versions:
+### Known Issues with React Native
 
-| iOS             | Android                                                                                                                |
-| --------------- | ---------------------------------------------------------------------------------------------------------------------- |
-| 0.68.x - 0.70.7 | 0.68.x - 0.70.7 -<br/>Visibility edge-case: see this [RN issue](https://github.com/facebook/react-native/issues/23870) |
-
-Newer versions, as well as React Native's ["New Architecture"](https://reactnative.dev/docs/the-new-architecture/landing-page), are most likely supported, but have not been tested out yet.
-Please [submit an issue](https://github.com/wix/Detox/issues/new/choose) if you find specific problems with more recent React Native versions.
-
-For support for React Native 0.71.x, please track [this issue](https://github.com/wix/Detox/issues/3884).
+- Visibility edge-case on Android: see this [RN issue](https://github.com/facebook/react-native/issues/23870).
 
 ## Get Started with Detox
 
diff --git a/detox/android/build.gradle b/detox/android/build.gradle
index 08251da36..c932960bb 100644
--- a/detox/android/build.gradle
+++ b/detox/android/build.gradle
@@ -1,11 +1,13 @@
 buildscript {
+    apply from: './rninfo.gradle'
+
     ext {
         isOfficialDetoxLib = true
-        kotlinVersion = '1.6.10' // Aligned with RN .69's version bump
+        kotlinVersion = '1.8.22'
         dokkaVersion = '1.6.0'
-        buildToolsVersion = '31.0.0'
-        compileSdkVersion = 31
-        targetSdkVersion = 31
+        buildToolsVersion = '33.0.0'
+        compileSdkVersion = 33
+        targetSdkVersion = 33
         minSdkVersion = 21
 
         if (System.properties['os.arch'] == "aarch64") {
@@ -19,11 +21,14 @@ buildscript {
     ext.detoxKotlinVersion = ext.kotlinVersion
 
     repositories {
-        mavenCentral()
         google()
+        mavenCentral()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:7.2.1'
+        if (!rnInfo.isRN71OrNewer) {
+            classpath "com.facebook.react:react-native-gradle-plugin"
+        }
+        classpath 'com.android.tools.build:gradle:7.3.1'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
         classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion"
 
@@ -34,11 +39,16 @@ buildscript {
 
 allprojects {
     repositories {
-        mavenLocal()
-        mavenCentral()
         google()
-        maven {
-            url "$projectDir/../../node_modules/react-native/android"
+        mavenCentral()
+        mavenLocal()
+
+        // In RN's below 71, the native code comes from within node_modules/ rather
+        // than from maven-central.
+        if (!rnInfo.isRN71OrNewer) {
+            maven {
+                url "$projectDir/../../node_modules/react-native/android"
+            }
         }
     }
 }
diff --git a/detox/android/detox/build.gradle b/detox/android/detox/build.gradle
index e57d6c952..d586778a2 100644
--- a/detox/android/detox/build.gradle
+++ b/detox/android/detox/build.gradle
@@ -1,5 +1,6 @@
 apply plugin: 'com.android.library'
 apply plugin: 'kotlin-android'
+apply from: '../rninfo.gradle'
 
 def _kotlinMinVersion = '1.2.0'
 def _materialMinVersion = '1.2.1'
@@ -12,6 +13,13 @@ def _minSdkVersion = _ext.has('minSdkVersion') ? _ext.minSdkVersion : 21
 def _kotlinVersion = _ext.has('detoxKotlinVersion') ? _ext.detoxKotlinVersion : _kotlinMinVersion
 def _kotlinStdlib = _ext.has('detoxKotlinStdlib') ? _ext.detoxKotlinStdlib : 'kotlin-stdlib-jdk8'
 
+// RN native code comes from either maven-central (in which case, need the *exact* version),
+// or otherwise from node_modules/, where the version is already aligned, by definition.
+// noinspection GradleDynamicVersion
+def _rnNativeArtifact = rnInfo.isRN71OrHigher
+        ? "com.facebook.react:react-android:${rnInfo.version}"
+            : 'com.facebook.react:react-native:+'
+
 android {
     compileSdkVersion _compileSdkVersion
     buildToolsVersion _buildToolsVersion
@@ -78,8 +86,8 @@ android {
 // Fundamental deps.
 dependencies {
     implementation "org.jetbrains.kotlin:$_kotlinStdlib:$_kotlinMinVersion"
-    //noinspection GradleDynamicVersion
-    compileOnly 'com.facebook.react:react-native:+'
+
+    compileOnly "${_rnNativeArtifact}"
 }
 
 // androidx.test deps.
@@ -131,8 +139,7 @@ dependencies {
 
 // Unit-testing deps.
 dependencies {
-    //noinspection GradleDynamicVersion
-    testImplementation 'com.facebook.react:react-native:+'
+    testImplementation "${_rnNativeArtifact}"
     testImplementation 'org.json:json:20140107'
 
 // https://github.com/spekframework/spek/issues/232#issuecomment-610732158
diff --git a/detox/android/detox/src/full/java/com/wix/detox/espresso/common/SliderHelper.kt b/detox/android/detox/src/full/java/com/wix/detox/espresso/common/SliderHelper.kt
index af77cda9d..6bd4080a0 100644
--- a/detox/android/detox/src/full/java/com/wix/detox/espresso/common/SliderHelper.kt
+++ b/detox/android/detox/src/full/java/com/wix/detox/espresso/common/SliderHelper.kt
@@ -4,9 +4,9 @@ import android.view.View
 import androidx.appcompat.widget.AppCompatSeekBar
 import com.facebook.react.bridge.JavaOnlyMap
 import com.facebook.react.uimanager.ReactStylesDiffMap
-import com.facebook.react.views.slider.ReactSlider
 import com.wix.detox.common.DetoxErrors.DetoxIllegalStateException
 import com.wix.detox.espresso.action.common.ReflectUtils
+import com.facebook.react.views.slider.ReactSlider
 import org.joor.Reflect
 
 private const val CLASS_REACT_SLIDER_LEGACY = "com.facebook.react.views.slider.ReactSlider"
@@ -58,7 +58,7 @@ abstract class SliderHelper(protected val slider: AppCompatSeekBar) {
     }
 }
 
-private class LegacySliderHelper(slider: AppCompatSeekBar): SliderHelper(slider) {
+private class LegacySliderHelper(slider: ReactSlider): SliderHelper(slider) {
     override fun setProgressJS(valueJS: Double) {
         val reactSliderManager = com.facebook.react.views.slider.ReactSliderManager()
         reactSliderManager.updateProperties(slider as ReactSlider, buildStyles("value", valueJS))
diff --git a/detox/android/gradle/wrapper/gradle-wrapper.properties b/detox/android/gradle/wrapper/gradle-wrapper.properties
index a36e959a9..59e6dfebf 100644
--- a/detox/android/gradle/wrapper/gradle-wrapper.properties
+++ b/detox/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip
 
diff --git a/detox/android/rninfo.gradle b/detox/android/rninfo.gradle
new file mode 100644
index 000000000..7385d88c8
--- /dev/null
+++ b/detox/android/rninfo.gradle
@@ -0,0 +1,25 @@
+import groovy.json.JsonSlurper
+
+def rnVersion = getRNVersion()
+def rnMajorVer = getMajorVersion(rnVersion)
+println "[$project] RNInfo: detected React Native version: $rnVersion (major=$rnMajorVer)"
+
+project.ext.rnInfo = [
+    version: rnVersion,
+    majorVersion: rnMajorVer,
+    isRN69OrHigher: rnMajorVer >= 69,
+    isRN70OrHigher: rnMajorVer >= 70,
+    isRN71OrHigher: rnMajorVer >= 71,
+]
+
+private static def getRNVersion() {
+    def jsonSlurper = new JsonSlurper()
+    Map<String, Object> packageJSON  = jsonSlurper.parse(new File('../node_modules/react-native/package.json'))
+    String rnVersion = packageJSON.get('version')
+    return rnVersion
+}
+
+private static def getMajorVersion(semanticVersion) {
+    Integer rnVersionMajor = semanticVersion.split('\\.')[1].toInteger()
+    return rnVersionMajor
+}
diff --git a/detox/android/settings.gradle b/detox/android/settings.gradle
index 8a8b3de9f..5ba1fee37 100644
--- a/detox/android/settings.gradle
+++ b/detox/android/settings.gradle
@@ -1,1 +1,2 @@
-include ':detox'
\ No newline at end of file
+include ':detox'
+includeBuild('../node_modules/react-native-gradle-plugin')
diff --git a/detox/local-cli/startCommand/AppStartCommand.js b/detox/local-cli/startCommand/AppStartCommand.js
index 72ba33169..8078e8d3b 100644
--- a/detox/local-cli/startCommand/AppStartCommand.js
+++ b/detox/local-cli/startCommand/AppStartCommand.js
@@ -36,7 +36,10 @@ class AppStartCommand {
       }
     };
 
-    this._cpHandle = execa.command(cmd, { stdio: 'inherit', shell: true });
+    this._cpHandle = execa.command(cmd, {
+      stdio: ['ignore', 'inherit', 'inherit'],
+      shell: true
+    });
     this._cpHandle.on('error', onError);
     this._cpHandle.on('exit', (code, signal) => {
       const reason = code == null ? `signal ${signal}` : `code ${code}`;
diff --git a/detox/package.json b/detox/package.json
index ad984f998..c625d5e5c 100644
--- a/detox/package.json
+++ b/detox/package.json
@@ -53,8 +53,8 @@
     "jest": "^28.1.3",
     "jest-allure2-reporter": "^1.2.1",
     "mockdate": "^2.0.1",
-    "prettier": "1.7.0",
-    "react-native": "0.70.7",
+    "prettier": "^2.4.1",
+    "react-native": "0.71.10",
     "react-native-codegen": "^0.0.8",
     "typescript": "^4.5.2",
     "wtfnode": "^0.9.1"
diff --git a/docs/articles/how-detox-works.md b/docs/articles/how-detox-works.md
index 2bab1bfd0..eae6f8264 100644
--- a/docs/articles/how-detox-works.md
+++ b/docs/articles/how-detox-works.md
@@ -27,6 +27,12 @@ Detox tries to eliminate flakiness by automatically synchronizing your tests wit
 - Keeping track of timers and waiting until they expire or are cancelled
 - Keeping track of the React Native operations
 
+:::info
+
+In this synchronization process, [`session.debugSynchronization`](../config/session.mdx#sessiondebugsynchronization-number) plays a significant role. This setting, enabled by default, keeps an eye on tasks that are keeping the app busy and logs the details if these actions take longer than the specified value to complete.
+
+:::
+
 ## Architecture
 
 Detox comprises the following components:
diff --git a/docs/config/logger.mdx b/docs/config/logger.mdx
index a7730771c..4e2671d5c 100644
--- a/docs/config/logger.mdx
+++ b/docs/config/logger.mdx
@@ -25,6 +25,22 @@ Possible values in the descending severity order: `fatal`, `error`, `warn`, `inf
 
 Please note that the log level has no effect on the [generated log files](artifacts.mdx) and their content – it filters only the messages printed to your terminal.
 
+:::info
+
+The verbosity of the logs can be influenced by the [`session.debugSynchronization`](session.mdx#sessiondebugsynchronization-number) setting in your Detox configuration. Enabled by default, this setting helps track the reasons preventing your current actions from completing by identifying what the app is still busy with. It produces logs like this:
+
+```
+15:13:07.309 detox[17005] i The app is busy with the following tasks:
+• There are 10 work items pending on the dispatch queue: "Main Queue (<OS_dispatch_queue_main: com.apple.main-thread>)".
+...
+```
+
+If you want to disable or decrease how often session logs are saved, change the `session.debugSynchronization` value to `0` (to turn off logs) or increase it to `60000` (to lower the frequency of logs). For a deeper understanding of this setting's purpose, refer to the [How Detox Works](../articles/how-detox-works.md#how-detox-automatically-synchronizes-with-your-app) article.
+
+:::
+
+---
+
 ## `logger.overrideConsole` \[boolean\]
 
 Default: `true`.
diff --git a/docs/config/session.mdx b/docs/config/session.mdx
index 675f040cf..f55a71697 100644
--- a/docs/config/session.mdx
+++ b/docs/config/session.mdx
@@ -78,8 +78,7 @@ If `false`, it is assumed that you will be running it independently via [`detox
 
 Default: `10000`.
 
-Tells Detox how long to wait for the app to become idle until it starts querying it for more details.
-Detox will be printing the list of busy idling resources every time an action takes more than the specified period:
+Tells Detox how long (in milliseconds) to wait for the app to become idle until it starts querying it for more details.
 
 ```json
 {
@@ -89,4 +88,22 @@ Detox will be printing the list of busy idling resources every time an action ta
 }
 ```
 
+Detox will be printing the list of busy idling resources every time an action takes more than the specified period, e.g.:
+
+```plain text
+15:13:07.309 detox[17005] i The app is busy with the following tasks:
+• There are 10 work items pending on the dispatch queue: "Main Queue (<OS_dispatch_queue_main: com.apple.main-thread>)".
+• UI elements are busy:
+  - Layers pending animations: 96.
+  - Layers needs layout: 173.
+  - View needs layout: 74.
+  - View needs display: 98.
+  - Layers needs display: 90.
+• Run loop "Main Run Loop" is awake.
+```
+
 To disable this behavior (i.e. querying the app periodically), set the value to `0`.
+
+Seeing logs like these usually indicates certain issues in your application, as mentioned in the [Troubleshooting Guide](../troubleshooting/synchronization.md).
+
+For the most detailed information, refer to the DetoxSync (iOS) [Status Documentation](https://github.com/wix-incubator/DetoxSync/blob/master/StatusDocumentation.md).
diff --git a/docs/contributing.md b/docs/contributing.md
index 42b12edaf..9014072c5 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -144,12 +144,16 @@ launchctl setenv PATH $PATH
 
 ###### 3. Android Native Unit-Tests
 
-Under `detox/android`:
+Under `detox/`:
 
 ```bash
-./gradlew testFullRelease
+npm run unit:android-release
 ```
 
+:::caution Note
+Due to limitations with Robolectric, this currently does not work with JDK 17. Best to use Java 11, as explained in the [Android environment setup guide](./guide/android-dev-env.md).
+:::
+
 ### Detox - Example Projects
 
 This is in fact a monorepo that also sports some example projects (for usage reference), alongside the main test project:
diff --git a/docs/introduction/partials/_getting-started-rn.md b/docs/introduction/partials/_getting-started-rn.md
index 9a858fa4b..f985e8da4 100644
--- a/docs/introduction/partials/_getting-started-rn.md
+++ b/docs/introduction/partials/_getting-started-rn.md
@@ -1,16 +1,15 @@
 <!-- markdownlint-configure-file { "first-line-h1": 0 } -->
 
+Detox was built from the ground up to support "classic" React Native projects.
+
 :::info Note
 
-Mind that Detox works, at least officially, with the following React Native versions:
+While Detox should work out of the box with almost any React Native version from the past few minor releases, official support is provided for React Native versions `0.70.x` and `0.71.x` without React Native's ["New Architecture"](https://reactnative.dev/docs/the-new-architecture/landing-page).
 
-| iOS             | Android                                                                                                                |
-| --------------- | ---------------------------------------------------------------------------------------------------------------------- |
-| 0.68.x - 0.70.7 | 0.68.x - 0.70.7 -<br/>Visibility edge-case: see this [RN issue](https://github.com/facebook/react-native/issues/23870) |
+Newer versions, as well as React Native's "New Architecture", may work with Detox, but have not been tested out yet by the Detox team.
 
-Newer versions, as well as React Native's ["New Architecture"](https://reactnative.dev/docs/the-new-architecture/landing-page), are most likely supported, but have not been tested out yet.
-Please [submit an issue](https://github.com/wix/Detox/issues/new/choose) if you find specific problems with more recent React Native versions.
+Although we do not officially support older React Native versions, we do our best to keep Detox compatible with them.
 
-For support for React Native 0.71.x, please track [this issue](https://github.com/wix/Detox/issues/3884).
+Also, in case of a problem with an unsupported version of React Native, please [submit an issue](https://github.com/wix/Detox/issues/new/choose) or write us in our [Discord server](https://discord.gg/CkD5QKheF5) and we will do our best to help out.
 
 :::
diff --git a/examples/demo-react-native/.gitignore b/examples/demo-react-native/.gitignore
index 82a802018..f05012ea3 100644
--- a/examples/demo-react-native/.gitignore
+++ b/examples/demo-react-native/.gitignore
@@ -40,3 +40,7 @@ buck-out/
 \.buckd/
 android/app/libs
 android/keystores/debug.keystore
+
+# keystore
+*.keystore
+!debug.keystore
diff --git a/examples/demo-react-native/Gemfile b/examples/demo-react-native/Gemfile
index c6622ea8f..9e08671a7 100644
--- a/examples/demo-react-native/Gemfile
+++ b/examples/demo-react-native/Gemfile
@@ -1,4 +1,6 @@
 # Gemfile
 source "https://rubygems.org"
 
-gem "cocoapods", '1.10.1'
+ruby '>= 2.6.10'
+
+gem 'cocoapods', '>= 1.11.3'
diff --git a/examples/demo-react-native/android/app/build.gradle b/examples/demo-react-native/android/app/build.gradle
index 4e315ea19..b5aaa900c 100644
--- a/examples/demo-react-native/android/app/build.gradle
+++ b/examples/demo-react-native/android/app/build.gradle
@@ -1,22 +1,20 @@
 apply plugin: 'com.android.application'
-apply from: '../rninfo.gradle'
-
-project.ext.react = [
-    enableHermes: true
-]
-
-apply from: '../../node_modules/react-native/react.gradle'
+apply plugin: 'com.facebook.react'
 
 android {
-    compileSdkVersion rootProject.ext.compileSdkVersion
+    namespace 'com.example'
+
     buildToolsVersion rootProject.ext.buildToolsVersion
 
     defaultConfig {
         applicationId 'com.detox.rn.example'
-        minSdkVersion rootProject.ext.minSdkVersion
-        targetSdkVersion rootProject.ext.targetSdkVersion
         versionCode 1
         versionName '1.0'
+
+        compileSdk rootProject.ext.compileSdkVersion
+        minSdkVersion rootProject.ext.minSdkVersion
+        targetSdkVersion rootProject.ext.targetSdkVersion
+
         ndk {
             abiFilters 'armeabi-v7a', 'x86', 'x86_64', 'arm64-v8a'
         }
@@ -60,28 +58,16 @@ android {
 }
 
 dependencies {
-    implementation 'androidx.appcompat:appcompat:1.4.2'
+    implementation 'androidx.appcompat:appcompat:1.6.1'
     implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
 
-    // noinspection GradleDynamicVersion
-    implementation 'com.facebook.react:react-native:+'
+    implementation 'com.facebook.react:react-android'
+    // Apply Hermes as the JS engine
+    implementation 'com.facebook.react:hermes-android'
 
-    implementation(project(path: ':AsyncStorage')) // Note: not required unless effectively used by your app
+    implementation(project(path: ':AsyncStorage')) // !!! Note: not required unless effectively used by your app
 
     // noinspection GradleDynamicVersion
     androidTestImplementation 'com.wix:detox:+'
     androidTestImplementation 'com.linkedin.testbutler:test-butler-library:2.2.1'
 }
-
-// Apply Hermes as the JS engine
-dependencies {
-    if (rnInfo.isRN69OrHigher) { // TODO this is actually likely to work with older RN versions as well
-        implementation('com.facebook.react:hermes-engine:+') {
-            exclude group: 'com.facebook.fbjni'
-        }
-    } else {
-        def hermesPath = '../../node_modules/hermes-engine/android'
-        debugImplementation files("$hermesPath/hermes-debug.aar")
-        releaseImplementation files("$hermesPath/hermes-release.aar")
-    }
-}
diff --git a/examples/demo-react-native/android/build.gradle b/examples/demo-react-native/android/build.gradle
index 471db75e8..151ca7932 100644
--- a/examples/demo-react-native/android/build.gradle
+++ b/examples/demo-react-native/android/build.gradle
@@ -1,18 +1,11 @@
 buildscript {
-    apply from: './rninfo.gradle'
-
-    def androidGradlePluginVersion =
-        rnInfo.isRN69OrHigher ? '7.1.1'
-            : rnInfo.isRN68OrHigher ? '7.0.4'
-            : '4.2.2'
-
-    println "[$project] Resorted to Android Gradle-plugin version $androidGradlePluginVersion"
+    def androidGradlePluginVersion = '7.3.1'
 
     ext {
-        kotlinVersion = '1.6.21'
-        buildToolsVersion = '31.0.0'
-        compileSdkVersion = 31
-        targetSdkVersion = 31
+        kotlinVersion = '1.8.22'
+        buildToolsVersion = '33.0.0'
+        compileSdkVersion = 33
+        targetSdkVersion = 33
         minSdkVersion = 21
     }
 
@@ -23,19 +16,15 @@ buildscript {
 
     dependencies {
         classpath "com.android.tools.build:gradle:$androidGradlePluginVersion"
-        classpath 'de.undercouch:gradle-download-task:5.0.1'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
+        classpath "com.facebook.react:react-native-gradle-plugin"
     }
 }
 
 allprojects {
     repositories {
-        mavenLocal()
-        mavenCentral()
         google()
-        maven {
-            url "$rootDir/../node_modules/react-native/android"
-        }
+        mavenCentral()
         maven {
             url "$rootDir/../node_modules/detox/Detox-android"
         }
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 0ef353fc4..21ba8be86 100644
--- a/examples/demo-react-native/android/gradle/wrapper/gradle-wrapper.properties
+++ b/examples/demo-react-native/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip
 
diff --git a/examples/demo-react-native/android/rninfo.gradle b/examples/demo-react-native/android/rninfo.gradle
deleted file mode 100644
index e6ec1be9e..000000000
--- a/examples/demo-react-native/android/rninfo.gradle
+++ /dev/null
@@ -1,20 +0,0 @@
-import groovy.json.JsonSlurper
-
-def rnMajorVer = getRNMajorVersion()
-println "[$project] rn-info: detected React Native version (major) $rnMajorVer"
-
-project.ext.rnInfo = [
-        majorVersion: rnMajorVer,
-        isRN64OrHigher: rnMajorVer >= 64,
-        isRN66OrHigher: rnMajorVer >= 66,
-        isRN68OrHigher: rnMajorVer >= 68,
-        isRN69OrHigher: rnMajorVer >= 69,
-]
-
-private static def getRNMajorVersion() {
-    def jsonSlurper = new JsonSlurper()
-    Map<String, Object> packageJSON  = jsonSlurper.parse(new File('../node_modules/react-native/package.json'))
-    String rnVersion = packageJSON.get('version')
-    Integer rnVersionMajor = rnVersion.split('\\.')[1].toInteger()
-    return rnVersionMajor
-}
diff --git a/examples/demo-react-native/android/settings.gradle b/examples/demo-react-native/android/settings.gradle
index 6a1f00670..fd5daed53 100644
--- a/examples/demo-react-native/android/settings.gradle
+++ b/examples/demo-react-native/android/settings.gradle
@@ -1,6 +1,7 @@
 rootProject.name = 'DetoxRNExample'
 
 include ':app'
+includeBuild('../node_modules/react-native-gradle-plugin')
 
 include ':AsyncStorage'
 project(':AsyncStorage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-async-storage/async-storage/android')
diff --git a/examples/demo-react-native/ios/Podfile b/examples/demo-react-native/ios/Podfile
index 471dbf0c0..e00e5f624 100644
--- a/examples/demo-react-native/ios/Podfile
+++ b/examples/demo-react-native/ios/Podfile
@@ -1,10 +1,11 @@
 require_relative '../node_modules/react-native/scripts/react_native_pods'
 require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
 
-platform :ios, '12.4'
+platform :ios, '13.0'
 
 target 'example' do
   config = use_native_modules!
+
   use_react_native!(
     :path => config[:reactNativePath],
     # to enable hermes on iOS, change `false` to `true` and then install pods
@@ -13,15 +14,26 @@ target 'example' do
   )
 
   post_install do |installer|
-      if ENV["REACT_NATIVE_VERSION"] && ENV["REACT_NATIVE_VERSION"].match(/0.6[6,7,8,9].*/)
-        react_native_post_install(installer)
-      else
-        react_native_post_install(
-          installer,
-            # Set `mac_catalyst_enabled` to `true` in order to apply patches
-            # necessary for Mac Catalyst builds
-            :mac_catalyst_enabled => false
-        )
+    __apply_update_deployment_target_workaround(installer)
+
+     react_native_post_install(
+       installer,
+       # Set `mac_catalyst_enabled` to `true` in order to apply patches
+       # necessary for Mac Catalyst builds
+       :mac_catalyst_enabled => false
+     )
+  end
+end
+
+def __apply_update_deployment_target_workaround(installer)
+  # This is a workaround for updating the deployment target of pod targets to the minimal supported version.
+  # See StackOverflow: https://stackoverflow.com/questions/72729591/fbreactnativespec-h-error-after-upgrading-from-0-68-x-to-0-69-0/75915794#75915794
+  installer.pods_project.targets.each do |target|
+    target.build_configurations.each do |config|
+      config.build_settings['SWIFT_VERSION'] = '5.0'
+      if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] < '12.4'
+        config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.4'
       end
+    end
   end
 end
diff --git a/examples/demo-react-native/ios/example.xcodeproj/project.pbxproj b/examples/demo-react-native/ios/example.xcodeproj/project.pbxproj
index 70640da91..0009379ab 100644
--- a/examples/demo-react-native/ios/example.xcodeproj/project.pbxproj
+++ b/examples/demo-react-native/ios/example.xcodeproj/project.pbxproj
@@ -330,7 +330,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;
@@ -378,7 +378,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;
diff --git a/examples/demo-react-native/package.json b/examples/demo-react-native/package.json
index 2c206abc5..a4c1650da 100644
--- a/examples/demo-react-native/package.json
+++ b/examples/demo-react-native/package.json
@@ -22,18 +22,20 @@
     "clean:android": "pushd android && ./gradlew clean && popd"
   },
   "dependencies": {
-    "@react-native-async-storage/async-storage": "^1.17.3",
-    "react": "18.1.0",
-    "react-native": "0.70.7",
+    "@react-native-async-storage/async-storage": "^1.17.11",
+    "react": "18.2.0",
+    "react-native": "0.71.10",
     "tslib": "^2.0.3"
   },
   "devDependencies": {
-    "@types/fs-extra": "^9.0.13",
-    "@types/jest": "^29.2.1",
     "detox": "^20.9.1",
     "fs-extra": "^9.1.0",
-    "jest": "^29.2.1",
+    "@types/fs-extra": "^9.0.13",
     "ts-jest": "^29.0.3",
-    "typescript": "^4.1.3"
+    "@tsconfig/react-native": "^2.0.2",
+    "@types/jest": "^29.2.1",
+    "@types/react": "^18.0.24",
+    "jest": "^29.2.1",
+    "typescript": "^4.8.4"
   }
 }
diff --git a/scripts/ci.android.sh b/scripts/ci.android.sh
index 6aca79283..8855d71db 100755
--- a/scripts/ci.android.sh
+++ b/scripts/ci.android.sh
@@ -3,31 +3,24 @@
 UPLOAD_ARTIFACT="$(pwd)/scripts/upload_artifact.sh"
 trap "$UPLOAD_ARTIFACT" EXIT
 
-# Approve unapproved SDK licenses
+### Approve unapproved SDK licenses
 yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses
 
-currentRnVersion=$(echo "${REACT_NATIVE_VERSION}" | cut -d "." -f2);
-if [[ $currentRnVersion -ge 68 ]]; then
-  source $(dirname "$0")/ci.sh
-else
-  echo 'Warning: Setting "skip" over invoke-code auto-generation because the react-native is lower than 68 and therefore contains patches (see detox/test/postinstall.js)'
-  source $(dirname "$0")/ci.sh 'noGenerate'
-fi
+source $(dirname "$0")/ci.sh
 
+### JS integration tests
 pushd detox/test
 run_f "npm run integration"
 popd
 
-if [[ $currentRnVersion -ge 66 ]]; then
-  pushd detox/android
-  run_f "npm run unit:android-release"
-  popd
-else
-  echo "Skipping Android unit tests (react-native version ${currentRnVersion} is not ≥66)"
-fi
+### Android native unit tests
+pushd detox/android
+run_f "npm run unit:android-release"
+popd
 
 mkdir -p coverage
 
+### E2E self-tests
 pushd detox/test
 
 run_f "npm run build:android"
diff --git a/scripts/ci.ios.sh b/scripts/ci.ios.sh
index 30bbf85c4..21ad56aee 100755
--- a/scripts/ci.ios.sh
+++ b/scripts/ci.ios.sh
@@ -3,7 +3,7 @@
 UPLOAD_ARTIFACT="$(pwd)/scripts/upload_artifact.sh"
 trap "$UPLOAD_ARTIFACT" EXIT
 
-source $(dirname "$0")/ci.sh 'noGenerate'
+source $(dirname "$0")/ci.sh
 
 mkdir -p coverage
 


Test patch:
diff --git a/.github/workflows/rapid-test.yml b/.github/workflows/rapid-test.yml
index ba75fd129..a75bd8c17 100644
--- a/.github/workflows/rapid-test.yml
+++ b/.github/workflows/rapid-test.yml
@@ -25,7 +25,7 @@ jobs:
         with:
           node-version: 14
       - name: Bootstrap Lerna
-        run: npx lerna bootstrap --no-ci
+        run: npx lerna@3 bootstrap --no-ci
         env:
           DETOX_DISABLE_POD_INSTALL: true
           DETOX_DISABLE_POSTINSTALL: true
@@ -57,7 +57,7 @@ jobs:
             with:
                 node-version: 14
           - name: Bootstrap Lerna (Light)
-            run: npx lerna bootstrap --no-ci --include-dependents --include-dependencies --scope=detox-test
+            run: npx lerna@3 bootstrap --no-ci --include-dependents --include-dependencies --scope=detox-test
             env:
                 DETOX_DISABLE_POD_INSTALL: true
                 DETOX_DISABLE_POSTINSTALL: true
diff --git a/detox/test/.gitignore b/detox/test/.gitignore
index 80e044004..393208085 100644
--- a/detox/test/.gitignore
+++ b/detox/test/.gitignore
@@ -38,8 +38,15 @@ local.properties
 node_modules/
 npm-debug.log
 
+# keystore
+*.keystore
+!debug.keystore
+
 # BUCK
 buck-out/
 \.buckd/
 android/app/libs
 android/keystores/debug.keystore
+
+# Temporary files created by Metro to check the health of the file watcher
+.metro-health-check*
diff --git a/detox/test/android/app/build.gradle b/detox/test/android/app/build.gradle
index ed0fffddc..022f8e8cb 100644
--- a/detox/test/android/app/build.gradle
+++ b/detox/test/android/app/build.gradle
@@ -1,12 +1,18 @@
 apply plugin: 'com.android.application'
-apply from: '../rninfo.gradle'
+apply from: '../../../android/rninfo.gradle'
 
-project.ext.react = [
-    enableHermes: true
-]
-apply from: '../../node_modules/react-native/react.gradle'
+if (rnInfo.isRN71OrHigher) {
+    apply plugin: 'com.facebook.react'
+} else {
+    project.ext.react = [
+        enableHermes: true
+    ]
+    apply from: '../../node_modules/react-native/react.gradle'
+}
 
 android {
+    namespace 'com.example'
+
     compileSdkVersion rootProject.ext.compileSdkVersion
 
     compileOptions {
@@ -60,12 +66,12 @@ android {
         }
     }
     productFlavors {
-        flavorDimensions 'compileRNFromSource'
-        fromSource {
-            dimension 'compileRNFromSource'
+        flavorDimensions 'reactNativeVersion'
+        rnDefault {
+            dimension 'reactNativeVersion'
         }
-        fromBin {
-            dimension 'compileRNFromSource'
+        rnLegacy { // For RN < 71
+            dimension 'reactNativeVersion'
         }
     }
     packagingOptions {
@@ -80,19 +86,14 @@ android {
 }
 
 dependencies {
+    // The version of react-native is set by the React Native Gradle Plugin
+    rnDefaultImplementation('com.facebook.react:react-android')
+    // noinspection GradleDynamicVersion
+    rnLegacyImplementation('com.facebook.react:react-native:+')
+
     implementation "androidx.appcompat:appcompat:${rootProject.ext.appCompatVersion}"
     implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
     implementation project(path: ':@react-native-community_slider')
-
-    // noinspection GradleDynamicVersion
-    fromBinImplementation('com.facebook.react:react-native:+')
-
-    // In order to build from source:
-    // 1. Uncomment the fromSourceImplementation() statement
-    // 2. Uncomment associated section in settings.gradle
-    // 3. Pave through the wilderness
-//    fromSourceImplementation(project(path: ':ReactAndroid'))
-
     implementation project(':AsyncStorage')
     implementation project(':react-native-webview')
     implementation project(':react-native-community-checkbox')
@@ -106,14 +107,10 @@ dependencies {
 
 // Apply Hermes as our JS engine
 dependencies {
-    if (rnInfo.isRN69OrHigher) { // TODO this is actually likely to work with older RN versions as well
-        implementation('com.facebook.react:hermes-engine:+') {
-            exclude group: 'com.facebook.fbjni'
-        }
-    } else {
-        def hermesPath = '../../node_modules/hermes-engine/android'
-        debugImplementation files("$hermesPath/hermes-debug.aar")
-        releaseImplementation files("$hermesPath/hermes-release.aar")
+    rnDefaultImplementation('com.facebook.react:hermes-android')
+    // noinspection GradleDynamicVersion
+    rnLegacyImplementation('com.facebook.react:hermes-engine:+') {
+        exclude group: 'com.facebook.fbjni'
     }
 }
 
diff --git a/detox/test/android/app/src/main/java/com/example/DetoxRNHost.java b/detox/test/android/app/src/main/java/com/example/ReactNativeAdapter.java
similarity index 55%
rename from detox/test/android/app/src/main/java/com/example/DetoxRNHost.java
rename to detox/test/android/app/src/main/java/com/example/ReactNativeAdapter.java
index 5672f3ddc..e0d255250 100644
--- a/detox/test/android/app/src/main/java/com/example/DetoxRNHost.java
+++ b/detox/test/android/app/src/main/java/com/example/ReactNativeAdapter.java
@@ -1,39 +1,20 @@
 package com.example;
 
-import android.app.Application;
-
-import com.facebook.react.ReactNativeHost;
 import com.facebook.react.ReactPackage;
 import com.facebook.react.shell.MainReactPackage;
+import com.reactcommunity.rndatetimepicker.RNDateTimePickerPackage;
 import com.reactnativecommunity.asyncstorage.AsyncStoragePackage;
 import com.reactnativecommunity.checkbox.ReactCheckBoxPackage;
 import com.reactnativecommunity.geolocation.GeolocationPackage;
 import com.reactnativecommunity.slider.ReactSliderPackage;
 import com.reactnativecommunity.webview.RNCWebViewPackage;
-import com.reactcommunity.rndatetimepicker.RNDateTimePickerPackage;
 import com.reactnativelauncharguments.LaunchArgumentsPackage;
 
 import java.util.Arrays;
 import java.util.List;
 
-class DetoxRNHost extends ReactNativeHost {
-    protected DetoxRNHost(Application application) {
-        super(application);
-    }
-
-    @Override
-    public boolean getUseDeveloperSupport() {
-        return BuildConfig.DEBUG;
-    }
-
-    @Override
-    protected List<ReactPackage> getPackages() {
-        // Packages that cannot be autolinked yet can be added manually here, for example:
-        //     packages.add(new MyReactNativePackage());
-        // TurboModules must also be loaded here providing a valid TurboReactPackage implementation:
-        //     packages.add(new TurboReactPackage() { ... });
-        // If you have custom Fabric Components, their ViewManagers should also be loaded here
-        // inside a ReactPackage.
+class ReactNativeAdapter {
+    public static List<ReactPackage> getManualLinkPackages() {
         return Arrays.asList(
             new MainReactPackage(),
             new ReactSliderPackage(),
diff --git a/detox/test/android/app/src/rnDefault/java/com/example/DetoxRNHost.java b/detox/test/android/app/src/rnDefault/java/com/example/DetoxRNHost.java
new file mode 100644
index 000000000..6cceef33b
--- /dev/null
+++ b/detox/test/android/app/src/rnDefault/java/com/example/DetoxRNHost.java
@@ -0,0 +1,40 @@
+package com.example;
+
+import android.app.Application;
+
+import com.facebook.react.ReactPackage;
+import com.facebook.react.defaults.DefaultReactNativeHost;
+
+import java.util.List;
+
+class DetoxRNHost extends DefaultReactNativeHost {
+    protected DetoxRNHost(Application application) {
+        super(application);
+    }
+
+    @Override
+    public boolean getUseDeveloperSupport() {
+        return BuildConfig.DEBUG;
+    }
+
+    @Override
+    protected boolean isNewArchEnabled() {
+        return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
+    }
+
+    @Override
+    protected Boolean isHermesEnabled() {
+        return BuildConfig.IS_HERMES_ENABLED;
+    }
+
+    @Override
+    protected List<ReactPackage> getPackages() {
+        // Packages that cannot be autolinked yet can be added manually here, for example:
+        //     packages.add(new MyReactNativePackage());
+        // TurboModules must also be loaded here providing a valid TurboReactPackage implementation:
+        //     packages.add(new TurboReactPackage() { ... });
+        // If you have custom Fabric Components, their ViewManagers should also be loaded here
+        // inside a ReactPackage.
+        return ReactNativeAdapter.getManualLinkPackages();
+    }
+}
diff --git a/detox/test/android/app/src/rnLegacy/java/com/example/DetoxRNHost.java b/detox/test/android/app/src/rnLegacy/java/com/example/DetoxRNHost.java
new file mode 100644
index 000000000..1fc65e9d3
--- /dev/null
+++ b/detox/test/android/app/src/rnLegacy/java/com/example/DetoxRNHost.java
@@ -0,0 +1,30 @@
+package com.example;
+
+import android.app.Application;
+
+import com.facebook.react.ReactPackage;
+import com.facebook.react.ReactNativeHost;
+
+import java.util.List;
+
+class DetoxRNHost extends ReactNativeHost {
+    protected DetoxRNHost(Application application) {
+        super(application);
+    }
+
+    @Override
+    public boolean getUseDeveloperSupport() {
+        return BuildConfig.DEBUG;
+    }
+
+    @Override
+    protected List<ReactPackage> getPackages() {
+        // Packages that cannot be autolinked yet can be added manually here, for example:
+        //     packages.add(new MyReactNativePackage());
+        // TurboModules must also be loaded here providing a valid TurboReactPackage implementation:
+        //     packages.add(new TurboReactPackage() { ... });
+        // If you have custom Fabric Components, their ViewManagers should also be loaded here
+        // inside a ReactPackage.
+        return ReactNativeAdapter.getManualLinkPackages();
+    }
+}
diff --git a/detox/test/android/build.gradle b/detox/test/android/build.gradle
index 81a1cc43d..cbcbe1ea3 100644
--- a/detox/test/android/build.gradle
+++ b/detox/test/android/build.gradle
@@ -1,35 +1,44 @@
 buildscript {
-    apply from: './rninfo.gradle'
+    apply from: '../../android/rninfo.gradle'
 
     def androidGradlePluginVersion =
-            rnInfo.isRN70OrHigher ? '7.2.1'
-            : rnInfo.isRN69OrHigher ? '7.1.1'
-            : rnInfo.isRN68OrHigher ? '7.0.4'
-            : rnInfo.isRN66OrHigher ? '4.2.2'
-            : '4.1.0'
+        rnInfo.isRN71OrHigher ? '7.3.1' :
+        rnInfo.isRN70OrHigher ? '7.2.1' :
+        rnInfo.isRN69OrHigher ? '7.1.1' :
+                                '7.0.4'
 
     println "[$project] Resorted to Android Gradle-plugin version $androidGradlePluginVersion"
 
     ext {
         isOfficialDetoxApp = true
         kotlinVersion = '1.6.21'
-        buildToolsVersion = '31.0.0'
-        compileSdkVersion = 31
-        targetSdkVersion = 31
+        buildToolsVersion = '33.0.0'
+        compileSdkVersion = 33
+        targetSdkVersion = 33
         minSdkVersion = 21
-        appCompatVersion = (rnInfo.isRN68OrHigher ? '1.4.2' : '1.3.0')
+        appCompatVersion = '1.4.2'
     }
     ext.detoxKotlinVersion = ext.kotlinVersion
 
     repositories {
         google()
-        mavenLocal()
         mavenCentral()
+        mavenLocal()
     }
 
     dependencies {
         classpath "com.android.tools.build:gradle:$androidGradlePluginVersion"
-        classpath 'de.undercouch:gradle-download-task:5.0.1'
+
+        // In RN .71, they've switched to the gradle plugin they've uploaded to maven-central
+        if (rnInfo.isRN71OrHigher) {
+            classpath 'com.facebook.react:react-native-gradle-plugin'
+        }
+
+        // Gradle task downloader seems to come built-in in newer versions of RN/Gradle
+        if (!rnInfo.isRN71OrHigher) {
+            classpath 'de.undercouch:gradle-download-task:5.0.1'
+        }
+
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
 
         // Needed by Spek (https://spekframework.org/setup-android)
@@ -40,20 +49,23 @@ buildscript {
 
 allprojects {
     repositories {
-        maven {
-            url "$rootDir/../../node_modules/react-native/android"
-        }
-        maven {
-            url("$rootDir/../node_modules/jsc-android/dist")
-        }
-        mavenCentral {
-            // We don't want to fetch react-native from Maven Central as there are
-            // older versions over there.
-            content {
-                excludeGroup "com.facebook.react"
+        // In RN below 71, we want RN and hermes/js-core native code to come from node_modules/ rather
+        // than from maven-central, because there are also older versions over there.
+        if (rnInfo.isRN71OrHigher) {
+            google()
+            mavenCentral()
+        } else {
+            maven {
+                url "$rootDir/../../node_modules/react-native/android"
+            }
+            google()
+            mavenCentral() {
+                content {
+                    excludeGroup 'com.facebook.react'
+                }
             }
         }
-        google()
+
         mavenLocal()
     }
 }
diff --git a/detox/test/android/gradle.properties b/detox/test/android/gradle.properties
index 843fdf4d8..a5fa801df 100644
--- a/detox/test/android/gradle.properties
+++ b/detox/test/android/gradle.properties
@@ -16,5 +16,18 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
 # This option should only be used with decoupled projects. More details, visit
 # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
 # org.gradle.parallel=true
+
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
 android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
 android.enableJetifier=true
+
+# Use this property to enable or disable the Hermes JS engine.
+# If set to false, you will be using JSC instead.
+hermesEnabled=true
+
+reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
+newArchEnabled=false
+
diff --git a/detox/test/android/gradle/wrapper/gradle-wrapper.properties b/detox/test/android/gradle/wrapper/gradle-wrapper.properties
index 37d820d8a..e81f2af1a 100644
--- a/detox/test/android/gradle/wrapper/gradle-wrapper.properties
+++ b/detox/test/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 #Thu Feb 09 21:36:01 IST 2023
 distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
 distributionPath=wrapper/dists
 zipStorePath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/detox/test/android/rninfo.gradle b/detox/test/android/rninfo.gradle
deleted file mode 100644
index 2813e15e6..000000000
--- a/detox/test/android/rninfo.gradle
+++ /dev/null
@@ -1,21 +0,0 @@
-import groovy.json.JsonSlurper
-
-def rnMajorVer = getRNMajorVersion()
-println "[$project] rn-info: detected React Native version (major) $rnMajorVer"
-
-project.ext.rnInfo = [
-    majorVersion: rnMajorVer,
-    isRN64OrHigher: rnMajorVer >= 64,
-    isRN66OrHigher: rnMajorVer >= 66,
-    isRN68OrHigher: rnMajorVer >= 68,
-    isRN69OrHigher: rnMajorVer >= 69,
-    isRN70OrHigher: rnMajorVer >= 70,
-]
-
-private static def getRNMajorVersion() {
-    def jsonSlurper = new JsonSlurper()
-    Map<String, Object> packageJSON  = jsonSlurper.parse(new File('../node_modules/react-native/package.json'))
-    String rnVersion = packageJSON.get('version')
-    Integer rnVersionMajor = rnVersion.split('\\.')[1].toInteger()
-    return rnVersionMajor
-}
diff --git a/detox/test/android/settings.gradle b/detox/test/android/settings.gradle
index 61f9bde4e..ca5d4b00f 100644
--- a/detox/test/android/settings.gradle
+++ b/detox/test/android/settings.gradle
@@ -1,11 +1,3 @@
-pluginManagement {
-    repositories {
-        gradlePluginPortal()
-        google()
-        mavenCentral()
-    }
-}
-
 rootProject.name = 'DetoxTest'
 include ':app'
 
@@ -14,16 +6,6 @@ includeBuild('../node_modules/react-native-gradle-plugin')
 include ':detox'
 project(':detox').projectDir = new File(rootProject.projectDir, '../../android/detox')
 
-// Technically needed in order to actually build RN from source
-//include ':ReactAndroid'
-//project(':ReactAndroid').projectDir = new File(rootProject.projectDir, '../node_modules/react-native/ReactAndroid')
-//
-//File hermesEnginePath = new File(rootProject.projectDir, '../node_modules/react-native/ReactAndroid/hermes-engine')
-//if (hermesEnginePath.exists()) {
-//    include(':ReactAndroid:hermes-engine')
-//    project(':ReactAndroid:hermes-engine').projectDir = hermesEnginePath
-//}
-
 include ':AsyncStorage'
 project(':AsyncStorage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-async-storage/async-storage/android')
 
diff --git a/detox/test/e2e/detox.config-android.js b/detox/test/e2e/detox.config-android.js
new file mode 100644
index 000000000..99044dd83
--- /dev/null
+++ b/detox/test/e2e/detox.config-android.js
@@ -0,0 +1,23 @@
+const _ = require('lodash');
+const { rnVersion } = require('./utils/rn-consts');
+
+const capitalizeFirstLetter = (str) => str.charAt(0).toUpperCase() + str.slice(1);
+const isLegacyRNVersion = (rnVersion.minor < 71);
+const warnOnce = _.once((...args) => console.warn(...args));
+
+function androidBaseAppConfig(buildType /* 'debug' | 'release' */) {
+  const warnRNLegacy = () => warnOnce(`⚠️ Detected a legacy RN version (v${rnVersion.raw}) - Using legacy build-flavor for Android config! 🤖🛠\n`);
+
+  const appFlavor = (isLegacyRNVersion ? warnRNLegacy() || 'rnLegacy' : 'rnDefault');
+  const appFlavorUC = capitalizeFirstLetter(appFlavor);
+  const buildTypeUC = capitalizeFirstLetter(buildType);
+  return {
+    type: 'android.apk',
+    binaryPath: `android/app/build/outputs/apk/${appFlavor}/${buildType}/app-${appFlavor}-${buildType}.apk`,
+    build: `cd android && ./gradlew assemble${appFlavorUC}${buildTypeUC} assemble${appFlavorUC}${buildTypeUC}AndroidTest -DtestBuildType=${buildType} && cd ..`,
+  };
+}
+
+module.exports = {
+  androidBaseAppConfig,
+};
diff --git a/detox/test/e2e/detox.config.js b/detox/test/e2e/detox.config.js
index 1c22b8593..597e769e3 100644
--- a/detox/test/e2e/detox.config.js
+++ b/detox/test/e2e/detox.config.js
@@ -1,3 +1,5 @@
+const { androidBaseAppConfig } = require('./detox.config-android');
+
 const launchArgs = {
   app: 'le',
   goo: 'gle?',
@@ -67,51 +69,28 @@ const config = {
     },
 
     'android.debug': {
-      type: 'android.apk',
+      ...androidBaseAppConfig('debug'),
       name: 'example',
-      binaryPath: 'android/app/build/outputs/apk/fromBin/debug/app-fromBin-debug.apk',
-      build: 'cd android && ./gradlew assembleFromBinDebug assembleFromBinDebugAndroidTest -DtestBuildType=debug && cd ..',
       start: 'react-native start',
       reversePorts: [8081],
     },
 
     'android.debug.withArgs': {
-      type: 'android.apk',
+      ...androidBaseAppConfig('debug'),
       name: 'exampleWithArgs',
-      binaryPath: 'android/app/build/outputs/apk/fromBin/debug/app-fromBin-debug.apk',
-      build: ':',
-      reversePorts: [8081],
-      launchArgs,
-    },
-
-    'android.fromSource': {
-      type: 'android.apk',
-      name: 'example',
-      binaryPath: 'android/app/build/outputs/apk/fromSource/debug/app-fromSource-debug.apk',
-      build: 'cd android && ./gradlew assembleFromSourceDebug assembleFromSourceDebugAndroidTest -DtestBuildType=debug && cd ..',
-      reversePorts: [8081],
-    },
-
-    'android.fromSource.withArgs': {
-      type: 'android.apk',
-      name: 'example',
-      binaryPath: 'android/app/build/outputs/apk/fromSource/debug/app-fromSource-debug.apk',
       build: ':',
       reversePorts: [8081],
       launchArgs,
     },
 
     'android.release': {
-      type: 'android.apk',
+      ...androidBaseAppConfig('release'),
       name: 'example',
-      binaryPath: 'android/app/build/outputs/apk/fromBin/release/app-fromBin-release.apk',
-      build: 'cd android && ./gradlew assembleFromBinRelease assembleFromBinReleaseAndroidTest -DtestBuildType=release && cd ..',
     },
 
     'android.release.withArgs': {
-      type: 'android.apk',
+      ...androidBaseAppConfig('release'),
       name: 'exampleWithArgs',
-      binaryPath: 'android/app/build/outputs/apk/fromBin/release/app-fromBin-release.apk',
       build: ':',
       launchArgs,
     },
@@ -192,10 +171,6 @@ const config = {
       device: 'android.emulator',
       apps: ['android.debug', 'android.debug.withArgs'],
     },
-    'android.emu.debug.fromSource': {
-      device: 'android.emulator',
-      apps: ['android.fromSource', 'android.fromSource.withArgs'],
-    },
     'android.emu.release': {
       device: 'android.emulator',
       apps: ['android.release', 'android.release.withArgs'],
diff --git a/detox/test/ios/Podfile b/detox/test/ios/Podfile
index 5c87f0f97..416db5bcf 100644
--- a/detox/test/ios/Podfile
+++ b/detox/test/ios/Podfile
@@ -1,11 +1,12 @@
 require_relative '../node_modules/react-native/scripts/react_native_pods'
 require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
 
-platform :ios, '12.4'
+platform :ios, '13.0'
+
 install! 'cocoapods', :deterministic_uuids => false
 
 def shared_pods
-  config = use_native_modules!
+    config = use_native_modules!
 
     if !ENV["REACT_NATIVE_VERSION"] || ENV["REACT_NATIVE_VERSION"].match(/0.(68|69|70).*/)
       # Flags change depending on the env values.
diff --git a/detox/test/package.json b/detox/test/package.json
index 3ac1453bc..6ce478b1f 100644
--- a/detox/test/package.json
+++ b/detox/test/package.json
@@ -16,7 +16,7 @@
     "e2e:ios": "detox test -c ios.sim.release",
     "e2e:android": "detox test -c android.emu.release",
     "e2e:android:genycloud": "detox test -c android.genycloud.release",
-    "e2e:android:debug": "detox test -c android.emu.debug",
+    "e2e:android-debug": "detox test -c android.emu.debug",
     "e2e-unhappy:ios": "scripts/ci_unhappy.sh ios",
     "e2e-unhappy:android": "scripts/ci_unhappy.sh android",
     "integration": "jest --clearCache && jest --config integration/jest.config.js",
@@ -39,19 +39,20 @@
     "@react-native-picker/picker": "^2.1.0",
     "@react-native-segmented-control/segmented-control": "2.3.0",
     "moment": "^2.24.0",
-    "react": "18.1.0",
-    "react-native": "0.70.7",
-    "react-native-gradle-plugin": "^0.0.7",
+    "react": "18.2.0",
+    "react-native": "0.71.10",
     "react-native-launch-arguments": "^4.0.0",
     "react-native-webview": "^11.18.1"
   },
   "devDependencies": {
-    "@babel/core": "^7.12.9",
-    "@babel/runtime": "^7.12.5",
+    "@babel/core": "^7.20.0",
+    "@babel/preset-env": "^7.20.0",
+    "@babel/runtime": "^7.20.0",
     "@react-native-community/eslint-config": "^3.0.1",
+    "@tsconfig/react-native": "^2.0.2",
     "@types/node": "^14.18.33",
-    "@types/react": "18.0.0",
-    "@types/react-native": "0.69.7",
+    "@types/jest": "^29.2.1",
+    "@types/react": "^18.0.24",
     "@typescript-eslint/eslint-plugin": "^5.4.0",
     "@typescript-eslint/parser": "^5.4.0",
     "cross-env": "^7.0.3",
@@ -61,11 +62,11 @@
     "execa": "^5.1.1",
     "express": "^4.15.3",
     "glob": "^7.2.0",
-    "jest": "^29.2.0",
+    "jest": "^29.2.1",
     "jest-allure2-reporter": "^1.2.0",
     "jest-junit": "^10.0.0",
     "lodash": "^4.14.1",
-    "metro-react-native-babel-preset": "^0.70.3",
+    "metro-react-native-babel-preset": "0.73.9",
     "nyc": "^15.1.0",
     "p-iteration": "^1.1.8",
     "pngjs": "^3.4.0",