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.
- 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
- package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0-sources.jar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0-sources.jar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0-sources.jar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0-sources.jar.sha512 +1 -0
- package/Detox-android/com/wix/detox/{20.50.0/detox-20.50.0.aar → 20.51.0/detox-20.51.0.aar} +0 -0
- package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.aar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.aar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.aar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.aar.sha512 +1 -0
- package/Detox-android/com/wix/detox/{20.50.0/detox-20.50.0.pom → 20.51.0/detox-20.51.0.pom} +1 -1
- package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.pom.md5 +1 -0
- package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.pom.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.pom.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.51.0/detox-20.51.0.pom.sha512 +1 -0
- package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
- package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
- package/Detox-ios-framework.tbz +0 -0
- package/Detox-ios-src.tbz +0 -0
- package/Detox-ios-xcuitest.tbz +0 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/ReactApplicationExt.kt +1 -1
- package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeExtension.kt +47 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeInfo.kt +7 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeLoadingMonitor.kt +2 -2
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/bridge/BridgeIdlingResource.kt +82 -15
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/factory/DetoxIdlingResourceFactory.kt +3 -1
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/fabric/FabricUIManagerIdlingResources.kt +46 -5
- package/android/detox/src/full/java/com/wix/detox/reactnative/reloader/ReactNativeReloaderFactory.kt +3 -1
- package/android/rninfo.gradle +1 -0
- package/package.json +2 -2
- package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0-sources.jar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0-sources.jar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0-sources.jar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0-sources.jar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.aar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.aar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.aar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.aar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.pom.md5 +0 -1
- package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.pom.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.pom.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.50.0/detox-20.50.0.pom.sha512 +0 -1
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
e27fa966e5fe0bb1e0fe9d8dd47eae6e
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3aa39b2445e86ce9bb4f83d7a9999a421beabee3
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
564e9102f35c86563510cf1331341bae4d6a84016f65f76dd53db4cd584cd013
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
9f1dea736c2ec071b9e24bc8ce64169dc7eff5d7e9e4daa574f17815e18a9bf253f7e13d20e3d2eddbd13d2937a35591afeb4ca5fb896abc131e7447e3ed15db
|
|
Binary file
|
|
@@ -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.
|
|
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.
|
|
7
|
-
<release>20.
|
|
6
|
+
<latest>20.51.0</latest>
|
|
7
|
+
<release>20.51.0</release>
|
|
8
8
|
<versions>
|
|
9
|
-
<version>20.
|
|
9
|
+
<version>20.51.0</version>
|
|
10
10
|
</versions>
|
|
11
|
-
<lastUpdated>
|
|
11
|
+
<lastUpdated>20260421082825</lastUpdated>
|
|
12
12
|
</versioning>
|
|
13
13
|
</metadata>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
7c4750894b2860af3f4c2188405c21fb
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
cb1abf1460fc914c466758e0508071bc8fb2d340
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
9238740367e0bd55c0158071793080db8843fe1639aead063c13aef8c67bae6c
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
99ac92f2098b7616421af419f05a7bab050af5298a38064e75cb384adc858394b8f9d8f6e4a41f6d6c74eb1d6a97b8f65e8a44e01477a37b1f6d042edc46785c
|
package/Detox-ios-framework.tbz
CHANGED
|
Binary file
|
package/Detox-ios-src.tbz
CHANGED
|
Binary file
|
package/Detox-ios-xcuitest.tbz
CHANGED
|
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
|
|
87
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
62
|
+
override fun onUnregistered() {
|
|
63
|
+
super.onUnregistered()
|
|
64
|
+
onDetach()
|
|
54
65
|
}
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
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
|
}
|
package/android/detox/src/full/java/com/wix/detox/reactnative/reloader/ReactNativeReloaderFactory.kt
CHANGED
|
@@ -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()
|
|
15
|
+
isFabricEnabled() || ReactNativeInfo.isNewArchitectureOnlyVersion() ->
|
|
16
|
+
NewArchitectureNativeReLoader(instrumentation, rnApplication)
|
|
15
17
|
else -> OldArchReactNativeReLoader(instrumentation, rnApplication)
|
|
16
18
|
}
|
|
17
19
|
}
|
package/android/rninfo.gradle
CHANGED
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.
|
|
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.
|
|
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
|