detox 20.11.5-smoke.0 → 20.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. package/Detox-android/com/wix/detox/{20.11.5-smoke.0/detox-20.11.5-smoke.0-javadoc.jar → 20.12.0/detox-20.12.0-javadoc.jar} +0 -0
  2. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0-javadoc.jar.md5 +1 -0
  3. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0-javadoc.jar.sha1 +1 -0
  4. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0-javadoc.jar.sha256 +1 -0
  5. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0-javadoc.jar.sha512 +1 -0
  6. package/Detox-android/com/wix/detox/{20.11.5-smoke.0/detox-20.11.5-smoke.0-sources.jar → 20.12.0/detox-20.12.0-sources.jar} +0 -0
  7. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0-sources.jar.md5 +1 -0
  8. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0-sources.jar.sha1 +1 -0
  9. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0-sources.jar.sha256 +1 -0
  10. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0-sources.jar.sha512 +1 -0
  11. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0.aar +0 -0
  12. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0.aar.md5 +1 -0
  13. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0.aar.sha1 +1 -0
  14. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0.aar.sha256 +1 -0
  15. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0.aar.sha512 +1 -0
  16. package/Detox-android/com/wix/detox/{20.11.5-smoke.0/detox-20.11.5-smoke.0.pom → 20.12.0/detox-20.12.0.pom} +1 -1
  17. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0.pom.md5 +1 -0
  18. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0.pom.sha1 +1 -0
  19. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0.pom.sha256 +1 -0
  20. package/Detox-android/com/wix/detox/20.12.0/detox-20.12.0.pom.sha512 +1 -0
  21. package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
  22. package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
  23. package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
  24. package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
  25. package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
  26. package/Detox-ios-src.tbz +0 -0
  27. package/Detox-ios.tbz +0 -0
  28. package/android/detox/src/full/java/com/wix/detox/espresso/DetoxAssertion.java +44 -25
  29. package/android/detox/src/full/java/com/wix/detox/espresso/EspressoDetox.java +6 -7
  30. package/android/detox/src/full/java/com/wix/detox/espresso/action/GetAttributesAction.kt +4 -4
  31. package/android/detox/src/full/java/com/wix/detox/espresso/performer/MultipleViewsActionPerformer.kt +43 -0
  32. package/android/detox/src/full/java/com/wix/detox/espresso/performer/SingleViewActionPerformer.kt +19 -0
  33. package/android/detox/src/full/java/com/wix/detox/espresso/performer/ViewActionPerformer.kt +24 -0
  34. package/android/detox/src/full/java/com/wix/invoke/types/Invocation.java +5 -4
  35. package/android/detox/src/main/java/com/wix/detox/espresso/MultipleViewsAction.kt +4 -0
  36. package/android/detox/src/main/java/com/wix/detox/espresso/UiControllerSpy.kt +0 -1
  37. package/android/detox/src/main/java/com/wix/detox/espresso/ViewActionWithResult.kt +2 -1
  38. package/android/detox/src/testFull/java/com/wix/detox/espresso/action/GetAttributesActionTest.kt +6 -5
  39. package/android/detox/src/testFull/java/com/wix/detox/espresso/performer/ViewActionPerformerSpec.kt +37 -0
  40. package/index.d.ts +4 -3
  41. package/package.json +2 -2
  42. package/src/android/core/NativeElement.js +26 -29
  43. package/src/android/espressoapi/DetoxAssertion.js +16 -14
  44. package/src/android/espressoapi/EspressoDetox.js +9 -2
  45. package/src/android/interactions/native.js +2 -3
  46. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0-javadoc.jar.md5 +0 -1
  47. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0-javadoc.jar.sha1 +0 -1
  48. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0-javadoc.jar.sha256 +0 -1
  49. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0-javadoc.jar.sha512 +0 -1
  50. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0-sources.jar.md5 +0 -1
  51. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0-sources.jar.sha1 +0 -1
  52. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0-sources.jar.sha256 +0 -1
  53. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0-sources.jar.sha512 +0 -1
  54. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0.aar +0 -0
  55. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0.aar.md5 +0 -1
  56. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0.aar.sha1 +0 -1
  57. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0.aar.sha256 +0 -1
  58. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0.aar.sha512 +0 -1
  59. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0.pom.md5 +0 -1
  60. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0.pom.sha1 +0 -1
  61. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0.pom.sha256 +0 -1
  62. package/Detox-android/com/wix/detox/20.11.5-smoke.0/detox-20.11.5-smoke.0.pom.sha512 +0 -1
