detox 20.26.2 → 20.27.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. 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
  2. package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0-sources.jar.md5 +1 -0
  3. package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0-sources.jar.sha1 +1 -0
  4. package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0-sources.jar.sha256 +1 -0
  5. package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0-sources.jar.sha512 +1 -0
  6. package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.aar +0 -0
  7. package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.aar.md5 +1 -0
  8. package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.aar.sha1 +1 -0
  9. package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.aar.sha256 +1 -0
  10. package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.aar.sha512 +1 -0
  11. package/Detox-android/com/wix/detox/{20.26.2/detox-20.26.2.pom → 20.27.0/detox-20.27.0.pom} +1 -1
  12. package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.pom.md5 +1 -0
  13. package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.pom.sha1 +1 -0
  14. package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.pom.sha256 +1 -0
  15. package/Detox-android/com/wix/detox/20.27.0/detox-20.27.0.pom.sha512 +1 -0
  16. package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
  17. package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
  18. package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
  19. package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
  20. package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
  21. 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
  22. package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0-sources.jar.md5 +1 -0
  23. package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0-sources.jar.sha1 +1 -0
  24. package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0-sources.jar.sha256 +1 -0
  25. package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0-sources.jar.sha512 +1 -0
  26. package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.aar +0 -0
  27. package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.aar.md5 +1 -0
  28. package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.aar.sha1 +1 -0
  29. package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.aar.sha256 +1 -0
  30. package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.aar.sha512 +1 -0
  31. 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
  32. package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.pom.md5 +1 -0
  33. package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.pom.sha1 +1 -0
  34. package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.pom.sha256 +1 -0
  35. package/Detox-android/com/wix/detox-legacy/20.27.0/detox-legacy-20.27.0.pom.sha512 +1 -0
  36. package/Detox-android/com/wix/detox-legacy/maven-metadata.xml +4 -4
  37. package/Detox-android/com/wix/detox-legacy/maven-metadata.xml.md5 +1 -1
  38. package/Detox-android/com/wix/detox-legacy/maven-metadata.xml.sha1 +1 -1
  39. package/Detox-android/com/wix/detox-legacy/maven-metadata.xml.sha256 +1 -1
  40. package/Detox-android/com/wix/detox-legacy/maven-metadata.xml.sha512 +1 -1
  41. package/Detox-ios-framework.tbz +0 -0
  42. package/Detox-ios-src.tbz +0 -0
  43. package/Detox-ios-xcuitest.tbz +0 -0
  44. package/android/detox/proguard-rules-app.pro +3 -0
  45. package/android/detox/src/full/java/com/wix/detox/espresso/hierarchy/ViewHierarchyGenerator.kt +86 -21
  46. package/detox.d.ts +15 -0
  47. package/globals.d.ts +2 -0
  48. package/jest.config.js +1 -0
  49. package/package.json +3 -3
  50. package/src/DetoxWorker.js +9 -0
  51. package/src/copilot/DetoxCopilot.js +33 -0
  52. package/src/copilot/detoxCopilotFrameworkDriver.js +320 -0
  53. package/src/realms/DetoxContext.js +2 -0
  54. package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2-sources.jar.md5 +0 -1
  55. package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2-sources.jar.sha1 +0 -1
  56. package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2-sources.jar.sha256 +0 -1
  57. package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2-sources.jar.sha512 +0 -1
  58. package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.aar +0 -0
  59. package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.aar.md5 +0 -1
  60. package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.aar.sha1 +0 -1
  61. package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.aar.sha256 +0 -1
  62. package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.aar.sha512 +0 -1
  63. package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.pom.md5 +0 -1
  64. package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.pom.sha1 +0 -1
  65. package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.pom.sha256 +0 -1
  66. package/Detox-android/com/wix/detox/20.26.2/detox-20.26.2.pom.sha512 +0 -1
  67. package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2-sources.jar.md5 +0 -1
  68. package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2-sources.jar.sha1 +0 -1
  69. package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2-sources.jar.sha256 +0 -1
  70. package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2-sources.jar.sha512 +0 -1
  71. package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.aar +0 -0
  72. package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.aar.md5 +0 -1
  73. package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.aar.sha1 +0 -1
  74. package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.aar.sha256 +0 -1
  75. package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.aar.sha512 +0 -1
  76. package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.pom.md5 +0 -1
  77. package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.pom.sha1 +0 -1
  78. package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.pom.sha256 +0 -1
  79. package/Detox-android/com/wix/detox-legacy/20.26.2/detox-legacy-20.26.2.pom.sha512 +0 -1
  80. package/src/copilot/CopilotDriver.js +0 -140
@@ -0,0 +1 @@
1
+ 79e88d1742382204b44c103b700a3955
@@ -0,0 +1 @@
1
+ 00febe099b40a8cfbfe63256e7dfcbfdc2ef1e01
@@ -0,0 +1 @@
1
+ 624acc61a54549d741cadf827c87a952ea65f4d71342b21c0bc78e4dba8f0bab
@@ -0,0 +1 @@
1
+ 84a8966526c12615b67ddb97618cfba60275eab2dbc6babe2fa8828f681d6cdc838a2f43f5fe34e3190390d4aff46fa583b27c512ab0c7881d8e5e75f74428f8
@@ -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.26.2</version>
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.26.2</latest>
7
- <release>20.26.2</release>
6
+ <latest>20.27.0</latest>
7
+ <release>20.27.0</release>
8
8
  <versions>
