detox 20.50.0 → 20.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/Detox-android/com/wix/detox/{20.50.0/detox-20.50.0-sources.jar → 20.51.0/detox-20.51.0-sources.jar} +0 -0
  2. package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0-sources.jar.md5 +1 -0
  3. package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0-sources.jar.sha1 +1 -0
  4. package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0-sources.jar.sha256 +1 -0
  5. package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0-sources.jar.sha512 +1 -0
  6. package/Detox-android/com/wix/detox/{20.50.0/detox-20.50.0.aar → 20.51.0/detox-20.51.0.aar} +0 -0
  7. package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.aar.md5 +1 -0
  8. package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.aar.sha1 +1 -0
  9. package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.aar.sha256 +1 -0
  10. package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.aar.sha512 +1 -0
  11. package/Detox-android/com/wix/detox/{20.50.0/detox-20.50.0.pom → 20.51.0/detox-20.51.0.pom} +1 -1
  12. package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.pom.md5 +1 -0
  13. package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.pom.sha1 +1 -0
  14. package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.pom.sha256 +1 -0
  15. package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.pom.sha512 +1 -0
  16. package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
  17. package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
  18. package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
  19. package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
  20. package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
  21. package/Detox-ios-framework.tbz +0 -0
  22. package/Detox-ios-src.tbz +0 -0
  23. package/Detox-ios-xcuitest.tbz +0 -0
  24. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactApplicationExt.kt +1 -1
  25. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeExtension.kt +47 -0
  26. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeInfo.kt +7 -0
  27. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeLoadingMonitor.kt +2 -2
  28. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/bridge/BridgeIdlingResource.kt +82 -15
  29. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/factory/DetoxIdlingResourceFactory.kt +3 -1
  30. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/fabric/FabricUIManagerIdlingResources.kt +46 -5
  31. package/android/detox/src/full/java/com/wix/detox/reactnative/reloader/ReactNativeReloaderFactory.kt +3 -1
  32. package/android/rninfo.gradle +1 -0
  33. package/package.json +2 -2
  34. package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0-sources.jar.md5 +0 -1
  35. package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0-sources.jar.sha1 +0 -1
  36. package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0-sources.jar.sha256 +0 -1
  37. package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0-sources.jar.sha512 +0 -1
  38. package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.aar.md5 +0 -1
  39. package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.aar.sha1 +0 -1
  40. package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.aar.sha256 +0 -1
  41. package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.aar.sha512 +0 -1
  42. package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.pom.md5 +0 -1
  43. package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.pom.sha1 +0 -1
  44. package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.pom.sha256 +0 -1
  45. package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.pom.sha512 +0 -1
@@ -0,0 +1 @@
1
+ e27fa966e5fe0bb1e0fe9d8dd47eae6e
@@ -0,0 +1 @@
1
+ 3aa39b2445e86ce9bb4f83d7a9999a421beabee3
@@ -0,0 +1 @@
1
+ 564e9102f35c86563510cf1331341bae4d6a84016f65f76dd53db4cd584cd013
@@ -0,0 +1 @@
1
+ 9f1dea736c2ec071b9e24bc8ce64169dc7eff5d7e9e4daa574f17815e18a9bf253f7e13d20e3d2eddbd13d2937a35591afeb4ca5fb896abc131e7447e3ed15db
@@ -0,0 +1 @@
1
+ 6c5b17b6a287db32fabf71f59d996c8c
@@ -0,0 +1 @@
1
+ ce92a1ede1c4f01124d52e3634d1f99ab04a1306
@@ -0,0 +1 @@
1
+ 1b268238bf728bad459c4d32b46d5311ce8e3a0d86203859ca03c1e7574b8eab
@@ -0,0 +1 @@
1
+ 3c9f2db925939dbb46592be2b21b864ecf2b7a0da3fba3841693721158618a6ca57d66346537c1e002dd184789c9bdf2892cefb8296fcf1e18a4eb724486fd59
@@ -3,7 +3,7 @@
3
3
  <modelVersion>4.0.0</modelVersion>
