detox 20.32.0 → 20.33.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 (72) hide show
  1. package/Detox-android/com/wix/detox/{20.32.0/detox-20.32.0-sources.jar → 20.33.0/detox-20.33.0-sources.jar} +0 -0
  2. package/Detox-android/com/wix/detox/20.33.0/detox-20.33.0-sources.jar.md5 +1 -0
  3. package/Detox-android/com/wix/detox/20.33.0/detox-20.33.0-sources.jar.sha1 +1 -0
  4. package/Detox-android/com/wix/detox/20.33.0/detox-20.33.0-sources.jar.sha256 +1 -0
  5. package/Detox-android/com/wix/detox/20.33.0/detox-20.33.0-sources.jar.sha512 +1 -0
  6. package/Detox-android/com/wix/detox/20.33.0/detox-20.33.0.aar +0 -0
  7. package/Detox-android/com/wix/detox/20.33.0/detox-20.33.0.aar.md5 +1 -0
  8. package/Detox-android/com/wix/detox/20.33.0/detox-20.33.0.aar.sha1 +1 -0
  9. package/Detox-android/com/wix/detox/20.33.0/detox-20.33.0.aar.sha256 +1 -0
  10. package/Detox-android/com/wix/detox/20.33.0/detox-20.33.0.aar.sha512 +1 -0
  11. package/Detox-android/com/wix/detox/{20.32.0/detox-20.32.0.pom → 20.33.0/detox-20.33.0.pom} +13 -1
  12. package/Detox-android/com/wix/detox/20.33.0/detox-20.33.0.pom.md5 +1 -0
  13. package/Detox-android/com/wix/detox/20.33.0/detox-20.33.0.pom.sha1 +1 -0
  14. package/Detox-android/com/wix/detox/20.33.0/detox-20.33.0.pom.sha256 +1 -0
  15. package/Detox-android/com/wix/detox/20.33.0/detox-20.33.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/build.gradle +6 -1
  25. package/android/detox/proguard-rules-app.pro +12 -0
  26. package/android/detox/src/full/java/com/wix/detox/DetoxMain.kt +4 -3
  27. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactApplicationExt.kt +34 -0
  28. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeExtension.kt +22 -27
  29. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeLoadingMonitor.kt +51 -71
  30. package/android/detox/src/full/java/com/wix/detox/reactnative/helpers/RNHelpers.kt +1 -1
  31. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/DetoxIdlingResource.kt +4 -2
  32. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/ReactNativeIdlingResources.kt +17 -13
  33. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/animations/AnimatedModuleIdlingResource.kt +5 -2
  34. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/factory/DetoxIdlingResourceFactory.kt +9 -20
  35. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/factory/DetoxIdlingResourceFactoryStrategy.kt +7 -0
  36. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/factory/FabricDetoxIdlingResourceFactoryStrategy.kt +31 -0
  37. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/factory/IdlingResourcesName.kt +1 -1
  38. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/factory/OldArchitectureDetoxIdlingResourceFactoryStrategy.kt +33 -0
  39. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/network/NetworkIdlingResource.kt +5 -0
  40. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/network/NetworkingModuleReflected.kt +11 -14
  41. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/storage/AsyncStorageIdlingResource.kt +31 -29
  42. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/FabricTimersIdlingResource.kt +50 -0
  43. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/JavaTimersReflected.kt +26 -0
  44. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/TimersIdlingResource.kt +6 -5
  45. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/fabric/FabricUIManagerIdlingResources.kt +70 -0
  46. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/{DispatchCommandOperationReflected.kt → paper/DispatchCommandOperationReflected.kt} +1 -1
  47. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/{NativeHierarchyManagerReflected.kt → paper/NativeHierarchyManagerReflected.kt} +5 -3
  48. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/{UIManagerModuleReflected.kt → paper/UIManagerModuleReflected.kt} +5 -3
  49. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/{UIModuleIdlingResource.kt → paper/UIModuleIdlingResource.kt} +12 -4
  50. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/{ViewCommandOpsQueueReflected.kt → paper/ViewCommandOpsQueueReflected.kt} +3 -3
  51. package/android/detox/src/full/java/com/wix/detox/reactnative/reloader/ReactNativeReloader.kt +34 -0
  52. package/android/detox/src/full/java/com/wix/detox/reactnative/reloader/ReactNativeReloaderFactory.kt +18 -0
  53. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/AsyncStorageIdlingResourceTest.kt +9 -2
  54. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/timers/TimersIdlingResourceTest.kt +9 -3
  55. package/package.json +2 -2
  56. package/scripts/updateGradle.js +37 -0
  57. package/src/DetoxWorker.js +1 -2
  58. package/src/copilot/detoxCopilotFrameworkDriver.js +2 -0
  59. package/Detox-android/com/wix/detox/20.32.0/detox-20.32.0-sources.jar.md5 +0 -1
  60. package/Detox-android/com/wix/detox/20.32.0/detox-20.32.0-sources.jar.sha1 +0 -1
  61. package/Detox-android/com/wix/detox/20.32.0/detox-20.32.0-sources.jar.sha256 +0 -1
  62. package/Detox-android/com/wix/detox/20.32.0/detox-20.32.0-sources.jar.sha512 +0 -1
  63. package/Detox-android/com/wix/detox/20.32.0/detox-20.32.0.aar +0 -0
  64. package/Detox-android/com/wix/detox/20.32.0/detox-20.32.0.aar.md5 +0 -1
  65. package/Detox-android/com/wix/detox/20.32.0/detox-20.32.0.aar.sha1 +0 -1
  66. package/Detox-android/com/wix/detox/20.32.0/detox-20.32.0.aar.sha256 +0 -1
  67. package/Detox-android/com/wix/detox/20.32.0/detox-20.32.0.aar.sha512 +0 -1
  68. package/Detox-android/com/wix/detox/20.32.0/detox-20.32.0.pom.md5 +0 -1
  69. package/Detox-android/com/wix/detox/20.32.0/detox-20.32.0.pom.sha1 +0 -1
  70. package/Detox-android/com/wix/detox/20.32.0/detox-20.32.0.pom.sha256 +0 -1
  71. package/Detox-android/com/wix/detox/20.32.0/detox-20.32.0.pom.sha512 +0 -1
  72. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeReloader.kt +0 -16