@@ -0,0 +1 @@
1
+ fd803ad3b1d79693779077e2d5a030be
@@ -0,0 +1 @@
1
+ 6e783294674107a7a02ffce8f5fc9bf5b5fa56de
@@ -0,0 +1 @@
1
+ 250dd66ec2b951c59dd18d382ff84a0e05e9210543d66965d1c3704a36a5f16f
@@ -0,0 +1 @@
1
+ b6b8f084acbebaa8057529fe74ce10a2a700ef882f863aecf01a6e263f76164deb3ace7c5aa49164f3345bc3c930d18ca37e704f8814194906f32641187aa0d3
@@ -0,0 +1 @@
1
+ 47c93868c2f09b970c68a3c21de6c2e8
@@ -0,0 +1 @@
1
+ 6513d86c59c32e3b5a0d41e86f55e01c15dc0b8a
@@ -0,0 +1 @@
1
+ dbf7d435856960f5a0440f74248d677c6b7bcec0d9100f1e0f941401bd05f554
@@ -0,0 +1 @@
1
+ 175323a595f1be240650f3d5109f0c7c099e93bdf1e9799b25f6ade82339aa42764dedbedefbfe6a9803cab1c0885f97dcd25ca77dd8840c9d5330bbbb66a9be
@@ -0,0 +1 @@
1
+ c90cc85230997f3663da1741d1eabc0e
@@ -0,0 +1 @@
1
+ 0e17ccf18dbb2e0295e72b77cae67275591d956a
@@ -0,0 +1 @@
1
+ 00528b5d136df63bc93f55c2f890a1ad69179f341663310b520227f6b7aad63b
@@ -0,0 +1 @@
1
+ 37984e2491685ede0325d74f460ad2a13dc55bd906124e0932fa09e29e2394a91cbbf691aa854848503113f04dfe148a483e616c736918f6e0077af6aa311a31
@@ -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.11.5-smoke.0</version>
6
+ <version>20.12.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
+ f936530422c3b067b14acf6882eb15c8
@@ -0,0 +1 @@
1
+ 2528825c4917ba8994b81030daef2f0a7a908227
@@ -0,0 +1 @@
1
+ a0a195aa8f7511f6ee3b1bacc22f29d444e3d3eaacb96bf902c3e06df9afef5d
@@ -0,0 +1 @@
1
+ 9702d3696a7170e3ac6cc0cdd15490cd1cd954903b602453a4ac53d336feda62822aef2796c83fd33552639f576a0234cb0052a76bd7ed2b2bc67a87df08544a
@@ -3,11 +3,11 @@
3
3
  <groupId>com.wix</groupId>
4
4
  <artifactId>detox</artifactId>
5
5
  <versioning>
6
- <latest>20.11.5-smoke.0</latest>
7
- <release>20.11.5-smoke.0</release>
6
+ <latest>20.12.0</latest>
7
+ <release>20.12.0</release>
8
8
  <versions>
9
- <version>20.11.5-smoke.0</version>
9
+ <version>20.12.0</version>
10
10
  </versions>
11
- <lastUpdated>20230830102548</lastUpdated>
11
+ <lastUpdated>20230912212516</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- b66fe11b8db07e94d8899e61497ff262
1
+ f9657c7ea977fa12455aefb694554e0b
@@ -1 +1 @@
1
- 5c083ffc3dc0b459da472f94acdf213e32320a29
1
+ e237f10dcfb62432425cceaa085e7abbbccce171
@@ -1 +1 @@
1
- 2572bcdd8effd233f1ffe93cf27906f4fc2efeac59f00fa2b90bc687051c0c77
1
+ 973055b62aa343c2551197a1f9b730cdad4fe64ff6aa327ac43e4de24aea047a
@@ -1 +1 @@
1
- ca1b38083670c2cc34bdf67a3a032c0a89f9fb6f6ff766571af6048fc02661013619eeb87817d430933aea0aa149c29396973bd432182f30dab31536dbe186eb
1
+ 00e33c2cefd740754934b7635d42ff6933ff1b387d806b3503c08fc16955d49e0297b0f4875d3ccdf532b4409459692e2b367947164e6ac7a75db84259cb0fab
package/Detox-ios-src.tbz CHANGED
Binary file
package/Detox-ios.tbz CHANGED
Binary file
@@ -24,42 +24,57 @@ import static org.hamcrest.Matchers.not;
24
24
 