9
- <version>20.26.2</version>
9
+ <version>20.27.0</version>
10
10
  </versions>
11
- <lastUpdated>20240905213155</lastUpdated>
11
+ <lastUpdated>20240922114824</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- 9e3e8a8d05fd018af3b4b1cecac85ceb
1
+ 166e0211e3c8ebd2b2b6605fb99d6a07
@@ -1 +1 @@
1
- 49a5d3258b3a4b18d55c0c803f65e9c7bb470c8e
1
+ d513122237af950d971097b9847eff9e6823c2d7
@@ -1 +1 @@
1
- f8b05715aaf9d675552325c062d5509e983560df36e2c0964bf858aac906be06
1
+ 086f6a6208ab027feb7a6cccc199bba56fc325e21f8ac890f16d2c8eaebfa385
@@ -1 +1 @@
1
- 9f65c4a4e108884baa631feba8abcc34372ee88b3d194b4eb9f94c88162e262fd8a0ed62f4ec94162c3891ea6ca085ba737892d73709ac2ec3b32e1d1e22ae69
1
+ 75146105b8ffcd9f6c54ccecab07fe38ec76f37368521d6d3b4d7c1f82115a9b8d22a9377de901c860c411bc47a275225e312c5e1c6f13ae36a5ad39ac3bd1bb
@@ -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.26.2</version>
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.26.2</latest>
7
- <release>20.26.2</release>
6
+ <latest>20.27.0</latest>
7
+ <release>20.27.0</release>
8
8
  <versions>
9
- <version>20.26.2</version>
9
+ <version>20.27.0</version>
10
10
  </versions>
11
- <lastUpdated>20240905213242</lastUpdated>
11
+ <lastUpdated>20240922114842</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- 7d1e75f8073d7e8507484042d83103e1
1
+ 63612a5f5bdbb48d5e1f3ff4e5cd200f
@@ -1 +1 @@
1
- f106fa7fa6ca0ecff78f8b6c7094118a0266de5a
1
+ c52523453872dead27e35540c753721d94d9a9cd
@@ -1 +1 @@
1
- 8d88d3608dc25dd1305f93cb629d308c3167f6e4ea232efbbc54f309288d5bba
1
+ 9158a9b035b7a341f654d02b939a8140531208e0b2814779156779c40390039a
@@ -1 +1 @@
1
- ce63462b86b96e3ee62a7817f56fb3f91d30801e93c41e0a9b8e8a0300a3bae285d17c1bd7a07054bc45557f9c1b16da338dcce8ef8197c7957088c327ea4b4d
1
+ 95157a82c725185c7ef2f908de02e89beebd86595763f7218596b7982dad2ad742900c52088e0b2276ddf108443e30a334a46e95e5f830fb699e566e5b3d29a7
Binary file
package/Detox-ios-src.tbz CHANGED
Binary file
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.** { *; }
@@ -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
- val rootViews = RootViewsHelper.getRootViews()
15
- return generateXmlFromViews(rootViews, shouldInjectTestIds)
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
- if (view is ViewGroup) {
52
- for (i in 0 until view.childCount) {
53
- serializeViewHierarchy(
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
@@ -75,6 +75,7 @@ module.exports = {
75
75
  'src/DetoxWorker.js',
76
76
  'src/logger/utils/streamUtils.js',
77
77
  'src/realms',
78
+ 'src/copilot',
78
79
  ],
79
80
  resetMocks: true,
80
81
  resetModules: true,
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.26.2",
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.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": "e0a1fefeeec0382b5dc75613a2076d0f6a05e97f"
119
+ "gitHead": "17d3f122836a7e5fe6861e2827ecb00556e69ff8"
120
120
  }
@@ -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;
@@ -81,6 +81,8 @@ class DetoxContext {
81
81
 
82
82
  web = funpermaproxy.callable(() => this[symbols.worker].web);
83
83
 
84
+ copilot = funpermaproxy.callable(() => this[symbols.worker].copilot);
85
+
84
86
  get DetoxConstants() {
85
87
  return DetoxConstants;
86
88
  }
@@ -1 +0,0 @@
1
- ea98a7bac84cd877ac7a13aa5b9d3cc1
@@ -1 +0,0 @@
1
- a67071d6d4bdcd041e91bc9f58d5d57637532cb9
@@ -1 +0,0 @@
1
- 9e325d7b0b5b8dab384773bcf96df7a44e7a76f5375b1afba2e29b9a53c60e7b
@@ -1 +0,0 @@
1
- 4ca91364a087ff16bd38d1721163b47f3d6435552605e1c2cba38138739e42bf45161458e997bc14be075b0bfa9df2ac8a311c79dc21e67216726b3f8a920db1
@@ -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
@@ -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;