4
4
  <groupId>com.wix</groupId>
5
5
  <artifactId>detox</artifactId>
6
- <version>20.50.0</version>
6
+ <version>20.51.0</version>
7
7
  <packaging>aar</packaging>
8
8
  <name>Detox</name>
9
9
  <description>Gray box end-to-end testing and automation library for mobile apps</description>
@@ -0,0 +1 @@
1
+ 7ad13486ca1c01283092cad941dd6ae8
@@ -0,0 +1 @@
1
+ e09ed2413140e577e1ea2878ad0138ca7bc1a578
@@ -0,0 +1 @@
1
+ 375e8a1ed0315e23de4b1e345da228178326bd03778099ca88d5cc3378c82dae
@@ -0,0 +1 @@
1
+ 8487e22276740d86ec2fd93a512124469b9bc1d30bd7705af851944c9f1fcc6c5e31eab6fd1df6dd3720c6835d8b884afd24d96ccd8dd17a4b5411fe10e98394
@@ -3,11 +3,11 @@
3
3
  <groupId>com.wix</groupId>
4
4
  <artifactId>detox</artifactId>
5
5
  <versioning>
6
- <latest>20.50.0</latest>
7
- <release>20.50.0</release>
6
+ <latest>20.51.0</latest>
7
+ <release>20.51.0</release>
8
8
  <versions>
9
- <version>20.50.0</version>
9
+ <version>20.51.0</version>
10
10
  </versions>
