detox 20.15.1-prerelease.0 → 20.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. package/.eslintrc.js +1 -5
  2. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-javadoc.jar +0 -0
  3. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-javadoc.jar.md5 +1 -0
  4. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-javadoc.jar.sha1 +1 -0
  5. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-javadoc.jar.sha256 +1 -0
  6. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-javadoc.jar.sha512 +1 -0
  7. package/Detox-android/com/wix/detox/{20.15.0-prerelease.0/detox-20.15.0-prerelease.0-sources.jar → 20.17.0/detox-20.17.0-sources.jar} +0 -0
  8. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-sources.jar.md5 +1 -0
  9. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-sources.jar.sha1 +1 -0
  10. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-sources.jar.sha256 +1 -0
  11. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-sources.jar.sha512 +1 -0
  12. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.aar +0 -0
  13. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.aar.md5 +1 -0
  14. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.aar.sha1 +1 -0
  15. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.aar.sha256 +1 -0
  16. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.aar.sha512 +1 -0
  17. package/Detox-android/com/wix/detox/{20.15.0-prerelease.0/detox-20.15.0-prerelease.0.pom → 20.17.0/detox-20.17.0.pom} +1 -1
  18. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.pom.md5 +1 -0
  19. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.pom.sha1 +1 -0
  20. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.pom.sha256 +1 -0
  21. package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.pom.sha512 +1 -0
  22. package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
  23. package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
  24. package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
  25. package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
  26. package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
  27. package/Detox-ios-src.tbz +0 -0
  28. package/Detox-ios.tbz +0 -0
  29. package/android/build.gradle +2 -10
  30. package/android/detox/build.gradle +2 -0
  31. package/android/detox/proguard-rules-app.pro +1 -2
  32. package/android/detox/publishing.gradle +2 -2
  33. package/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java +11 -6
  34. package/android/detox/src/full/java/com/wix/detox/espresso/common/ReactSliderHelper.kt +5 -5
  35. package/android/detox/src/main/java/com/wix/detox/espresso/DeviceDisplay.kt +1 -1
  36. package/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java +113 -16
  37. package/android/detox/src/testFull/java/com/wix/detox/espresso/action/GetAttributesActionTest.kt +3 -3
  38. package/android/detox/src/testFull/java/com/wix/detox/espresso/common/ReactSliderHelperTest.kt +25 -23
  39. package/android/detox/src/testFull/java/com/wix/detox/espresso/scroll/ScrollHelperTest.kt +188 -0
  40. package/android/rninfo.gradle +27 -15
  41. package/android/settings.gradle +13 -1
  42. package/detox.d.ts +26 -20
  43. package/jest.config.js +1 -1
  44. package/local-cli/init.js +1 -1
  45. package/package.json +22 -18
  46. package/runners/jest/testEnvironment/index.js +24 -9
  47. package/src/DetoxWorker.js +1 -1
  48. package/src/android/actions/native.js +2 -2
  49. package/src/android/core/NativeElement.js +3 -4
  50. package/src/android/espressoapi/DetoxAction.js +9 -1
  51. package/src/android/interactions/native.js +2 -2
  52. package/src/android/matchers/native.js +1 -1
  53. package/src/artifacts/providers/index.js +1 -1
  54. package/src/artifacts/screenshot/SimulatorScreenshotPlugin.js +0 -1
  55. package/src/artifacts/templates/artifact/Artifact.js +1 -1
  56. package/src/client/AsyncWebSocket.js +2 -2
  57. package/src/client/Client.js +2 -2
  58. package/src/devices/allocation/drivers/android/attached/AttachedAndroidAllocDriver.js +0 -1
  59. package/src/devices/allocation/drivers/android/emulator/EmulatorAllocDriver.js +0 -1
  60. package/src/devices/allocation/drivers/android/emulator/EmulatorVersionResolver.js +1 -1
  61. package/src/devices/allocation/drivers/ios/SimulatorAllocDriver.js +0 -1
  62. package/src/devices/allocation/factories/android.js +1 -1
  63. package/src/devices/allocation/factories/ios.js +1 -1
  64. package/src/devices/common/drivers/android/exec/ADB.js +1 -2
  65. package/src/devices/common/drivers/android/tools/ApkValidator.js +1 -1
  66. package/src/devices/common/drivers/android/tools/AppInstallHelper.js +0 -2
  67. package/src/devices/common/drivers/ios/tools/AppleSimUtils.js +111 -15
  68. package/src/devices/runtime/drivers/android/AndroidDriver.js +1 -1
  69. package/src/devices/runtime/drivers/android/emulator/EmulatorDriver.js +0 -1
  70. package/src/devices/runtime/factories/ios.js +0 -2
  71. package/src/ios/expectTwo.js +6 -3
  72. package/src/ipc/IPCClient.js +2 -2
  73. package/src/ipc/IPCServer.js +4 -4
  74. package/src/realms/DetoxContext.js +3 -3
  75. package/src/realms/DetoxPrimaryContext.js +74 -29
  76. package/src/realms/DetoxSecondaryContext.js +2 -2
  77. package/src/utils/childProcess/exec.js +3 -2
  78. package/src/utils/invocationTraceDescriptions.js +3 -2
  79. package/tsconfig.json +1 -1
  80. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-javadoc.jar +0 -0
  81. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-javadoc.jar.md5 +0 -1
  82. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-javadoc.jar.sha1 +0 -1
  83. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-javadoc.jar.sha256 +0 -1
  84. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-javadoc.jar.sha512 +0 -1
  85. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-sources.jar.md5 +0 -1
  86. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-sources.jar.sha1 +0 -1
  87. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-sources.jar.sha256 +0 -1
  88. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-sources.jar.sha512 +0 -1
  89. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.aar +0 -0
  90. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.aar.md5 +0 -1
  91. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.aar.sha1 +0 -1
  92. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.aar.sha256 +0 -1
  93. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.aar.sha512 +0 -1
  94. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.pom.md5 +0 -1
  95. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.pom.sha1 +0 -1
  96. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.pom.sha256 +0 -1
  97. package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.pom.sha512 +0 -1