@@ -0,0 +1 @@
1
+ 498f29f164887f0f2274e4abd874c003
@@ -0,0 +1 @@
1
+ 9fa9b37f66d8ab4ce655492c5cae5c689600ba32
@@ -0,0 +1 @@
1
+ 4f832f9e782b74e2dd8cc9e0dc7cce16d8237787ba41c4959766c8dfb1eff3ff
@@ -0,0 +1 @@
1
+ 26c7411525f22292763035d43009a695d5d83f15683d3d25cac36bf81fa64403a9b10d748b1064b7ce9922b43ac0e6ceeb3af1cf254d362471a6e4d334877bb7
@@ -0,0 +1 @@
1
+ 4ab2c960e9f1d12a9daa555f3db6554c
@@ -0,0 +1 @@
1
+ 4068d06887f8949b32a0cf36fed6f3e70bb4f922
@@ -0,0 +1 @@
1
+ 189187fc397bdb111df5485b64ff7154c84ce232240594646896f11c6f433ef6
@@ -0,0 +1 @@
1
+ 3906c9b7a0dc913a2697a735819c907f62c4eb516f40e492bba0df74e54ee99927b36d5771f942e29054a94dd03c573bfed9a372756c5fcb42f0d3f0b53f85df
@@ -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.32.0</version>
6
+ <version>20.33.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>
@@ -84,6 +84,18 @@
84
84
  <version>2.2.0</version>
85
85
  <scope>compile</scope>
86
86
  </dependency>
87
+ <dependency>
88
+ <groupId>androidx.test</groupId>
89
+ <artifactId>core-ktx</artifactId>
90
+ <version>1.6.1</version>
91
+ <scope>compile</scope>
92
+ </dependency>
93
+ <dependency>
94
+ <groupId>org.jetbrains.kotlin</groupId>
95
+ <artifactId>kotlin-reflect</artifactId>
96
+ <version>1.9.24</version>
97
+ <scope>runtime</scope>
98
+ </dependency>
87
99
  <dependency>
88
100
  <groupId>org.apache.commons</groupId>
89
101
  <artifactId>commons-lang3</artifactId>
@@ -0,0 +1 @@
1
+ 855397c71ecf7f34e546fbd6470004e6
@@ -0,0 +1 @@
1
+ 1775da0d745f62fb048530ec154f391cae1bf4d0
@@ -0,0 +1 @@
1
+ 2e0b47850ee572f6e29957534156cec647ce6e526b137cef2a8108c4627a7a6c
@@ -0,0 +1 @@
1
+ ebf42a136cb4a15b368e79a2acefaf7a36a17dba77ef24f84e5abc8f63067cc18558256690ab8b6a37858d0e4bc203ea5e93875d51a490089f2bf344359696b3
@@ -3,11 +3,11 @@
3
3
  <groupId>com.wix</groupId>
4
4
  <artifactId>detox</artifactId>
