detox 20.41.2 → 20.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Detox-android/com/wix/detox/{20.41.2/detox-20.41.2-sources.jar → 20.42.0/detox-20.42.0-sources.jar} +0 -0
- package/Detox-android/com/wix/detox/20.42.0/detox-20.42.0-sources.jar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.42.0/detox-20.42.0-sources.jar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.42.0/detox-20.42.0-sources.jar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.42.0/detox-20.42.0-sources.jar.sha512 +1 -0
- package/Detox-android/com/wix/detox/20.42.0/detox-20.42.0.aar +0 -0
- package/Detox-android/com/wix/detox/20.42.0/detox-20.42.0.aar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.42.0/detox-20.42.0.aar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.42.0/detox-20.42.0.aar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.42.0/detox-20.42.0.aar.sha512 +1 -0
- package/Detox-android/com/wix/detox/{20.41.2/detox-20.41.2.pom → 20.42.0/detox-20.42.0.pom} +1 -1
- package/Detox-android/com/wix/detox/20.42.0/detox-20.42.0.pom.md5 +1 -0
- package/Detox-android/com/wix/detox/20.42.0/detox-20.42.0.pom.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.42.0/detox-20.42.0.pom.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.42.0/detox-20.42.0.pom.sha512 +1 -0
- package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
- package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
- package/Detox-ios-framework.tbz +0 -0
- package/Detox-ios-src.tbz +0 -0
- package/Detox-ios-xcuitest.tbz +0 -0
- package/android/detox/src/full/java/com/wix/detox/espresso/hierarchy/ViewHierarchyGenerator.kt +3 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/DetoxIdlingResource.kt +0 -2
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/ReactNativeIdlingResources.kt +0 -1
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/StabilizedIdlingResource.kt +74 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/factory/FabricDetoxIdlingResourceFactoryStrategy.kt +2 -2
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/storage/AsyncStorageIdlingResource.kt +17 -23
- package/detox.d.ts +29 -0
- package/package.json +3 -3
- package/runners/jest/reporter.js +1 -1
- package/src/devices/allocation/drivers/ios/SimulatorAllocDriver.js +10 -3
- package/src/devices/common/drivers/android/exec/ADB.js +12 -0
- package/src/devices/common/drivers/ios/tools/SimulatorAppCache.js +148 -0
- package/src/devices/runtime/RuntimeDevice.js +10 -1
- package/src/devices/runtime/drivers/DeviceDriverBase.js +4 -0
- package/src/devices/runtime/drivers/android/AndroidDriver.js +6 -0
- package/src/devices/runtime/drivers/ios/AppStateResetFallback.js +69 -0
- package/src/devices/runtime/drivers/ios/SimulatorDriver.js +17 -1
- package/src/utils/constructSafeFilename.js +2 -1
- package/src/utils/environment.js +12 -0
- package/src/utils/fsext.js +13 -0
- package/Detox-android/com/wix/detox/20.41.2/detox-20.41.2-sources.jar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.41.2/detox-20.41.2-sources.jar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.41.2/detox-20.41.2-sources.jar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.41.2/detox-20.41.2-sources.jar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.41.2/detox-20.41.2.aar +0 -0
- package/Detox-android/com/wix/detox/20.41.2/detox-20.41.2.aar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.41.2/detox-20.41.2.aar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.41.2/detox-20.41.2.aar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.41.2/detox-20.41.2.aar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.41.2/detox-20.41.2.pom.md5 +0 -1
- package/Detox-android/com/wix/detox/20.41.2/detox-20.41.2.pom.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.41.2/detox-20.41.2.pom.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.41.2/detox-20.41.2.pom.sha512 +0 -1
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1887d4991e0ed4ee1f60fe19496a9cfa
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
697906ce37fa13e262c3cfdaba6a6e23af05123d
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
81b7cec264869ffb4a51c3f49d0458ea42f02a8921afeeef9fba6d13f81f76c6
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2345745b36a549ab7230b6cbc55cad2a2a469720a8622a2592669c3a25be55c8a9d0bb7b36123c68e91bc37c6df04f477849018c034e0dd956d1d5e9cb2a1cfb
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
69c3525aa2e433c05bee1f04c2724502
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
5dac9bbd10339143f33f66269d560c940dabde5a
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
04cb33cabcf007a0767a85ea4b9322efe008d9ffb2e4a5636f2eb6513fcc81d3
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aec098beccb4827b287051cfd924ba72a4b8ad6a5d3cf3c5f645dea50ba704f683a2dbe5e724a94801189aded1fe494a69cef0b31f9128dde89ee1ec2fc92e2d
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<modelVersion>4.0.0</modelVersion>
|
|
4
4
|
<groupId>com.wix</groupId>
|
|
5
5
|
<artifactId>detox</artifactId>
|
|
6
|
-
<version>20.
|
|
6
|
+
<version>20.42.0</version>
|
|
7
7
|
<packaging>aar</packaging>
|
|
8
8
|
<name>Detox</name>
|
|
9
9
|
<description>Gray box end-to-end testing and automation library for mobile apps</description>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
c194caa27e6cabf5bc5bbede5c1e22ad
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
d844bb306d465880c159bb56238879e2395cee9a
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
6989731d5223c5a9f236e3911eaef2e85fbca229eb68bfbec44e31b3529ae70d
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
13500f06af3b22637f0469dbce8b92b7da859ec3b08a28b8874be4ee1116599b97533cc7307a23beebe6c0508e57038ba2f6f195d69a09a994a599071bdce886
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
<groupId>com.wix</groupId>
|
|
4
4
|
<artifactId>detox</artifactId>
|
|
5
5
|
<versioning>
|
|
6
|
-
<latest>20.
|
|
7
|
-
<release>20.
|
|
6
|
+
<latest>20.42.0</latest>
|
|
7
|
+
<release>20.42.0</release>
|
|
8
8
|
<versions>
|
|
9
|
-
<version>20.
|
|
9
|
+
<version>20.42.0</version>
|
|
10
10
|
</versions>
|
|
11
|
-
<lastUpdated>
|
|
11
|
+
<lastUpdated>20250925083025</lastUpdated>
|
|
12
12
|
</versioning>
|
|
13
13
|
</metadata>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
cffd00ec1fa7d24dfa670f1f73cb2157
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
2f1f1e00242921f53643aeea67e863765c97bdef
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
be3543d79c53d124eb70e536793549e36b7f2a5c196b524f55d39cdb322a3216
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
003eb81934b9bf5c98fc53dfa0d8871d436a7ef88fd0d430206672a38c898e47c4535ca1a2b621a65163b4c672fbbf65daba9ed9dbc0ee17461a64c674d63cd6
|
package/Detox-ios-framework.tbz
CHANGED
|
Binary file
|
package/Detox-ios-src.tbz
CHANGED
|
Binary file
|
package/Detox-ios-xcuitest.tbz
CHANGED
|
Binary file
|
package/android/detox/src/full/java/com/wix/detox/espresso/hierarchy/ViewHierarchyGenerator.kt
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
package com.wix.detox.espresso.hierarchy
|
|
2
2
|
|
|
3
|
+
import android.util.DisplayMetrics
|
|
3
4
|
import android.util.Xml
|
|
4
5
|
import android.view.View
|
|
5
6
|
import android.view.ViewGroup
|
|
6
7
|
import android.webkit.WebView
|
|
7
8
|
import android.widget.TextView
|
|
9
|
+
import com.wix.detox.espresso.DeviceDisplay
|
|
8
10
|
import com.wix.detox.reactnative.ui.getAccessibilityLabel
|
|
9
11
|
import kotlinx.coroutines.Dispatchers
|
|
10
12
|
import kotlinx.coroutines.runBlocking
|
|
@@ -63,6 +65,7 @@ object ViewHierarchyGenerator {
|
|
|
63
65
|
startDocument(Xml.Encoding.UTF_8.name, true)
|
|
64
66
|
setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true)
|
|
65
67
|
startTag("", "ViewHierarchy")
|
|
68
|
+
attribute("", "density", DeviceDisplay.getDensity().toString())
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
rootViews?.forEach { rootView ->
|
package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/DetoxIdlingResource.kt
CHANGED
|
@@ -19,7 +19,6 @@ abstract class DetoxIdlingResource : DescriptiveIdlingResource {
|
|
|
19
19
|
paused.set(false)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
22
|
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
|
|
24
23
|
this.callback = callback
|
|
25
24
|
}
|
|
@@ -37,7 +36,6 @@ abstract class DetoxIdlingResource : DescriptiveIdlingResource {
|
|
|
37
36
|
|
|
38
37
|
protected abstract fun checkIdle(): Boolean
|
|
39
38
|
|
|
40
|
-
|
|
41
39
|
fun notifyIdle() {
|
|
42
40
|
callback?.onTransitionToIdle()
|
|
43
41
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
package com.wix.detox.reactnative.idlingresources
|
|
2
|
+
|
|
3
|
+
import androidx.test.espresso.IdlingResource.ResourceCallback
|
|
4
|
+
/**
|
|
5
|
+
* A wrapper for idling resources that exhibit "flapping" behavior.
|
|
6
|
+
* This wrapper artificially stabilizes them by mindfully extending their "busy" state periods.
|
|
7
|
+
*
|
|
8
|
+
* #### What does flapping mean?
|
|
9
|
+
*
|
|
10
|
+
* Some idling resources have extremely short busy periods. While this shouldn't theoretically be an issue,
|
|
11
|
+
* in real-world scenarios (e.g., a React Native environment), the number of idling resources can accumulate.
|
|
12
|
+
* Unfortunately, the process of checking if all resources are idle (idle interrogation)
|
|
13
|
+
* isn't atomic. With many resources involved, this process might miss those brief busy periods
|
|
14
|
+
* of resources that "flap" (quickly switch between busy and idle) during the interrogation.
|
|
15
|
+
*
|
|
16
|
+
* #### How does this wrapper fix flapping?
|
|
17
|
+
*
|
|
18
|
+
* This wrapper requires the wrapped resource to report itself as "idle" multiple times consecutively
|
|
19
|
+
* before the wrapper itself is considered idle. This dramatically reduces the likelihood of it's busy phase being
|
|
20
|
+
* missed by the idle interrogation process.
|
|
21
|
+
*/
|
|
22
|
+
class StabilizedIdlingResource(
|
|
23
|
+
private val idlingResource: DetoxIdlingResource,
|
|
24
|
+
private val size: Int): DetoxIdlingResource(), ResourceCallback {
|
|
25
|
+
|
|
26
|
+
private val name = "${idlingResource.name} (stable@$size)"
|
|
27
|
+
private var idleCounter = 0
|
|
28
|
+
|
|
29
|
+
init {
|
|
30
|
+
if (size <= 1) {
|
|
31
|
+
throw IllegalArgumentException("Size must be > 1 in order for the usage of this class to make sense")
|
|
32
|
+
}
|
|
33
|
+
idlingResource.registerIdleTransitionCallback(this)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Implemented according to these 3 guiding principles:
|
|
38
|
+
* 1. As long as the actual resource is busy - we consider ourselves busy too.
|
|
39
|
+
* 2. Once actual resource is idle enough times in a row - we consider ourselves "idle", in a *sticky* way
|
|
40
|
+
* (until it returns "busy" in an interrogation).
|
|
41
|
+
* 3. Espresso requires that just before we officially transition ourselves -- idle -> busy, we actively notify it
|
|
42
|
+
* via the callback.
|
|
43
|
+
*/
|
|
44
|
+
@Synchronized
|
|
45
|
+
override fun checkIdle(): Boolean {
|
|
46
|
+
if (!idlingResource.isIdleNow) {
|
|
47
|
+
idleCounter = 0
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (idleCounter < size) {
|
|
52
|
+
idleCounter++
|
|
53
|
+
|
|
54
|
+
if (idleCounter == size) {
|
|
55
|
+
notifyIdle()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return (idleCounter == size)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resets the counter to 0, assuming that Espresso will re-query this (among all other) resources
|
|
63
|
+
* immediately after this "I'm idle" notification.
|
|
64
|
+
*/
|
|
65
|
+
@Synchronized
|
|
66
|
+
override fun onTransitionToIdle() {
|
|
67
|
+
idleCounter = 0
|
|
68
|
+
notifyIdle()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override fun getName(): String = name
|
|
72
|
+
override fun getDebugName(): String = idlingResource.getDebugName()
|
|
73
|
+
override fun getBusyHint(): Map<String, Any>? = idlingResource.getBusyHint()
|
|
74
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
package com.wix.detox.reactnative.idlingresources.factory
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.ReactApplication
|
|
4
|
-
import com.wix.detox.reactnative.getCurrentReactContext
|
|
5
4
|
import com.wix.detox.reactnative.getCurrentReactContextSafe
|
|
6
5
|
import com.wix.detox.reactnative.idlingresources.DetoxIdlingResource
|
|
6
|
+
import com.wix.detox.reactnative.idlingresources.StabilizedIdlingResource
|
|
7
7
|
import com.wix.detox.reactnative.idlingresources.animations.AnimatedModuleIdlingResource
|
|
8
8
|
import com.wix.detox.reactnative.idlingresources.network.NetworkIdlingResource
|
|
9
9
|
import com.wix.detox.reactnative.idlingresources.storage.AsyncStorageIdlingResource
|
|
@@ -23,7 +23,7 @@ class FabricDetoxIdlingResourceFactoryStrategy(private val reactApplication: Rea
|
|
|
23
23
|
IdlingResourcesName.Animations to AnimatedModuleIdlingResource(reactContext),
|
|
24
24
|
IdlingResourcesName.Timers to FabricTimersIdlingResource(reactContext),
|
|
25
25
|
IdlingResourcesName.Network to NetworkIdlingResource(reactContext),
|
|
26
|
-
IdlingResourcesName.AsyncStorage to AsyncStorageIdlingResource(reactContext)
|
|
26
|
+
IdlingResourcesName.AsyncStorage to StabilizedIdlingResource(AsyncStorageIdlingResource(reactContext), 2)
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
return@withContext result
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
package com.wix.detox.reactnative.idlingresources.storage
|
|
2
2
|
|
|
3
|
-
import android.util.Log
|
|
4
3
|
import androidx.test.espresso.IdlingResource
|
|
5
4
|
import com.facebook.react.bridge.NativeModule
|
|
6
5
|
import com.facebook.react.bridge.ReactContext
|
|
@@ -9,36 +8,17 @@ import com.wix.detox.reactnative.idlingresources.DetoxIdlingResource
|
|
|
9
8
|
import org.joor.Reflect
|
|
10
9
|
import java.util.concurrent.Executor
|
|
11
10
|
|
|
12
|
-
private const val LOG_TAG = "AsyncStorageIR"
|
|
13
|
-
|
|
14
11
|
private typealias SExecutorReflectedGenFnType = (executor: Executor) -> SerialExecutorReflected
|
|
15
12
|
|
|
16
13
|
private val defaultSExecutorReflectedGenFn: SExecutorReflectedGenFnType =
|
|
17
14
|
{ executor: Executor -> SerialExecutorReflected(executor) }
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
private val executorReflected: SerialExecutorReflected
|
|
21
|
-
|
|
22
|
-
init {
|
|
23
|
-
val reflected = Reflect.on(module)
|
|
24
|
-
val executor: Executor = reflected.field("executor").get()
|
|
25
|
-
executorReflected = sexecutorReflectedGen(executor)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
val sexecutor: SerialExecutorReflected
|
|
29
|
-
get() = executorReflected
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
class AsyncStorageIdlingResource
|
|
33
|
-
@JvmOverloads constructor(
|
|
16
|
+
class AsyncStorageIdlingResource @JvmOverloads constructor(
|
|
34
17
|
private val reactContext: ReactContext,
|
|
35
18
|
private val sexecutorReflectedGenFn: SExecutorReflectedGenFnType = defaultSExecutorReflectedGenFn,
|
|
36
|
-
private val rnHelpers: RNHelpers = RNHelpers()
|
|
19
|
+
private val rnHelpers: RNHelpers = RNHelpers(),
|
|
37
20
|
) : DetoxIdlingResource() {
|
|
38
21
|
|
|
39
|
-
val logTag: String
|
|
40
|
-
get() = LOG_TAG
|
|
41
|
-
|
|
42
22
|
private val moduleReflected: ModuleReflected? = null
|
|
43
23
|
private var idleCheckTask: Runnable? = null
|
|
44
24
|
private val idleCheckTaskImpl = Runnable {
|
|
@@ -86,13 +66,14 @@ class AsyncStorageIdlingResource
|
|
|
86
66
|
|
|
87
67
|
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
|
|
88
68
|
super.registerIdleTransitionCallback(callback)
|
|
69
|
+
|
|
70
|
+
// TODO Don't do?
|
|
89
71
|
enqueueIdleCheckTask()
|
|
90
72
|
}
|
|
91
73
|
|
|
92
74
|
override fun checkIdle(): Boolean =
|
|
93
75
|
checkIdleInternal().also { idle ->
|
|
94
76
|
if (!idle) {
|
|
95
|
-
Log.d(logTag, "Async-storage is busy!")
|
|
96
77
|
enqueueIdleCheckTask()
|
|
97
78
|
}
|
|
98
79
|
}
|
|
@@ -117,3 +98,16 @@ class AsyncStorageIdlingResource
|
|
|
117
98
|
idleCheckTask = null
|
|
118
99
|
}
|
|
119
100
|
}
|
|
101
|
+
|
|
102
|
+
private class ModuleReflected(module: NativeModule, sexecutorReflectedGen: SExecutorReflectedGenFnType) {
|
|
103
|
+
private val executorReflected: SerialExecutorReflected
|
|
104
|
+
|
|
105
|
+
init {
|
|
106
|
+
val reflected = Reflect.on(module)
|
|
107
|
+
val executor: Executor = reflected.field("executor").get()
|
|
108
|
+
executorReflected = sexecutorReflectedGen(executor)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
val sexecutor: SerialExecutorReflected
|
|
112
|
+
get() = executorReflected
|
|
113
|
+
}
|
package/detox.d.ts
CHANGED
|
@@ -773,6 +773,28 @@ declare global {
|
|
|
773
773
|
*/
|
|
774
774
|
reloadReactNative(): Promise<void>;
|
|
775
775
|
|
|
776
|
+
/**
|
|
777
|
+
* Resets the app state by clearing app data and restoring it to a clean state.
|
|
778
|
+
*
|
|
779
|
+
* On Android, this command clears the app's data using the `pm clear` command,
|
|
780
|
+
* effectively resetting the app to its initial installed state without uninstalling it.
|
|
781
|
+
*
|
|
782
|
+
* On iOS, Detox uses a fallback mechanism - it backs up, deletes and installs the app from cache.
|
|
783
|
+
* This process ensures the app is returned to a clean state.
|
|
784
|
+
*
|
|
785
|
+
* @param bundleIds Optional bundle IDs to reset. If none provided, resets the currently selected app.
|
|
786
|
+
* @example
|
|
787
|
+
* // Reset current app state
|
|
788
|
+
* await device.resetAppState();
|
|
789
|
+
* @example
|
|
790
|
+
* // Reset specific app state
|
|
791
|
+
* await device.resetAppState('com.example.app');
|
|
792
|
+
* @example
|
|
793
|
+
* // Reset multiple apps
|
|
794
|
+
* await device.resetAppState('com.app1', 'com.app2');
|
|
795
|
+
*/
|
|
796
|
+
resetAppState(...bundleIds: string[]): Promise<void>;
|
|
797
|
+
|
|
776
798
|
/**
|
|
777
799
|
* By default, installApp() with no params will install the app file defined in the current configuration.
|
|
778
800
|
* To install another app, specify its path
|
|
@@ -2014,6 +2036,13 @@ declare global {
|
|
|
2014
2036
|
* Launch with user activity
|
|
2015
2037
|
*/
|
|
2016
2038
|
userActivity?: any;
|
|
2039
|
+
/**
|
|
2040
|
+
* Similar to {@link Detox.DeviceLaunchAppConfig.delete | { delete: true }}, but instead of uninstalling and installing the app,
|
|
2041
|
+
* it runs {@link Detox.Device.resetAppState | device.resetAppState()} instead.
|
|
2042
|
+
* @example
|
|
2043
|
+
* await device.launchApp({resetAppState: true});
|
|
2044
|
+
*/
|
|
2045
|
+
resetAppState?: boolean;
|
|
2017
2046
|
/**
|
|
2018
2047
|
* Launch into a fresh installation
|
|
2019
2048
|
* A flag that enables relaunching into a fresh installation of the app (it will uninstall and install the binary again), default is false.
|
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.42.0",
|
|
5
5
|
"bin": {
|
|
6
6
|
"detox": "local-cli/cli.js"
|
|
7
7
|
},
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"wtfnode": "^0.9.1"
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@wix-pilot/core": "^3.4.
|
|
71
|
+
"@wix-pilot/core": "^3.4.2",
|
|
72
72
|
"@wix-pilot/detox": "^1.0.13",
|
|
73
73
|
"ajv": "^8.6.3",
|
|
74
74
|
"bunyan": "^1.8.12",
|
|
@@ -120,5 +120,5 @@
|
|
|
120
120
|
"browserslist": [
|
|
121
121
|
"node 14"
|
|
122
122
|
],
|
|
123
|
-
"gitHead": "
|
|
123
|
+
"gitHead": "e86f949767d1fb178f97fa31ec9cb6e2226e86fa"
|
|
124
124
|
}
|
package/runners/jest/reporter.js
CHANGED
|
@@ -8,6 +8,7 @@ const _ = require('lodash');
|
|
|
8
8
|
|
|
9
9
|
const { DetoxRuntimeError } = require('../../../../errors');
|
|
10
10
|
const log = require('../../../../utils/logger').child({ cat: 'device,device-allocation' });
|
|
11
|
+
const SimulatorAppCache = require('../../../common/drivers/ios/tools/SimulatorAppCache');
|
|
11
12
|
|
|
12
13
|
const SimulatorQuery = require('./SimulatorQuery');
|
|
13
14
|
|
|
@@ -24,6 +25,7 @@ class SimulatorAllocDriver {
|
|
|
24
25
|
constructor({ detoxConfig, deviceRegistry, applesimutils }) {
|
|
25
26
|
this._deviceRegistry = deviceRegistry;
|
|
26
27
|
this._applesimutils = applesimutils;
|
|
28
|
+
this._appCache = new SimulatorAppCache({ applesimutils });
|
|
27
29
|
this._launchInfo = {};
|
|
28
30
|
this._shouldShutdown = detoxConfig.behavior.cleanup.shutdownDevice;
|
|
29
31
|
}
|
|
@@ -58,7 +60,9 @@ class SimulatorAllocDriver {
|
|
|
58
60
|
async postAllocate(deviceCookie) {
|
|
59
61
|
const { udid } = deviceCookie;
|
|
60
62
|
const { deviceConfig } = this._launchInfo[udid];
|
|
63
|
+
|
|
61
64
|
await this._applesimutils.boot(udid, deviceConfig.bootArgs, deviceConfig.headless);
|
|
65
|
+
await this._appCache.cleanupOnce(udid);
|
|
62
66
|
|
|
63
67
|
return {
|
|
64
68
|
id: udid,
|
|
@@ -86,12 +90,15 @@ class SimulatorAllocDriver {
|
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
async cleanup() {
|
|
93
|
+
const sessionDevices = await this._deviceRegistry.readSessionDevices();
|
|
94
|
+
const deviceIds = sessionDevices.getIds();
|
|
95
|
+
|
|
89
96
|
if (this._shouldShutdown) {
|
|
90
|
-
const
|
|
91
|
-
const shutdownPromises = sessionDevices.getIds().map((udid) => this._doShutdown(udid));
|
|
97
|
+
const shutdownPromises = deviceIds.map((udid) => this._doShutdown(udid));
|
|
92
98
|
await Promise.all(shutdownPromises);
|
|
93
99
|
}
|
|
94
100
|
|
|
101
|
+
await Promise.all(deviceIds.map((udid) => this._appCache.cleanup(udid)));
|
|
95
102
|
await this._deviceRegistry.unregisterSessionDevices();
|
|
96
103
|
}
|
|
97
104
|
|
|
@@ -172,7 +179,7 @@ class SimulatorAllocDriver {
|
|
|
172
179
|
deviceQuery,
|
|
173
180
|
{
|
|
174
181
|
trying: `Searching for device ${deviceQuery} ...`,
|
|
175
|
-
fields: ['udid', 'os', 'identifier'],
|
|
182
|
+
fields: ['udid', 'name', 'deviceType', 'os', 'identifier'],
|
|
176
183
|
}
|
|
177
184
|
);
|
|
178
185
|
|
|
@@ -148,6 +148,18 @@ class ADB {
|
|
|
148
148
|
await this.shell(deviceId, `am force-stop ${appId}`);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
async clearAppData(deviceId, packageId) {
|
|
152
|
+
try {
|
|
153
|
+
return await this.shell(deviceId, `pm clear ${packageId}`);
|
|
154
|
+
} catch (reason) {
|
|
155
|
+
throw new DetoxRuntimeError({
|
|
156
|
+
message: `Failed to clear ${packageId} app data on ${deviceId}`,
|
|
157
|
+
hint: `Please verify that the package is installed on the device:\nadb -s ${deviceId} shell pm list packages ${packageId}`,
|
|
158
|
+
debugInfo: reason,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
151
163
|
async setLocation(deviceId, lat, lon) {
|
|
152
164
|
// NOTE: QEMU for Android for the telnet part relies on C stdlib
|
|
153
165
|
// function `strtod` which is locale-sensitive, meaning that depending
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
const { DetoxInternalError, DetoxRuntimeError } = require('../../../../../errors');
|
|
4
|
+
const { getDetoxAppsCachePath } = require('../../../../../utils/environment');
|
|
5
|
+
const fse = require('../../../../../utils/fsext');
|
|
6
|
+
const log = require('../../../../../utils/logger').child({ cat: 'device,app-cache' });
|
|
7
|
+
|
|
8
|
+
class SimulatorAppCache {
|
|
9
|
+
constructor({ applesimutils, rootDir = getDetoxAppsCachePath() }) {
|
|
10
|
+
this.applesimutils = applesimutils;
|
|
11
|
+
this.rootDir = rootDir;
|
|
12
|
+
this.cleanDeviceIds = new Set();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Add an app to cache from a binary path
|
|
17
|
+
* @param {string} deviceId - The device identifier
|
|
18
|
+
* @param {string} bundleId - The app's bundle identifier
|
|
19
|
+
* @param {string} binaryPath - Path to the app binary
|
|
20
|
+
*/
|
|
21
|
+
async add(deviceId, bundleId, binaryPath) {
|
|
22
|
+
log.trace({ deviceId }, `Caching app (${bundleId}) from binary path: ${binaryPath}`);
|
|
23
|
+
|
|
24
|
+
const cacheAppPath = this._getAppCachePath(deviceId, bundleId);
|
|
25
|
+
await fse.ensureDir(path.dirname(cacheAppPath));
|
|
26
|
+
await fse.remove(cacheAppPath);
|
|
27
|
+
await fse.copy(binaryPath, cacheAppPath);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Back up an app to cache, if it is not already cached
|
|
32
|
+
* @param {string} deviceId - The device identifier
|
|
33
|
+
* @param {string} bundleId - The app's bundle identifier
|
|
34
|
+
*/
|
|
35
|
+
async backup(deviceId, bundleId) {
|
|
36
|
+
const appContainerPath = await this.applesimutils.getAppContainer(deviceId, bundleId).catch((reason) => {
|
|
37
|
+
throw new DetoxRuntimeError({
|
|
38
|
+
message: `App with bundle ID '${bundleId}' is not installed on this device (${deviceId}). Please install the app first before attempting to reset its state.`,
|
|
39
|
+
hint: `To check apps installed on the device, use: xcrun simctl listapps ${deviceId}`,
|
|
40
|
+
debugInfo: reason,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (!await this.exists(deviceId, bundleId)) {
|
|
45
|
+
await this.add(deviceId, bundleId, appContainerPath);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Restore an app from cache
|
|
51
|
+
* @param {string} deviceId - The device identifier
|
|
52
|
+
* @param {string} bundleId - The app's bundle identifier
|
|
53
|
+
* @returns {Promise<void>}
|
|
54
|
+
*/
|
|
55
|
+
async restore(deviceId, bundleId) {
|
|
56
|
+
log.trace(`Restoring app (${bundleId}) from cache to device (${deviceId})`);
|
|
57
|
+
|
|
58
|
+
const cacheAppPath = this._getAppCachePath(deviceId, bundleId);
|
|
59
|
+
|
|
60
|
+
if (!await fse.exists(cacheAppPath)) {
|
|
61
|
+
throw new DetoxInternalError(`No backup found for bundle ID '${bundleId}' on device ${deviceId}. This should not happen unless you're calling SimulatorAppCache#restore() directly.`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await this.applesimutils.install(deviceId, cacheAppPath);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if an app is cached
|
|
69
|
+
* @param {string} deviceId - The device identifier
|
|
70
|
+
* @param {string} bundleId - The app's bundle identifier
|
|
71
|
+
* @returns {Promise<boolean>} True if the app is cached
|
|
72
|
+
*/
|
|
73
|
+
async exists(deviceId, bundleId) {
|
|
74
|
+
const cacheAppPath = this._getAppCachePath(deviceId, bundleId);
|
|
75
|
+
return await fse.exists(cacheAppPath);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Remove cache for a specific app
|
|
80
|
+
* @param {string} deviceId - The device identifier
|
|
81
|
+
* @param {string} bundleId - The app's bundle identifier
|
|
82
|
+
* @returns {Promise<void>}
|
|
83
|
+
*/
|
|
84
|
+
async remove(deviceId, bundleId) {
|
|
85
|
+
try {
|
|
86
|
+
const cacheAppPath = this._getAppCachePath(deviceId, bundleId);
|
|
87
|
+
if (await fse.remove(cacheAppPath)) {
|
|
88
|
+
log.trace({ path: cacheAppPath }, `Removed cached app (${bundleId}) for device (${deviceId})`);
|
|
89
|
+
}
|
|
90
|
+
} catch (err) {
|
|
91
|
+
log.warn({ err }, `Failed to remove app cache for ${bundleId} on device ${deviceId}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Clean up cache for a device
|
|
97
|
+
* @param {string} deviceId - The device identifier
|
|
98
|
+
* @returns {Promise<void>}
|
|
99
|
+
*/
|
|
100
|
+
async cleanup(deviceId) {
|
|
101
|
+
try {
|
|
102
|
+
const deviceCachePath = this._getDeviceCachePath(deviceId);
|
|
103
|
+
if (await fse.remove(deviceCachePath)) {
|
|
104
|
+
log.trace({ path: deviceCachePath }, `Cleaned up the app cache for device (${deviceId})`);
|
|
105
|
+
}
|
|
106
|
+
} catch (err) {
|
|
107
|
+
log.warn({ err }, `Failed to cleanup app cache for device ${deviceId}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.cleanDeviceIds.add(deviceId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Clean up cache for a device only once per instance.
|
|
115
|
+
* If the device has already been cleaned, this is a no-op.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} deviceId - The device identifier
|
|
118
|
+
* @returns {Promise<void>}
|
|
119
|
+
*/
|
|
120
|
+
async cleanupOnce(deviceId) {
|
|
121
|
+
if (!this.cleanDeviceIds.has(deviceId)) {
|
|
122
|
+
await this.cleanup(deviceId);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get the cache path for a specific app on a device
|
|
128
|
+
* @param {string} deviceId - The device identifier
|
|
129
|
+
* @param {string} bundleId - The app's bundle identifier
|
|
130
|
+
* @returns {string} The full path to the cached app
|
|
131
|
+
* @private
|
|
132
|
+
*/
|
|
133
|
+
_getAppCachePath(deviceId, bundleId) {
|
|
134
|
+
return path.join(this._getDeviceCachePath(deviceId), `${bundleId}.app`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get the device cache directory path
|
|
139
|
+
* @param {string} deviceId - The device identifier
|
|
140
|
+
* @returns {string} The device cache directory path
|
|
141
|
+
* @private
|
|
142
|
+
*/
|
|
143
|
+
_getDeviceCachePath(deviceId) {
|
|
144
|
+
return path.join(this.rootDir, deviceId);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = SimulatorAppCache;
|
|
@@ -31,6 +31,7 @@ class RuntimeDevice {
|
|
|
31
31
|
'pressBack',
|
|
32
32
|
'relaunchApp',
|
|
33
33
|
'reloadReactNative',
|
|
34
|
+
'resetAppState',
|
|
34
35
|
'resetContentAndSettings',
|
|
35
36
|
'resetStatusBar',
|
|
36
37
|
'reverseTcpPort',
|
|
@@ -119,7 +120,10 @@ class RuntimeDevice {
|
|
|
119
120
|
? params.newInstance
|
|
120
121
|
: this._processes[bundleId] == null;
|
|
121
122
|
|
|
122
|
-
if (params.
|
|
123
|
+
if (params.resetAppState) {
|
|
124
|
+
await this.terminateApp(bundleId);
|
|
125
|
+
await this.resetAppState(bundleId);
|
|
126
|
+
} else if (params.delete) {
|
|
123
127
|
await this.terminateApp(bundleId);
|
|
124
128
|
await this.uninstallApp();
|
|
125
129
|
await this.installApp();
|
|
@@ -261,6 +265,11 @@ class RuntimeDevice {
|
|
|
261
265
|
await this.deviceDriver.uninstallApp(_bundleId);
|
|
262
266
|
}
|
|
263
267
|
|
|
268
|
+
async resetAppState(...bundleIds) {
|
|
269
|
+
const _bundleIds = bundleIds.length > 0 ? bundleIds : [this._bundleId];
|
|
270
|
+
await this.deviceDriver.resetAppState(..._bundleIds);
|
|
271
|
+
}
|
|
272
|
+
|
|
264
273
|
async installUtilBinaries() {
|
|
265
274
|
const paths = this._deviceConfig.utilBinaryPaths;
|
|
266
275
|
if (paths) {
|
|
@@ -84,6 +84,12 @@ class AndroidDriver extends DeviceDriverBase {
|
|
|
84
84
|
await this.appUninstallHelper.uninstall(this.adbName, bundleId);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
async resetAppState(...bundleIds) {
|
|
88
|
+
for (const bundleId of bundleIds) {
|
|
89
|
+
await this.adb.clearAppData(this.adbName, bundleId);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
87
93
|
async installUtilBinaries(paths) {
|
|
88
94
|
for (const path of paths) {
|
|
89
95
|
const packageId = await this.getBundleIdFromBinary(path);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const SimulatorAppCache = require('../../../common/drivers/ios/tools/SimulatorAppCache');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AppStateResetFallback provides app state reset functionality for iOS simulators.
|
|
5
|
+
*
|
|
6
|
+
* This class serves as a fallback implementation because iOS does not provide a native
|
|
7
|
+
* way to reset an app's state (unlike Android's `pm clear` command). To achieve app
|
|
8
|
+
* state reset on iOS, this class implements a backup-uninstall-reinstall strategy:
|
|
9
|
+
*
|
|
10
|
+
* 1. Backs up the app installation to cache
|
|
11
|
+
* 2. Uninstalls the current app instance
|
|
12
|
+
* 3. Restores the app installation from cache
|
|
13
|
+
*
|
|
14
|
+
* This approach ensures the app is returned to a clean state without requiring
|
|
15
|
+
* a full app reinstallation from the original bundle, making it more friendly
|
|
16
|
+
* to local development flows.
|
|
17
|
+
*
|
|
18
|
+
* @class AppStateResetFallback
|
|
19
|
+
*/
|
|
20
|
+
class AppStateResetFallback {
|
|
21
|
+
/**
|
|
22
|
+
* Creates an instance of AppStateResetFallback.
|
|
23
|
+
* @param {Object} config
|
|
24
|
+
* @param {import('../../../common/drivers/ios/tools/AppleSimUtils')} config.applesimutils - AppleSimUtils instance
|
|
25
|
+
* @param {SimulatorAppCache} [config.appCache] - Optional SimulatorAppCache instance (for testing/mocking)
|
|
26
|
+
*/
|
|
27
|
+
constructor({ applesimutils, appCache }) {
|
|
28
|
+
this.applesimutils = applesimutils;
|
|
29
|
+
this.appCache = appCache ?? new SimulatorAppCache({ applesimutils });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resets the app state for multiple apps by backing them up and restoring them.
|
|
34
|
+
* This effectively clears the app state while preserving the app installation.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} udid - The device identifier
|
|
37
|
+
* @param {string[]} bundleIds - Array of app bundle identifiers to reset
|
|
38
|
+
* @returns {Promise<void>}
|
|
39
|
+
*/
|
|
40
|
+
async resetAppState(udid, bundleIds) {
|
|
41
|
+
for (const bundleId of bundleIds) {
|
|
42
|
+
await this.appCache.backup(udid, bundleId);
|
|
43
|
+
await this.applesimutils.uninstall(udid, bundleId);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const bundleId of bundleIds) {
|
|
47
|
+
await this.appCache.restore(udid, bundleId);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Ensures removal of cached app(s) after a potentially
|
|
53
|
+
* destructive action (such as install or uninstall),
|
|
54
|
+
* so {@link resetAppState} can't use a stale backup from cache.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} udid - The device identifier
|
|
57
|
+
* @param {string} [bundleId] - Optional bundle identifier for specific app invalidation
|
|
58
|
+
* @returns {Promise<void>}
|
|
59
|
+
*/
|
|
60
|
+
async invalidate(udid, bundleId) {
|
|
61
|
+
if (bundleId) {
|
|
62
|
+
await this.appCache.remove(udid, bundleId);
|
|
63
|
+
} else {
|
|
64
|
+
await this.appCache.cleanup(udid);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = AppStateResetFallback;
|
|
@@ -15,6 +15,7 @@ const log = require('../../../../utils/logger').child({ cat: 'device' });
|
|
|
15
15
|
const pressAnyKey = require('../../../../utils/pressAnyKey');
|
|
16
16
|
const traceInvocationCall = require('../../../../utils/traceInvocationCall').bind(null, log);
|
|
17
17
|
|
|
18
|
+
const AppStateResetFallback = require('./AppStateResetFallback');
|
|
18
19
|
const IosDriver = require('./IosDriver');
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -37,12 +38,15 @@ class SimulatorDriver extends IosDriver {
|
|
|
37
38
|
constructor(deps, { udid, type, bootArgs, headless }) {
|
|
38
39
|
super(deps);
|
|
39
40
|
|
|
41
|
+
this.getBundleIdFromBinary = _.memoize(this.getBundleIdFromBinary.bind(this));
|
|
42
|
+
|
|
40
43
|
this.udid = udid;
|
|
41
44
|
this._type = type;
|
|
42
45
|
this._bootArgs = bootArgs;
|
|
43
46
|
this._headless = headless;
|
|
44
47
|
this._deviceName = `${udid} (${this._type})`;
|
|
45
48
|
this._applesimutils = deps.applesimutils;
|
|
49
|
+
this._appStateResetFallback = new AppStateResetFallback({ applesimutils: this._applesimutils });
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
withAction(xcuitestRunner, action, traceDescription, ...params) {
|
|
@@ -80,13 +84,24 @@ class SimulatorDriver extends IosDriver {
|
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
async installApp(binaryPath) {
|
|
83
|
-
|
|
87
|
+
const absoluteBinaryPath = getAbsoluteBinaryPath(binaryPath);
|
|
88
|
+
await this._applesimutils.install(this.udid, absoluteBinaryPath);
|
|
89
|
+
|
|
90
|
+
const bundleId = await this.getBundleIdFromBinary(binaryPath);
|
|
91
|
+
await this._appStateResetFallback.invalidate(this.udid, bundleId);
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
async uninstallApp(bundleId) {
|
|
87
95
|
const { udid } = this;
|
|
88
96
|
await this.emitter.emit('beforeUninstallApp', { deviceId: udid, bundleId });
|
|
89
97
|
await this._applesimutils.uninstall(udid, bundleId);
|
|
98
|
+
await this._appStateResetFallback.invalidate(udid, bundleId);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async resetAppState(...bundleIds) {
|
|
102
|
+
const { udid } = this;
|
|
103
|
+
const _bundleIds = bundleIds.length > 0 ? bundleIds : [this._bundleId];
|
|
104
|
+
await this._appStateResetFallback.resetAppState(udid, _bundleIds);
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
async launchApp(bundleId, launchArgs, languageAndLocale) {
|
|
@@ -186,6 +201,7 @@ class SimulatorDriver extends IosDriver {
|
|
|
186
201
|
await this._applesimutils.shutdown(this.udid);
|
|
187
202
|
await this.emitter.emit('shutdownDevice', { deviceId: this.udid });
|
|
188
203
|
await this._applesimutils.resetContentAndSettings(this.udid);
|
|
204
|
+
await this._appStateResetFallback.invalidate(this.udid);
|
|
189
205
|
await this._applesimutils.boot(this.udid, this._bootArgs, this._headless);
|
|
190
206
|
await this.emitter.emit('bootDevice', { deviceId: this.udid });
|
|
191
207
|
}
|
|
@@ -41,7 +41,8 @@ function constructSafeFilename(prefix = '', trimmable = '', suffix = '') {
|
|
|
41
41
|
|
|
42
42
|
const trimmed = trimmable.slice(-MAX_FILE_LENGTH + nonTrimmableLength);
|
|
43
43
|
const unsafe = prefix + trimmed + suffix;
|
|
44
|
-
const sanitized = sanitize(unsafe, sanitizeOptions)
|
|
44
|
+
const sanitized = sanitize(unsafe, sanitizeOptions)
|
|
45
|
+
.replace(/\$/g, sanitizeOptions.replacement);
|
|
45
46
|
|
|
46
47
|
return sanitized;
|
|
47
48
|
}
|
package/src/utils/environment.js
CHANGED
|
@@ -20,6 +20,7 @@ function which(executable, path) {
|
|
|
20
20
|
const DETOX_LIBRARY_ROOT_PATH = path.join(appdatapath.appDataPath(), 'Detox');
|
|
21
21
|
const MISSING_SDK_ERROR = `$ANDROID_SDK_ROOT is not defined, set the path to the SDK installation directory into $ANDROID_SDK_ROOT,
|
|
22
22
|
Go to https://developer.android.com/studio/command-line/variables.html for more details`;
|
|
23
|
+
const DETOX_APPS_CACHE_PATH = path.join(DETOX_LIBRARY_ROOT_PATH, 'apps-cache');
|
|
23
24
|
const DETOX_LOCK_FILE_PATH = path.join(DETOX_LIBRARY_ROOT_PATH, 'global-context.json');
|
|
24
25
|
const DEVICE_REGISTRY_PATH = path.join(DETOX_LIBRARY_ROOT_PATH, 'device.registry.json');
|
|
25
26
|
const LAST_FAILED_TESTS_PATH = path.join(DETOX_LIBRARY_ROOT_PATH, 'last-failed.txt');
|
|
@@ -217,6 +218,16 @@ function getDetoxLibraryRootPath() {
|
|
|
217
218
|
return DETOX_LIBRARY_ROOT_PATH;
|
|
218
219
|
}
|
|
219
220
|
|
|
221
|
+
function getDetoxAppsCachePath(udid, bundleId) {
|
|
222
|
+
if (udid && bundleId) {
|
|
223
|
+
return path.join(DETOX_APPS_CACHE_PATH, udid, bundleId);
|
|
224
|
+
} else if (udid) {
|
|
225
|
+
return path.join(DETOX_APPS_CACHE_PATH, udid);
|
|
226
|
+
} else {
|
|
227
|
+
return DETOX_APPS_CACHE_PATH;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
220
231
|
function getDetoxLockFilePath() {
|
|
221
232
|
return DETOX_LOCK_FILE_PATH;
|
|
222
233
|
}
|
|
@@ -249,6 +260,7 @@ module.exports = {
|
|
|
249
260
|
getXCUITestRunnerPath,
|
|
250
261
|
getAndroidSDKPath,
|
|
251
262
|
getAndroidEmulatorPath,
|
|
263
|
+
getDetoxAppsCachePath,
|
|
252
264
|
getDetoxLibraryRootPath,
|
|
253
265
|
getDetoxLockFilePath,
|
|
254
266
|
getDeviceRegistryPath,
|
package/src/utils/fsext.js
CHANGED
|
@@ -19,8 +19,21 @@ async function getDirectories (rootPath) {
|
|
|
19
19
|
return dirs.sort();
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
async function remove(filePath) {
|
|
23
|
+
if (await fs.exists(filePath)) {
|
|
24
|
+
await fs.remove(filePath);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
module.exports = {
|
|
32
|
+
copy: fs.copy,
|
|
33
|
+
ensureDir: fs.ensureDir,
|
|
34
|
+
exists: fs.exists,
|
|
23
35
|
getDirectories,
|
|
24
36
|
isDirEmptySync,
|
|
25
37
|
readdirSync: fs.readdirSync,
|
|
38
|
+
remove,
|
|
26
39
|
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
78e6a14fb3535afbf9e26a44532bad26
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
6608ad7ef3136c6a6a96d8c0196c015266fad90b
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
8cad5bb8dcadcb1489d21e9eb02b4e404ba142611cac6c9e0b5b870eabd02644
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
420217e7885b9b62f1345d4a75fa684508898a054f188e60816f6cdbfc95bc8ca4af71d09f60d81246fb723f551b52b211765c59f9edc10394b508387f1293d1
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
92af4eb56025da6478dcf052cc974ed9
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
caa2f11c005b65f021c582cf67539513db45db0c
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
69db0de12353910d42ff6cfb919ff43ddfb3225f762463bc326f31741f20c118
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
aa19d01f92ff6aa9724f8eba5d9b705e0d549105a08f11b643e5fcd530d292ee9be73a354a447d2d1377ed80ebe07cf7da405da06c6a382ea1127b69e105b543
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
03211aed18c8bbd28357bdbbc15b17ed
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
3d506946f8dd584cd3f5f849f81ce8badba26ff1
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2b73ef91d19c21879c9f7562235484c19750e46011ba430b2255a50a6c23bc90
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
3cf68662b57730aa809c02a55762284adaae491b7ff7b96781630e7e2829c530d956f099d12bd5a27d45a72d995befd5b127c4d966ddfbd322dcf43715316d3d
|