25
25
  public class DetoxAssertion {
26
26
 
27
+ private static final double NANOSECONDS_IN_A_SECOND = 1_000_000_000.0;
28
+
27
29
  private DetoxAssertion() {
28
- // static class
30
+ // This is a utility class and shouldn't be instantiated.
29
31
  }
30
32
 
31
- public static ViewInteraction assertMatcher(ViewInteraction i, Matcher<View> m) {
32
- return i.check(matches(m));
33
+ /**
34
+ * Asserts the given matcher for the provided view interaction.
35
+ */
36
+ public static ViewInteraction assertMatcher(ViewInteraction viewInteraction, Matcher<View> viewMatcher) {
37
+ return viewInteraction.check(matches(viewMatcher));
33
38
  }
34
39
 
35
- public static ViewInteraction assertNotVisible(ViewInteraction i) {
36
- ViewInteraction ret;
40
+ /**
41
+ * Asserts that the given view interaction is not visible.
42
+ */
43
+ public static ViewInteraction assertNotVisible(ViewInteraction viewInteraction) {
44
+ ViewInteraction result;
37
45
  try {
38
- ret = i.check(doesNotExist());
39
- return ret;
46
+ result = viewInteraction.check(doesNotExist());
47
+ return result;
40
48
  } catch (AssertionFailedError e) {
41
- ret = i.check(matches(not(isDisplayed())));
42
- return ret;
49
+ result = viewInteraction.check(matches(not(isDisplayed())));
50
+ return result;
43
51
  }
44
52
  }
45
53
 
46
- public static ViewInteraction assertNotExists(ViewInteraction i) {
47
- return i.check(doesNotExist());
54
+ /**
55
+ * Asserts that the given view interaction does not exist.
56
+ */
57
+ public static ViewInteraction assertNotExists(ViewInteraction viewInteraction) {
58
+ return viewInteraction.check(doesNotExist());
48
59
  }
49
60
 
50
- public static void waitForAssertMatcher(final ViewInteraction i, final Matcher<View> m, double timeoutSeconds) {
51
- final long originTime = System.nanoTime();
61
+ /**
62
+ * Waits until the provided matcher matches the view interaction or a timeout occurs.
63
+ */
64
+ public static void waitForAssertMatcher(final ViewInteraction viewInteraction, final Matcher<View> viewMatcher, double timeoutSeconds) {
65
+ final long startTime = System.nanoTime();
52
66
 
53
67
  while (true) {
54
68
  long currentTime = System.nanoTime();
55
- long elapsed = currentTime - originTime;
56
- double seconds = (double) elapsed / 1000000000.0;
57
- if (seconds >= timeoutSeconds) {
58
- throw new DetoxRuntimeException("" + timeoutSeconds + "sec timeout expired without matching of given matcher: " + m);
69
+ long elapsedTime = currentTime - startTime;
70
+ double elapsedSeconds = (double) elapsedTime / NANOSECONDS_IN_A_SECOND;
71
+ if (elapsedSeconds >= timeoutSeconds) {
72
+ throw new DetoxRuntimeException(
73
+ "" + timeoutSeconds + "sec timeout expired without matching of given matcher: " + viewMatcher);
59
74
  }
60
75
 
61
76
  try {
62
- i.check(matches(m));
77
+ viewInteraction.check(matches(viewMatcher));
63
78
  break;
64
79
  } catch (AssertionFailedError err) {
65
80
  UiAutomatorHelper.espressoSync(20);
@@ -67,21 +82,25 @@ public class DetoxAssertion {
67
82
  }
68
83
  }
69
84
 
85
+ /**
86
+ * Continually asserts the provided matcher until a search action returns a matching view or a
87
+ * `StaleActionException` error is thrown.
88
+ */
70
89
  public static void waitForAssertMatcherWithSearchAction(
71
- final ViewInteraction i,
72
- final Matcher<View> vm,
73
- final ViewAction searchAction,
74
- final Matcher<View> searchMatcher) {
75
-
90
+ final ViewInteraction viewInteraction,
91
+ final Matcher<View> viewMatcher,
92
+ final ViewAction searchAction,
93
+ final Matcher<View> searchMatcher
94
+ ) {
76
95
  while (true) {
77
96
  try {
78
- assertMatcher(i, vm);
97
+ assertMatcher(viewInteraction, viewMatcher);
79
98
  break;
80
99
  } catch (AssertionFailedError err) {
81
100
  try {
82
101
  onView(searchMatcher).perform(searchAction);
83
102
  } catch (StaleActionException exStaleAction) {
84
- assertMatcher(i, vm);
103
+ assertMatcher(viewInteraction, viewMatcher);
85
104
  break;
86
105
  }
87
106
  }
@@ -1,5 +1,7 @@
1
1
  package com.wix.detox.espresso;
2
2
 
3
+ import com.wix.detox.espresso.performer.ViewActionPerformer;
4
+
3
5
  import android.app.Activity;
4
6
  import android.content.Context;
5
7
  import android.content.ContextWrapper;
@@ -20,6 +22,7 @@ import java.util.ArrayList;
20
22
  import androidx.test.espresso.UiController;
21
23
  import androidx.test.espresso.ViewAction;
22
24
  import androidx.test.espresso.ViewInteraction;
25
+ import androidx.test.espresso.NoMatchingViewException;
23
26
  import androidx.test.platform.app.InstrumentationRegistry;
24
27
 
25
28
  import static androidx.test.espresso.Espresso.onView;
@@ -31,13 +34,9 @@ import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
31
34
  public class EspressoDetox {
32
35
  private static final String LOG_TAG = "detox";
33
36
 
34
- public static Object perform(ViewInteraction interaction, ViewAction action) {
35
- interaction.perform(action);
36
-
37
- if (action instanceof ViewActionWithResult) {
38
- return ((ViewActionWithResult) action).getResult();
39
- }
40
- return null;
37
+ public static Object perform(Matcher<View> matcher, ViewAction action) {
38
+ ViewActionPerformer performer = ViewActionPerformer.forAction(action);
39
+ return performer.performOn(matcher);
41
40
  }
42
41
 
43
42
  public static Activity getActivity(Context context) {
@@ -9,6 +9,7 @@ import android.widget.TextView
9
9
  import androidx.test.espresso.UiController
10
10
  import com.google.android.material.slider.Slider
11
11
  import com.wix.detox.espresso.ViewActionWithResult
12
+ import com.wix.detox.espresso.MultipleViewsAction
12
13
  import com.wix.detox.espresso.common.SliderHelper
13
14
  import com.wix.detox.reactnative.ui.getAccessibilityLabel
14
15
  import org.hamcrest.Matcher
@@ -17,26 +18,25 @@ import org.hamcrest.Matchers.allOf
17
18
  import org.hamcrest.Matchers.notNullValue
18
19
  import org.json.JSONObject
19
20
 
20
- class GetAttributesAction() : ViewActionWithResult<String?> {
21
+ class GetAttributesAction() : ViewActionWithResult<JSONObject?>, MultipleViewsAction {
21
22
  private val commonAttributes = CommonAttributes()
22
23
  private val textViewAttributes = TextViewAttributes()
23
24
  private val checkBoxAttributes = CheckBoxAttributes()
24
25
  private val progressBarAttributes = ProgressBarAttributes()
25
26
  private val sliderAttributes = SliderAttributes()
26
- private var result: String = ""
27
+ private var result: JSONObject? = null
27
28
 
28
29
  override fun perform(uiController: UiController?, view: View?) {
29
30
  view!!
30
31
 
31
32
  val json = JSONObject()
32
-
33
33
  commonAttributes.get(json, view)
34
34
  textViewAttributes.get(json, view)
35
35
  checkBoxAttributes.get(json, view)
36
36
  progressBarAttributes.get(json, view)
37
37
  sliderAttributes.get(json, view)
38
38
 
39
- result = json.toString()
39
+ result = json
40
40
  }
41
41
 
42
42
  override fun getResult() = result
@@ -0,0 +1,43 @@
1
+ package com.wix.detox.espresso.performer
2
+
3
+ import com.wix.detox.espresso.DetoxMatcher
4
+ import com.wix.detox.espresso.ViewActionWithResult
5
+
6
+ import android.view.View
7
+ import androidx.test.espresso.Espresso.onView
8
+ import androidx.test.espresso.NoMatchingViewException
9
+ import androidx.test.espresso.ViewAction
10
+ import org.hamcrest.Matcher
11
+
12
+ class MultipleViewsActionPerformer(
13
+ private val action: ViewAction
14
+ ) : ViewActionPerformer {
15
+ override fun performOn(matcher: Matcher<View>): Any? {
16
+ val results = mutableListOf<Any?>()
17
+ var index = 0
18
+
19
+ while (true) {
20
+ val indexedMatcher = DetoxMatcher.matcherForAtIndex(index, matcher)
21
+
22
+ try {
23
+ onView(indexedMatcher).perform(action)
24
+
25
+ (action as? ViewActionWithResult<*>)?.getResult()?.let { results.add(it) }
26
+
27
+ index++
28
+ } catch (e: NoMatchingViewException) {
29
+ if (index == 0) {
30
+ throw e
31
+ }
32
+
33
+ break
34
+ }
35
+ }
36
+
37
+ return when {
38
+ results.isEmpty() -> null
39
+ results.size == 1 -> results.first()
40
+ else -> mapOf("elements" to results)
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,19 @@
1
+ package com.wix.detox.espresso.performer
2
+
3
+ import com.wix.detox.espresso.ViewActionWithResult
4
+
5
+ import android.view.View
6
+ import androidx.test.espresso.Espresso.onView
7
+ import androidx.test.espresso.NoMatchingViewException
8
+ import androidx.test.espresso.ViewAction
9
+ import org.hamcrest.Matcher
10
+
11
+ class SingleViewActionPerformer(
12
+ private val action: ViewAction
13
+ ) : ViewActionPerformer {
14
+ override fun performOn(matcher: Matcher<View>): Any? {
15
+ onView(matcher).perform(action)
16
+
17
+ return (action as? ViewActionWithResult<*>)?.getResult()
18
+ }
19
+ }
@@ -0,0 +1,24 @@
1
+ package com.wix.detox.espresso.performer
2
+
3
+ import com.wix.detox.espresso.MultipleViewsAction
4
+
5
+ import android.view.View
6
+ import androidx.test.espresso.Espresso.onView
7
+ import androidx.test.espresso.NoMatchingViewException
8
+ import androidx.test.espresso.ViewAction
9
+ import org.hamcrest.Matcher
10
+
11
+ interface ViewActionPerformer {
12
+ fun performOn(matcher: Matcher<View>): Any?
13
+
14
+ companion object {
15
+ @JvmStatic
16
+ fun forAction(action: ViewAction): ViewActionPerformer {
17
+ return if (action is MultipleViewsAction) {
18
+ MultipleViewsActionPerformer(action)
19
+ } else {
20
+ SingleViewActionPerformer(action)
21
+ }
22
+ }
23
+ }
24
+ }
@@ -92,9 +92,9 @@ public class Invocation {
92
92
  } else if (type.equals("boolean")) {
93
93
  argument = jsonArgument.optBoolean("value");
94
94
  } else if (type.equals("Invocation")) {
95
- argument = new Invocation(jsonArgument.optJSONObject("value"));
95
+ argument = new Invocation(jsonArgument.optJSONObject("value"));
96
96
  } else {
97
- throw new RuntimeException("Unhandled arg type" + type);
97
+ throw new RuntimeException("Unhandled arg type " + type);
98
98
  }
99
99
  }
100
100
  }
@@ -105,6 +105,8 @@ public class Invocation {
105
105
  }
106
106
 
107
107
  public void setArgs(Object[] args) {
108
+ JsonParser parser = new JsonParser();
109
+
108
110
  for (int i = 0; i < args.length; i++) {
109
111
  Object argument = args[i];
110
112
  if (argument instanceof HashMap && !((HashMap) argument).isEmpty()) {
@@ -125,10 +127,9 @@ public class Invocation {
125
127
  } else if (type.equals("boolean")) {
126
128
  argument = ((Boolean) value).booleanValue();
127
129
  } else if (type.equals("Invocation")) {
128
- JsonParser parser = new JsonParser();
129
130
  argument = parser.parse((String)value);
130
131
  } else {
131
- throw new RuntimeException("Unhandled arg type" + type);
132
+ throw new RuntimeException("Unhandled arg type " + type);
132
133
  }
133
134
 
134
135
  args[i] = argument;
@@ -0,0 +1,4 @@
1
+ package com.wix.detox.espresso
2
+
3
+ // Marker interface for actions that should be applied to all matching elements without ambiguity.
4
+ interface MultipleViewsAction
@@ -1,4 +1,3 @@
1
-
2
1
  package com.wix.detox.espresso
3
2
 
4
3
  import androidx.test.espresso.UiController
@@ -2,6 +2,7 @@ package com.wix.detox.espresso
2
2
 
3
3
  import androidx.test.espresso.ViewAction
4
4
 
5
- interface ViewActionWithResult<R: Any?>: ViewAction {
5
+ // Interface for actions that return a result.
6
+ interface ViewActionWithResult<R: Any?> : ViewAction {
6
7
  fun getResult(): R
7
8
  }
@@ -37,7 +37,7 @@ class GetAttributesActionTest {
37
37
 
38
38
  private fun perform(v: View = view): JSONObject {
39
39
  uut.perform(null, v)
40
- return JSONObject(uut.getResult())
40
+ return uut.getResult()!!
41
41
  }
42
42
 
43
43
  @Test
@@ -135,10 +135,10 @@ class GetAttributesActionTest {
135
135
  }
136
136
 
137
137
  val resultJson = perform()
138
- assertThat(resultJson.opt("alpha")).isEqualTo(0.42)
138
+ assertThat(resultJson.opt("alpha")).isEqualTo(0.42f)
139
139
  assertThat(resultJson.opt("width")).isEqualTo(123)
140
140
  assertThat(resultJson.opt("height")).isEqualTo(456)
141
- assertThat(resultJson.opt("elevation")).isEqualTo(0.314)
141
+ assertThat(resultJson.opt("elevation")).isEqualTo(0.314f)
142
142
  }
143
143
 
144
144
  @Test
@@ -208,7 +208,8 @@ class GetAttributesActionTest {
208
208
  }
209
209
 
210
210
  val resultJson = perform(slider)
211
- assertThat(resultJson.opt("value")).isEqualTo(0.42)
211
+ android.util.Log.i("TESTS", "should return material-Slider state through value attribute: "+ resultJson)
212
+ assertThat(resultJson.opt("value")).isEqualTo(0.42f)
212
213
  }
213
214
 
214
215
  @Test
@@ -221,7 +222,7 @@ class GetAttributesActionTest {
221
222
 
222
223
  val resultJson = perform(textView)
223
224
  assertThat(resultJson.opt("text")).isEqualTo("mock-text")
224
- assertThat(resultJson.opt("textSize")).isEqualTo(24)
225
+ assertThat(resultJson.opt("textSize")).isEqualTo(24f)
225
226
  assertThat(resultJson.opt("length")).isEqualTo(111)
226
227
  }
227
228
 
@@ -0,0 +1,37 @@
1
+ package com.wix.detox.espresso.performer
2
+
3
+ import org.spekframework.spek2.Spek
4
+ import org.spekframework.spek2.style.specification.describe
5
+ import androidx.test.espresso.ViewAction
6
+ import com.wix.detox.espresso.MultipleViewsAction
7
+ import org.hamcrest.Matcher
8
+ import org.mockito.Mockito.*
9
+ import org.mockito.kotlin.mock
10
+
11
+ object ViewActionPerformerSpec : Spek({
12
+
13
+ describe("ViewActionPerformer") {
14
+ context("forAction") {
15
+ context("given a regular ViewAction") {
16
+ val action = mock(ViewAction::class.java)
17
+
18
+ it("should return a SingleViewActionPerformer") {
19
+ val performer = ViewActionPerformer.forAction(action)
20
+ assert(performer is SingleViewActionPerformer)
21
+ }
22
+ }
23
+
24
+ context("given a MultipleViewsAction") {
25
+ val multipleViewsAction: ViewAction = mock(
26
+ ViewAction::class.java,
27
+ withSettings().extraInterfaces(MultipleViewsAction::class.java)
28
+ )
29
+
30
+ it("should return a MultipleViewsActionPerformer") {
31
+ val performer = ViewActionPerformer.forAction(multipleViewsAction)
32
+ assert(performer is MultipleViewsActionPerformer)
33
+ }
34
+ }
35
+ }
36
+ }
37
+ })
package/index.d.ts CHANGED
@@ -1430,8 +1430,9 @@ declare global {
1430
1430
  takeScreenshot(name: string): Promise<string>;
1431
1431
 
1432
1432
  /**
1433
- * Gets the native (OS-dependent) attributes of the element.
1434
- * For more information, see {@link https://wix.github.io/Detox/docs/api/actions-on-element/#getattributes}
1433
+ * Retrieves the OS-dependent attributes of an element.
1434
+ * If there are multiple matches, it returns an array of attributes for all matched elements.
1435
+ * For detailed information, refer to {@link https://wix.github.io/Detox/docs/api/actions-on-element/#getattributes}
1435
1436
  *
1436
1437
  * @example
1437
1438
  * test('Get the attributes for my text element', async () => {
@@ -1445,7 +1446,7 @@ declare global {
1445
1446
  * jestExpect(attributes.width).toHaveValue(100);
1446
1447
  * })
1447
1448
  */
1448
- getAttributes(): Promise<IosElementAttributes | AndroidElementAttributes | { elements: IosElementAttributes[]; }>;
1449
+ getAttributes(): Promise<IosElementAttributes | AndroidElementAttributes | { elements: IosElementAttributes[] } | { elements: AndroidElementAttributes[] } >;
1449
1450
  }
1450
1451
 
1451
1452
  interface WebExpect<R = Promise<void>> {
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.11.5-smoke.0",
4
+ "version": "20.12.0",
5
5
  "bin": {
6
6
  "detox": "local-cli/cli.js"
7
7
  },
@@ -206,5 +206,5 @@
206
206
  "browserslist": [
207
207
  "node 14"
208
208
  ],
209
- "gitHead": "03762775a345289d7466a1163fb168797da1dc2b"
209
+ "gitHead": "8102c59b8bf62854f7767c68e35412eb981792d0"
210
210
  }
@@ -15,39 +15,37 @@ class NativeElement {
15
15
  constructor(invocationManager, emitter, matcher) {
16
16
  this._invocationManager = invocationManager;
17
17
  this._emitter = emitter;
18
- this._originalMatcher = matcher;
19
- this._selectElementWithMatcher(this._originalMatcher);
18
+ this._matcher = matcher;
20
19
  }
21
20
 
22
- _selectElementWithMatcher(matcher) {
23
- this._call = invoke.call(invoke.Espresso, 'onView', matcher._call);
21
+ get _call() {
22
+ return invoke.call(invoke.Espresso, 'onView', this._matcher._call);
24
23
  }
25
24
 
26
25
  atIndex(index) {
27
26
  if (typeof index !== 'number') throw new DetoxRuntimeError({ message: `Element atIndex argument must be a number, got ${typeof index}` });
28
- const matcher = this._originalMatcher;
29
- this._originalMatcher._call = invoke.callDirectly(DetoxMatcherApi.matcherForAtIndex(index, matcher._call.value));
27
+ const matcher = this._matcher;
28
+ this._matcher._call = invoke.callDirectly(DetoxMatcherApi.matcherForAtIndex(index, matcher._call.value));
30
29
 
31
- this._selectElementWithMatcher(this._originalMatcher);
32
30
  return this;
33
31
  }
34
32
 
35
33
  async tap(value) {
36
34
  const action = new actions.TapAction(value);
37
35
  const traceDescription = actionDescription.tapAtPoint(value);
38
- return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
36
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
39
37
  }
40
38
 
41
39
  async tapAtPoint(value) {
42
40
  const action = new actions.TapAtPointAction(value);
43
41
  const traceDescription = actionDescription.tapAtPoint(value);
44
- return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
42
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
45
43
  }
46
44
 
47
45
  async longPress() {
48
46
  const action = new actions.LongPressAction();
49
47
  const traceDescription = actionDescription.longPress();
50
- return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
48
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
51
49
  }
52
50
 
53
51
  async multiTap(times) {
@@ -56,61 +54,61 @@ class NativeElement {
56
54
 
57
55
  const action = new actions.MultiClickAction(times);
58
56
  const traceDescription = actionDescription.multiTap(times);
59
- return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
57
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
60
58
  }
61
59
 
62
60
  async tapBackspaceKey() {
63
61
  const action = new actions.PressKeyAction(67);
64
62
  const traceDescription = actionDescription.tapBackspaceKey();
65
- return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
63
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
66
64
  }
67
65
 
68
66
  async tapReturnKey() {
69
67
  const action = new actions.TypeTextAction('\n');
70
68
  const traceDescription = actionDescription.tapReturnKey();
71
- return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
69
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
72
70
  }
73
71
 
74
72
  async typeText(value) {
75
73
  const action = new actions.TypeTextAction(value);
76
74
  const traceDescription = actionDescription.typeText(value);
77
- return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
75
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
78
76
  }
79
77
 
80
78
  async replaceText(value) {
81
79
  const action = new actions.ReplaceTextAction(value);
82
80
  const traceDescription = actionDescription.replaceText(value);
83
- return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
81
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
84
82
  }
85
83
 
86
84
  async clearText() {
87
85
  const action = new actions.ClearTextAction();
88
86
  const traceDescription = actionDescription.clearText();
89
- return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
87
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
90
88
  }
91
89
 
92
90
  async scroll(amount, direction = 'down', startPositionX, startPositionY) {
93
91
  const action = new actions.ScrollAmountAction(direction, amount, startPositionX, startPositionY);
94
92
  const traceDescription = actionDescription.scroll(amount, direction, startPositionX, startPositionY);
95
- return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
93
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
96
94
  }
97
95
 
98
96
  async scrollTo(edge) {
99
97
  // override the user's element selection with an extended matcher that looks for UIScrollView children
100
- this._selectElementWithMatcher(this._originalMatcher._extendToDescendantScrollViews());
98
+ this._matcher = this._matcher._extendToDescendantScrollViews();
101
99
 
102
100
  const action = new actions.ScrollEdgeAction(edge);
103
101
  const traceDescription = actionDescription.scrollTo(edge);
104
- return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
102
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
105
103
  }
106
104
 
107
105
  async scrollToIndex(index) {
108
106
  // override the user's element selection with an extended matcher that looks for UIScrollView children
109
- this._selectElementWithMatcher(this._originalMatcher._extendToDescendantScrollViews());
107
+ this._matcher = this._matcher._extendToDescendantScrollViews();
110
108
 
111
109
  const action = new actions.ScrollToIndex(index);
112
110
  const traceDescription = actionDescription.scrollToIndex(index);
113
- return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
111
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
114
112
  }
115
113
 
116
114
  async setDatePickerDate(rawDateString, formatString) {
@@ -120,7 +118,7 @@ class NativeElement {
120
118
 
121
119
  const action = new actions.SetDatePickerDateAction(dateString, formatString);
122
120
  const traceDescription = actionDescription.setDatePickerDate(dateString, formatString);
123
- return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
121
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
124
122
  }
125
123
 
126
124
  /**
@@ -134,18 +132,18 @@ class NativeElement {
134
132
  normalizedSwipeOffset = Number.isNaN(normalizedSwipeOffset) ? 0.75 : normalizedSwipeOffset;
135
133
 
136
134
  // override the user's element selection with an extended matcher that avoids RN issues with RCTScrollView
137
- this._selectElementWithMatcher(this._originalMatcher._avoidProblematicReactNativeElements());
135
+ this._matcher = this._matcher._avoidProblematicReactNativeElements();
138
136
 
139
137
  const action = new actions.SwipeAction(direction, speed, normalizedSwipeOffset, normalizedStartingPointX, normalizedStartingPointY);
140
138
  const traceDescription = actionDescription.swipe(direction, speed, normalizedSwipeOffset, normalizedStartingPointX, normalizedStartingPointY);
141
- return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
139
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
142
140
  }
143
141
 
144
142
  async takeScreenshot(screenshotName) {
145
143
  // TODO this should be moved to a lower-layer handler of this use-case
146
144
  const action = new actions.TakeElementScreenshot();
147
145
  const traceDescription = actionDescription.takeScreenshot(screenshotName);
148
- const resultBase64 = await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
146
+ const resultBase64 = await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
149
147
  const filePath = tempfile('detox.element-screenshot.png');
150
148
  await fs.writeFile(filePath, resultBase64, 'base64');
151
149
 
@@ -160,19 +158,18 @@ class NativeElement {
160
158
  async getAttributes() {
161
159
  const action = new actions.GetAttributes();
162
160
  const traceDescription = actionDescription.getAttributes();
163
- const result = await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
164
- return JSON.parse(result);
161
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
165
162
  }
166
163
 
167
164
  async adjustSliderToPosition(newPosition) {
168
165
  const action = new actions.AdjustSliderToPosition(newPosition);
169
166
  const traceDescription = actionDescription.adjustSliderToPosition(newPosition);
170
- return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute();
167
+ return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
171
168
  }
172
169
 
173
170
  async performAccessibilityAction(actionName) {
174
171
  const traceDescription = actionDescription.performAccessibilityAction(actionName);
175
- return await new ActionInteraction(this._invocationManager, this, new actions.AccessibilityActionAction(actionName), traceDescription).execute();
172
+ return await new ActionInteraction(this._invocationManager, this._matcher, new actions.AccessibilityActionAction(actionName), traceDescription).execute();
176
173
  }
177
174
  }
178
175
 
@@ -14,7 +14,7 @@ function sanitize_matcher(matcher) {
14
14
  return originalMatcher.type ? originalMatcher.value : originalMatcher;
15
15
  }
16
16
  class DetoxAssertion {
17
- static assertMatcher(i, m) {
17
+ static assertMatcher(viewInteraction, viewMatcher) {
18
18
  return {
19
19
  target: {
20
20
  type: "Class",
@@ -23,15 +23,15 @@ class DetoxAssertion {
23
23
  method: "assertMatcher",
24
24
  args: [{
25
25
  type: "Invocation",
26
- value: i
26
+ value: viewInteraction
27
27
  }, {
28
28
  type: "Invocation",
29
- value: sanitize_matcher(m)
29
+ value: sanitize_matcher(viewMatcher)
30
30
  }]
31
31
  };
32
32
  }
33
33
 
34
- static assertNotVisible(i) {
34
+ static assertNotVisible(viewInteraction) {
35
35
  return {
36
36
  target: {
37
37
  type: "Class",
@@ -40,12 +40,12 @@ class DetoxAssertion {
40
40
  method: "assertNotVisible",
41
41
  args: [{
42
42
  type: "Invocation",
43
- value: i
43
+ value: viewInteraction
44
44
  }]
45
45
  };
46
46
  }
47
47
 
48
- static assertNotExists(i) {
48
+ static assertNotExists(viewInteraction) {
49
49
  return {
50
50
  target: {
51
51
  type: "Class",
@@ -54,12 +54,12 @@ class DetoxAssertion {
54
54
  method: "assertNotExists",
55
55
  args: [{
56
56
  type: "Invocation",
57
- value: i
57
+ value: viewInteraction
58
58
  }]
59
59
  };
60
60
  }
61
61
 
62
- static waitForAssertMatcher(i, m, timeoutSeconds) {
62
+ static waitForAssertMatcher(viewInteraction, viewMatcher, timeoutSeconds) {
63
63
  if (typeof timeoutSeconds !== "number") throw new Error("timeoutSeconds should be a number, but got " + (timeoutSeconds + (" (" + (typeof timeoutSeconds + ")"))));
64
64
  return {
65
65
  target: {
@@ -69,10 +69,10 @@ class DetoxAssertion {
69
69
  method: "waitForAssertMatcher",
70
70
  args: [{
71
71
  type: "Invocation",
72
- value: i
72
+ value: viewInteraction
73
73
  }, {
74
74
  type: "Invocation",
75
- value: sanitize_matcher(m)
75
+ value: sanitize_matcher(viewMatcher)
76
76
  }, {
77
77
  type: "Double",
78
78
  value: timeoutSeconds
@@ -80,7 +80,8 @@ class DetoxAssertion {
80
80
  };
81
81
  }
82
82
 
83
- static waitForAssertMatcherWithSearchAction(i, vm, searchAction, searchMatcher) {
83
+ static waitForAssertMatcherWithSearchAction(viewInteraction, viewMatcher, searchAction, searchMatcher
84
+ ) {
84
85
  return {
85
86
  target: {
86
87
  type: "Class",
@@ -89,13 +90,14 @@ class DetoxAssertion {
89
90
  method: "waitForAssertMatcherWithSearchAction",
90
91
  args: [{
91
92
  type: "Invocation",
92
- value: i
93
+ value: viewInteraction
93
94
  }, {
94
95
  type: "Invocation",
95
- value: sanitize_matcher(vm)
96
+ value: sanitize_matcher(viewMatcher)
96
97
  }, searchAction, {
97
98
  type: "Invocation",
98
- value: sanitize_matcher(searchMatcher)
99
+ value: sanitize_matcher(searchMatcher
100
+ )
99
101
  }]
100
102
  };
101
103
  }
@@ -5,9 +5,16 @@
5
5
  */
6
6
 
7
7
 
8
+ function sanitize_matcher(matcher) {
9
+ if (!matcher._call) {
10
+ return matcher;
11
+ }
8
12
 
13
+ const originalMatcher = typeof matcher._call === 'function' ? matcher._call() : matcher._call;
14
+ return originalMatcher.type ? originalMatcher.value : originalMatcher;
15
+ }
9
16
  class EspressoDetox {
10
- static perform(interaction, action) {
17
+ static perform(matcher, action) {
11
18
  return {
12
19
  target: {
13
20
  type: "Class",
@@ -16,7 +23,7 @@ class EspressoDetox {
16
23
  method: "perform",
17
24
  args: [{
18
25
  type: "Invocation",
19
- value: interaction
26
+ value: sanitize_matcher(matcher)
20
27
  }, action]
21
28
  };
22
29
  }
@@ -25,9 +25,9 @@ class Interaction {
25
25
  }
26
26
 
27
27
  class ActionInteraction extends Interaction {
28
- constructor(invocationManager, element, action, traceDescription) {
28
+ constructor(invocationManager, matcher, action, traceDescription) {
29
29
  super(invocationManager, traceDescription);
30
- this._call = EspressoDetoxApi.perform(call(element._call), action._call);
30
+ this._call = EspressoDetoxApi.perform(matcher, action._call);
31
31
  // TODO: move this.execute() here from the caller
32
32
  }
33
33
  }
@@ -48,7 +48,6 @@ class WaitForInteraction extends Interaction {
48
48
  super(invocationManager, expectTraceDescription);
49
49
  this._element = element;
50
50
  this._assertionMatcher = assertionMatcher;
51
- this._element._selectElementWithMatcher(this._element._originalMatcher);
52
51
  }
53
52
 
54
53
  async withTimeout(timeout) {
@@ -1 +0,0 @@
1
- 235483fd76fb7161631a1afc88d3f9d1
@@ -1 +0,0 @@
1
- 89144d2a6b22b7ebfe93beda4a6cf75fc57bda9f
@@ -1 +0,0 @@
1
- ac3fb0fbc41c393e18b1e7ae39a220083248259c7f3323dd6050ee077f9f83b3
@@ -1 +0,0 @@
1
- 20d07161d52ebed5ba3b1794b999db267b5b2884f0da5682a6eb85eaebabcbbfeee5a6dee2900f18050c947f0a24aadebc2a716539f96c1e18ae1f4126ebf3fc
@@ -1 +0,0 @@
1
- edfe514aaecdd6062dbe420a987009cb
@@ -1 +0,0 @@
1
- f21c0c0f7af9925dc3b4d3c632bfb3eae0c79639
@@ -1 +0,0 @@
1
- d6f76fa01c04a511378b7b8f6f2dbca4d0e99ecc272f4af3a26056e62dcf80f6
@@ -1 +0,0 @@
1
- c87f17d07fe06d93c080e1e89a10a879d503984998a95f125042f712530f20f8aed49c1ebc345508d3c89e35f2d955cac942d51b9d174be0be0b3ec820392fd7
@@ -1 +0,0 @@
1
- e1d6c5353b93ff848864f887b4763050
@@ -1 +0,0 @@
1
- 809426e1ab6811b1e6a727fdcd22bbbf20f89b02
@@ -1 +0,0 @@
1
- dadebda11f4a94f751e23277fadbbaef518955f72ba353a5ae5f52ef1ba9acde
@@ -1 +0,0 @@
1
- ba77a84e27f54bc932e73fac36cf2aa3b2a45fc51975088af59632256b214365fb1decbb77c117e5889013b10e4dc110e074100ac439b201e58ffd7333aa370e
@@ -1 +0,0 @@
1
- 64c43d4e57385c7226222c464fdc849c
@@ -1 +0,0 @@
1
- 3e2cf8fb12f021efee7ea5c2c223f50882e3dc84
@@ -1 +0,0 @@
1
- d05e592024d3ea7c74f749f164fb99ad2f953e545d6cc5526c087342f95eceb5
@@ -1 +0,0 @@
1
- 58f4e355f0cb537010ea3caa78857445fbfcd47e5c41c990541243cba24f2818d8ab45596e2ddb2c741d0fd65c4f1eaee716e75586c3c4c37a5b6b22e7ec2fa3