5
5
  <versioning>
6
- <latest>20.32.0</latest>
7
- <release>20.32.0</release>
6
+ <latest>20.33.0</latest>
7
+ <release>20.33.0</release>
8
8
  <versions>
9
- <version>20.32.0</version>
9
+ <version>20.33.0</version>
10
10
  </versions>
11
- <lastUpdated>20250106191456</lastUpdated>
11
+ <lastUpdated>20250205141419</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- a21a21f5f33f0521ea5b27a6b7c9a53d
1
+ 2079b2c121b2426b536bb1a82d453023
@@ -1 +1 @@
1
- 3c0a9c339f4d397ac37a6779311c1c121ab01863
1
+ 9404bd86287244a491d5a33e36f0ff8c6f3c167c
@@ -1 +1 @@
1
- fa21aff43ff2f349f95760a2467987cef206152f4277db7d005193124667457b
1
+ 0e1c8604589283e252ed7e050af9fd4b7523e6ea049676e160cc9c62c682cbb0
@@ -1 +1 @@
1
- 91de105f632722548240b0ce79abfa9758dfb44827badc8828bdb09404f917038e5e2157757003141f2d676cf641b9734f063ea0f950e01842d491d3eb8f04fc
1
+ 904d92790c6064bdef677e74ae821a5a02b37d86c16a28f29847779b936ba62142be465c13800c475360491906348a27925ca807d34e0cd322a7052ed6943756
Binary file
package/Detox-ios-src.tbz CHANGED
Binary file
Binary file
@@ -147,6 +147,12 @@ dependencies {
147
147
  api('androidx.test.uiautomator:uiautomator:2.2.0') {
148
148
  because 'Needed by Detox but also makes UIAutomator seamlessly provided to Detox users with hybrid apps/E2E-tests.'
149
149
  }
150
+ api('androidx.test:core-ktx:1.6.1') {
151
+ because 'Needed by Detox but also makes AndroidX test core seamlessly provided to Detox users with hybrid apps/E2E-tests.'
152
+ }
153
+ implementation("org.jetbrains.kotlin:kotlin-reflect:$_kotlinVersion") {
154
+ because('Needed by Detox for kotlin reflection')
155
+ }
150
156
  }
151
157
 
152
158
  // Third-party/extension deps.
@@ -199,7 +205,6 @@ if (rootProject.hasProperty('isOfficialDetoxLib') ||
199
205
  dependencies {
200
206
  testImplementation 'org.spekframework.spek2:spek-dsl-jvm:2.0.15'
201
207
  testImplementation 'org.spekframework.spek2:spek-runner-junit5:2.0.15'
202
- testImplementation "org.jetbrains.kotlin:kotlin-reflect:$_kotlinVersion"
203
208
  }
204
209
  }
205
210
 
@@ -1,10 +1,18 @@
1
1
  -keepattributes InnerClasses, Exceptions
2
2
 
3
+ -keep class com.facebook.react.fabric.FabricUIManager { *; }
4
+ -keep class com.facebook.react.fabric.mounting.MountItemDispatcher { *; }
3
5
  -keep class com.facebook.react.modules.** { *; }
4
6
  -keep class com.facebook.react.uimanager.** { *; }
5
7
  -keep class com.facebook.react.animated.** { *; }
6
8
  -keep class com.facebook.react.ReactApplication { *; }
7
9
  -keep class com.facebook.react.ReactNativeHost { *; }
10
+ -keep class com.facebook.react.ReactHost { *; }
11
+ -keep class com.facebook.react.runtime.ReactHostImpl { *; }
12
+ -keep class com.facebook.react.runtime.BridgelessReactContext { *; }
13
+ -keep class com.facebook.react.runtime.ReactInstance { *; }
14
+ -keep class com.facebook.react.modules.core.JavaTimerManager { *; }
15
+
8
16
  -keep class com.facebook.react.ReactInstanceManager { *; }
9
17
  -keep class com.facebook.react.ReactInstanceManager** { *; }
10
18
  -keep class com.facebook.react.ReactInstanceEventListener { *; }
@@ -18,6 +26,10 @@
18
26
  -keep class com.reactnativecommunity.asyncstorage.** { *; }
19
27
 
20
28
  -keep class kotlin.reflect.** { *; }
29
+ -keep class kotlin.KotlinVersion { *; }
30
+ -keep class kotlin.sequences.** { *; }
31
+ -keep class kotlin.Triple { *; }
32
+ -keep class kotlin.properties.** { *; }
21
33
  -keep class kotlin.coroutines.CoroutineDispatcher { *; }