@@ -0,0 +1,188 @@
1
+ package com.wix.detox.espresso.scroll
2
+
3
+ import android.graphics.Insets
4
+ import android.view.MotionEvent
5
+ import android.view.View
6
+ import android.view.WindowInsets
7
+ import androidx.test.espresso.UiController
8
+ import androidx.test.platform.app.InstrumentationRegistry
9
+ import com.wix.detox.action.common.MOTION_DIR_DOWN
10
+ import com.wix.detox.action.common.MOTION_DIR_LEFT
11
+ import com.wix.detox.action.common.MOTION_DIR_RIGHT
12
+ import com.wix.detox.action.common.MOTION_DIR_UP
13
+ import com.wix.detox.espresso.DeviceDisplay
14
+ import org.junit.Test
15
+ import org.junit.runner.RunWith
16
+ import org.mockito.kotlin.any
17
+ import org.mockito.kotlin.argumentCaptor
18
+ import org.mockito.kotlin.mock
19
+ import org.mockito.kotlin.verify
20
+ import org.mockito.kotlin.whenever
21
+ import org.robolectric.RobolectricTestRunner
22
+ import org.robolectric.annotation.Config
23
+ import kotlin.test.assertEquals
24
+
25
+ private const val INSETS_SIZE = 100
26
+ private const val SCROLL_RANGE_SAFE_PERCENT = 0.9f // ScrollHelper.SCROLL_RANGE_SAFE_PERCENT
27
+
28
+ @Config(
29
+ qualifiers = "xxxhdpi", // 1280x1880
30
+ sdk = [33]
31
+ )
32
+ @RunWith(RobolectricTestRunner::class)
33
+ class ScrollHelperTest {
34
+
35
+ private val display = DeviceDisplay.getScreenSizeInPX()
36
+ private val displayWidth = display[0].toInt()
37
+ private val displayHeight = display[1].toInt()
38
+ private val touchSlopPx = ScrollHelper.getViewConfiguration().scaledTouchSlop
39
+ private val safetyMarginPx = DeviceDisplay.convertDpiToPx(2.0)
40
+
41
+ private val uiControllerMock = mock<UiController>()
42
+ private val viewMock = mockViewWithGestureNavigation(displayWidth, displayHeight)
43
+
44
+ @Test
45
+ fun `should scrolling down by 200 when gesture navigation enabled`() {
46
+ val amountInDp = 200.0
47
+ val amountInPx = amountInDp * DeviceDisplay.getDensity()
48
+
49
+ ScrollHelper.perform(uiControllerMock, viewMock, MOTION_DIR_DOWN, amountInDp, null, null)
50
+
51
+ val upEvent = getUpEvent()
52
+ // Verify that the scroll started at the center of the view
53
+ assertEquals(displayWidth / 2.0, upEvent.x.toDouble(), 0.0)
54
+ // Verify that the scroll ended at the center of the view minus the requested amount
55
+ assertEquals(displayHeight - amountInPx - touchSlopPx - safetyMarginPx - INSETS_SIZE, upEvent.y.toDouble(), 0.0)
56
+ }
57
+
58
+ @Test
59
+ fun `should scrolling down by 200 when gesture navigation disabled`() {
60
+ val amountInDp = 200.0
61
+ val amountInPx = amountInDp * DeviceDisplay.getDensity()
62
+
63
+ val viewMock = mockViewWithoutGestureNavigation(displayWidth, displayHeight)
64
+ ScrollHelper.perform(uiControllerMock, viewMock, MOTION_DIR_DOWN, amountInDp, null, null)
65
+
66
+ val upEvent = getUpEvent()
67
+ // Verify that the scroll started at the center of the view
68
+ assertEquals(displayWidth / 2.0, upEvent.x.toDouble(), 0.0)
69
+ // Verify that the scroll ended at the center of the view minus the requested amount
70
+ assertEquals(displayHeight - amountInPx - touchSlopPx - safetyMarginPx, upEvent.y.toDouble(), 0.0)
71
+ }
72
+
73
+ @Test
74
+ fun `should scroll down to edge on full screen view when gesture navigation enabled`() {
75
+ ScrollHelper.performOnce(uiControllerMock, viewMock, MOTION_DIR_DOWN, null, null)
76
+ val upEvent = getUpEvent()
77
+ val amountInPx = displayHeight * SCROLL_RANGE_SAFE_PERCENT
78
+
79
+ // Calculate where the scroll should end
80
+ val targetY = displayHeight - amountInPx -
81
+ touchSlopPx -
82
+ safetyMarginPx -
83
+ INSETS_SIZE
84
+
85
+ assertEquals(displayWidth / 2.0, upEvent.x.toDouble(), 0.0)
86
+ assertEquals(targetY, upEvent.y, 0.0f)
87
+ }
88
+
89
+ @Test
90
+ fun `should scroll left to edge on full screen view when gesture navigation enabled`() {
91
+ ScrollHelper.performOnce(uiControllerMock, viewMock, MOTION_DIR_LEFT, null, null)
92
+ val upEvent = getUpEvent()
93
+ val amountInPx = displayWidth * SCROLL_RANGE_SAFE_PERCENT
94
+
95
+ // Calculate where the scroll should end
96
+ val targetX = amountInPx +
97
+ touchSlopPx +
98
+ INSETS_SIZE
99
+
100
+ assertEquals(targetX, upEvent.x, 0.0f)
101
+ assertEquals(displayHeight / 2.0, upEvent.y.toDouble(), 0.0)
102
+ }
103
+
104
+ @Test
105
+ fun `should scroll up to edge on full screen view when gesture navigation enabled`() {
106
+ ScrollHelper.performOnce(uiControllerMock, viewMock, MOTION_DIR_UP,null, null)
107
+ val upEvent = getUpEvent()
108
+ val amountInPx = displayHeight * SCROLL_RANGE_SAFE_PERCENT
109
+
110
+ // Calculate where the scroll should end
111
+ val targetY = amountInPx +
112
+ touchSlopPx +
113
+ INSETS_SIZE
114
+
115
+ assertEquals(displayWidth / 2.0, upEvent.x.toDouble(), 0.0)
116
+ assertEquals(targetY, upEvent.y, 0.0f)
117
+ }
118
+
119
+ @Test
120
+ fun `should scroll right to edge on full screen view when gesture navigation enabled`() {
121
+ ScrollHelper.performOnce(uiControllerMock, viewMock, MOTION_DIR_RIGHT, null, null)
122
+ val upEvent = getUpEvent()
123
+ val amountInPx = displayWidth * SCROLL_RANGE_SAFE_PERCENT
124
+
125
+ // Calculate where the scroll should end
126
+ val targetX = displayWidth - amountInPx -
127
+ touchSlopPx -
128
+ safetyMarginPx -
129
+ INSETS_SIZE
130
+
131
+ assertEquals(targetX, upEvent.x, 0.0f)
132
+ assertEquals(displayHeight / 2.0, upEvent.y.toDouble(), 0.0)
133
+ }
134
+
135
+ /**
136
+ * Get the performed UP event from the ui controller
137
+ */
138
+ private fun getUpEvent(): MotionEvent {
139
+ val capture = argumentCaptor<Iterable<MotionEvent>>()
140
+ // Capture the events from the ui controller
141
+ verify(uiControllerMock).injectMotionEventSequence(capture.capture())
142
+
143
+ val listOfCapturedEvents = capture.firstValue.toList()
144
+ // The last event is the UP event with the target coordinates. All of the rest are not interesting
145
+ return listOfCapturedEvents.last()
146
+ }
147
+
148
+ private fun mockViewWithoutGestureNavigation(displayWidth: Int, displayHeight: Int): View {
149
+ // This is how we disable gesture navigation
150
+ val windowInsets = mock<WindowInsets>() {
151
+ whenever(it.systemGestureInsets).thenReturn(
152
+ Insets.of(0, 0, 0, 0)
153
+ )
154
+ }
155
+
156
+ return mockView(displayWidth, displayHeight, windowInsets)
157
+ }
158
+
159
+ /**
160
+ * Mock a view with gesture navigation enabled
161
+ */
162
+ private fun mockViewWithGestureNavigation(displayWidth: Int, displayHeight: Int): View {
163
+ // This is how we enable gesture navigation
164
+ val windowInsets = mock<WindowInsets>() {
165
+ whenever(it.systemGestureInsets).thenReturn(
166
+ Insets.of(INSETS_SIZE, INSETS_SIZE, INSETS_SIZE, INSETS_SIZE)
167
+ )
168
+ }
169
+
170
+ return mockView(displayWidth, displayHeight, windowInsets)
171
+ }
172
+
173
+ private fun mockView(
174
+ displayWidth: Int,
175
+ displayHeight: Int,
176
+ windowInsets: WindowInsets
177
+ ): View {
178
+ val view = mock<View>() {
179
+ whenever(it.width).thenReturn(displayWidth)
180
+ whenever(it.height).thenReturn(displayHeight)
181
+ whenever(it.canScrollVertically(any())).thenReturn(true) // We allow endless scroll
182
+ whenever(it.canScrollHorizontally(any())).thenReturn(true) // We allow endless scroll
183
+ whenever(it.context).thenReturn(InstrumentationRegistry.getInstrumentation().targetContext)
184
+ whenever(it.rootWindowInsets).thenReturn(windowInsets)
185
+ }
186
+ return view
187
+ }
188
+ }
@@ -1,25 +1,37 @@
1
1
  import groovy.json.JsonSlurper