11
- <lastUpdated>20260323133220</lastUpdated>
11
+ <lastUpdated>20260421082825</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- db2e61fd07646d2d9cd9a8641211e903
1
+ 7c4750894b2860af3f4c2188405c21fb
@@ -1 +1 @@
1
- 36f3c591d789ebaf11d182401c67daf44c4d5c39
1
+ cb1abf1460fc914c466758e0508071bc8fb2d340
@@ -1 +1 @@
1
- 8183d020e6e3d9c56a0b784ae748e423df5619c5fbee8e15a2591c95e239a7cb
1
+ 9238740367e0bd55c0158071793080db8843fe1639aead063c13aef8c67bae6c
@@ -1 +1 @@
1
- 1eb5509ad9927df6454d648784c23c87280382e122f96f5c302d4eeb68781d8a74ed13f85ce96a8d9ded0a4609f1d04a81582a6ed9c55cf6f0d34b2947b84cf0
1
+ 99ac92f2098b7616421af419f05a7bab050af5298a38064e75cb384adc858394b8f9d8f6e4a41f6d6c74eb1d6a97b8f65e8a44e01477a37b1f6d042edc46785c
Binary file
package/Detox-ios-src.tbz CHANGED
Binary file
Binary file
@@ -14,7 +14,7 @@ fun ReactApplication.getInstanceManagerSafe(): ReactInstanceManager {
14
14
 
15
15
  @SuppressLint("VisibleForTests")
16
16
  fun ReactApplication.getCurrentReactContext(): ReactContext? {
17
- return if (isFabricEnabled()) {
17
+ return if (isFabricEnabled() || ReactNativeInfo.isNewArchitectureOnlyVersion()) {
18
18
  reactHost?.currentReactContext
19
19
  } else {
20
20
  getInstanceManagerSafe().currentReactContext
@@ -2,10 +2,16 @@ package com.wix.detox.reactnative
2
2
 
3
3
  import android.app.Activity
4
4
  import android.content.Context
5
+ import android.os.Build
6
+ import android.os.Handler
7
+ import android.os.Looper
5
8
  import android.util.Log
9
+ import androidx.activity.ComponentActivity
10
+ import androidx.activity.OnBackPressedCallback
6
11
  import androidx.test.platform.app.InstrumentationRegistry
7
12
  import com.facebook.react.ReactApplication
8
13
  import com.facebook.react.bridge.ReactContext
14
+ import com.facebook.react.modules.core.DeviceEventManagerModule
9
15
  import com.wix.detox.LaunchArgs
10
16
  import com.wix.detox.reactnative.idlingresources.ReactNativeIdlingResources
11
17
  import com.wix.detox.reactnative.reloader.ReactNativeReloaderFactory
@@ -14,6 +20,7 @@ private const val LOG_TAG = "DetoxRNExt"
14
20
 
15
21
  object ReactNativeExtension {
16
22
  private var rnIdlingResources: ReactNativeIdlingResources? = null
23
+ private var backPressCallbackRegistered = false
17
24
 
18
25
  fun initIfNeeded() {
19
26
  if (!ReactNativeInfo.isReactNativeApp()) {
@@ -37,6 +44,7 @@ object ReactNativeExtension {
37
44
  awaitNewReactNativeContext(it, null)
38
45
 
39
46
  enableOrDisableSynchronization(it)
47
+ registerBackPressCallbackIfNeeded(applicationContext, it)
40
48
  }
41
49
  }
42
50
 
@@ -58,6 +66,7 @@ object ReactNativeExtension {
58
66
 
59
67
  (applicationContext as ReactApplication).let {
60
68
  clearIdlingResources()
69
+ backPressCallbackRegistered = false
61
70
 
62
71
  val previousReactContext = it.getCurrentReactContext()
63
72
 
@@ -65,6 +74,7 @@ object ReactNativeExtension {
65
74
  awaitNewReactNativeContext(it, previousReactContext)
66
75
 
67
76
  enableOrDisableSynchronization(it)
77
+ registerBackPressCallbackIfNeeded(applicationContext, it)
68
78
  }
69
79
  }
70
80
 
@@ -110,6 +120,43 @@ object ReactNativeExtension {
110
120
  }
111
121
  }
112
122
 
123
+ /**
124
+ * On API 35+, the system routes back presses through [OnBackPressedDispatcher] instead of
125
+ * calling [Activity.onBackPressed]. Older RN versions (< 0.83) don't register an
126
+ * [OnBackPressedCallback], so the back press bypasses React Native's BackHandler entirely,
127
+ * causing the activity to finish instead of letting JS handle it.
128
+ *
129
+ * This registers a callback that routes back presses through [ReactInstanceManager.onBackPressed],
130
+ * restoring the expected behavior.
131
+ */
132
+ private fun registerBackPressCallbackIfNeeded(context: Context, reactApp: ReactApplication) {
133
+ if (backPressCallbackRegistered) return
134
+ if (Build.VERSION.SDK_INT < 35) return
135
+ if (ReactNativeInfo.isNewArchitectureOnlyVersion()) return
136
+
137
+ val activity = getRNActivity(context) as? ComponentActivity ?: return
138
+
139
+ val callback = object : OnBackPressedCallback(true) {
140
+ override fun handleOnBackPressed() {
141
+ try {
142
+ val reactContext = reactApp.getCurrentReactContext()
143
+ reactContext
144
+ ?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
145
+ ?.emit("hardwareBackPress", null)
146
+ ?: Log.w(LOG_TAG, "ReactContext is null, cannot emit hardwareBackPress")
147
+ } catch (e: Exception) {
148
+ Log.w(LOG_TAG, "Failed to emit hardwareBackPress", e)
149
+ }
150
+ }
151
+ }
152
+
153
+ backPressCallbackRegistered = true
154
+ Handler(Looper.getMainLooper()).post {
155
+ activity.onBackPressedDispatcher.addCallback(activity, callback)
156
+ Log.i(LOG_TAG, "Registered OnBackPressedCallback for legacy RN back press handling")
157
+ }
158
+ }
159
+
113
160
  private fun reloadReactNativeInBackground(reactApplication: ReactApplication) {
114
161
  val rnReloader = ReactNativeReloaderFactory(InstrumentationRegistry.getInstrumentation(), reactApplication).create()
115
162
  rnReloader.reloadInBackground()
@@ -12,6 +12,13 @@ object ReactNativeInfo {
12
12
  @JvmStatic
13
13
  fun rnVersion() = rnVersion
14
14
 
15
+ @JvmStatic
16
+ fun isNewArchitectureOnlyVersion(): Boolean {
17
+ return rnVersion.run {
18
+ major > 0 || (major == 0 && minor >= 84)
19
+ }
20
+ }
21
+
15
22
  fun isReactNativeApp(): Boolean = try {
16
23
  Class.forName("com.facebook.react.ReactApplication")
17
24
  true
@@ -83,8 +83,8 @@ open class ReactNativeLoadingMonitor(
83
83
  reactContext != null && reactContext !== previousReactContext && reactContext.hasActiveReactInstance()
84
84
 
85
85
  private fun subscribeAsyncRNContextHandler(onReactContextInitialized: () -> Any) {
86
- val isFabric = isFabricEnabled()
87
- if (isFabric) {
86
+ val useNewArchitectureListener = isFabricEnabled() || ReactNativeInfo.isNewArchitectureOnlyVersion()
87
+ if (useNewArchitectureListener) {
88
88
  // We do a casting for supporting RN 0.75 and above
89
89
  val host = rnApplication.reactHost as ReactHostImpl?
90
90
  host?.addReactInstanceEventListener(object : ReactInstanceEventListener {
@@ -1,9 +1,11 @@
1
1
  package com.wix.detox.reactnative.idlingresources.bridge
2
2
 
3
3
  import android.util.Log
4
- import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener
5
4
  import com.facebook.react.bridge.ReactContext
6
5
  import com.wix.detox.reactnative.idlingresources.DetoxIdlingResource
6
+ import java.lang.reflect.InvocationHandler
7
+ import java.lang.reflect.Method
8
+ import java.lang.reflect.Proxy
7
9
  import java.util.concurrent.atomic.AtomicBoolean
8
10
 
9
11
  /**
@@ -16,16 +18,25 @@ import java.util.concurrent.atomic.AtomicBoolean
16
18
  * React Native's JS bridge.
17
19
  *
18
20
  */
19
- class BridgeIdlingResource(private val reactContext: ReactContext) : DetoxIdlingResource(),
20
- NotThreadSafeBridgeIdleDebugListener {
21
+ class BridgeIdlingResource(private val reactContext: ReactContext) : DetoxIdlingResource() {
21
22
  private val idleNow = AtomicBoolean(true)
23
+ private val bridgeIdleListenerApi = BridgeIdleListenerApi(reactContext)
22
24
 
23
25
  init {
24
- reactContext.catalystInstance.addBridgeIdleDebugListener(this)
26
+ bridgeIdleListenerApi.attach(
27
+ onBridgeIdle = {
28
+ idleNow.set(true)
29
+ notifyIdle()
30
+ },
31
+ onBridgeBusy = {
32
+ idleNow.set(false)
33
+ },
34
+ onBridgeDestroyed = {}
35
+ )
25
36
  }
26
37
 
27
38
  fun onDetach() {
28
- reactContext.catalystInstance.removeBridgeIdleDebugListener(this)
39
+ bridgeIdleListenerApi.detach()
29
40
  }
30
41
 
31
42
  override fun getName(): String {
@@ -48,25 +59,81 @@ class BridgeIdlingResource(private val reactContext: ReactContext) : DetoxIdling
48
59
  return ret
49
60
  }
50
61
 
51
- override fun onTransitionToBridgeIdle() {
52
- idleNow.set(true)
53
- notifyIdle()
62
+ override fun onUnregistered() {
63
+ super.onUnregistered()
64
+ onDetach()
54
65
  }
55
66
 
56
- override fun onTransitionToBridgeBusy() {
57
- idleNow.set(false)
58
- // Log.i(LOG_TAG, "JS Bridge transitions to busy.");
67
+ companion object {
68
+ private const val LOG_TAG = "Detox"
59
69
  }
70
+ }
71
+
72
+ private class BridgeIdleListenerApi(private val reactContext: ReactContext) {
73
+ private var listenerProxy: Any? = null
74
+ private var removeListenerMethod: Method? = null
60
75
 
61
- override fun onBridgeDestroyed() {
76
+ fun attach(
77
+ onBridgeIdle: () -> Unit,
78
+ onBridgeBusy: () -> Unit,
79
+ onBridgeDestroyed: () -> Unit,
80
+ ) {
81
+ try {
82
+ val listenerClass = Class.forName(BRIDGE_IDLE_LISTENER_CLASS_NAME)
83
+ val catalystInstance = reactContext.catalystInstance
84
+ val addListenerMethod = catalystInstance.javaClass.methods.firstOrNull { method ->
85
+ method.name == ADD_LISTENER_METHOD && method.parameterTypes.contentEquals(arrayOf(listenerClass))
86
+ } ?: run {
87
+ Log.i(LOG_TAG, "RN bridge idle debug listener API is unavailable.")
88
+ return
89
+ }
90
+
91
+ val proxy = Proxy.newProxyInstance(
92
+ listenerClass.classLoader,
93
+ arrayOf(listenerClass),
94
+ InvocationHandler { proxy, method, args ->
95
+ when (method.name) {
96
+ ON_BRIDGE_IDLE_METHOD -> onBridgeIdle()
97
+ ON_BRIDGE_BUSY_METHOD -> onBridgeBusy()
98
+ ON_BRIDGE_DESTROYED_METHOD -> onBridgeDestroyed()
99
+ "equals" -> proxy === args?.firstOrNull()
100
+ "hashCode" -> System.identityHashCode(proxy)
101
+ "toString" -> "BridgeIdleListenerProxy"
102
+ }
103
+ }
104
+ )
105
+
106
+ addListenerMethod.invoke(catalystInstance, proxy)
107
+ listenerProxy = proxy
108
+ removeListenerMethod = catalystInstance.javaClass.methods.firstOrNull { method ->
109
+ method.name == REMOVE_LISTENER_METHOD && method.parameterTypes.contentEquals(arrayOf(listenerClass))
110
+ }
111
+ } catch (e: Throwable) {
112
+ Log.w(LOG_TAG, "Could not attach RN bridge idle debug listener.", e)
113
+ }
62
114
  }
63
115
 
64
- override fun onUnregistered() {
65
- super.onUnregistered()
66
- onDetach()
116
+ fun detach() {
117
+ val listener = listenerProxy ?: return
118
+ val removeMethod = removeListenerMethod ?: return
119
+ try {
120
+ removeMethod.invoke(reactContext.catalystInstance, listener)
121
+ } catch (e: Throwable) {
122
+ Log.w(LOG_TAG, "Could not detach RN bridge idle debug listener.", e)
123
+ } finally {
124
+ listenerProxy = null
125
+ removeListenerMethod = null
126
+ }
67
127
  }
68
128
 
69
129
  companion object {
70
130
  private const val LOG_TAG = "Detox"
131
+ private const val BRIDGE_IDLE_LISTENER_CLASS_NAME =
132
+ "com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener"
133
+ private const val ADD_LISTENER_METHOD = "addBridgeIdleDebugListener"
134
+ private const val REMOVE_LISTENER_METHOD = "removeBridgeIdleDebugListener"
135
+ private const val ON_BRIDGE_IDLE_METHOD = "onTransitionToBridgeIdle"
136
+ private const val ON_BRIDGE_BUSY_METHOD = "onTransitionToBridgeBusy"
137
+ private const val ON_BRIDGE_DESTROYED_METHOD = "onBridgeDestroyed"
71
138
  }
72
139
  }
@@ -1,6 +1,7 @@
1
1
  package com.wix.detox.reactnative.idlingresources.factory
2
2
 
3
3
  import com.facebook.react.ReactApplication
4
+ import com.wix.detox.reactnative.ReactNativeInfo
4
5
  import com.wix.detox.reactnative.idlingresources.DetoxIdlingResource
5
6
  import com.wix.detox.reactnative.isFabricEnabled
6
7
  import kotlinx.coroutines.Dispatchers
@@ -9,7 +10,8 @@ import kotlinx.coroutines.withContext
9
10
 
10
11
  class DetoxIdlingResourceFactory(private val reactApplication: ReactApplication) {
11
12
  suspend fun create(): Map<IdlingResourcesName, DetoxIdlingResource> = withContext(Dispatchers.Main) {
12
- val strategy = if (isFabricEnabled()) {
13
+ val useNewArchitectureStrategy = isFabricEnabled() || ReactNativeInfo.isNewArchitectureOnlyVersion()
14
+ val strategy = if (useNewArchitectureStrategy) {
13
15
  FabricDetoxIdlingResourceFactoryStrategy(reactApplication)
14
16
  } else {
15
17
  OldArchitectureDetoxIdlingResourceFactoryStrategy(reactApplication)
@@ -1,5 +1,6 @@
1
1
  package com.wix.detox.reactnative.idlingresources.uimodule.fabric
2
2
 
3
+ import android.os.SystemClock
3
4
  import android.view.Choreographer
4
5
  import androidx.test.espresso.IdlingResource
5
6
  import com.facebook.react.bridge.ReactContext
@@ -15,14 +16,51 @@ class FabricUIManagerIdlingResources(
15
16
  private val reactContext: ReactContext
16
17
  ) : DetoxIdlingResource(), Choreographer.FrameCallback {
17
18
 
19
+ private var firstBusyTimestamp: Long = 0
20
+ private var isSteadyState: Boolean = false
21
+
18
22
  override fun checkIdle(): Boolean {
19
- return if (getViewCommandMountItemsSize() == 0 && getMountItemsSize() == 0) {
23
+ val mountItemsCount = getMountItemsSize()
24
+ val viewCommandMountItemsCount = getViewCommandMountItemsSize()
25
+
26
+ if (mountItemsCount == 0 && viewCommandMountItemsCount == 0) {
27
+ firstBusyTimestamp = 0
28
+ isSteadyState = false
20
29
  notifyIdle()
21
- true
22
- } else {
23
- Choreographer.getInstance().postFrameCallback(this)
24
- false
30
+ return true
31
+ }
32
+
33
+ // Once we've determined this is a steady-state (a stuck mount item that never
34
+ // resolves), keep reporting idle as long as the count stays low.
35
+ if (isSteadyState && mountItemsCount <= 1 && viewCommandMountItemsCount == 0) {
36
+ notifyIdle()
37
+ return true
38
+ }
39
+
40
+ // Count increased beyond steady-state threshold — reset and treat as genuinely busy.
41
+ if (isSteadyState) {
42
+ isSteadyState = false
43
+ firstBusyTimestamp = 0
25
44
  }
45
+
46
+ val now = SystemClock.uptimeMillis()
47
+ if (firstBusyTimestamp == 0L) {
48
+ firstBusyTimestamp = now
49
+ }
50
+
51
+ // On API 36+, edge-to-edge enforcement can cause a single mount item to remain
52
+ // permanently in the queue on older RN versions. If the count stays at 1 for over
53
+ // 1.5s, treat it as a steady-state condition rather than a genuinely busy UI.
54
+ if (now - firstBusyTimestamp >= BUSY_TOLERANCE_MS
55
+ && mountItemsCount <= 1
56
+ && viewCommandMountItemsCount == 0) {
57
+ isSteadyState = true
58
+ notifyIdle()
59
+ return true
60
+ }
61
+
62
+ Choreographer.getInstance().postFrameCallback(this)
63
+ return false
26
64
  }
27
65
 
28
66
  override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
@@ -78,4 +116,7 @@ class FabricUIManagerIdlingResources(
78
116
  return viewCommandMountItems.size
79
117
  }
80
118
 
119
+ companion object {
120
+ private const val BUSY_TOLERANCE_MS = 1500L
121
+ }
81
122
  }
@@ -2,6 +2,7 @@ package com.wix.detox.reactnative.reloader
2
2
 
3
3
  import android.app.Instrumentation
4
4
  import com.facebook.react.ReactApplication
5
+ import com.wix.detox.reactnative.ReactNativeInfo
5
6
  import com.wix.detox.reactnative.isFabricEnabled
6
7
 
7
8
  class ReactNativeReloaderFactory(
@@ -11,7 +12,8 @@ class ReactNativeReloaderFactory(
11
12
 
12
13
  fun create(): ReactNativeReLoader {
13
14
  return when {
14
- isFabricEnabled() -> NewArchitectureNativeReLoader(instrumentation, rnApplication)
15
+ isFabricEnabled() || ReactNativeInfo.isNewArchitectureOnlyVersion() ->
16
+ NewArchitectureNativeReLoader(instrumentation, rnApplication)
15
17
  else -> OldArchReactNativeReLoader(instrumentation, rnApplication)
16
18
  }
17
19
  }
@@ -48,4 +48,5 @@ ext.rnInfo = [
48
48
  isRN79OrHigher: rnMajorVer >= 79,
49
49
  isRN80OrHigher: rnMajorVer >= 80,
50
50
  isRN81OrHigher: rnMajorVer >= 81,
51
+ isRN84OrHigher: rnMajorVer >= 84,
51
52
  ]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "detox",
3
3
  "description": "E2E tests and automation for mobile",
4
- "version": "20.50.0",
4
+ "version": "20.51.0",
5
5
  "bin": "local-cli/cli.js",
6
6
  "files": [
7
7
  "android",
@@ -82,7 +82,7 @@
82
82
  "jest-allure2-reporter": "^2.2.6",
83
83
  "metro-react-native-babel-preset": "0.76.8",
84
84
  "prettier": "^3.1.1",
85
- "react-native": "0.83.0",
85
+ "react-native": "0.84.0",
86
86
  "react-native-codegen": "^0.0.8",
87
87
  "typescript": "^5.8.3",
88
88
  "wtfnode": "^0.9.1"
@@ -1 +0,0 @@
1
- a6d99f485a189ad0c3fae192f139a3a8
@@ -1 +0,0 @@
1
- e6fb42a3472abc664d9f1c47c93358335c78f5da
@@ -1 +0,0 @@
1
- 515d4ebccdc79501113c26de6082c73dde56cb565b95ad6854e92a47aed9a2e8
@@ -1 +0,0 @@
1
- 5fef55cb7985b9df085d7eecfc9fb1dea033c616b2fb6d4b4ed3e2d76683f76b5776ab4e5ab8347644636c0ee110997fa81ef0df27c90541342241a849ae4972
@@ -1 +0,0 @@
1
- 2efbc9d699ba52f968f6a277dd0a748b
@@ -1 +0,0 @@
1
- 046cbd9eb51a0e97c6013a23c28c12a72e4e3852
@@ -1 +0,0 @@
1
- 6d217114c204d18d424505dbe5f09fa7997cd9d9211fbc2d85a05d9e7da8d080
@@ -1 +0,0 @@
1
- bb3d06e47653ead1443e6220a13418c7106f1f282226b119b16499e93e92bef7b9b18307e9344fc3e599341aed90d5b9dec4cacc2d7201b6fdadf2991c4262cd
@@ -1 +0,0 @@
1
- 71a3ddcf799b7c8c0ebe80e4301c6e05
@@ -1 +0,0 @@
1
- db822e34a2d31738a49e7d229bbb6e48f4973eb1
@@ -1 +0,0 @@
1
- e415f368ecdec539bff9fc306e51c4db1d80cd830b31d4447e32ba764cb10047
@@ -1 +0,0 @@
1
- f214c65fc077f826a60795520e676fe5bffd158e548e50e3ac4880f9bb7232d9178622b9c5636812c28db7a83792e874ae20b15c675bbd7e6dea4da73f7ca765