detox 20.26.2 → 20.27.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.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;
|