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.
- package/.eslintrc.js +1 -5
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-javadoc.jar +0 -0
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-javadoc.jar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-javadoc.jar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-javadoc.jar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-javadoc.jar.sha512 +1 -0
- 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
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-sources.jar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-sources.jar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-sources.jar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0-sources.jar.sha512 +1 -0
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.aar +0 -0
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.aar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.aar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.aar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.aar.sha512 +1 -0
- 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
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.pom.md5 +1 -0
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.pom.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.0.pom.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.17.0/detox-20.17.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-src.tbz +0 -0
- package/Detox-ios.tbz +0 -0
- package/android/build.gradle +2 -10
- package/android/detox/build.gradle +2 -0
- package/android/detox/proguard-rules-app.pro +1 -2
- package/android/detox/publishing.gradle +2 -2
- package/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java +11 -6
- package/android/detox/src/full/java/com/wix/detox/espresso/common/ReactSliderHelper.kt +5 -5
- package/android/detox/src/main/java/com/wix/detox/espresso/DeviceDisplay.kt +1 -1
- package/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java +113 -16
- package/android/detox/src/testFull/java/com/wix/detox/espresso/action/GetAttributesActionTest.kt +3 -3
- package/android/detox/src/testFull/java/com/wix/detox/espresso/common/ReactSliderHelperTest.kt +25 -23
- package/android/detox/src/testFull/java/com/wix/detox/espresso/scroll/ScrollHelperTest.kt +188 -0
- package/android/rninfo.gradle +27 -15
- package/android/settings.gradle +13 -1
- package/detox.d.ts +26 -20
- package/jest.config.js +1 -1
- package/local-cli/init.js +1 -1
- package/package.json +22 -18
- package/runners/jest/testEnvironment/index.js +24 -9
- package/src/DetoxWorker.js +1 -1
- package/src/android/actions/native.js +2 -2
- package/src/android/core/NativeElement.js +3 -4
- package/src/android/espressoapi/DetoxAction.js +9 -1
- package/src/android/interactions/native.js +2 -2
- package/src/android/matchers/native.js +1 -1
- package/src/artifacts/providers/index.js +1 -1
- package/src/artifacts/screenshot/SimulatorScreenshotPlugin.js +0 -1
- package/src/artifacts/templates/artifact/Artifact.js +1 -1
- package/src/client/AsyncWebSocket.js +2 -2
- package/src/client/Client.js +2 -2
- package/src/devices/allocation/drivers/android/attached/AttachedAndroidAllocDriver.js +0 -1
- package/src/devices/allocation/drivers/android/emulator/EmulatorAllocDriver.js +0 -1
- package/src/devices/allocation/drivers/android/emulator/EmulatorVersionResolver.js +1 -1
- package/src/devices/allocation/drivers/ios/SimulatorAllocDriver.js +0 -1
- package/src/devices/allocation/factories/android.js +1 -1
- package/src/devices/allocation/factories/ios.js +1 -1
- package/src/devices/common/drivers/android/exec/ADB.js +1 -2
- package/src/devices/common/drivers/android/tools/ApkValidator.js +1 -1
- package/src/devices/common/drivers/android/tools/AppInstallHelper.js +0 -2
- package/src/devices/common/drivers/ios/tools/AppleSimUtils.js +111 -15
- package/src/devices/runtime/drivers/android/AndroidDriver.js +1 -1
- package/src/devices/runtime/drivers/android/emulator/EmulatorDriver.js +0 -1
- package/src/devices/runtime/factories/ios.js +0 -2
- package/src/ios/expectTwo.js +6 -3
- package/src/ipc/IPCClient.js +2 -2
- package/src/ipc/IPCServer.js +4 -4
- package/src/realms/DetoxContext.js +3 -3
- package/src/realms/DetoxPrimaryContext.js +74 -29
- package/src/realms/DetoxSecondaryContext.js +2 -2
- package/src/utils/childProcess/exec.js +3 -2
- package/src/utils/invocationTraceDescriptions.js +3 -2
- package/tsconfig.json +1 -1
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-javadoc.jar +0 -0
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-javadoc.jar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-javadoc.jar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-javadoc.jar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-javadoc.jar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-sources.jar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-sources.jar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-sources.jar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0-sources.jar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.aar +0 -0
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.aar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.aar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.aar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.aar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.pom.md5 +0 -1
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.pom.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.15.0-prerelease.0/detox-20.15.0-prerelease.0.pom.sha256 +0 -1
- 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
|
+
}
|
package/android/rninfo.gradle
CHANGED
@@ -1,25 +1,37 @@
|
|
1
1
|
import groovy.json.JsonSlurper
|
2
2
|
|
3
|
-
def
|
4
|
-
|
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
|
-
|
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
|
-
|
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
|
+
}
|
package/android/settings.gradle
CHANGED
@@ -1,2 +1,14 @@
|
|
1
|
+
apply from: '../android/rninfo.gradle'
|
1
2
|
include ':detox'
|
2
|
-
|
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
|
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/
|
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
|
-
* @
|
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
|
-
|
1626
|
-
type CameraPermission =
|
1627
|
-
type ContactsPermission =
|
1628
|
-
type CalendarPermission =
|
1629
|
-
type HealthPermission =
|
1630
|
-
type HomekitPermission =
|
1631
|
-
type MediaLibraryPermission =
|
1632
|
-
type MicrophonePermission =
|
1633
|
-
type MotionPermission =
|
1634
|
-
type PhotosPermission =
|
1635
|
-
type RemindersPermission =
|
1636
|
-
type SiriPermission =
|
1637
|
-
type SpeechPermission =
|
1638
|
-
type NotificationsPermission =
|
1639
|
-
type FaceIDPermission =
|
1640
|
-
type UserTrackingPermission =
|
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
package/local-cli/init.js
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.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": "^
|
40
|
-
"@types/jest": "^
|
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": "^
|
45
|
-
"@typescript-eslint/parser": "^
|
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.
|
48
|
-
"eslint-plugin-ecmascript-compat": "^3.
|
49
|
-
"eslint-plugin-import": "^2.
|
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": "^
|
53
|
-
"jest": "^
|
54
|
-
"jest-allure2-reporter": "2.0.0-
|
55
|
-
"
|
56
|
-
"prettier": "^
|
57
|
-
"react-native": "0.
|
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": "^
|
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": ">=
|
111
|
+
"node": ">=16"
|
108
112
|
},
|
109
113
|
"browserslist": [
|
110
|
-
"node
|
114
|
+
"node 16"
|
111
115
|
],
|
112
|
-
"gitHead": "
|
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`;
|
package/src/DetoxWorker.js
CHANGED
@@ -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
|
-
//
|
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
|
|
@@ -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:
|
171
|
+
default:
|
172
172
|
return undefined;
|
173
173
|
}
|
174
174
|
}
|