22
34
  -keep class kotlin.coroutines.CoroutineScope { *; }
23
35
  -keep class kotlin.coroutines.CoroutineContext { *; }
@@ -47,10 +47,11 @@ object DetoxMain {
47
47
  * not by instrumentation itself, but based on the `AppWillTerminateWithError` message; In it's own, it is a good
48
48
  * thing, but for a reason we're not sure of yet, it is ignored by the test runner at this point in the flow.
49
49
  */
50
- @Synchronized
51
50
  private fun launchActivityOnCue(rnHostHolder: Context, activityLaunchHelper: ActivityLaunchHelper) {
52
- awaitHandshake()
53
- launchActivity(rnHostHolder, activityLaunchHelper)
51
+ synchronized(this) {
52
+ awaitHandshake()
53
+ launchActivity(rnHostHolder, activityLaunchHelper)
54
+ }
54
55
  }
55
56
 
56
57
  private fun awaitHandshake() {
@@ -0,0 +1,34 @@
1
+ package com.wix.detox.reactnative
2
+
3
+ import android.annotation.SuppressLint
4
+ import com.facebook.react.ReactApplication
5
+ import com.facebook.react.ReactInstanceManager
6
+ import com.facebook.react.bridge.ReactContext
7
+ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
8
+
9
+
10
+ fun ReactApplication.getInstanceManagerSafe(): ReactInstanceManager {
11
+ return reactNativeHost.reactInstanceManager
12
+ ?: throw RuntimeException("ReactInstanceManager is null!")
13
+ }
14
+
15
+ @SuppressLint("VisibleForTests")
16
+ fun ReactApplication.getCurrentReactContext(): ReactContext? {
17
+ return if (isFabricEnabled()) {
18
+ reactHost?.currentReactContext
19
+ } else {
20
+ getInstanceManagerSafe().currentReactContext
21
+ }
22
+ }
23
+
24
+ fun ReactApplication.getCurrentReactContextSafe(): ReactContext {
25
+ return getCurrentReactContext()
26
+ ?: throw RuntimeException("ReactContext is null!")
27
+ }
28
+
29
+ /**
30
+ * A method to check if Fabric is enabled in the React Native application.
31
+ */
32
+ fun isFabricEnabled(): Boolean {
33
+ return DefaultNewArchitectureEntryPoint.fabricEnabled
34
+ }
@@ -5,10 +5,10 @@ import android.content.Context
5
5
  import android.util.Log
6
6
  import androidx.test.platform.app.InstrumentationRegistry
7
7
  import com.facebook.react.ReactApplication
8
- import com.facebook.react.ReactInstanceManager
9
8
  import com.facebook.react.bridge.ReactContext
10
9
  import com.wix.detox.LaunchArgs
11
10
  import com.wix.detox.reactnative.idlingresources.ReactNativeIdlingResources
11
+ import com.wix.detox.reactnative.reloader.ReactNativeReloaderFactory
12
12
 
13
13
  private const val LOG_TAG = "DetoxRNExt"
14
14
 
@@ -34,9 +34,9 @@ object ReactNativeExtension {
34
34
  }
35
35
 
36
36
  (applicationContext as ReactApplication).let {
37
- val reactContext = awaitNewReactNativeContext(it, null)
37
+ awaitNewReactNativeContext(it, null)
38
38
 
39
- enableOrDisableSynchronization(reactContext)
39
+ enableOrDisableSynchronization(it)
40
40
  }
41
41
  }
42
42
 
@@ -59,12 +59,12 @@ object ReactNativeExtension {
59
59
  (applicationContext as ReactApplication).let {
60
60
  clearIdlingResources()
61
61
 
62
- val previousReactContext = getCurrentReactContextSafe(it)
62
+ val previousReactContext = it.getCurrentReactContext()
63
63
 
64
64
  reloadReactNativeInBackground(it)
65
- val reactContext = awaitNewReactNativeContext(it, previousReactContext)
65
+ awaitNewReactNativeContext(it, previousReactContext)
66
66
 
67
- enableOrDisableSynchronization(reactContext)
67
+ enableOrDisableSynchronization(it)
68
68
  }
69
69
  }
70
70
 
@@ -75,11 +75,7 @@ object ReactNativeExtension {
75
75
 
76
76
  @JvmStatic
77
77
  fun enableAllSynchronization(applicationContext: ReactApplication) {
78
- val reactContext = getCurrentReactContextSafe(applicationContext)
79
-
80
- if (reactContext != null) {
81
- setupIdlingResources(reactContext)
82
- }
78
+ setupIdlingResources(applicationContext)
83
79
  }
84
80
 
85
81
  @JvmStatic
@@ -88,7 +84,7 @@ object ReactNativeExtension {
88
84
  @JvmStatic
89
85
  fun getRNActivity(applicationContext: Context): Activity? {
90
86
  if (ReactNativeInfo.isReactNativeApp()) {
91
- return getCurrentReactContextSafe(applicationContext as ReactApplication)?.currentActivity
87
+ return (applicationContext as ReactApplication).getCurrentReactContext()?.currentActivity
92
88
  }
93
89
  return null
94
90
  }
@@ -115,20 +111,27 @@ object ReactNativeExtension {
115
111
  }
116
112
 
117
113
  private fun reloadReactNativeInBackground(reactApplication: ReactApplication) {
118
- val rnReloader = ReactNativeReLoader(InstrumentationRegistry.getInstrumentation(), reactApplication)
114
+ val rnReloader = ReactNativeReloaderFactory(InstrumentationRegistry.getInstrumentation(), reactApplication).create()
119
115
  rnReloader.reloadInBackground()
120
116
  }
121
117
 
122
- private fun awaitNewReactNativeContext(reactApplication: ReactApplication, previousReactContext: ReactContext?): ReactContext {
123
- val rnLoadingMonitor = ReactNativeLoadingMonitor(InstrumentationRegistry.getInstrumentation(), reactApplication, previousReactContext)
118
+ private fun awaitNewReactNativeContext(
119
+ reactApplication: ReactApplication,
120
+ previousReactContext: ReactContext?
121
+ ): ReactContext {
122
+ val rnLoadingMonitor = ReactNativeLoadingMonitor(
123
+ InstrumentationRegistry.getInstrumentation(),
124
+ reactApplication,
125
+ previousReactContext
126
+ )
124
127
  return rnLoadingMonitor.getNewContext()!!
125
128
  }
126
129
 
127
- private fun enableOrDisableSynchronization(reactContext: ReactContext) {
130
+ private fun enableOrDisableSynchronization(reactApplication: ReactApplication) {
128
131
  if (shouldDisableSynchronization()) {
129
132
  clearAllSynchronization()
130
133
  } else {
131
- setupIdlingResources(reactContext)
134
+ setupIdlingResources(reactApplication)
132
135
  }
133
136
  }
134
137
 
@@ -137,10 +140,10 @@ object ReactNativeExtension {
137
140
  return launchArgs.hasEnableSynchronization() && launchArgs.enableSynchronization.equals("0")
138
141
  }
139
142
 
140
- private fun setupIdlingResources(reactContext: ReactContext) {
143
+ private fun setupIdlingResources(reactApplication: ReactApplication) {
141
144
  val launchArgs = LaunchArgs()
142
145
 
143
- rnIdlingResources = ReactNativeIdlingResources(reactContext, launchArgs).apply {
146
+ rnIdlingResources = ReactNativeIdlingResources(reactApplication, launchArgs).apply {
144
147
  registerAll()
145
148
  }
146
149
  }
@@ -150,12 +153,4 @@ object ReactNativeExtension {
150
153
  rnIdlingResources = null
151
154
  }
152
155
 
153
- private fun getInstanceManagerSafe(reactApplication: ReactApplication): ReactInstanceManager {
154
- return reactApplication.reactNativeHost.reactInstanceManager
155
- ?: throw RuntimeException("ReactInstanceManager is null!")
156
- }
157
-
158
- private fun getCurrentReactContextSafe(reactApplication: ReactApplication): ReactContext? {
159
- return getInstanceManagerSafe(reactApplication).currentReactContext
160
- }
161
156
  }
@@ -3,25 +3,22 @@ package com.wix.detox.reactnative
3
3
  import android.app.Instrumentation
4
4
  import android.util.Log
5
5
  import com.facebook.react.ReactApplication
6
- import com.facebook.react.ReactInstanceManager
6
+ import com.facebook.react.ReactInstanceEventListener
7
7
  import com.facebook.react.bridge.ReactContext
8
+ import com.facebook.react.runtime.ReactHostImpl
8
9
  import com.wix.detox.common.DetoxErrors
9
10
  import com.wix.detox.config.DetoxConfig
10
- import org.joor.Reflect
11
- import java.lang.reflect.Proxy
12
11
  import java.util.concurrent.CountDownLatch
13
12
  import java.util.concurrent.TimeUnit
14
13
 
15
14
  private const val LOG_TAG = "DetoxRNLoading"
16
15
 
17
- private const val REACT_INSTANCE_EVENT_LISTENER_CLASS = "com.facebook.react.ReactInstanceEventListener"
18
- private const val REACT_INSTANCE_EVENT_LISTENER_CLASS_COMPAT = "com.facebook.react.ReactInstanceManager\$ReactInstanceEventListener"
19
-
20
16
  open class ReactNativeLoadingMonitor(
21
- private val instrumentation: Instrumentation,
22
- private val rnApplication: ReactApplication,
23
- private val previousReactContext: ReactContext?,
24
- private val config: DetoxConfig = DetoxConfig.CONFIG) {
17
+ private val instrumentation: Instrumentation,
18
+ private val rnApplication: ReactApplication,
19
+ private val previousReactContext: ReactContext?,
20
+ private val config: DetoxConfig = DetoxConfig.CONFIG
21
+ ) {
25
22
  private val countDownLatch = CountDownLatch(1)
26
23
 
27
24
  fun getNewContext(): ReactContext? {
@@ -31,24 +28,21 @@ open class ReactNativeLoadingMonitor(
31
28
 
32
29
  private fun subscribeToNewRNContextUpdates() {
33
30
  instrumentation.runOnMainSync(
34
- Runnable {
35
- val rnInstanceManager = rnApplication.reactNativeHost.reactInstanceManager
36
- val reactContext = rnInstanceManager.currentReactContext
37
- if (reactContext != null && reactContext !== previousReactContext) {
38
- Log.d(LOG_TAG, "Got new RN-context directly and immediately")
39
- countDownLatch.countDown()
40
- return@Runnable
41
- }
31
+ Runnable {
32
+ val reactContext = rnApplication.getCurrentReactContext()
33
+ if (isReactNativeLoaded(reactContext)) {
34
+ Log.d(LOG_TAG, "Got new RN-context directly and immediately")
35
+ countDownLatch.countDown()
36
+ return@Runnable
37
+ }
42
38
 
43
- subscribeAsyncRNContextHandler(rnInstanceManager) {
44
- countDownLatch.countDown()
45
- }
46
- })
39
+ subscribeAsyncRNContextHandler() {
40
+ countDownLatch.countDown()
41
+ }
42
+ })
47
43
  }
48
44
 
49
45
  private fun awaitNewRNContext(): ReactContext? {
50
- val rnInstanceManager = rnApplication.reactNativeHost.reactInstanceManager
51
-
52
46
  var i = 0
53
47
  while (true) {
54
48
  try {
@@ -58,19 +52,22 @@ open class ReactNativeLoadingMonitor(
58
52
  // First load can take a lot of time. (packager)
59
53
  // Loads afterwards should take less than a second.
60
54
  throw DetoxErrors.DetoxRuntimeException(
61
- """Waited for the new RN-context for too long! (${config.rnContextLoadTimeoutSec} seconds)
55
+ """Waited for the new RN-context for too long! (${config.rnContextLoadTimeoutSec} seconds)
62
56
  |If you think that's not long enough, consider applying a custom Detox runtime-config in DetoxTest.runTests()."""
63
- .trimMargin())
57
+ .trimMargin()
58
+ )
64
59
  }
65
60
  } else {
66
61
  break
67
62
  }
68
63
 
69
- // Due to an ugly timing issue in RN
64
+ // Due to timing in RN
70
65
  // it is possible that our listener won't be ever called
71
66
  // That's why we have to check the reactContext regularly.
72
- val reactContext = rnInstanceManager.currentReactContext
73
- if (reactContext != null && reactContext !== previousReactContext) {
67
+ val reactContext = rnApplication.getCurrentReactContext()
68
+
69
+ // We also need to wait for rect native instance to be initialized
70
+ if (isReactNativeLoaded(reactContext)) {
74
71
  Log.d(LOG_TAG, "Got new RN-context explicitly while polling (#iteration=$i)")
75
72
  break
76
73
  }
@@ -79,51 +76,34 @@ open class ReactNativeLoadingMonitor(
79
76
  }
80
77
  }
81
78
 
82
- return rnInstanceManager.currentReactContext
79
+ return rnApplication.getCurrentReactContext()
83
80
  }
84
- }
85
81
 
86
- private interface DummyListenerIdentifier
87
-
88
- /**
89
- * This baby bridges over RN's breaking change introduced in version 0.68:
90
- * `ReactInstanceManager$ReactInstanceEventListener` was extracted onto a separate interface, having
91
- * `ReactInstanceManager.{add|remove}ReactInstanceEventLister()` changing their signature to use it, accordingly.
92
- *
93
- * This made us resort to a solution based on dynamic proxies, because - depending on RN's version (at runtime), we
94
- * need to dynamically decide what interface we "extend" (or actually shadow) via the proxy.
95
- *
96
- * @see RNDiff https://github.com/facebook/react-native/compare/v0.67.4..v0.68.0#diff-2f01f0cd7ff8c9ea58f12ef0eff5fa8250cad144dd5490598d80d8e9e743458aR1009
97
- * @see DynamicProxies https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html
98
- */
99
- private fun subscribeAsyncRNContextHandler(rnInstanceManager: ReactInstanceManager, onReactContextInitialized: () -> Any) {
100
- val listenerClass = resolveListenerClass()
101
- val proxyInterfaces = arrayOf(
102
- listenerClass,
103
- DummyListenerIdentifier::class.java // In order to be able to implement equals()
104
- )
105
- val listener = Proxy.newProxyInstance(listenerClass.classLoader, proxyInterfaces) { listener, method, args ->
106
- Log.d(LOG_TAG, "Listener-proxy method called: ${method.name}")
107
-
108
- val result = when (method.name) {
109
- "onReactContextInitialized" -> {
110
- Log.i(LOG_TAG, "Got new RN-context async'ly through listener")
111
- Reflect.on(rnInstanceManager).call("removeReactInstanceEventListener", listener)
112
- onReactContextInitialized()
113
- }
114
- "equals" -> {
115
- val candidate = args[0]
116
- candidate is DummyListenerIdentifier
117
- }
118
- else -> Any()
82
+ private fun isReactNativeLoaded(reactContext: ReactContext?) =
83
+ reactContext != null && reactContext !== previousReactContext && reactContext.hasActiveReactInstance()
84
+
85
+ private fun subscribeAsyncRNContextHandler(onReactContextInitialized: () -> Any) {
86
+ val isFabric = isFabricEnabled()
87
+ if (isFabric) {
88
+ // We do a casting for supporting RN 0.75 and above
89
+ val host = rnApplication.reactHost as ReactHostImpl?
90
+ host?.addReactInstanceEventListener(object : ReactInstanceEventListener {
91
+ override fun onReactContextInitialized(context: ReactContext) {
92
+ Log.i(LOG_TAG, "Got new RN-context through listener")
93
+ onReactContextInitialized()
94
+ host.removeReactInstanceEventListener(this)
95
+ }
96
+ })
97
+ } else {
98
+ val rnInstanceManager = rnApplication.getInstanceManagerSafe()
99
+ rnInstanceManager.addReactInstanceEventListener(object : ReactInstanceEventListener {
100
+ override fun onReactContextInitialized(context: ReactContext) {
101
+ Log.i(LOG_TAG, "Got new RN-context directly through listener")
102
+ onReactContextInitialized()
103
+ rnInstanceManager.removeReactInstanceEventListener(this)
104
+ }
105
+ })
119
106
  }
120
-
121
- result
122
107
  }
123
- Reflect.on(rnInstanceManager).call("addReactInstanceEventListener", listener)
124
108
  }
125
109
 
126
- private fun resolveListenerClass(): Class<*> {
127
- val className = if (ReactNativeInfo.rnVersion().minor >= 68) REACT_INSTANCE_EVENT_LISTENER_CLASS else REACT_INSTANCE_EVENT_LISTENER_CLASS_COMPAT
128
- return Class.forName(className)
129
- }
@@ -6,7 +6,7 @@ import com.facebook.react.bridge.ReactContext
6
6
 
7
7
  private const val LOG_TAG = "DetoxRNHelpers"
8
8
 
9
- object RNHelpers {
9
+ class RNHelpers {
10
10
  fun getNativeModule(reactContext: ReactContext, className: String): NativeModule? =
11
11
  try {
12
12
  val moduleClass = Class.forName(className) as Class<NativeModule>
@@ -1,12 +1,14 @@
1
1
  package com.wix.detox.reactnative.idlingresources
2
2
 
3
+ import androidx.annotation.VisibleForTesting
3
4
  import androidx.test.espresso.IdlingResource
4
5
  import com.wix.detox.espresso.idlingresources.DescriptiveIdlingResource
5
6
  import java.util.concurrent.atomic.AtomicBoolean
6
7
 
7
8
  abstract class DetoxIdlingResource : DescriptiveIdlingResource {
8
9
  private var callback: IdlingResource.ResourceCallback? = null
9
- private var paused: AtomicBoolean = AtomicBoolean(false)
10
+ @VisibleForTesting
11
+ internal var paused: AtomicBoolean = AtomicBoolean(false)
10
12
 
11
13
  fun pause() {
12
14
  paused.set(true)
@@ -30,7 +32,7 @@ abstract class DetoxIdlingResource : DescriptiveIdlingResource {
30
32
  }
31
33
 
32
34
  open fun onUnregistered() {
33
- // no-op
35
+ pause()
34
36
  }
35
37
 
36
38
  protected abstract fun checkIdle(): Boolean
@@ -5,8 +5,9 @@ import android.util.Log
5
5
  import androidx.test.espresso.Espresso
6
6
  import androidx.test.espresso.IdlingRegistry
7
7
  import androidx.test.espresso.base.IdlingResourceRegistry
8
- import com.facebook.react.bridge.ReactContext
8
+ import com.facebook.react.ReactApplication
9
9
  import com.wix.detox.LaunchArgs
10
+ import com.wix.detox.reactnative.getCurrentReactContext
10
11
  import com.wix.detox.reactnative.idlingresources.factory.DetoxIdlingResourceFactory
11
12
  import com.wix.detox.reactnative.idlingresources.factory.IdlingResourcesName
12
13
  import com.wix.detox.reactnative.idlingresources.factory.LooperName
@@ -19,9 +20,9 @@ import org.joor.Reflect
19
20
  private const val LOG_TAG = "DetoxRNIdleRes"
20
21
 
21
22
  class ReactNativeIdlingResources(
22
- private val reactContext: ReactContext,
23
+ private val reactApplication: ReactApplication,
23
24
  private var launchArgs: LaunchArgs,
24
- private val idlingResourcesFactory: DetoxIdlingResourceFactory = DetoxIdlingResourceFactory(reactContext)
25
+ private val idlingResourcesFactory: DetoxIdlingResourceFactory = DetoxIdlingResourceFactory(reactApplication)
25
26
  ) {
26
27
 
27
28
  private val idlingResources = mutableMapOf<IdlingResourcesName, DetoxIdlingResource>()
@@ -52,8 +53,8 @@ class ReactNativeIdlingResources(
52
53
 
53
54
  fun pauseRNTimersIdlingResource() = pauseIdlingResource(IdlingResourcesName.Timers)
54
55
  fun resumeRNTimersIdlingResource() = resumeIdlingResource(IdlingResourcesName.Timers)
55
- fun pauseUIIdlingResource() = pauseIdlingResource(IdlingResourcesName.UIModule)
56
- fun resumeUIIdlingResource() = resumeIdlingResource(IdlingResourcesName.UIModule)
56
+ fun pauseUIIdlingResource() = pauseIdlingResource(IdlingResourcesName.UI)
57
+ fun resumeUIIdlingResource() = resumeIdlingResource(IdlingResourcesName.UI)
57
58
 
58
59
  fun setBlacklistUrls(urlList: String) {
59
60
  setIdlingResourceBlacklist(urlList)
@@ -77,16 +78,19 @@ class ReactNativeIdlingResources(
77
78
  }
78
79
 
79
80
  private fun setupMQThreadsInterrogator(looperName: LooperName) {
80
- val mqThreadsReflector = MQThreadsReflector(reactContext)
81
- val looper = when (looperName) {
82
- LooperName.JS -> mqThreadsReflector.getJSMQueue()?.getLooper()
83
- LooperName.NativeModules -> mqThreadsReflector.getNativeModulesQueue()?.getLooper()
81
+ reactApplication.getCurrentReactContext()?.let {
82
+ val mqThreadsReflector = MQThreadsReflector(it)
83
+ val looper = when (looperName) {
84
+ LooperName.JS -> mqThreadsReflector.getJSMQueue()?.getLooper()
85
+ LooperName.NativeModules -> mqThreadsReflector.getNativeModulesQueue()?.getLooper()
86
+ }
87
+
88
+ looper?.let {
89
+ IdlingRegistry.getInstance().registerLooperAsIdlingResource(it)
90
+ loopers[looperName] = it
91
+ }
84
92
  }
85
93
 
86
- looper?.let {
87
- IdlingRegistry.getInstance().registerLooperAsIdlingResource(it)
88
- loopers[looperName] = it
89
- }
90
94
  }
91
95
 
92
96
  private suspend fun setupIdlingResources() {
@@ -53,9 +53,12 @@ class AnimatedModuleIdlingResource(private val reactContext: ReactContext) : Det
53
53
  Choreographer.getInstance().postFrameCallback(this)
54
54
  }
55
55
 
56
+ override fun onUnregistered() {
57
+ super.onUnregistered()
58
+ Choreographer.getInstance().removeFrameCallback(this)
59
+ }
60
+
56
61
  override fun doFrame(frameTimeNanos: Long) {
57
62
  isIdleNow
58
63
  }
59
64
  }
60
-
61
-