detox 20.26.2 → 20.27.0
Sign up to get free protection for your applications and to get access to all the features.
- package/Detox-android/com/wix/detox/{20.26.2/detox-20.26.2-sources.jar → 20.27.0/detox-20.27.0-sources.jar} +0 -0
- package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0-sources.jar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0-sources.jar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0-sources.jar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0-sources.jar.sha512 +1 -0
- package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.aar +0 -0
- package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.aar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.aar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.aar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.aar.sha512 +1 -0
- package/Detox-android/com/wix/detox/{20.26.2/detox-20.26.2.pom → 20.27.0/detox-20.27.0.pom} +1 -1
- package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.pom.md5 +1 -0
- package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.pom.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.pom.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.27.0/detox-20.27.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-android/com/wix/detox-legacy/{20.26.2/detox-legacy-20.26.2-sources.jar → 20.27.0/detox-legacy-20.27.0-sources.jar} +0 -0
- package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0-sources.jar.md5 +1 -0
- package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0-sources.jar.sha1 +1 -0
- package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0-sources.jar.sha256 +1 -0
- package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0-sources.jar.sha512 +1 -0
- package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.aar +0 -0
- package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.aar.md5 +1 -0
- package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.aar.sha1 +1 -0
- package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.aar.sha256 +1 -0
- package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.aar.sha512 +1 -0
- package/Detox-android/com/wix/detox-legacy/{20.26.2/detox-legacy-20.26.2.pom → 20.27.0/detox-legacy-20.27.0.pom} +1 -1
- package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.pom.md5 +1 -0
- package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.pom.sha1 +1 -0
- package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.pom.sha256 +1 -0
- package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.pom.sha512 +1 -0
- package/Detox-android/com/wix/detox-legacy/maven-metadata.xml +4 -4
- package/Detox-android/com/wix/detox-legacy/maven-metadata.xml.md5 +1 -1
- package/Detox-android/com/wix/detox-legacy/maven-metadata.xml.sha1 +1 -1
- package/Detox-android/com/wix/detox-legacy/maven-metadata.xml.sha256 +1 -1
- package/Detox-android/com/wix/detox-legacy/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/proguard-rules-app.pro +3 -0
- package/android/detox/src/full/java/com/wix/detox/espresso/hierarchy/ViewHierarchyGenerator.kt +86 -21
- package/detox.d.ts +15 -0
- package/globals.d.ts +2 -0
- package/jest.config.js +1 -0
- package/package.json +3 -3
- package/src/DetoxWorker.js +9 -0
- package/src/copilot/DetoxCopilot.js +33 -0
- package/src/copilot/detoxCopilotFrameworkDriver.js +320 -0
- package/src/realms/DetoxContext.js +2 -0
- package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2-sources.jar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2-sources.jar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2-sources.jar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2-sources.jar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.aar +0 -0
- package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.aar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.aar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.aar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.aar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.pom.md5 +0 -1
- package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.pom.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.pom.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.pom.sha512 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2-sources.jar.md5 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2-sources.jar.sha1 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2-sources.jar.sha256 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2-sources.jar.sha512 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.aar +0 -0
- package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.aar.md5 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.aar.sha1 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.aar.sha256 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.aar.sha512 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.pom.md5 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.pom.sha1 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.pom.sha256 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.pom.sha512 +0 -1
- package/src/copilot/CopilotDriver.js +0 -140
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
79e88d1742382204b44c103b700a3955
|
@@ -0,0 +1 @@
|
|
1
|
+
00febe099b40a8cfbfe63256e7dfcbfdc2ef1e01
|
@@ -0,0 +1 @@
|
|
1
|
+
624acc61a54549d741cadf827c87a952ea65f4d71342b21c0bc78e4dba8f0bab
|
@@ -0,0 +1 @@
|
|
1
|
+
84a8966526c12615b67ddb97618cfba60275eab2dbc6babe2fa8828f681d6cdc838a2f43f5fe34e3190390d4aff46fa583b27c512ab0c7881d8e5e75f74428f8
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
14161501d9cf4606ee49296e14c047e4
|
@@ -0,0 +1 @@
|
|
1
|
+
38e956edb72453c00c00ab752443f394389737bf
|
@@ -0,0 +1 @@
|
|
1
|
+
0a76b1d5efeea5e83fa9e92e75bc29913b9e7db28b7b30652638966506299f90
|
@@ -0,0 +1 @@
|
|
1
|
+
f3b3510a065744da054200cbcfb62549d469aa27068939879f07c64421cfef223aa34ed6346b6e9c5839a1a42cdc76699b16ad4395545abfb01cd611381df408
|
@@ -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.27.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
|
+
5c6bf656a59d5647008990b67f1b4e81
|
@@ -0,0 +1 @@
|
|
1
|
+
85d02ca3eda8e1ce5c153b48ac70b616f22463ba
|
@@ -0,0 +1 @@
|
|
1
|
+
9c73feb5baf2a9735096e31d94461bbe307d0ba1d1f1d0f0f2648ecf5ab249e4
|
@@ -0,0 +1 @@
|
|
1
|
+
ea0529ec2403df8b8067c11470980b0b6cde32394df212b2f0ce32cbf7df7c744b0a0c4af342362f5a8429b4cb177fd25035b2eb55f887d5e7148d579a23312d
|
@@ -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.27.0</latest>
|
7
|
+
<release>20.27.0</release>
|
8
8
|
<versions>
|
9
|
-
<version>20.
|
9
|
+
<version>20.27.0</version>
|
10
10
|
</versions>
|
11
|
-
<lastUpdated>
|
11
|
+
<lastUpdated>20240922114824</lastUpdated>
|
12
12
|
</versioning>
|
13
13
|
</metadata>
|
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
166e0211e3c8ebd2b2b6605fb99d6a07
|
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
d513122237af950d971097b9847eff9e6823c2d7
|
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
086f6a6208ab027feb7a6cccc199bba56fc325e21f8ac890f16d2c8eaebfa385
|
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
75146105b8ffcd9f6c54ccecab07fe38ec76f37368521d6d3b4d7c1f82115a9b8d22a9377de901c860c411bc47a275225e312c5e1c6f13ae36a5ad39ac3bd1bb
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
22d037b8bca358792d7de28a6a963bba
|
@@ -0,0 +1 @@
|
|
1
|
+
d1b241b7df98a2c353cc824f73a659d0dc57a3e6
|
@@ -0,0 +1 @@
|
|
1
|
+
50afa0349c83682fc243fae98cb13e6565a4e2124cdba82715d53892c96fae90
|
@@ -0,0 +1 @@
|
|
1
|
+
0d774c88ba641c7d463f19ccbf61fcf47db714f2435bd1cecdf96ef7f772bd26513bf4a44b2e65c170b63978c5d8ec2026bd9c7461550176e544361b4591b9af
|
@@ -0,0 +1 @@
|
|
1
|
+
02d974914d78e64b365486e3369ae1e1
|
@@ -0,0 +1 @@
|
|
1
|
+
173b898bfecd41c34b0ad24d7e1996c8d3b0621b
|
@@ -0,0 +1 @@
|
|
1
|
+
8a30f740e17ad80816f17ceb61b354d7da12f651764e040ddf85b1c3609992a3
|
@@ -0,0 +1 @@
|
|
1
|
+
90f61852b868773b3548daf4c04793cc0285df750cbfb8947375ec81f5882469cf6e6132295e3b115b27d61733c1bb5f4954a14e7246aa8da19e97bd28191c5f
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<modelVersion>4.0.0</modelVersion>
|
4
4
|
<groupId>com.wix</groupId>
|
5
5
|
<artifactId>detox-legacy</artifactId>
|
6
|
-
<version>20.
|
6
|
+
<version>20.27.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
|
+
01a5760f91440cf467f45061196c119b
|
@@ -0,0 +1 @@
|
|
1
|
+
7ed4037d9d36d59e2f3553f356f6a8493de47557
|
@@ -0,0 +1 @@
|
|
1
|
+
adf8a669ed45ec95add12cdaa62aaa21b2f99cce73a7681e8ec0652614782f9c
|
@@ -0,0 +1 @@
|
|
1
|
+
62aa84c10e18fa5db598cb5d88d197c09b4502648f946fe1ae18571e63a7292c8d48532d897fb866f9dd01ec261379bf2c5334fcdb56795f3df13dc3b3115b33
|
@@ -3,11 +3,11 @@
|
|
3
3
|
<groupId>com.wix</groupId>
|
4
4
|
<artifactId>detox-legacy</artifactId>
|
5
5
|
<versioning>
|
6
|
-
<latest>20.
|
7
|
-
<release>20.
|
6
|
+
<latest>20.27.0</latest>
|
7
|
+
<release>20.27.0</release>
|
8
8
|
<versions>
|
9
|
-
<version>20.
|
9
|
+
<version>20.27.0</version>
|
10
10
|
</versions>
|
11
|
-
<lastUpdated>
|
11
|
+
<lastUpdated>20240922114842</lastUpdated>
|
12
12
|
</versioning>
|
13
13
|
</metadata>
|
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
63612a5f5bdbb48d5e1f3ff4e5cd200f
|
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
c52523453872dead27e35540c753721d94d9a9cd
|
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
9158a9b035b7a341f654d02b939a8140531208e0b2814779156779c40390039a
|
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
95157a82c725185c7ef2f908de02e89beebd86595763f7218596b7982dad2ad742900c52088e0b2276ddf108443e30a334a46e95e5f830fb699e566e5b3d29a7
|
package/Detox-ios-framework.tbz
CHANGED
Binary file
|
package/Detox-ios-src.tbz
CHANGED
Binary file
|
package/Detox-ios-xcuitest.tbz
CHANGED
Binary file
|
@@ -16,6 +16,9 @@
|
|
16
16
|
|
17
17
|
-keep class kotlin.reflect.** { *; }
|
18
18
|
-keep class kotlin.coroutines.CoroutineDispatcher { *; }
|
19
|
+
-keep class kotlin.coroutines.CoroutineScope { *; }
|
20
|
+
-keep class kotlin.coroutines.CoroutineContext { *; }
|
21
|
+
-keep class kotlinx.coroutines.BuildersKt { *; }
|
19
22
|
-keep class kotlin.jvm.** { *; }
|
20
23
|
-keep class kotlin.collections.** { *; }
|
21
24
|
-keep class kotlin.text.** { *; }
|
package/android/detox/src/full/java/com/wix/detox/espresso/hierarchy/ViewHierarchyGenerator.kt
CHANGED
@@ -3,19 +3,53 @@ package com.wix.detox.espresso.hierarchy
|
|
3
3
|
import android.util.Xml
|
4
4
|
import android.view.View
|
5
5
|
import android.view.ViewGroup
|
6
|
+
import android.webkit.WebView
|
6
7
|
import android.widget.TextView
|
7
8
|
import com.wix.detox.reactnative.ui.getAccessibilityLabel
|
9
|
+
import kotlinx.coroutines.Dispatchers
|
10
|
+
import kotlinx.coroutines.runBlocking
|
11
|
+
import kotlinx.coroutines.suspendCancellableCoroutine
|
12
|
+
import kotlinx.coroutines.withContext
|
8
13
|
import org.xmlpull.v1.XmlSerializer
|
9
14
|
import java.io.StringWriter
|
15
|
+
import kotlin.coroutines.resume
|
16
|
+
|
17
|
+
|
18
|
+
private const val GET_HTML_SCRIPT = """
|
19
|
+
const blacklistedTags = ['script', 'style', 'head', 'meta'];
|
20
|
+
const blackListedTagsSelector = blacklistedTags.join(',');
|
21
|
+
|
22
|
+
(function() {
|
23
|
+
// Clone the entire document
|
24
|
+
var clonedDoc = document.documentElement.cloneNode(true);
|
25
|
+
|
26
|
+
// Remove all <script> and <style> tags from the cloned document
|
27
|
+
var scripts = clonedDoc.querySelectorAll(blackListedTagsSelector);
|
28
|
+
scripts.forEach(function(script) {
|
29
|
+
script.remove();
|
30
|
+
});
|
31
|
+
|
32
|
+
// Create an instance of XMLSerializer
|
33
|
+
var serializer = new XMLSerializer();
|
34
|
+
|
35
|
+
// Serialize the cloned DOM to a string
|
36
|
+
var serializedHtml = serializer.serializeToString(clonedDoc);
|
37
|
+
|
38
|
+
// Return the serialized HTML as a string
|
39
|
+
return serializedHtml;
|
40
|
+
})();
|
41
|
+
"""
|
10
42
|
|
11
43
|
object ViewHierarchyGenerator {
|
12
44
|
@JvmStatic
|
13
45
|
fun generateXml(shouldInjectTestIds: Boolean): String {
|
14
|
-
|
15
|
-
|
46
|
+
return runBlocking {
|
47
|
+
val rootViews = RootViewsHelper.getRootViews()
|
48
|
+
generateXmlFromViews(rootViews, shouldInjectTestIds)
|
49
|
+
}
|
16
50
|
}
|
17
51
|
|
18
|
-
private fun generateXmlFromViews(rootViews: List<View?>?, shouldInjectTestIds: Boolean): String {
|
52
|
+
private suspend fun generateXmlFromViews(rootViews: List<View?>?, shouldInjectTestIds: Boolean): String {
|
19
53
|
return StringWriter().use { writer ->
|
20
54
|
val serializer = Xml.newSerializer().apply {
|
21
55
|
setOutput(writer)
|
@@ -39,7 +73,7 @@ object ViewHierarchyGenerator {
|
|
39
73
|
}
|
40
74
|
}
|
41
75
|
|
42
|
-
private fun serializeViewHierarchy(
|
76
|
+
private suspend fun serializeViewHierarchy(
|
43
77
|
view: View,
|
44
78
|
serializer: XmlSerializer,
|
45
79
|
shouldInjectTestIds: Boolean,
|
@@ -48,20 +82,59 @@ object ViewHierarchyGenerator {
|
|
48
82
|
serializer.startTag("", view.javaClass.simpleName)
|
49
83
|
serializeViewAttributes(view, serializer, shouldInjectTestIds, indexPath)
|
50
84
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
view.getChildAt(i),
|
55
|
-
serializer,
|
56
|
-
shouldInjectTestIds,
|
57
|
-
indexPath + i
|
58
|
-
)
|
59
|
-
}
|
85
|
+
when (view) {
|
86
|
+
is WebView -> serializeWebView(view, serializer)
|
87
|
+
is ViewGroup -> serializeViewGroupChildren(view, serializer, shouldInjectTestIds, indexPath)
|
60
88
|
}
|
61
89
|
|
62
90
|
serializer.endTag("", view.javaClass.simpleName)
|
63
91
|
}
|
64
92
|
|
93
|
+
private suspend fun serializeWebView(
|
94
|
+
webView: WebView,
|
95
|
+
serializer: XmlSerializer,
|
96
|
+
) {
|
97
|
+
val html = getWebViewHtml(webView)
|
98
|
+
serializer.cdsect(html)
|
99
|
+
}
|
100
|
+
|
101
|
+
private suspend fun getWebViewHtml(webView: WebView): String = withContext(Dispatchers.Main) {
|
102
|
+
suspendCancellableCoroutine { cancellableContinuation ->
|
103
|
+
webView.evaluateJavascript(GET_HTML_SCRIPT) { html ->
|
104
|
+
cancellableContinuation.resume(html.unescapeUnicodeString())
|
105
|
+
}
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
private fun String.unescapeUnicodeString(): String {
|
110
|
+
// Replace all Unicode escape sequences (e.g., \u003C -> <)
|
111
|
+
return this
|
112
|
+
.replace("\\u003C", "<")
|
113
|
+
.replace("\\u003E", ">")
|
114
|
+
.replace("\\u0022", "\"")
|
115
|
+
.replace("\\u0027", "'")
|
116
|
+
.replace("\\u0026", "&")
|
117
|
+
.replace("\\u003D", "=")
|
118
|
+
.replace("\\u002F", "/")
|
119
|
+
.replace("\\n", "\n")
|
120
|
+
}
|
121
|
+
|
122
|
+
private suspend fun serializeViewGroupChildren(
|
123
|
+
view: ViewGroup,
|
124
|
+
serializer: XmlSerializer,
|
125
|
+
shouldInjectTestIds: Boolean,
|
126
|
+
indexPath: List<Int>
|
127
|
+
) {
|
128
|
+
for (i in 0 until view.childCount) {
|
129
|
+
serializeViewHierarchy(
|
130
|
+
view.getChildAt(i),
|
131
|
+
serializer,
|
132
|
+
shouldInjectTestIds,
|
133
|
+
indexPath + i
|
134
|
+
)
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
65
138
|
private fun serializeViewAttributes(
|
66
139
|
view: View,
|
67
140
|
serializer: XmlSerializer,
|
@@ -79,14 +152,6 @@ object ViewHierarchyGenerator {
|
|
79
152
|
"label" to (view.getAccessibilityLabel()?.toString() ?: "")
|
80
153
|
)
|
81
154
|
|
82
|
-
view.id.takeIf { it != View.NO_ID }?.let {
|
83
|
-
attributes["id"] = try {
|
84
|
-
view.resources.getResourceName(it)
|
85
|
-
} catch (e: Exception) {
|
86
|
-
it.toString()
|
87
|
-
}
|
88
|
-
}
|
89
|
-
|
90
155
|
val location = IntArray(2).apply { view.getLocationInWindow(this) }
|
91
156
|
attributes["x"] = location[0].toString()
|
92
157
|
attributes["y"] = location[1].toString()
|
package/detox.d.ts
CHANGED
@@ -9,6 +9,7 @@
|
|
9
9
|
// * Dor Ben Baruch <https://github.com/Dor256>
|
10
10
|
|
11
11
|
import { BunyanDebugStreamOptions } from 'bunyan-debug-stream';
|
12
|
+
import { CopilotFacade, PromptHandler } from "detox-copilot";
|
12
13
|
|
13
14
|
declare global {
|
14
15
|
namespace Detox {
|
@@ -445,6 +446,8 @@ declare global {
|
|
445
446
|
|
446
447
|
readonly system: SystemFacade;
|
447
448
|
|
449
|
+
readonly copilot: DetoxCopilotFacade;
|
450
|
+
|
448
451
|
readonly DetoxConstants: {
|
449
452
|
userNotificationTriggers: {
|
450
453
|
push: 'push';
|
@@ -1287,6 +1290,18 @@ declare global {
|
|
1287
1290
|
element(systemMatcher: SystemMatcher): IndexableSystemElement;
|
1288
1291
|
}
|
1289
1292
|
|
1293
|
+
interface DetoxCopilotFacade extends Pick<CopilotFacade, "perform"> {
|
1294
|
+
/**
|
1295
|
+
* Initializes the Copilot with the given prompt handler.
|
1296
|
+
* Must be called before any other Copilot methods.
|
1297
|
+
* @note Copilot APIs are still in experimental phase and are subject to changes in the near future.
|
1298
|
+
* @param promptHandler The prompt handler to use.
|
1299
|
+
*/
|
1300
|
+
init: (promptHandler: DetoxCopilotPromptHandler) => void;
|
1301
|
+
}
|
1302
|
+
|
1303
|
+
interface DetoxCopilotPromptHandler extends PromptHandler {}
|
1304
|
+
|
1290
1305
|
interface IndexableSystemElement extends SystemElement {
|
1291
1306
|
/**
|
1292
1307
|
* Choose from multiple elements matching the same matcher using index
|
package/globals.d.ts
CHANGED
@@ -9,6 +9,7 @@ declare global {
|
|
9
9
|
const by: Detox.DetoxExportWrapper['by'];
|
10
10
|
const web: Detox.DetoxExportWrapper['web'];
|
11
11
|
const system: Detox.DetoxExportWrapper['system'];
|
12
|
+
const copilot: Detox.DetoxExportWrapper['copilot'];
|
12
13
|
|
13
14
|
namespace NodeJS {
|
14
15
|
interface Global {
|
@@ -20,6 +21,7 @@ declare global {
|
|
20
21
|
by: Detox.DetoxExportWrapper['by'];
|
21
22
|
web: Detox.DetoxExportWrapper['web'];
|
22
23
|
system: Detox.DetoxExportWrapper['system'];
|
24
|
+
copilot: Detox.DetoxExportWrapper['copilot'];
|
23
25
|
}
|
24
26
|
}
|
25
27
|
}
|
package/jest.config.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.27.0",
|
5
5
|
"bin": {
|
6
6
|
"detox": "local-cli/cli.js"
|
7
7
|
},
|
@@ -71,7 +71,7 @@
|
|
71
71
|
"caf": "^15.0.1",
|
72
72
|
"chalk": "^4.0.0",
|
73
73
|
"child-process-promise": "^2.2.0",
|
74
|
-
"detox-copilot": "^0.0.
|
74
|
+
"detox-copilot": "^0.0.5",
|
75
75
|
"execa": "^5.1.1",
|
76
76
|
"find-up": "^5.0.0",
|
77
77
|
"fs-extra": "^11.0.0",
|
@@ -116,5 +116,5 @@
|
|
116
116
|
"browserslist": [
|
117
117
|
"node 14"
|
118
118
|
],
|
119
|
-
"gitHead": "
|
119
|
+
"gitHead": "17d3f122836a7e5fe6861e2827ecb00556e69ff8"
|
120
120
|
}
|
package/src/DetoxWorker.js
CHANGED
@@ -2,6 +2,7 @@ const CAF = require('caf');
|
|
2
2
|
const _ = require('lodash');
|
3
3
|
|
4
4
|
const Client = require('./client/Client');
|
5
|
+
const DetoxCopilot = require('./copilot/DetoxCopilot');
|
5
6
|
const environmentFactory = require('./environmentFactory');
|
6
7
|
const { DetoxRuntimeErrorComposer } = require('./errors');
|
7
8
|
const { InvocationManager } = require('./invoke');
|
@@ -58,6 +59,8 @@ class DetoxWorker {
|
|
58
59
|
this.by = null;
|
59
60
|
/** @type {Detox.WebFacade} */
|
60
61
|
this.web = null;
|
62
|
+
/** @type {Detox.DetoxCopilotFacade} */
|
63
|
+
this.copilot = null;
|
61
64
|
|
62
65
|
this._deviceCookie = null;
|
63
66
|
|
@@ -121,6 +124,8 @@ class DetoxWorker {
|
|
121
124
|
runtimeDeviceFactory,
|
122
125
|
} = environmentFactory.createFactories(deviceConfig);
|
123
126
|
|
127
|
+
this.copilot = new DetoxCopilot();
|
128
|
+
|
124
129
|
const envValidator = envValidatorFactory.createValidator();
|
125
130
|
yield envValidator.validate();
|
126
131
|
|
@@ -157,6 +162,7 @@ class DetoxWorker {
|
|
157
162
|
const injectedGlobals = {
|
158
163
|
...matchers,
|
159
164
|
device: this.device,
|
165
|
+
copilot: this.copilot,
|
160
166
|
detox: this,
|
161
167
|
};
|
162
168
|
|
@@ -219,6 +225,9 @@ class DetoxWorker {
|
|
219
225
|
};
|
220
226
|
|
221
227
|
onTestStart = function* (_signal, testSummary) {
|
228
|
+
// Copilot is reset before each test to ensure a clean state
|
229
|
+
this.copilot.resetIfNeeded();
|
230
|
+
|
222
231
|
this._validateTestSummary('beforeEach', testSummary);
|
223
232
|
|
224
233
|
yield this._dumpUnhandledErrorsIfAny({
|
@@ -0,0 +1,33 @@
|
|
1
|
+
const copilot = require('detox-copilot').default;
|
2
|
+
|
3
|
+
const detoxCopilotFrameworkDriver = require('./detoxCopilotFrameworkDriver');
|
4
|
+
|
5
|
+
class DetoxCopilot {
|
6
|
+
constructor() {
|
7
|
+
this.isInitialized = false;
|
8
|
+
}
|
9
|
+
|
10
|
+
init(promptHandler) {
|
11
|
+
copilot.init({
|
12
|
+
frameworkDriver: detoxCopilotFrameworkDriver,
|
13
|
+
promptHandler: promptHandler
|
14
|
+
});
|
15
|
+
|
16
|
+
this.isInitialized = true;
|
17
|
+
}
|
18
|
+
|
19
|
+
resetIfNeeded() {
|
20
|
+
if (!this.isInitialized) {
|
21
|
+
// Copilot is not initialized, nothing to reset
|
22
|
+
return;
|
23
|
+
}
|
24
|
+
|
25
|
+
copilot.reset();
|
26
|
+
}
|
27
|
+
|
28
|
+
perform(intent) {
|
29
|
+
return copilot.perform(intent);
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
module.exports = DetoxCopilot;
|
@@ -0,0 +1,320 @@
|
|
1
|
+
// eslint-disable-next-line node/no-extraneous-require
|
2
|
+
const jestExpect = require('expect').default;
|
3
|
+
|
4
|
+
const detox = require('../..');
|
5
|
+
|
6
|
+
const detoxCopilotFrameworkDriver = {
|
7
|
+
apiCatalog: {
|
8
|
+
context: { ...detox, jestExpect },
|
9
|
+
categories: [
|
10
|
+
{
|
11
|
+
title: 'Matchers',
|
12
|
+
items: [
|
13
|
+
{
|
14
|
+
signature: 'by.id(id: string)',
|
15
|
+
description: 'Matches elements by their test ID.',
|
16
|
+
example: "element(by.id('loginButton'))",
|
17
|
+
guidelines: ['Always use test-ids (accessibility identifiers) from the UI hierarchy to identify elements.'],
|
18
|
+
},
|
19
|
+
{
|
20
|
+
signature: 'by.text(text: string)',
|
21
|
+
description: 'Matches elements by their text.',
|
22
|
+
example: "element(by.text('Login'))",
|
23
|
+
guidelines: ['Avoid using text matchers when possible; prefer test-ids.'],
|
24
|
+
},
|
25
|
+
{
|
26
|
+
signature: 'by.type(type: string)',
|
27
|
+
description: 'Matches elements by their type.',
|
28
|
+
example: "element(by.type('RCTTextInput'))",
|
29
|
+
guidelines: ['Use type matchers as a last resort; prefer test-ids.'],
|
30
|
+
},
|
31
|
+
{
|
32
|
+
signature: 'atIndex(index: number)',
|
33
|
+
description: 'Selects the element at the specified index from a set of matched elements.',
|
34
|
+
example: "element(by.id('listItem')).atIndex(2)",
|
35
|
+
guidelines: ['Use `atIndex` when multiple elements match the same matcher to select a specific one by index.'],
|
36
|
+
},
|
37
|
+
],
|
38
|
+
},
|
39
|
+
{
|
40
|
+
title: 'Actions',
|
41
|
+
items: [
|
42
|
+
{
|
43
|
+
signature: 'tap(point?: Point2D)',
|
44
|
+
description: 'Simulates tap on an element.',
|
45
|
+
example: "await element(by.id('loginButton')).tap();",
|
46
|
+
guidelines: ["Use `element(by.id('testID'))` to locate elements."],
|
47
|
+
},
|
48
|
+
{
|
49
|
+
signature: 'longPress(point?: Point2D, duration?: number)',
|
50
|
+
description: 'Simulates long press on an element.',
|
51
|
+
example: "await element(by.id('menuItem')).longPress();",
|
52
|
+
guidelines: [
|
53
|
+
'If the target element is not accessible, interact with its container or the most relevant parent element.',
|
54
|
+
'Long-press should be called with the relevant params only, e.g. `longPress(2000)`, `longPress({ x: 100, y: 200 })` or `longPress({ x: 100, y: 200 }, 2000)`.',
|
55
|
+
],
|
56
|
+
},
|
57
|
+
{
|
58
|
+
signature: 'multiTap(times: number)',
|
59
|
+
description: 'Simulates multiple taps on an element.',
|
60
|
+
example: "await element(by.id('tappable')).multiTap(3);",
|
61
|
+
guidelines: ['All taps are applied as part of the same gesture.'],
|
62
|
+
},
|
63
|
+
{
|
64
|
+
signature: 'typeText(text: string)',
|
65
|
+
description: 'Types text into a text field.',
|
66
|
+
example: "await element(by.id('usernameInput')).typeText('myusername');",
|
67
|
+
guidelines: ['Typing can only be done on text field elements.'],
|
68
|
+
},
|
69
|
+
{
|
70
|
+
signature: 'replaceText(text: string)',
|
71
|
+
description: 'Replaces text in a text field.',
|
72
|
+
example: "await element(by.id('textField')).replaceText('new text');",
|
73
|
+
guidelines: ['Faster than `typeText()`, but may not trigger all text input callbacks.'],
|
74
|
+
},
|
75
|
+
{
|
76
|
+
signature: 'clearText()',
|
77
|
+
description: 'Clears text from a text field.',
|
78
|
+
example: "await element(by.id('textField')).clearText();",
|
79
|
+
guidelines: ['Use this to clear text from input fields.'],
|
80
|
+
},
|
81
|
+
{
|
82
|
+
signature: 'tapReturnKey()',
|
83
|
+
description: 'Simulates tapping the return key on the keyboard while the element is focused.',
|
84
|
+
example: "await element(by.id('textField')).tapReturnKey();",
|
85
|
+
guidelines: ['Use this to simulate pressing the return key while typing into a text input field.'],
|
86
|
+
},
|
87
|
+
{
|
88
|
+
signature: 'tapBackspaceKey()',
|
89
|
+
description: 'Simulates tapping the backspace key on the keyboard while the element is focused.',
|
90
|
+
example: "await element(by.id('textField')).tapBackspaceKey();",
|
91
|
+
guidelines: ['Use this to simulate deleting text by pressing the backspace key in a text input field.'],
|
92
|
+
},
|
93
|
+
{
|
94
|
+
signature: 'adjustSliderToPosition(normalizedPosition: number)',
|
95
|
+
description: 'Adjusts the slider to a specified position between its minimum and maximum values.',
|
96
|
+
example: "await element(by.id('slider')).adjustSliderToPosition(0.75);",
|
97
|
+
guidelines: ['The position is a normalized value between 0 and 1, where 0 is minimum and 1 is maximum.'],
|
98
|
+
},
|
99
|
+
{
|
100
|
+
signature: 'scroll(offset: number, direction: string, startPositionX?: number, startPositionY?: number)',
|
101
|
+
description: 'Scrolls an element.',
|
102
|
+
example: "await element(by.id('scrollView')).scroll(100, 'down');",
|
103
|
+
guidelines: ['Specify direction as "up", "down", "left", or "right".'],
|
104
|
+
},
|
105
|
+
{
|
106
|
+
signature: 'scrollTo(edge: string)',
|
107
|
+
description: 'Scrolls to an edge of the element.',
|
108
|
+
example: "await element(by.id('scrollView')).scrollTo('bottom');",
|
109
|
+
guidelines: ['Specify edge as "top", "bottom", "left", or "right".'],
|
110
|
+
},
|
111
|
+
{
|
112
|
+
signature: 'scrollToIndex(index: number)',
|
113
|
+
description: 'Scrolls the element to the specified index. (Android only)',
|
114
|
+
example: "await element(by.id('scrollView')).scrollToIndex(10);",
|
115
|
+
guidelines: ['Use this to scroll to a specific item in a list. Only available on Android.'],
|
116
|
+
},
|
117
|
+
{
|
118
|
+
signature: 'swipe(direction: string, speed?: string, normalizedOffset?: number)',
|
119
|
+
description: 'Simulates a swipe on the element.',
|
120
|
+
example: "await element(by.id('scrollView')).swipe('up', 'slow', 0.5);",
|
121
|
+
guidelines: [
|
122
|
+
'Specify direction as "up", "down", "left", or "right".',
|
123
|
+
'Speed can be "fast", "slow". default is "fast".',
|
124
|
+
],
|
125
|
+
},
|
126
|
+
{
|
127
|
+
signature: 'setColumnToValue(column: number, value: string)',
|
128
|
+
description: 'Sets a picker column to a specific value (iOS only).',
|
129
|
+
example: "await element(by.id('pickerView')).setColumnToValue(1, '6');",
|
130
|
+
guidelines: ['Use this for picker views on iOS.'],
|
131
|
+
},
|
132
|
+
{
|
133
|
+
signature: 'setDatePickerDate(dateString: string, dateFormat: string)',
|
134
|
+
description: 'Sets a date picker to a specific date.',
|
135
|
+
example: "await element(by.id('datePicker')).setDatePickerDate('2023-05-25', 'yyyy-MM-dd');",
|
136
|
+
guidelines: ['Use ISO8601 format when possible.'],
|
137
|
+
},
|
138
|
+
{
|
139
|
+
signature: 'performAccessibilityAction(actionName: string)',
|
140
|
+
description: 'Triggers an accessibility action.',
|
141
|
+
example: "await element(by.id('scrollView')).performAccessibilityAction('activate');",
|
142
|
+
guidelines: ['Use this to trigger specific accessibility actions.'],
|
143
|
+
},
|
144
|
+
{
|
145
|
+
signature: 'pinch(scale: number, speed?: string, angle?: number)',
|
146
|
+
description: 'Simulates a pinch gesture.',
|
147
|
+
example: "await element(by.id('PinchableScrollView')).pinch(1.1);",
|
148
|
+
guidelines: ['Use scale < 1 to zoom out, > 1 to zoom in.'],
|
149
|
+
},
|
150
|
+
|
151
|
+
{
|
152
|
+
signature: 'getAttributes()',
|
153
|
+
description: `
|
154
|
+
Retrieves various attributes of the element.
|
155
|
+
|
156
|
+
**Attributes include:**
|
157
|
+
- **Common**: text (string), label (string), placeholder (string), enabled (boolean), identifier (string), visible (boolean), value (string | number | boolean), frame (object: x (number), y (number), width (number), height (number))
|
158
|
+
- **iOS-only**: activationPoint (object: x (number), y (number)), normalizedActivationPoint (object: x (number), y (number)), hittable (boolean), elementFrame (object: x (number), y (number), width (number), height (number)), elementBounds (object: x (number), y (number), width (number), height (number)), safeAreaInsets (object: top (number), bottom (number), left (number), right (number)), elementSafeBounds (object: x (number), y (number), width (number), height (number)), date (Date), normalizedSliderPosition (number), contentOffset (object: x (number), y (number)), contentInset (object: top (number), bottom (number), left (number), right (number)), adjustedContentInset (object: top (number), bottom (number), left (number), right (number)))
|
159
|
+
- **Android-only**: visibility (string: 'visible', 'invisible', 'gone'), width (number) *(deprecated)*, height (number) *(deprecated)*, elevation (number), alpha (number), focused (boolean), textSize (number), length (number)
|
160
|
+
|
161
|
+
*Note:* Attributes may vary based on the platform and element type. If an attribute's value is null or cannot be computed, the key might be absent or contain an empty string.
|
162
|
+
`,
|
163
|
+
example: `
|
164
|
+
// Retrieve attributes of an element
|
165
|
+
const attributes = await element(by.text('Tap Me')).getAttributes();
|
166
|
+
jestExpect(attributes.text).toBe('Tap Me');
|
167
|
+
|
168
|
+
// Numerical assertions with allowed error range
|
169
|
+
jestExpect(attributes.frame.x).toBeCloseTo(100, 1);
|
170
|
+
jestExpect(attributes.frame.y).toBeCloseTo(200, 1);
|
171
|
+
|
172
|
+
// Platform-specific attribute check
|
173
|
+
if (device.getPlatform() === 'ios') {
|
174
|
+
jestExpect(attributes.hittable).toBe(true);
|
175
|
+
} else if (device.getPlatform() === 'android') {
|
176
|
+
jestExpect(attributes.visibility).toBe('visible');
|
177
|
+
}
|
178
|
+
`,
|
179
|
+
guidelines: [
|
180
|
+
'Use this to get properties like text, value, visibility, etc., for assertions or debugging. But only if the regular matchers or assertions are not sufficient.',
|
181
|
+
'Note that numerical values like position or size may not be very accurate; consider allowing a small error range in assertions.',
|
182
|
+
'Check the platform using `device.getPlatform()` before using platform-specific attributes.',
|
183
|
+
'Attributes include text, label, placeholder, enabled, identifier, visible, value, frame (with x, y, width, height), and platform-specific attributes.',
|
184
|
+
],
|
185
|
+
},
|
186
|
+
{
|
187
|
+
signature: 'takeScreenshot(name: string)',
|
188
|
+
description: 'Captures a screenshot of the element.',
|
189
|
+
example: "const imagePath = await element(by.id('menuRoot')).takeScreenshot('menu_screenshot');",
|
190
|
+
guidelines: ['Use this to capture screenshots of elements for documentation or debugging purposes.'],
|
191
|
+
},
|
192
|
+
{
|
193
|
+
signature: 'longPressAndDrag(duration: number, normalizedPositionX: number, normalizedPositionY: number, targetElement: NativeElement, normalizedTargetPositionX?: number, normalizedTargetPositionY?: number, speed?: string, holdDuration?: number)',
|
194
|
+
description: 'Simulates a long press on the element and then drags it to a target element.',
|
195
|
+
example: "await element(by.id('draggable')).longPressAndDrag(2000, NaN, NaN, element(by.id('target')), NaN, NaN, 'fast', 0);",
|
196
|
+
guidelines: ['Use this to simulate drag-and-drop interactions between elements.'],
|
197
|
+
},
|
198
|
+
{
|
199
|
+
signature: 'launchApp(params: object)',
|
200
|
+
description: 'Launches the app with specified parameters.',
|
201
|
+
example: 'await device.launchApp({newInstance: true});',
|
202
|
+
guidelines: ['Use this to launch the app with specific configurations.'],
|
203
|
+
},
|
204
|
+
{
|
205
|
+
signature: 'reloadReactNative()',
|
206
|
+
description: 'Reloads the React Native JS bundle.',
|
207
|
+
example: 'await device.reloadReactNative();',
|
208
|
+
guidelines: ['Faster than `launchApp()`, use when you just need to reset React Native state/logic.'],
|
209
|
+
},
|
210
|
+
],
|
211
|
+
},
|
212
|
+
{
|
213
|
+
title: 'Assertions',
|
214
|
+
items: [
|
215
|
+
{
|
216
|
+
signature: 'toBeVisible()',
|
217
|
+
description: 'Asserts that the element is visible.',
|
218
|
+
example: "await expect(element(by.id('loginButton'))).toBeVisible();",
|
219
|
+
guidelines: ['Use this to check if an element is visible on the screen.'],
|
220
|
+
},
|
221
|
+
{
|
222
|
+
signature: 'toExist()',
|
223
|
+
description: 'Asserts that the element exists.',
|
224
|
+
example: "await expect(element(by.id('username'))).toExist();",
|
225
|
+
guidelines: ['Use this to check if an element exists in the hierarchy, even if not visible.'],
|
226
|
+
},
|
227
|
+
{
|
228
|
+
signature: 'toHaveText(text: string)',
|
229
|
+
description: 'Asserts that the element has the specified text.',
|
230
|
+
example: "await expect(element(by.id('label'))).toHaveText('Hello, World!');",
|
231
|
+
guidelines: ['Use this to check the text content of an element.'],
|
232
|
+
},
|
233
|
+
{
|
234
|
+
signature: 'toHaveValue(value: string)',
|
235
|
+
description: 'Asserts that the element has the specified value.',
|
236
|
+
example: "await expect(element(by.id('slider'))).toHaveValue('0.5');",
|
237
|
+
guidelines: ['Use this to check the value of an element.'],
|
238
|
+
},
|
239
|
+
{
|
240
|
+
signature: 'toBeFocused()',
|
241
|
+
description: 'Asserts that the element is focused.',
|
242
|
+
example: "await expect(element(by.id('emailInput'))).toBeFocused();",
|
243
|
+
guidelines: ['Use this to check if an element is currently focused.'],
|
244
|
+
},
|
245
|
+
{
|
246
|
+
signature: 'toHaveLabel(label: string)',
|
247
|
+
description: 'Asserts that the element has the specified accessibility label.',
|
248
|
+
example: "await expect(element(by.id('submitButton'))).toHaveLabel('Submit');",
|
249
|
+
guidelines: [
|
250
|
+
'Use this to check the accessibility label of an element. Note that in React Native, the `accessibilityLabel` prop may behave differently on iOS and Android.',
|
251
|
+
],
|
252
|
+
},
|
253
|
+
{
|
254
|
+
signature: 'toHaveId(id: string)',
|
255
|
+
description: 'Asserts that the element has the specified accessibility identifier.',
|
256
|
+
example: "await expect(element(by.text('Submit'))).toHaveId('submitButton');",
|
257
|
+
guidelines: ['Use this to check the testID/accessibility identifier of an element.'],
|
258
|
+
},
|
259
|
+
{
|
260
|
+
signature: 'toHaveSliderPosition(normalizedPosition: number, tolerance?: number)',
|
261
|
+
description:
|
262
|
+
'Asserts that the slider element has the specified normalized position [0, 1], within an optional tolerance.',
|
263
|
+
example:
|
264
|
+
"await expect(element(by.id('slider'))).toHaveSliderPosition(0.75);\nawait expect(element(by.id('slider'))).toHaveSliderPosition(0.3113, 0.00001);",
|
265
|
+
guidelines: ['Use this to verify the slider\'s position. Normalized position is between 0 and 1.'],
|
266
|
+
},
|
267
|
+
{
|
268
|
+
signature: 'toHaveToggleValue(value: boolean)',
|
269
|
+
description: 'Asserts that a toggle-able element is on/checked or off/unchecked.',
|
270
|
+
example:
|
271
|
+
"await expect(element(by.id('switch'))).toHaveToggleValue(true);\nawait expect(element(by.id('checkbox'))).toHaveToggleValue(false);",
|
272
|
+
guidelines: ['Use this to check the state of toggleable elements.'],
|
273
|
+
},
|
274
|
+
{
|
275
|
+
signature: 'withTimeout(timeout: number)',
|
276
|
+
description:
|
277
|
+
'Waits until the expectation is resolved for the specified amount of time.',
|
278
|
+
example:
|
279
|
+
"await waitFor(element(by.id('bigButton'))).toBeVisible().withTimeout(2000);",
|
280
|
+
guidelines: ['Use this to set a custom timeout for an expectation.'],
|
281
|
+
},
|
282
|
+
{
|
283
|
+
signature: 'not',
|
284
|
+
description: 'Negates the expectation.',
|
285
|
+
example:
|
286
|
+
"await expect(element(by.id('tinyButton'))).not.toBeVisible();\nawait expect(element(by.id('tinyButton'))).not.toExist();",
|
287
|
+
guidelines: ["Use 'not' to negate an expectation."],
|
288
|
+
},
|
289
|
+
],
|
290
|
+
},
|
291
|
+
{
|
292
|
+
title: 'Utilities',
|
293
|
+
items: [
|
294
|
+
{
|
295
|
+
signature: 'jestExpect',
|
296
|
+
description: 'Jest expect utility for jest-assisted assertions. It is already imported in the environment.',
|
297
|
+
example: `
|
298
|
+
// Use jestExpect for assertions
|
299
|
+
jestExpect(2 + 2).toBe(4);
|
300
|
+
jestExpect('hello').toBe('hello');
|
301
|
+
jestExpect(true).toBeTruthy();
|
302
|
+
`,
|
303
|
+
guidelines: ['Use jestExpect for assertions in tests, only when the default expect is not helpful for the specific case.'],
|
304
|
+
},
|
305
|
+
],
|
306
|
+
}
|
307
|
+
],
|
308
|
+
},
|
309
|
+
|
310
|
+
captureSnapshotImage: async function () {
|
311
|
+
const fileName = `snapshot_${Date.now()}.png`;
|
312
|
+
return await detox.device.takeScreenshot(fileName);
|
313
|
+
},
|
314
|
+
|
315
|
+
captureViewHierarchyString: async function () {
|
316
|
+
return detox.device.generateViewHierarchyXml();
|
317
|
+
},
|
318
|
+
};
|
319
|
+
|
320
|
+
module.exports = detoxCopilotFrameworkDriver;
|
@@ -1 +0,0 @@
|
|
1
|
-
ea98a7bac84cd877ac7a13aa5b9d3cc1
|
@@ -1 +0,0 @@
|
|
1
|
-
a67071d6d4bdcd041e91bc9f58d5d57637532cb9
|
@@ -1 +0,0 @@
|
|
1
|
-
9e325d7b0b5b8dab384773bcf96df7a44e7a76f5375b1afba2e29b9a53c60e7b
|
@@ -1 +0,0 @@
|
|
1
|
-
4ca91364a087ff16bd38d1721163b47f3d6435552605e1c2cba38138739e42bf45161458e997bc14be075b0bfa9df2ac8a311c79dc21e67216726b3f8a920db1
|
Binary file
|
@@ -1 +0,0 @@
|
|
1
|
-
19245568a5b1fc7e18eae4c34019b2d0
|
@@ -1 +0,0 @@
|
|
1
|
-
dfb1a4d4b6a06bbe8952fe821ab3278364fc3cf2
|
@@ -1 +0,0 @@
|
|
1
|
-
294b99bfcfc4acf89c10c76c4cfb07b61dec92d8f45a2ed137092aa766d7e215
|
@@ -1 +0,0 @@
|
|
1
|
-
546fa7e195d2c2077d9e41f2b003ead174c820ce45027752e86a366e673023cc41d09b5b5ccd3bdb693f403e6c8b65567af12208ece66e65d85937e9a65f677d
|
@@ -1 +0,0 @@
|
|
1
|
-
f5574f4b81f29bd42c5da2cbaae85003
|
@@ -1 +0,0 @@
|
|
1
|
-
65101d817b9185e67e3d5fb396107ad252a6ade7
|
@@ -1 +0,0 @@
|
|
1
|
-
8830fd9064558962e2d7195512f9db4aae71801865f20b240a17ba4cebc21ab4
|
@@ -1 +0,0 @@
|
|
1
|
-
d026c556f62910b4728822cea8b03745bf74617c2bf3df7d97b29eb0007a1ace87a00af9aec32fa1798f0345e551ed62abd9be7e16f96b59ae56a88950ea46ce
|
@@ -1 +0,0 @@
|
|
1
|
-
5fe1ece0c6f59a509e44c153a67c0aad
|
@@ -1 +0,0 @@
|
|
1
|
-
af24ad68bc24fda67b522b4853d47193de81fb59
|
@@ -1 +0,0 @@
|
|
1
|
-
56e2fc034e270dd194fbfa0a8fe24e1b95fb298564cfa6a31c1b79d5eeb5f3f2
|
@@ -1 +0,0 @@
|
|
1
|
-
109252b02fbd404778467ad18d611ebbb1a442dde9dca1d8e3feb469d81a0d6aff4a5285fb3677dc219b23acc78c88a134f3f3ef5f19487b7b7d3fb06049a4c8
|
Binary file
|
@@ -1 +0,0 @@
|
|
1
|
-
0dd6f951f83bfb1c258c09e57291b5f3
|
@@ -1 +0,0 @@
|
|
1
|
-
54212838677b6cf72c2eb964dc911a9a80cf54a7
|
@@ -1 +0,0 @@
|
|
1
|
-
c2452ceba0cb3a2a6517086d81a0e990d82ab263395a0e1166b8178769cd8a51
|
@@ -1 +0,0 @@
|
|
1
|
-
3d71f12171376fcf35b46ebc6a6cf942e0a7151a8f26287b41b6e2807f4bd8d37704aeaa89b7b0231c2286a301ebbfa3ff77a0a9cee92c02463c0085a4190085
|
@@ -1 +0,0 @@
|
|
1
|
-
817a01b2f72e4cc9ee814d9aa83a6898
|
@@ -1 +0,0 @@
|
|
1
|
-
5ab9e6d2bf60cc87c07b26d73ded613cc4692fe4
|
@@ -1 +0,0 @@
|
|
1
|
-
26768c504f24bc35eee880bfe2fcc6c062aac63e5eee306d2196d27c989dbc40
|
@@ -1 +0,0 @@
|
|
1
|
-
af66500c5a4d075c5d9c739439fc49bcdebe5a7b2f8d364b7e8b1f879fc0901184b928d33ed66b59f3b5d7b89050acc3b6d4e1bfd151e114561ba975c9ca6e59
|
@@ -1,140 +0,0 @@
|
|
1
|
-
const { device } = require('../..');
|
2
|
-
|
3
|
-
class DetoxDriver {
|
4
|
-
constructor() {
|
5
|
-
this.availableAPI = {
|
6
|
-
matchers: [
|
7
|
-
{
|
8
|
-
signature: 'by.id(id: string)',
|
9
|
-
description: 'Matches elements by their test ID.',
|
10
|
-
example: "element(by.id('loginButton'))",
|
11
|
-
guidelines: ['Always use test-ids (accessibility identifiers) from the UI hierarchy to identify elements.']
|
12
|
-
},
|
13
|
-
{
|
14
|
-
signature: 'by.text(text: string)',
|
15
|
-
description: 'Matches elements by their text.',
|
16
|
-
example: "element(by.text('Login'))",
|
17
|
-
guidelines: ['Avoid using text matchers when possible, prefer test-ids.']
|
18
|
-
},
|
19
|
-
{
|
20
|
-
signature: 'by.type(type: string)',
|
21
|
-
description: 'Matches elements by their type.',
|
22
|
-
example: "element(by.type('RCTTextInput'))",
|
23
|
-
guidelines: ['Use type matchers as a last resort, prefer test-ids.']
|
24
|
-
}
|
25
|
-
],
|
26
|
-
actions: [
|
27
|
-
{
|
28
|
-
signature: 'tap(point?: Point2D)',
|
29
|
-
description: 'Simulates tap on an element',
|
30
|
-
example: "await element(by.id('loginButton')).tap();",
|
31
|
-
guidelines: ['Use element(by.id(\'testID\')) to locate elements.']
|
32
|
-
},
|
33
|
-
{
|
34
|
-
signature: 'longPress(point?: Point2D, duration?: number)',
|
35
|
-
description: 'Simulates long press on an element',
|
36
|
-
example: "await element(by.id('menuItem')).longPress();",
|
37
|
-
guidelines: ['If the target element is not accessible, interact with its container or the most relevant parent element.']
|
38
|
-
},
|
39
|
-
{
|
40
|
-
signature: 'typeText(text: string)',
|
41
|
-
description: 'Types text into a text field',
|
42
|
-
example: "await element(by.id('usernameInput')).typeText('myusername');",
|
43
|
-
guidelines: ['Typing can only be done on text field elements.']
|
44
|
-
},
|
45
|
-
{
|
46
|
-
signature: 'replaceText(text: string)',
|
47
|
-
description: 'Replaces text in a text field',
|
48
|
-
example: "await element(by.id('usernameInput')).replaceText('newusername');",
|
49
|
-
guidelines: ['Use this to replace existing text in a field.']
|
50
|
-
},
|
51
|
-
{
|
52
|
-
signature: 'clearText()',
|
53
|
-
description: 'Clears text from a text field',
|
54
|
-
example: "await element(by.id('usernameInput')).clearText();",
|
55
|
-
guidelines: ['Use this to clear existing text from a field.']
|
56
|
-
},
|
57
|
-
{
|
58
|
-
signature: 'scrollTo(edge: Direction, startPositionX?: number, startPositionY?: number)',
|
59
|
-
description: 'Scrolls to an edge',
|
60
|
-
example: "await element(by.id('scrollView')).scrollTo('bottom');",
|
61
|
-
guidelines: ['Scrolling must be done only on scroll-view elements.']
|
62
|
-
},
|
63
|
-
{
|
64
|
-
signature: 'scrollToIndex(index: Number)',
|
65
|
-
description: 'Scrolls to a specific index',
|
66
|
-
example: "await element(by.id('flatList')).scrollToIndex(5);",
|
67
|
-
guidelines: ['Use this for scrolling to a specific item in a list.']
|
68
|
-
},
|
69
|
-
{
|
70
|
-
signature: 'adjustSliderToPosition(newPosition: number)',
|
71
|
-
description: 'Adjusts slider to a position',
|
72
|
-
example: "await element(by.id('slider')).adjustSliderToPosition(0.75);",
|
73
|
-
guidelines: ['The position should be a number between 0 and 1.']
|
74
|
-
},
|
75
|
-
{
|
76
|
-
signature: 'setColumnToValue(column: number, value: string)',
|
77
|
-
description: 'Sets picker view column to a value (iOS only)',
|
78
|
-
example: "await element(by.id('datePicker')).setColumnToValue(1, '2023');",
|
79
|
-
guidelines: ['This is only available on iOS.']
|
80
|
-
},
|
81
|
-
{
|
82
|
-
signature: 'performAccessibilityAction(actionName: string)',
|
83
|
-
description: 'Triggers an accessibility action',
|
84
|
-
example: "await element(by.id('button')).performAccessibilityAction('longpress');",
|
85
|
-
guidelines: ['Use the provided value from the intent and do not change it.']
|
86
|
-
},
|
87
|
-
{
|
88
|
-
signature: 'swipe(direction: Direction, speed?: Speed, percentage?: number)',
|
89
|
-
description: 'Swipes in the specified direction',
|
90
|
-
example: "await element(by.id('card')).swipe('left', 'fast');",
|
91
|
-
guidelines: ['Use this for swiping gestures on elements.']
|
92
|
-
},
|
93
|
-
{
|
94
|
-
signature: 'pinch(scale: number, speed?: Speed, angle?: number)',
|
95
|
-
description: 'Performs a pinch gesture (iOS only)',
|
96
|
-
example: "await element(by.id('image')).pinch(0.5);",
|
97
|
-
guidelines: ['This is only available on iOS. Scale < 1 zooms out, scale > 1 zooms in.']
|
98
|
-
}
|
99
|
-
],
|
100
|
-
assertions: [
|
101
|
-
{
|
102
|
-
signature: 'toBeVisible()',
|
103
|
-
description: 'Asserts that the element is visible',
|
104
|
-
example: "await expect(element(by.id('loginButton'))).toBeVisible();",
|
105
|
-
guidelines: ['Use this to check if an element is visible on the screen.']
|
106
|
-
},
|
107
|
-
{
|
108
|
-
signature: 'toExist()',
|
109
|
-
description: 'Asserts that the element exists',
|
110
|
-
example: "await expect(element(by.id('username'))).toExist();",
|
111
|
-
guidelines: ['Use this to check if an element exists in the hierarchy, even if not visible.']
|
112
|
-
},
|
113
|
-
{
|
114
|
-
signature: 'toHaveText(text: string)',
|
115
|
-
description: 'Asserts that the element has the specified text',
|
116
|
-
example: "await expect(element(by.id('label'))).toHaveText('Hello, World!');",
|
117
|
-
guidelines: ['Use this to check the text content of an element.']
|
118
|
-
},
|
119
|
-
{
|
120
|
-
signature: 'toHaveValue(value: string)',
|
121
|
-
description: 'Asserts that the element has the specified value',
|
122
|
-
example: "await expect(element(by.id('slider'))).toHaveValue('0.5');",
|
123
|
-
guidelines: ['Use this to check the value of an element.']
|
124
|
-
}
|
125
|
-
]
|
126
|
-
};
|
127
|
-
}
|
128
|
-
|
129
|
-
async captureSnapshotImage() {
|
130
|
-
const fileName = `snapshot_${Date.now()}.png`;
|
131
|
-
await device.takeScreenshot(fileName);
|
132
|
-
return fileName;
|
133
|
-
}
|
134
|
-
|
135
|
-
async captureViewHierarchyString() {
|
136
|
-
return device.generateViewHierarchyXml();
|
137
|
-
}
|
138
|
-
}
|
139
|
-
|
140
|
-
module.exports = DetoxDriver;
|