2
2
 
3
- def rnVersion = getRNVersion(project.rootDir)
4
- def rnMajorVer = getMajorVersion(rnVersion)
5
- println "[$project] RNInfo: detected React Native version: $rnVersion (major=$rnMajorVer)"
6
-
7
- project.ext.rnInfo = [
8
- version: rnVersion,
9
- majorVersion: rnMajorVer,
10
- isRN69OrHigher: rnMajorVer >= 69,
11
- isRN70OrHigher: rnMajorVer >= 70,
12
- isRN71OrHigher: rnMajorVer >= 71,
13
- ]
14
-
15
- private static def getRNVersion(workingDir) {
3
+ def getRNVersion = { workingDir ->
4
+ println("RNInfo: workingDir=$workingDir")
16
5
  def jsonSlurper = new JsonSlurper()
17
- Map<String, Object> packageJSON = jsonSlurper.parse(new File("$workingDir/../node_modules/react-native/package.json"))
6
+ def packageFile = "$workingDir/../node_modules/react-native/package.json"
7
+ println("RNInfo: reading $packageFile")
8
+ Map<String, Object> packageJSON = jsonSlurper.parse(new File(packageFile))
18
9
  String rnVersion = packageJSON.get('version')
19
10
  return rnVersion
20
11
  }
21
12
 
22
- private static def getMajorVersion(semanticVersion) {
13
+ def getMajorVersionInternal = { semanticVersion ->
23
14
  Integer rnVersionMajor = semanticVersion.split('\\.')[1].toInteger()
24
15
  return rnVersionMajor
25
16
  }
17
+
18
+ ext.getRnMajorVersion = { workingDir ->
19
+ String rnVersion = getRNVersion(workingDir)
20
+ Integer rnVersionMajor = getMajorVersionInternal(rnVersion)
21
+ return rnVersionMajor
22
+ }
23
+
24
+ def rnVersion = getRNVersion(rootDir)
25
+ def rnMajorVer = getMajorVersionInternal(rnVersion)
26
+ if (hasProperty('project')) {
27
+ println "[$project] RNInfo: detected React Native version: $rnVersion (major=$rnMajorVer)"
28
+
29
+ project.ext.rnInfo = [
30
+ version : rnVersion,
31
+ majorVersion : rnMajorVer,
32
+ isRN69OrHigher: rnMajorVer >= 69,
33
+ isRN70OrHigher: rnMajorVer >= 70,
34
+ isRN71OrHigher: rnMajorVer >= 71,
35
+ isRN72OrHigher: rnMajorVer >= 72,
36
+ ]
37
+ }
@@ -1,2 +1,14 @@
1
+ apply from: '../android/rninfo.gradle'
1
2
  include ':detox'
2
- includeBuild('../node_modules/react-native-gradle-plugin')
3
+
4
+ println("RNInfo: rootDir=$rootDir")
5
+
6
+ def rnMajorVer = getRnMajorVersion(rootDir)
7
+ println "[settings] RNInfo: detected React Native version: (major=$rnMajorVer)"
8
+
9
+ if (rnMajorVer < 72) {
10
+ includeBuild('../node_modules/react-native-gradle-plugin')
11
+ } else {
12
+ includeBuild('../node_modules/@react-native/gradle-plugin')
13
+ }
14
+
package/detox.d.ts CHANGED
@@ -214,7 +214,7 @@ declare global {
214
214
  * This flag tells Detox to print messages like these every time the device gets assigned to a specific suite:
215
215
  *
216
216
  * ```plain text
217
- * 18:03:29.869 detox[40125] i starter.test.js is assigned to 4EC84833-C7EA-4CA3-A6E9-5C30A29EA596 (iPhone 12 Pro Max)
217
+ * 18:03:29.869 detox[40125] i starter.test.js is assigned to 4EC84833-C7EA-4CA3-A6E9-5C30A29EA596 (iPhone 15)
218
218
  * ```
219
219
  *
220
220
  * @default true
@@ -644,10 +644,11 @@ declare global {
644
644
  */
645
645
  selectApp(app: string): Promise<void>;
646
646
 
647
+ // TODO: document permissions types.
647
648
  /**
648
649
  * Launch the app.
649
650
  *
650
- * <p>For info regarding launch arguments, refer to the [dedicated guide](https://wix.github.io/Detox/docs/api/launch-args).
651
+ * <p>For info regarding launch arguments, refer to the [dedicated guide](https://wix.github.io/Detox/docs/guide/launch-args).
651
652
  *
652
653
  * @example
653
654
  * // Terminate the app and launch it again. If set to false, the simulator will try to bring app from background,
@@ -1363,10 +1364,13 @@ declare global {
1363
1364
 
1364
1365
  /**
1365
1366
  * Scroll to edge.
1366
- * @example await element(by.id('scrollView')).scrollTo('bottom');
1367
+ * @param edge - left|right|top|bottom
1368
+ * @param startPositionX - the X starting scroll position, in percentage; valid input: `[0.0, 1.0]`, `NaN`; default: `NaN`—choose the best value automatically
1369
+ * @param startPositionY - the Y starting scroll position, in percentage; valid input: `[0.0, 1.0]`, `NaN`; default: `NaN`—choose the best value automatically
1370
+ * @example await element(by.id('scrollView')).scrollTo('bottom', NaN, 0.85);
1367
1371
  * @example await element(by.id('scrollView')).scrollTo('top');
1368
1372
  */
1369
- scrollTo(edge: Direction): Promise<void>;
1373
+ scrollTo(edge: Direction, startPositionX?: number, startPositionY?: number): Promise<void>;
1370
1374
 
1371
1375
  /**
1372
1376
  * Adjust slider to position.
@@ -1621,23 +1625,25 @@ declare global {
1621
1625
  userTracking?: UserTrackingPermission;
1622
1626
  }
1623
1627
 
1628
+ type BasicPermissionState = 'YES' | 'NO' | 'unset';
1629
+ type ExtendedPermissionState = 'YES' | 'NO' | 'unset' | 'limited';
1624
1630
  type LocationPermission = 'always' | 'inuse' | 'never' | 'unset';
1625
- type PermissionState = 'YES' | 'NO' | 'unset';
1626
- type CameraPermission = PermissionState;
1627
- type ContactsPermission = PermissionState;
1628
- type CalendarPermission = PermissionState;
1629
- type HealthPermission = PermissionState;
1630
- type HomekitPermission = PermissionState;
1631
- type MediaLibraryPermission = PermissionState;
1632
- type MicrophonePermission = PermissionState;
1633
- type MotionPermission = PermissionState;
1634
- type PhotosPermission = PermissionState;
1635
- type RemindersPermission = PermissionState;
1636
- type SiriPermission = PermissionState;
1637
- type SpeechPermission = PermissionState;
1638
- type NotificationsPermission = PermissionState;
1639
- type FaceIDPermission = PermissionState;
1640
- type UserTrackingPermission = PermissionState;
1631
+
1632
+ type CameraPermission = BasicPermissionState;
1633
+ type ContactsPermission = ExtendedPermissionState;
1634
+ type CalendarPermission = BasicPermissionState;
1635
+ type HealthPermission = BasicPermissionState;
1636
+ type HomekitPermission = BasicPermissionState;
1637
+ type MediaLibraryPermission = BasicPermissionState;
1638
+ type MicrophonePermission = BasicPermissionState;
1639
+ type MotionPermission = BasicPermissionState;
1640
+ type PhotosPermission = ExtendedPermissionState;
1641
+ type RemindersPermission = BasicPermissionState;
1642
+ type SiriPermission = BasicPermissionState;
1643
+ type SpeechPermission = BasicPermissionState;
1644
+ type NotificationsPermission = BasicPermissionState;
1645
+ type FaceIDPermission = BasicPermissionState;
1646
+ type UserTrackingPermission = BasicPermissionState;
1641
1647
 
1642
1648
  interface DeviceLaunchAppConfig {
1643
1649
  /**
package/jest.config.js CHANGED
@@ -74,7 +74,7 @@ module.exports = {
74
74
  'runners/jest/testEnvironment',
75
75
  'src/DetoxWorker.js',
76
76
  'src/logger/utils/streamUtils.js',
77
- 'src/realms'
77
+ 'src/realms',
78
78
  ],
79
79
  resetMocks: true,
80
80
  resetModules: true,
package/local-cli/init.js CHANGED
@@ -106,7 +106,7 @@ function createDefaultConfigurations() {
106
106
  simulator: {
107
107
  type: 'ios.simulator',
108
108
  device: {
109
- type: 'iPhone 12',
109
+ type: 'iPhone 15',
110
110
  },
111
111
  },
112
112
  attached: {
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.15.1-prerelease.0",
4
+ "version": "20.17.0",
5
5
  "bin": {
6
6
  "detox": "local-cli/cli.js"
7
7
  },
@@ -34,29 +34,32 @@
34
34
  "postinstall": "node scripts/postinstall.js"
35
35
  },
36
36
  "devDependencies": {
37
+ "@react-native/eslint-config": "^0.72.2",
38
+ "@react-native/metro-config": "^0.72.11",
39
+ "@tsconfig/react-native": "^3.0.0",
37
40
  "@types/bunyan": "^1.8.8",
38
41
  "@types/child-process-promise": "^2.2.1",
39
- "@types/fs-extra": "^9.0.13",
40
- "@types/jest": "^28.1.8",
42
+ "@types/fs-extra": "^11.0.4",
43
+ "@types/jest": "^29.0.0",
41
44
  "@types/node": "^14.18.33",
42
45
  "@types/node-ipc": "^9.2.0",
43
46
  "@types/ws": "^7.4.0",
44
- "@typescript-eslint/eslint-plugin": "^5.59.8",
45
- "@typescript-eslint/parser": "^5.59.8",
47
+ "@typescript-eslint/eslint-plugin": "^6.16.0",
48
+ "@typescript-eslint/parser": "^6.16.0",
46
49
  "cross-env": "^7.0.3",
47
- "eslint": "^8.41.0",
48
- "eslint-plugin-ecmascript-compat": "^3.0.0",
49
- "eslint-plugin-import": "^2.27.5",
50
+ "eslint": "^8.56.0",
51
+ "eslint-plugin-ecmascript-compat": "^3.1.0",
52
+ "eslint-plugin-import": "^2.29.1",
50
53
  "eslint-plugin-no-only-tests": "^3.1.0",
51
54
  "eslint-plugin-node": "^11.1.0",
52
- "eslint-plugin-unicorn": "^47.0.0",
53
- "jest": "^28.1.3",
54
- "jest-allure2-reporter": "2.0.0-alpha.11",
55
- "mockdate": "^2.0.1",
56
- "prettier": "^2.4.1",
57
- "react-native": "0.71.10",
55
+ "eslint-plugin-unicorn": "^50.0.1",
56
+ "jest": "^29.0.0",
57
+ "jest-allure2-reporter": "^2.0.0-beta.4",
58
+ "metro-react-native-babel-preset": "0.76.8",
59
+ "prettier": "^3.1.1",
60
+ "react-native": "0.72.8",
58
61
  "react-native-codegen": "^0.0.8",
59
- "typescript": "^4.5.2",
62
+ "typescript": "^5.3.3",
60
63
  "wtfnode": "^0.9.1"
61
64
  },
62
65
  "dependencies": {
@@ -72,6 +75,7 @@
72
75
  "funpermaproxy": "^1.1.0",
73
76
  "glob": "^8.0.3",
74
77
  "ini": "^1.3.4",
78
+ "jest-environment-emit": "^1.0.5",
75
79
  "json-cycle": "^1.3.0",
76
80
  "lodash": "^4.17.11",
77
81
  "multi-sort-stream": "^1.0.3",
@@ -104,10 +108,10 @@
104
108
  }
105
109
  },
106
110
  "engines": {
107
- "node": ">=14.5.0"
111
+ "node": ">=16"
108
112
  },
109
113
  "browserslist": [
110
- "node 14"
114
+ "node 16"
111
115
  ],
112
- "gitHead": "513f6af839bdcce38f0f76c04c3c8c78a05c9803"
116
+ "gitHead": "7563d08ce984f7091dcd170f539e680cc5d80080"
113
117
  }
@@ -1,5 +1,6 @@
1
1
  const path = require('path');
2
2
 
3
+ const WithEmitter = require('jest-environment-emit').default;
3
4
  const resolveFrom = require('resolve-from');
4
5
  const maybeNodeEnvironment = require(resolveFrom(process.cwd(), 'jest-environment-node'));
5
6
  /** @type {typeof import('@jest/environment').JestEnvironment} */
@@ -31,7 +32,7 @@ const log = detox.log.child({ cat: 'lifecycle,jest-environment' });
31
32
  /**
32
33
  * @see https://www.npmjs.com/package/jest-circus#overview
33
34
  */
34
- class DetoxCircusEnvironment extends NodeEnvironment {
35
+ class DetoxCircusEnvironment extends WithEmitter(NodeEnvironment) {
35
36
  constructor(config, context) {
36
37
  super(assertJestCircus27(config), assertExistingContext(context));
37
38
 
@@ -62,6 +63,8 @@ class DetoxCircusEnvironment extends NodeEnvironment {
62
63
  SpecReporter,
63
64
  WorkerAssignReporter,
64
65
  });
66
+
67
+ this.testEvents.on('*', this._onTestEvent.bind(this));
65
68
  }
66
69
 
67
70
  /** @override */
@@ -72,19 +75,14 @@ class DetoxCircusEnvironment extends NodeEnvironment {
72
75
 
73
76
  // @ts-expect-error TS2425
74
77
  async handleTestEvent(event, state) {
78
+ // @ts-expect-error TS2855
79
+ await super.handleTestEvent(event, state);
80
+
75
81
  if (detox.session.unsafe_earlyTeardown) {
76
82
  if (event.name === 'test_fn_start' || event.name === 'hook_start') {
77
83
  throw new Error('Detox halted test execution due to an early teardown request');
78
84
  }
79
85
  }
80
-
81
- this._timer.schedule(state.testTimeout != null ? state.testTimeout : this.setupTimeout);
82
-
83
- if (SYNC_CIRCUS_EVENTS.has(event.name)) {
84
- this._handleTestEventSync(event, state);
85
- } else {
86
- await this._handleTestEventAsync(event, state);
87
- }
88
86
  }
89
87
 
90
88
  /** @override */
@@ -147,6 +145,23 @@ class DetoxCircusEnvironment extends NodeEnvironment {
147
145
  }
148
146
  }
149
147
 
148
+ /** @private */
149
+ _onTestEvent({ type, event, state }) {
150
+ const timeout = state && state.testTimeout != null ? state.testTimeout : this.setupTimeout;
151
+
152
+ this._timer.schedule(timeout);
153
+
154
+ if (event) {
155
+ if (SYNC_CIRCUS_EVENTS.has(event.name)) {
156
+ this._handleTestEventSync(event, state);
157
+ } else {
158
+ return this._handleTestEventAsync(event, state);
159
+ }
160
+ } else {
161
+ return this._handleTestEventAsync({ name: type }, null);
162
+ }
163
+ }
164
+
150
165
  /** @private */
151
166
  async _handleTestEventAsync(event, state = null) {
152
167
  const description = `handling ${state ? 'jest-circus' : 'jest-environment'} "${event.name}" event`;
@@ -132,7 +132,7 @@ class DetoxWorker {
132
132
  };
133
133
 
134
134
  this._artifactsManager = artifactsManagerFactory.createArtifactsManager(this._artifactsConfig, commonDeps);
135
- this._deviceCookie = yield this._context[symbols.allocateDevice]();
135
+ this._deviceCookie = yield this._context[symbols.allocateDevice](this._deviceConfig);
136
136
 
137
137
  this.device = runtimeDeviceFactory.createRuntimeDevice(
138
138
  this._deviceCookie,
@@ -81,10 +81,10 @@ class ScrollAmountStopAtEdgeAction extends Action {
81
81
  }
82
82
 
83
83
  class ScrollEdgeAction extends Action {
84
- constructor(edge) {
84
+ constructor(edge, startPositionX = -1, startPositionY = -1) {
85
85
  super();
86
86
 
87
- this._call = invoke.callDirectly(DetoxActionApi.scrollToEdge(edge));
87
+ this._call = invoke.callDirectly(DetoxActionApi.scrollToEdge(edge, startPositionX, startPositionY));
88
88
  }
89
89
  }
90
90
 
@@ -93,12 +93,12 @@ class NativeElement {
93
93
  return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
94
94
  }
95
95
 
96
- async scrollTo(edge) {
96
+ async scrollTo(edge, startPositionX, startPositionY) {
97
97
  // override the user's element selection with an extended matcher that looks for UIScrollView children
98
98
  this._matcher = this._matcher._extendToDescendantScrollViews();
99
99
 
100
- const action = new actions.ScrollEdgeAction(edge);
101
- const traceDescription = actionDescription.scrollTo(edge);
100
+ const action = new actions.ScrollEdgeAction(edge, startPositionX, startPositionY);
101
+ const traceDescription = actionDescription.scrollTo(edge, startPositionX, startPositionY);
102
102
  return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
103
103
  }
104
104
 
@@ -140,7 +140,6 @@ class NativeElement {
140
140
  }
141
141
 
142
142
  async takeScreenshot(screenshotName) {
143
- // TODO this should be moved to a lower-layer handler of this use-case
144
143
  const action = new actions.TakeElementScreenshot();
145
144
  const traceDescription = actionDescription.takeScreenshot(screenshotName);
146
145
  const resultBase64 = await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
@@ -68,8 +68,10 @@ class DetoxAction {
68
68
  };
69
69
  }
70
70
 
71
- static scrollToEdge(edge) {
71
+ static scrollToEdge(edge, startOffsetPercentX, startOffsetPercentY) {
72
72
  if (typeof edge !== "string") throw new Error("edge should be a string, but got " + (edge + (" (" + (typeof edge + ")"))));
73
+ if (typeof startOffsetPercentX !== "number") throw new Error("startOffsetPercentX should be a number, but got " + (startOffsetPercentX + (" (" + (typeof startOffsetPercentX + ")"))));
74
+ if (typeof startOffsetPercentY !== "number") throw new Error("startOffsetPercentY should be a number, but got " + (startOffsetPercentY + (" (" + (typeof startOffsetPercentY + ")"))));
73
75
  return {
74
76
  target: {
75
77
  type: "Class",
@@ -79,6 +81,12 @@ class DetoxAction {
79
81
  args: [{
80
82
  type: "Integer",
81
83
  value: sanitize_android_edge(edge)
84
+ }, {
85
+ type: "Double",
86
+ value: startOffsetPercentX
87
+ }, {
88
+ type: "Double",
89
+ value: startOffsetPercentY
82
90
  }]
83
91
  };
84
92
  }
@@ -28,7 +28,7 @@ class ActionInteraction extends Interaction {
28
28
  constructor(invocationManager, matcher, action, traceDescription) {
29
29
  super(invocationManager, traceDescription);
30
30
  this._call = EspressoDetoxApi.perform(matcher, action._call);
31
- // TODO: move this.execute() here from the caller
31
+ // TODO [2024-12-01]: move this.execute() here from the caller
32
32
  }
33
33
  }
34
34
 
@@ -39,7 +39,7 @@ class MatcherAssertionInteraction extends Interaction {
39
39
 
40
40
  matcher = notCondition ? matcher.not : matcher;
41
41
  this._call = DetoxAssertionApi.assertMatcher(call(element._call), matcher._call.value);
42
- // TODO: move this.execute() here from the caller
42
+ // TODO [2024-12-01]: move this.execute() here from the caller
43
43
  }
44
44
  }
45
45
 
@@ -76,7 +76,7 @@ class ToggleMatcher extends NativeMatcher {
76
76
  }
77
77
  }
78
78
 
79
- // TODO: Please be aware, that this is just a dummy matcher
79
+ // NOTE: Please be aware, that this is just a dummy matcher
80
80
  class TraitsMatcher extends NativeMatcher {
81
81
  constructor(value) {
82
82
  super();
@@ -1,5 +1,5 @@
1
1
  class ArtifactPluginsProvider {
2
- declareArtifactPlugins({ client }) {} // eslint-disable-line no-unused-vars
2
+ declareArtifactPlugins({ client }) {} // eslint-disable-line no-unused-vars,@typescript-eslint/no-unused-vars
3
3
  }
4
4
 
5
5
  class AndroidArtifactPluginsProvider extends ArtifactPluginsProvider {
@@ -1,7 +1,6 @@
1
1
  const path = require('path');
2
2
 
3
3
  const fs = require('../../utils/fsext');
4
- const log = require('../../utils/logger').child({ cat: 'artifacts-plugin,artifacts' });
5
4
  const FileArtifact = require('../templates/artifact/FileArtifact');
6
5
  const temporaryPath = require('../utils/temporaryPath');
7
6
 
@@ -107,7 +107,7 @@ class Artifact {
107
107
 
108
108
  async doStop() {}
109
109
 
110
- async doSave(artifactPath) {} // eslint-disable-line no-unused-vars
110
+ async doSave(_artifactPath) {}
111
111
 
112
112
  async doDiscard() {}
113
113
  }
@@ -134,7 +134,7 @@ class AsyncWebSocket {
134
134
  }
135
135
  }
136
136
 
137
- // TODO: handle this leaked abstraction some day
137
+ // TODO [2024-12-01]: handle this leaked abstraction some day
138
138
  hasPendingActions() {
139
139
  return _.some(this.inFlightPromises, p => p.message.type !== 'currentStatus');
140
140
  }
@@ -168,7 +168,7 @@ class AsyncWebSocket {
168
168
  case WebSocket.CONNECTING: return 'opening';
169
169
  case WebSocket.OPEN: return 'open';
170
170
  /* istanbul ignore next */
171
- default: // TODO: [2021-12-01] throw new DetoxInternalError('...'); instead
171
+ default:
172
172
  return undefined;
173
173
  }
174
174
  }