detox 20.5.0 → 20.7.0
Sign up to get free protection for your applications and to get access to all the features.
- package/Detox-android/com/wix/detox/{20.5.0/detox-20.5.0-javadoc.jar → 20.7.0/detox-20.7.0-javadoc.jar} +0 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0-javadoc.jar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0-javadoc.jar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0-javadoc.jar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0-javadoc.jar.sha512 +1 -0
- package/Detox-android/com/wix/detox/{20.5.0/detox-20.5.0-sources.jar → 20.7.0/detox-20.7.0-sources.jar} +0 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0-sources.jar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0-sources.jar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0-sources.jar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0-sources.jar.sha512 +1 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0.aar +0 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0.aar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0.aar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0.aar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0.aar.sha512 +1 -0
- package/Detox-android/com/wix/detox/{20.5.0/detox-20.5.0.pom → 20.7.0/detox-20.7.0.pom} +1 -1
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0.pom.md5 +1 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0.pom.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0.pom.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.7.0/detox-20.7.0.pom.sha512 +1 -0
- package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
- package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
- package/Detox-ios-src.tbz +0 -0
- package/Detox-ios.tbz +0 -0
- package/android/detox/src/full/java/com/wix/detox/common/UIExtensions.kt +28 -0
- package/android/detox/src/full/java/com/wix/detox/espresso/DetoxMatcher.java +11 -1
- package/android/detox/src/full/java/com/wix/detox/espresso/action/GetAttributesAction.kt +4 -3
- package/android/detox/src/full/java/com/wix/detox/espresso/matcher/ViewMatchers.kt +8 -5
- package/android/detox/src/full/java/com/wix/detox/espresso/matcher/WithAccessibilityLabelMatcher.kt +23 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/ui/UIExtensions.kt +37 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/utils/RNUtils.kt +6 -0
- package/android/detox/src/testFull/java/com/wix/detox/UTHelpers.kt +12 -0
- package/android/detox/src/testFull/java/com/wix/detox/common/UIExtensionsTest.kt +107 -0
- package/android/detox/src/testFull/java/com/wix/detox/espresso/action/GetAttributesActionTest.kt +7 -6
- package/android/detox/src/testFull/java/com/wix/detox/espresso/matcher/ViewAtIndexMatcherSpec.kt +1 -2
- package/index.d.ts +24 -21
- package/package.json +3 -3
- package/runners/jest/testEnvironment/listeners/SpecReporter.js +1 -1
- package/runners/jest/testEnvironment/listeners/WorkerAssignReporter.js +1 -1
- package/src/DetoxWorker.js +6 -1
- package/src/android/core/NativeMatcher.js +17 -0
- package/src/android/espressoapi/DetoxMatcher.js +24 -0
- package/src/android/matchers/index.js +2 -2
- package/src/android/matchers/native.js +9 -1
- package/src/devices/allocation/DeviceAllocator.js +13 -1
- package/src/devices/allocation/drivers/AllocationDriverBase.js +6 -0
- package/src/devices/allocation/drivers/android/attached/AttachedAndroidAllocDriver.js +10 -1
- package/src/devices/allocation/drivers/android/emulator/EmulatorAllocDriver.js +27 -29
- package/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.js +27 -19
- package/src/devices/allocation/drivers/ios/SimulatorAllocDriver.js +12 -7
- package/src/logger/DetoxLogger.js +5 -5
- package/src/utils/Timer.js +6 -0
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0-javadoc.jar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0-javadoc.jar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0-javadoc.jar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0-javadoc.jar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0-sources.jar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0-sources.jar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0-sources.jar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0-sources.jar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0.aar +0 -0
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0.aar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0.aar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0.aar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0.aar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0.pom.md5 +0 -1
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0.pom.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0.pom.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.5.0/detox-20.5.0.pom.sha512 +0 -1
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
f81d27f94760c9593fb2465e3e85b8c6
|
@@ -0,0 +1 @@
|
|
1
|
+
d54cd363e0644bcd2c5e80d38aea9951ed5a5875
|
@@ -0,0 +1 @@
|
|
1
|
+
8a069a0d9b81a4da63c1669dda4132884116d1fd59535b9f2042a2de1d2192fb
|
@@ -0,0 +1 @@
|
|
1
|
+
b5b1b636a1a188f0fbab9fdcea0778bc7843c32b11f3d7f4723de639d44cf2954e94f4724e86a04bc25e93701aea54210786f36a97804c514b6c37c54cc7c694
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
1d700c36ee5a9f920dc9b11b8aaba734
|
@@ -0,0 +1 @@
|
|
1
|
+
cde0211d368551547ba536f6d48f82701f0cb54d
|
@@ -0,0 +1 @@
|
|
1
|
+
098e816f3ba9c3a19cfdaa7a3a42d6282d2fce21f311388503f1cac1677af4a7
|
@@ -0,0 +1 @@
|
|
1
|
+
7a5d1df7fcb8894f17f4e1ecd502c977369bcfb83f1f86c4df4d53f05517f710a49fe0a1bb417ee621027f8effc8344d6d0ceb5d8b5f46f9caa739f8cbfb2d3f
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
f62ab63d03ba71b4776965696f3d4a88
|
@@ -0,0 +1 @@
|
|
1
|
+
1a38f931edf9925af1d131a0e0a0822bd880dcc8
|
@@ -0,0 +1 @@
|
|
1
|
+
4145b5a8078b613e10205937ffb3da254d006fe78fbb169d8b2cedb3d02c021c
|
@@ -0,0 +1 @@
|
|
1
|
+
ab25979faccc1aa8200a637ea866a5fc59e35bf340170673a01253ae8ce479fb5b7af87167dbde2d43d5b536f3fb350b34b478bbf75f5e87d5276940283a8ab6
|
@@ -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.7.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
|
+
5613ab9eb27dcbb4d5e7779c2137b156
|
@@ -0,0 +1 @@
|
|
1
|
+
622796a4e65be99d5fc209fb47cf2f8dd11f0c6b
|
@@ -0,0 +1 @@
|
|
1
|
+
6a3a55e38f2a0f92863399c78071ba55c8ccfdd8fae13e668f13a52f1d8a20a1
|
@@ -0,0 +1 @@
|
|
1
|
+
19988e0ea2b71c2283a48de7a4a35ac043ddba4629f9db146fe320392f8961d51e1ea570dde8c282e7a191fec69683321f6db223a7bb663e7b2c950deff3dc7f
|
@@ -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.7.0</latest>
|
7
|
+
<release>20.7.0</release>
|
8
8
|
<versions>
|
9
|
-
<version>20.
|
9
|
+
<version>20.7.0</version>
|
10
10
|
</versions>
|
11
|
-
<lastUpdated>
|
11
|
+
<lastUpdated>20230411123945</lastUpdated>
|
12
12
|
</versioning>
|
13
13
|
</metadata>
|
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
9e29832400056f30d8baa679804465e1
|
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
95431bbe6516e3f812d80a6fa2a874591f4db65b
|
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
8ced38de926b73ba9b74dd7ffe47c7775885527060e97544bf37e0d8edf847f9
|
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
25c96c6db588afca21ce6a630e25c8cb619f8fa7967ad641fd0f6811d243e736ac3986b4828773d9152cd7f789fca65308fd6f8094f52822d6edff5674719afb
|
package/Detox-ios-src.tbz
CHANGED
Binary file
|
package/Detox-ios.tbz
CHANGED
Binary file
|
@@ -0,0 +1,28 @@
|
|
1
|
+
package com.wix.detox.common
|
2
|
+
|
3
|
+
import android.view.View
|
4
|
+
import android.view.ViewGroup
|
5
|
+
|
6
|
+
fun View.forEachChild(callback: (child: View) -> Unit) {
|
7
|
+
if (this is ViewGroup) {
|
8
|
+
for (index in 0 until childCount) {
|
9
|
+
val child = getChildAt(index)
|
10
|
+
callback(child)
|
11
|
+
}
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
/**
|
16
|
+
* In-order traverse the view-hierarchy specified by a view, considered to be the hierarchy's root.
|
17
|
+
*
|
18
|
+
* @param view The hierarchy's root-view.
|
19
|
+
* @param callback A function to call per each view. Returning `false` from the callback indicates
|
20
|
+
* a request to refrain from traversing the sub-hierarchy associated with the current view.
|
21
|
+
*/
|
22
|
+
fun traverseViewHierarchy(view: View, callback: (view: View) -> Boolean) {
|
23
|
+
if (callback(view)) {
|
24
|
+
view.forEachChild { child ->
|
25
|
+
traverseViewHierarchy(child, callback)
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
@@ -12,15 +12,17 @@ import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
|
|
12
12
|
import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
|
13
13
|
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
14
14
|
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
|
15
|
+
import static androidx.test.espresso.matcher.ViewMatchers.isFocused;
|
15
16
|
import static androidx.test.espresso.matcher.ViewMatchers.isNotChecked;
|
16
17
|
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
|
17
18
|
import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
|
18
19
|
import static androidx.test.espresso.matcher.ViewMatchers.withTagValue;
|
19
20
|
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
20
|
-
import static androidx.test.espresso.matcher.ViewMatchers.isFocused;
|
21
21
|
import static com.wix.detox.espresso.matcher.ViewMatchers.isMatchingAtIndex;
|
22
22
|
import static com.wix.detox.espresso.matcher.ViewMatchers.isOfClassName;
|
23
23
|
import static com.wix.detox.espresso.matcher.ViewMatchers.toHaveSliderPosition;
|
24
|
+
import static com.wix.detox.espresso.matcher.ViewMatchers.withAccessibilityLabel;
|
25
|
+
import static com.wix.detox.espresso.matcher.ViewMatchers.withShallowAccessibilityLabel;
|
24
26
|
import static org.hamcrest.Matchers.allOf;
|
25
27
|
import static org.hamcrest.Matchers.anyOf;
|
26
28
|
import static org.hamcrest.Matchers.is;
|
@@ -43,6 +45,14 @@ public class DetoxMatcher {
|
|
43
45
|
return allOf(withText(text), withEffectiveVisibility(Visibility.VISIBLE));
|
44
46
|
}
|
45
47
|
|
48
|
+
public static Matcher<View> matcherForAccessibilityLabel(String label) {
|
49
|
+
return allOf(withAccessibilityLabel(label), withEffectiveVisibility(Visibility.VISIBLE));
|
50
|
+
}
|
51
|
+
|
52
|
+
public static Matcher<View> matcherForShallowAccessibilityLabel(String label) {
|
53
|
+
return allOf(withShallowAccessibilityLabel(label), withEffectiveVisibility(Visibility.VISIBLE));
|
54
|
+
}
|
55
|
+
|
46
56
|
public static Matcher<View> matcherForContentDescription(String contentDescription) {
|
47
57
|
return allOf(withContentDescription(contentDescription), withEffectiveVisibility(Visibility.VISIBLE));
|
48
58
|
}
|
@@ -10,6 +10,7 @@ import androidx.test.espresso.UiController
|
|
10
10
|
import com.google.android.material.slider.Slider
|
11
11
|
import com.wix.detox.espresso.ViewActionWithResult
|
12
12
|
import com.wix.detox.espresso.common.SliderHelper
|
13
|
+
import com.wix.detox.reactnative.ui.getAccessibilityLabel
|
13
14
|
import org.hamcrest.Matcher
|
14
15
|
import org.hamcrest.Matchers
|
15
16
|
import org.hamcrest.Matchers.allOf
|
@@ -47,7 +48,7 @@ private class CommonAttributes {
|
|
47
48
|
fun get(json: JSONObject, view: View) {
|
48
49
|
getId(json, view)
|
49
50
|
getVisibility(json, view)
|
50
|
-
|
51
|
+
getAccessibilityLabel(json, view)
|
51
52
|
getAlpha(json, view)
|
52
53
|
getElevation(json, view)
|
53
54
|
getHeight(json, view)
|
@@ -66,8 +67,8 @@ private class CommonAttributes {
|
|
66
67
|
json.put("visible", view.getLocalVisibleRect(Rect()))
|
67
68
|
}
|
68
69
|
|
69
|
-
private fun
|
70
|
-
view.
|
70
|
+
private fun getAccessibilityLabel(json: JSONObject, view: View) =
|
71
|
+
view.getAccessibilityLabel()?.let {
|
71
72
|
json.put("label", it)
|
72
73
|
}
|
73
74
|
|
@@ -8,17 +8,20 @@ import androidx.test.espresso.matcher.BoundedMatcher
|
|
8
8
|
import androidx.test.espresso.matcher.ViewMatchers
|
9
9
|
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
|
10
10
|
import com.wix.detox.espresso.common.SliderHelper
|
11
|
-
import org.hamcrest
|
12
|
-
import org.hamcrest.
|
13
|
-
import org.hamcrest.Matcher
|
14
|
-
import org.hamcrest.Matchers.allOf
|
15
|
-
import org.hamcrest.TypeSafeMatcher
|
11
|
+
import org.hamcrest.*
|
12
|
+
import org.hamcrest.Matchers.*
|
16
13
|
import kotlin.math.abs
|
17
14
|
|
18
15
|
/*
|
19
16
|
* An extension of [androidx.test.espresso.matcher.ViewMatchers].
|
20
17
|
*/
|
21
18
|
|
19
|
+
fun withAccessibilityLabel(text: String) =
|
20
|
+
WithAccessibilityLabelMatcher(`is`(text))
|
21
|
+
|
22
|
+
fun withShallowAccessibilityLabel(label: String): Matcher<View>
|
23
|
+
= anyOf(ViewMatchers.withContentDescription(label), ViewMatchers.withText(label))
|
24
|
+
|
22
25
|
fun isOfClassName(className: String): Matcher<View> {
|
23
26
|
try {
|
24
27
|
val cls = Class.forName(className)
|
package/android/detox/src/full/java/com/wix/detox/espresso/matcher/WithAccessibilityLabelMatcher.kt
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
package com.wix.detox.espresso.matcher
|
2
|
+
|
3
|
+
import android.view.View
|
4
|
+
import com.wix.detox.reactnative.ui.getAccessibilityLabel
|
5
|
+
import org.hamcrest.Description
|
6
|
+
import org.hamcrest.Matcher
|
7
|
+
import org.hamcrest.TypeSafeDiagnosingMatcher
|
8
|
+
|
9
|
+
class WithAccessibilityLabelMatcher(private val textMatcher: Matcher<String>): TypeSafeDiagnosingMatcher<View>() {
|
10
|
+
override fun matchesSafely(view: View, mismatchDescription: Description): Boolean =
|
11
|
+
view.getAccessibilityLabel().let { contentDescription ->
|
12
|
+
return textMatcher.matches(contentDescription).also { matched ->
|
13
|
+
if (!matched) {
|
14
|
+
mismatchDescription.appendText("view.getAccessibilityLabel() ")
|
15
|
+
textMatcher.describeMismatch(contentDescription, mismatchDescription)
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
override fun describeTo(description: Description) {
|
21
|
+
description.appendText("view.getAccessibilityLabel() ").appendDescriptionOf(textMatcher)
|
22
|
+
}
|
23
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
package com.wix.detox.reactnative.ui
|
2
|
+
|
3
|
+
import android.view.View
|
4
|
+
import android.widget.TextView
|
5
|
+
import com.wix.detox.common.traverseViewHierarchy
|
6
|
+
import com.wix.detox.reactnative.utils.isReactNativeObject
|
7
|
+
|
8
|
+
fun View.getAccessibilityLabel(
|
9
|
+
isReactNativeObjectFn: (Any) -> Boolean = { isReactNativeObject(it) }
|
10
|
+
): CharSequence? =
|
11
|
+
if (isReactNativeObjectFn(this)) {
|
12
|
+
val subLabels = collectAccessibilityLabelsFromHierarchy(this)
|
13
|
+
if (subLabels.isEmpty()) null else subLabels.joinToString(" ")
|
14
|
+
} else {
|
15
|
+
getRawAccessibilityLabel(this)
|
16
|
+
}
|
17
|
+
|
18
|
+
private fun collectAccessibilityLabelsFromHierarchy(
|
19
|
+
rootView: View,
|
20
|
+
subLabels: MutableList<CharSequence> = mutableListOf(),
|
21
|
+
): List<CharSequence> {
|
22
|
+
traverseViewHierarchy(rootView) { view ->
|
23
|
+
getRawAccessibilityLabel(view)?.let { rawLabel ->
|
24
|
+
subLabels.add(rawLabel)
|
25
|
+
false
|
26
|
+
} ?: true
|
27
|
+
|
28
|
+
}
|
29
|
+
return subLabels
|
30
|
+
}
|
31
|
+
|
32
|
+
private fun getRawAccessibilityLabel(view: View): CharSequence? =
|
33
|
+
if (view.contentDescription != null) {
|
34
|
+
view.contentDescription
|
35
|
+
} else if (view is TextView) {
|
36
|
+
view.text
|
37
|
+
} else null
|
@@ -1,8 +1,20 @@
|
|
1
1
|
package com.wix.detox
|
2
2
|
|
3
|
+
import android.view.View
|
4
|
+
import android.view.ViewGroup
|
5
|
+
import org.mockito.ArgumentMatchers.eq
|
6
|
+
import org.mockito.kotlin.whenever
|
3
7
|
import java.util.concurrent.ExecutorService
|
4
8
|
import java.util.concurrent.TimeUnit
|
5
9
|
|
6
10
|
object UTHelpers {
|
7
11
|
fun yieldToOtherThreads(executor: ExecutorService) = executor.awaitTermination(100L, TimeUnit.MILLISECONDS)
|
12
|
+
|
13
|
+
fun mockViewHierarchy(parent: ViewGroup, vararg children: View) {
|
14
|
+
whenever(parent.childCount).thenReturn(children.size)
|
15
|
+
|
16
|
+
children.forEachIndexed { index, view ->
|
17
|
+
whenever(parent.getChildAt(eq(index))).thenReturn(view)
|
18
|
+
}
|
19
|
+
}
|
8
20
|
}
|
@@ -0,0 +1,107 @@
|
|
1
|
+
package com.wix.detox.common
|
2
|
+
|
3
|
+
import android.view.View
|
4
|
+
import android.view.ViewGroup
|
5
|
+
import android.widget.TextView
|
6
|
+
import com.wix.detox.UTHelpers.mockViewHierarchy
|
7
|
+
import com.wix.detox.reactnative.ui.getAccessibilityLabel
|
8
|
+
import org.assertj.core.api.Assertions
|
9
|
+
import org.junit.Test
|
10
|
+
import org.junit.runner.RunWith
|
11
|
+
import org.mockito.kotlin.doReturn
|
12
|
+
import org.mockito.kotlin.mock
|
13
|
+
import org.mockito.kotlin.whenever
|
14
|
+
import org.robolectric.RobolectricTestRunner
|
15
|
+
|
16
|
+
@RunWith(RobolectricTestRunner::class)
|
17
|
+
class UIExtensionsTest {
|
18
|
+
private val alwaysReactNativeObjFn: (Any) -> Boolean = { true }
|
19
|
+
private val neverReactNativeObjFn: (Any) -> Boolean = { false }
|
20
|
+
|
21
|
+
private fun withContentDescription(value: String, v: View) { whenever(v.contentDescription).doReturn(value) }
|
22
|
+
private fun withText(value: String, v: TextView) { whenever(v.text).doReturn(value) }
|
23
|
+
|
24
|
+
@Test
|
25
|
+
fun `should return accessibility label according to content-description`() {
|
26
|
+
val view: View = mock()
|
27
|
+
|
28
|
+
val contentDescription = "content-description-mock"
|
29
|
+
withContentDescription(contentDescription, view)
|
30
|
+
|
31
|
+
val label = view.getAccessibilityLabel()
|
32
|
+
Assertions.assertThat(label).isEqualTo(contentDescription)
|
33
|
+
}
|
34
|
+
|
35
|
+
@Test
|
36
|
+
fun `should return accessibility label according to children's content-description, recursively`() {
|
37
|
+
val contentDescription1st = "cd.1"
|
38
|
+
val contentDescription2nd = "cd.2"
|
39
|
+
val expectedLabel = "$contentDescription1st $contentDescription2nd"
|
40
|
+
|
41
|
+
|
42
|
+
val parent: ViewGroup = mock()
|
43
|
+
val sibling1: ViewGroup = mock()
|
44
|
+
val sibling2: ViewGroup = mock<ViewGroup>().also {
|
45
|
+
withContentDescription(contentDescription2nd, it)
|
46
|
+
}
|
47
|
+
val grandchild: View = mock<View>().also {
|
48
|
+
withContentDescription(contentDescription1st, it)
|
49
|
+
}
|
50
|
+
|
51
|
+
mockViewHierarchy(parent, sibling1, sibling2)
|
52
|
+
mockViewHierarchy(sibling1, grandchild)
|
53
|
+
|
54
|
+
val label = parent.getAccessibilityLabel(alwaysReactNativeObjFn)
|
55
|
+
Assertions.assertThat(label).isEqualTo(expectedLabel)
|
56
|
+
}
|
57
|
+
|
58
|
+
@Test
|
59
|
+
fun `should return accessibility label according to children's text, on top of label`() {
|
60
|
+
val text = "some mocked text"
|
61
|
+
|
62
|
+
val parent: ViewGroup = mock()
|
63
|
+
val grandchild: TextView = mock<TextView>().also {
|
64
|
+
withText(text, it)
|
65
|
+
}
|
66
|
+
mockViewHierarchy(parent, grandchild)
|
67
|
+
|
68
|
+
val label = parent.getAccessibilityLabel(alwaysReactNativeObjFn)
|
69
|
+
Assertions.assertThat(label).isEqualTo(text)
|
70
|
+
}
|
71
|
+
|
72
|
+
@Test
|
73
|
+
fun `should not return accessibility label if content description not set in view nor its descendants`() {
|
74
|
+
val parent: ViewGroup = mock()
|
75
|
+
val child: View = mock()
|
76
|
+
|
77
|
+
mockViewHierarchy(parent, child)
|
78
|
+
|
79
|
+
val label = parent.getAccessibilityLabel(alwaysReactNativeObjFn)
|
80
|
+
Assertions.assertThat(label).isNull()
|
81
|
+
}
|
82
|
+
|
83
|
+
@Test
|
84
|
+
fun `should not return accessibility label based on children for non-RN views`() {
|
85
|
+
val childContentDescription = "content-description-mock"
|
86
|
+
|
87
|
+
val parent: ViewGroup = mock()
|
88
|
+
val child: View = mock<View>().also {
|
89
|
+
withContentDescription(childContentDescription, it)
|
90
|
+
}
|
91
|
+
mockViewHierarchy(parent, child)
|
92
|
+
|
93
|
+
val label = parent.getAccessibilityLabel(neverReactNativeObjFn)
|
94
|
+
Assertions.assertThat(label).isNull()
|
95
|
+
}
|
96
|
+
|
97
|
+
@Test
|
98
|
+
fun `should return accessibility label for non-RN views`() {
|
99
|
+
val view: View = mock()
|
100
|
+
|
101
|
+
val contentDescription = "content-description-mock"
|
102
|
+
withContentDescription(contentDescription, view)
|
103
|
+
|
104
|
+
val label = view.getAccessibilityLabel(neverReactNativeObjFn)
|
105
|
+
Assertions.assertThat(label).isEqualTo(contentDescription)
|
106
|
+
}
|
107
|
+
}
|
package/android/detox/src/testFull/java/com/wix/detox/espresso/action/GetAttributesActionTest.kt
CHANGED
@@ -6,6 +6,7 @@ import android.widget.ProgressBar
|
|
6
6
|
import android.widget.TextView
|
7
7
|
import com.facebook.react.views.slider.ReactSlider
|
8
8
|
import com.google.android.material.slider.Slider
|
9
|
+
import com.wix.detox.reactnative.ui.getAccessibilityLabel
|
9
10
|
import org.assertj.core.api.Assertions.assertThat
|
10
11
|
import org.json.JSONObject
|
11
12
|
import org.junit.Before
|
@@ -32,7 +33,7 @@ class GetAttributesActionTest {
|
|
32
33
|
private fun givenNoViewTag() = givenViewTag(null)
|
33
34
|
private fun givenVisibility(value: Int) { whenever(view.visibility).doReturn(value) }
|
34
35
|
private fun givenVisibilityRectAvailability(value: Boolean) { whenever(view.getLocalVisibleRect(any())).doReturn(value) }
|
35
|
-
private fun
|
36
|
+
private fun givenAccessibilityLabel(value: String) { whenever(view.getAccessibilityLabel()).doReturn(value) }
|
36
37
|
|
37
38
|
private fun perform(v: View = view): JSONObject {
|
38
39
|
uut.perform(null, v)
|
@@ -110,16 +111,16 @@ class GetAttributesActionTest {
|
|
110
111
|
}
|
111
112
|
|
112
113
|
@Test
|
113
|
-
fun `should return label according to
|
114
|
-
val
|
115
|
-
|
114
|
+
fun `should return label according to accessibilityLabel extension`() {
|
115
|
+
val accessibilityLabel = "label-mock"
|
116
|
+
givenAccessibilityLabel(accessibilityLabel)
|
116
117
|
|
117
118
|
val resultJson = perform()
|
118
|
-
assertThat(resultJson.opt("label")).isEqualTo(
|
119
|
+
assertThat(resultJson.opt("label")).isEqualTo(accessibilityLabel)
|
119
120
|
}
|
120
121
|
|
121
122
|
@Test
|
122
|
-
fun `should not return label if
|
123
|
+
fun `should not return label if accessibility label is not available`() {
|
123
124
|
val resultJson = perform()
|
124
125
|
assertThat(resultJson.opt("label")).isNull()
|
125
126
|
}
|
package/android/detox/src/testFull/java/com/wix/detox/espresso/matcher/ViewAtIndexMatcherSpec.kt
CHANGED
@@ -3,7 +3,6 @@ package com.wix.detox.espresso.matcher
|
|
3
3
|
import android.view.View
|
4
4
|
import org.hamcrest.Description
|
5
5
|
import org.hamcrest.Matcher
|
6
|
-
import org.hamcrest.Matchers
|
7
6
|
import org.mockito.kotlin.mock
|
8
7
|
import org.mockito.kotlin.verify
|
9
8
|
import org.mockito.kotlin.whenever
|
@@ -28,7 +27,7 @@ object ViewAtIndexMatcherSpec: Spek({
|
|
28
27
|
uut.describeTo(description)
|
29
28
|
verify(description).appendText("View at index #0, of those matching MATCHER(innerMatcher description)")
|
30
29
|
}
|
31
|
-
|
30
|
+
|
32
31
|
it("should append a valid description for index≥0") {
|
33
32
|
val uut = ViewAtIndexMatcher(7, innerMatcher)
|
34
33
|
uut.describeTo(description)
|
package/index.d.ts
CHANGED
@@ -626,9 +626,8 @@ declare global {
|
|
626
626
|
|
627
627
|
interface Device {
|
628
628
|
/**
|
629
|
-
* Holds the environment-unique ID of the device
|
629
|
+
* Holds the environment-unique ID of the device, namely, the adb ID on Android (e.g. emulator-5554) and the Mac-global simulator UDID on iOS -
|
630
630
|
* as used by simctl (e.g. AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE).
|
631
|
-
*
|
632
631
|
*/
|
633
632
|
id: string;
|
634
633
|
/**
|
@@ -1122,39 +1121,39 @@ declare global {
|
|
1122
1121
|
* expectation with a `not` expects the view's visible area to be smaller than N%.
|
1123
1122
|
* @param pct optional integer ranging from 1 to 100, indicating how much percent of the view should be
|
1124
1123
|
* visible to the user to be accepted.
|
1125
|
-
* @example await expect(element(by.id('
|
1124
|
+
* @example await expect(element(by.id('mainTitle'))).toBeVisible(35);
|
1126
1125
|
*/
|
1127
1126
|
toBeVisible(pct?: number): R;
|
1128
1127
|
|
1129
1128
|
/**
|
1130
1129
|
* Negate the expectation.
|
1131
|
-
* @example await expect(element(by.id('
|
1130
|
+
* @example await expect(element(by.id('cancelButton'))).not.toBeVisible();
|
1132
1131
|
*/
|
1133
1132
|
not: this;
|
1134
1133
|
|
1135
1134
|
/**
|
1136
1135
|
* Expect the view to not be visible.
|
1137
|
-
* @example await expect(element(by.id('
|
1136
|
+
* @example await expect(element(by.id('cancelButton'))).toBeNotVisible();
|
1138
1137
|
* @deprecated Use `.not.toBeVisible()` instead.
|
1139
1138
|
*/
|
1140
1139
|
toBeNotVisible(): R;
|
1141
1140
|
|
1142
1141
|
/**
|
1143
1142
|
* Expect the view to exist in the UI hierarchy.
|
1144
|
-
* @example await expect(element(by.id('
|
1143
|
+
* @example await expect(element(by.id('okButton'))).toExist();
|
1145
1144
|
*/
|
1146
1145
|
toExist(): R;
|
1147
1146
|
|
1148
1147
|
/**
|
1149
1148
|
* Expect the view to not exist in the UI hierarchy.
|
1150
|
-
* @example await expect(element(by.id('
|
1149
|
+
* @example await expect(element(by.id('cancelButton'))).toNotExist();
|
1151
1150
|
* @deprecated Use `.not.toExist()` instead.
|
1152
1151
|
*/
|
1153
1152
|
toNotExist(): R;
|
1154
1153
|
|
1155
1154
|
/**
|
1156
1155
|
* Expect the view to be focused.
|
1157
|
-
* @example await expect(element(by.id('
|
1156
|
+
* @example await expect(element(by.id('emailInput'))).toBeFocused();
|
1158
1157
|
*/
|
1159
1158
|
toBeFocused(): R;
|
1160
1159
|
|
@@ -1168,21 +1167,23 @@ declare global {
|
|
1168
1167
|
/**
|
1169
1168
|
* In React Native apps, expect UI component of type <Text> to have text.
|
1170
1169
|
* In native iOS apps, expect UI elements of type UIButton, UILabel, UITextField or UITextViewIn to have inputText with text.
|
1171
|
-
* @example await expect(element(by.id('
|
1170
|
+
* @example await expect(element(by.id('mainTitle'))).toHaveText('Welcome back!);
|
1172
1171
|
*/
|
1173
1172
|
toHaveText(text: string): R;
|
1174
1173
|
|
1175
1174
|
/**
|
1176
|
-
*
|
1177
|
-
*
|
1178
|
-
*
|
1175
|
+
* Expects a specific accessibilityLabel, as specified via the `accessibilityLabel` prop in React Native.
|
1176
|
+
* On the native side (in both React Native and pure-native apps), that is equivalent to `accessibilityLabel`
|
1177
|
+
* on iOS and contentDescription on Android. Refer to Detox's documentation in order to learn about caveats
|
1178
|
+
* with accessibility-labels in React Native apps.
|
1179
|
+
* @example await expect(element(by.id('submitButton'))).toHaveLabel('Submit');
|
1179
1180
|
*/
|
1180
1181
|
toHaveLabel(label: string): R;
|
1181
1182
|
|
1182
1183
|
/**
|
1183
1184
|
* In React Native apps, expect UI component to have testID with that id.
|
1184
1185
|
* In native iOS apps, expect UI element to have accessibilityIdentifier with that id.
|
1185
|
-
* @example await expect(element(by.text('
|
1186
|
+
* @example await expect(element(by.text('Submit'))).toHaveId('submitButton');
|
1186
1187
|
*/
|
1187
1188
|
toHaveId(id: string): R;
|
1188
1189
|
|
@@ -1195,7 +1196,7 @@ declare global {
|
|
1195
1196
|
|
1196
1197
|
/**
|
1197
1198
|
* Expect components like a Switch to have a value ('0' for off, '1' for on).
|
1198
|
-
* @example await expect(element(by.id('
|
1199
|
+
* @example await expect(element(by.id('temperatureDial'))).toHaveValue('25');
|
1199
1200
|
*/
|
1200
1201
|
toHaveValue(value: any): R;
|
1201
1202
|
|
@@ -1212,7 +1213,7 @@ declare global {
|
|
1212
1213
|
/**
|
1213
1214
|
* This API polls using the given expectation continuously until the expectation is met. Use manual synchronization with waitFor only as a last resort.
|
1214
1215
|
* NOTE: Every waitFor call must set a timeout using withTimeout(). Calling waitFor without setting a timeout will do nothing.
|
1215
|
-
* @example await waitFor(element(by.id('
|
1216
|
+
* @example await waitFor(element(by.id('bigButton'))).toExist().withTimeout(2000);
|
1216
1217
|
*/
|
1217
1218
|
(element: NativeElement): Expect<WaitFor>;
|
1218
1219
|
}
|
@@ -1220,13 +1221,13 @@ declare global {
|
|
1220
1221
|
interface WaitFor {
|
1221
1222
|
/**
|
1222
1223
|
* Waits for the condition to be met until the specified time (millis) have elapsed.
|
1223
|
-
* @example await waitFor(element(by.id('
|
1224
|
+
* @example await waitFor(element(by.id('bigButton'))).toExist().withTimeout(2000);
|
1224
1225
|
*/
|
1225
1226
|
withTimeout(millis: number): Promise<void>;
|
1226
1227
|
|
1227
1228
|
/**
|
1228
1229
|
* Performs the action repeatedly on the element until an expectation is met
|
1229
|
-
* @example await waitFor(element(by.text('
|
1230
|
+
* @example await waitFor(element(by.text('Item #5'))).toBeVisible().whileElement(by.id('itemsList')).scroll(50, 'down');
|
1230
1231
|
*/
|
1231
1232
|
whileElement(by: NativeMatcher): NativeElement & WaitFor;
|
1232
1233
|
|
@@ -1438,7 +1439,7 @@ declare global {
|
|
1438
1439
|
interface WebExpect<R = Promise<void>> {
|
1439
1440
|
/**
|
1440
1441
|
* Negate the expectation.
|
1441
|
-
* @example await expect(web.element(by.web.id('
|
1442
|
+
* @example await expect(web.element(by.web.id('sessionTimeout'))).not.toExist();
|
1442
1443
|
*/
|
1443
1444
|
not: this;
|
1444
1445
|
|
@@ -1446,13 +1447,13 @@ declare global {
|
|
1446
1447
|
* Expect the element content to have the `text` supplied
|
1447
1448
|
* @param text expected to be on the element content
|
1448
1449
|
* @example
|
1449
|
-
* await expect(web.element(by.web.id('
|
1450
|
+
* await expect(web.element(by.web.id('checkoutButton'))).toHaveText('Proceed to check out');
|
1450
1451
|
*/
|
1451
1452
|
toHaveText(text: string): R;
|
1452
1453
|
|
1453
1454
|
/**
|
1454
1455
|
* Expect the view to exist in the webview DOM tree.
|
1455
|
-
* @example await expect(web.element(by.web.id('
|
1456
|
+
* @example await expect(web.element(by.web.id('submitButton'))).toExist();
|
1456
1457
|
*/
|
1457
1458
|
toExist(): R;
|
1458
1459
|
}
|
@@ -1651,7 +1652,9 @@ declare global {
|
|
1651
1652
|
*/
|
1652
1653
|
text?: string;
|
1653
1654
|
/**
|
1654
|
-
* The label of the element.
|
1655
|
+
* The label of the element. Largely matches accessibilityLabel for ios, and contentDescription for android.
|
1656
|
+
* Refer to Detox's documentation (`toHaveLabel()` subsection) in order to learn about caveats associated with
|
1657
|
+
* this property in React Native apps.
|
1655
1658
|
*/
|
1656
1659
|
label?: string;
|
1657
1660
|
/**
|
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.7.0",
|
5
5
|
"bin": {
|
6
6
|
"detox": "local-cli/cli.js"
|
7
7
|
},
|
@@ -61,7 +61,7 @@
|
|
61
61
|
"bunyan": "^1.8.12",
|
62
62
|
"bunyan-debug-stream": "^3.1.0",
|
63
63
|
"caf": "^15.0.1",
|
64
|
-
"chalk": "^
|
64
|
+
"chalk": "^4.0.0",
|
65
65
|
"child-process-promise": "^2.2.0",
|
66
66
|
"execa": "^5.1.1",
|
67
67
|
"find-up": "^4.1.0",
|
@@ -200,5 +200,5 @@
|
|
200
200
|
}
|
201
201
|
}
|
202
202
|
},
|
203
|
-
"gitHead": "
|
203
|
+
"gitHead": "4d316b08ec884b8dbc6bb8a19eeb134e2e4dc4d2"
|
204
204
|
}
|
package/src/DetoxWorker.js
CHANGED
@@ -138,6 +138,8 @@ class DetoxWorker {
|
|
138
138
|
this._deviceAllocator = deviceAllocatorFactory.createDeviceAllocator(commonDeps);
|
139
139
|
this._deviceCookie = yield this._deviceAllocator.allocate(this._deviceConfig);
|
140
140
|
|
141
|
+
yield this._deviceAllocator.postAllocate(this._deviceCookie);
|
142
|
+
|
141
143
|
this.device = runtimeDeviceFactory.createRuntimeDevice(
|
142
144
|
this._deviceCookie,
|
143
145
|
commonDeps,
|
@@ -200,9 +202,12 @@ class DetoxWorker {
|
|
200
202
|
}
|
201
203
|
|
202
204
|
if (this.device) {
|
203
|
-
const shutdown = this._behaviorConfig ? this._behaviorConfig.cleanup.shutdownDevice : false;
|
204
205
|
// @ts-ignore
|
205
206
|
await this.device._cleanup();
|
207
|
+
}
|
208
|
+
|
209
|
+
if (this._deviceCookie) {
|
210
|
+
const shutdown = this._behaviorConfig ? this._behaviorConfig.cleanup.shutdownDevice : false;
|
206
211
|
await this._deviceAllocator.free(this._deviceCookie, { shutdown });
|
207
212
|
}
|
208
213
|
|
@@ -1,27 +1,44 @@
|
|
1
|
+
const { inspect } = require('util');
|
2
|
+
|
3
|
+
const { DetoxRuntimeError } = require('../../errors');
|
1
4
|
const invoke = require('../../invoke');
|
2
5
|
const DetoxMatcherApi = require('../espressoapi/DetoxMatcher');
|
3
6
|
|
4
7
|
class NativeMatcher {
|
8
|
+
static _assertMatcher(matcher) {
|
9
|
+
if (!(matcher instanceof NativeMatcher)) {
|
10
|
+
throw new DetoxRuntimeError({ message: `Expected a matcher, got: ${inspect(matcher)}` });
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
5
14
|
constructor(call) {
|
6
15
|
this._call = call || null;
|
7
16
|
}
|
8
17
|
|
9
18
|
withAncestor(matcher) {
|
19
|
+
NativeMatcher._assertMatcher(matcher);
|
20
|
+
|
10
21
|
const call = invoke.callDirectly(DetoxMatcherApi.matcherWithAncestor(this, matcher));
|
11
22
|
return new NativeMatcher(call);
|
12
23
|
}
|
13
24
|
|
14
25
|
withDescendant(matcher) {
|
26
|
+
NativeMatcher._assertMatcher(matcher);
|
27
|
+
|
15
28
|
const call = invoke.callDirectly(DetoxMatcherApi.matcherWithDescendant(this, matcher));
|
16
29
|
return new NativeMatcher(call);
|
17
30
|
}
|
18
31
|
|
19
32
|
and(matcher) {
|
33
|
+
NativeMatcher._assertMatcher(matcher);
|
34
|
+
|
20
35
|
const call = invoke.callDirectly(DetoxMatcherApi.matcherForAnd(this, matcher));
|
21
36
|
return new NativeMatcher(call);
|
22
37
|
}
|
23
38
|
|
24
39
|
or(matcher) {
|
40
|
+
NativeMatcher._assertMatcher(matcher);
|
41
|
+
|
25
42
|
const call = invoke.callDirectly(DetoxMatcherApi.matcherForOr(this, matcher));
|
26
43
|
return new NativeMatcher(call);
|
27
44
|
}
|
@@ -26,6 +26,30 @@ class DetoxMatcher {
|
|
26
26
|
};
|
27
27
|
}
|
28
28
|
|
29
|
+
static matcherForAccessibilityLabel(label) {
|
30
|
+
if (typeof label !== "string") throw new Error("label should be a string, but got " + (label + (" (" + (typeof label + ")"))));
|
31
|
+
return {
|
32
|
+
target: {
|
33
|
+
type: "Class",
|
34
|
+
value: "com.wix.detox.espresso.DetoxMatcher"
|
35
|
+
},
|
36
|
+
method: "matcherForAccessibilityLabel",
|
37
|
+
args: [label]
|
38
|
+
};
|
39
|
+
}
|
40
|
+
|
41
|
+
static matcherForShallowAccessibilityLabel(label) {
|
42
|
+
if (typeof label !== "string") throw new Error("label should be a string, but got " + (label + (" (" + (typeof label + ")"))));
|
43
|
+
return {
|
44
|
+
target: {
|
45
|
+
type: "Class",
|
46
|
+
value: "com.wix.detox.espresso.DetoxMatcher"
|
47
|
+
},
|
48
|
+
method: "matcherForShallowAccessibilityLabel",
|
49
|
+
args: [label]
|
50
|
+
};
|
51
|
+
}
|
52
|
+
|
29
53
|
static matcherForContentDescription(contentDescription) {
|
30
54
|
if (typeof contentDescription !== "string") throw new Error("contentDescription should be a string, but got " + (contentDescription + (" (" + (typeof contentDescription + ")"))));
|
31
55
|
return {
|
@@ -2,9 +2,9 @@ const native = require('./native');
|
|
2
2
|
const web = require('./web');
|
3
3
|
|
4
4
|
module.exports = {
|
5
|
-
accessibilityLabel: (value) => new native.LabelMatcher(value),
|
6
5
|
id: (value) => new native.IdMatcher(value),
|
7
|
-
label: (value) => new native.
|
6
|
+
label: (value) => new native.ShallowLabelMatcher(value),
|
7
|
+
accessibilityLabel: (value) => new native.ShallowLabelMatcher(value),
|
8
8
|
text: (value) => new native.TextMatcher(value),
|
9
9
|
traits: (value) => new native.TraitsMatcher(value),
|
10
10
|
type: (value) => new native.TypeMatcher(value),
|
@@ -6,7 +6,14 @@ const DetoxMatcherApi = require('../espressoapi/DetoxMatcher');
|
|
6
6
|
class LabelMatcher extends NativeMatcher {
|
7
7
|
constructor(value) {
|
8
8
|
super();
|
9
|
-
this._call = invoke.callDirectly(DetoxMatcherApi.
|
9
|
+
this._call = invoke.callDirectly(DetoxMatcherApi.matcherForAccessibilityLabel(value));
|
10
|
+
}
|
11
|
+
}
|
12
|
+
|
13
|
+
class ShallowLabelMatcher extends NativeMatcher {
|
14
|
+
constructor(value) {
|
15
|
+
super();
|
16
|
+
this._call = invoke.callDirectly(DetoxMatcherApi.matcherForShallowAccessibilityLabel(value));
|
10
17
|
}
|
11
18
|
}
|
12
19
|
|
@@ -90,6 +97,7 @@ class SliderPositionMatcher extends NativeMatcher {
|
|
90
97
|
|
91
98
|
module.exports = {
|
92
99
|
LabelMatcher,
|
100
|
+
ShallowLabelMatcher,
|
93
101
|
IdMatcher,
|
94
102
|
TypeMatcher,
|
95
103
|
TraitsMatcher,
|
@@ -8,7 +8,7 @@ class DeviceAllocator {
|
|
8
8
|
*/
|
9
9
|
constructor(allocationDriver) {
|
10
10
|
this._driver = allocationDriver;
|
11
|
-
traceMethods(log, this, ['allocate', 'free']);
|
11
|
+
traceMethods(log, this, ['allocate', 'postAllocate', 'free']);
|
12
12
|
}
|
13
13
|
|
14
14
|
/**
|
@@ -19,6 +19,18 @@ class DeviceAllocator {
|
|
19
19
|
return this._driver.allocate(deviceConfig);
|
20
20
|
}
|
21
21
|
|
22
|
+
/**
|
23
|
+
* @param {DeviceCookie} deviceCookie
|
24
|
+
* @return {Promise<unknown>}
|
25
|
+
*/
|
26
|
+
postAllocate(deviceCookie) {
|
27
|
+
if (typeof this._driver.postAllocate !== 'function') {
|
28
|
+
return Promise.resolve();
|
29
|
+
}
|
30
|
+
|
31
|
+
return this._driver.postAllocate(deviceCookie);
|
32
|
+
}
|
33
|
+
|
22
34
|
/**
|
23
35
|
* @param cookie { DeviceCookie }
|
24
36
|
* @param options { DeallocOptions }
|
@@ -13,6 +13,12 @@ class AllocationDriverBase {
|
|
13
13
|
*/
|
14
14
|
async allocate(deviceConfig) {}
|
15
15
|
|
16
|
+
/**
|
17
|
+
* @param {DeviceCookie} deviceCookie
|
18
|
+
* @return {Promise<void>}
|
19
|
+
*/
|
20
|
+
async postAllocate(deviceCookie) {}
|
21
|
+
|
16
22
|
/**
|
17
23
|
* @param cookie { DeviceCookie }
|
18
24
|
* @param options { DeallocOptions }
|
@@ -25,11 +25,20 @@ class AttachedAndroidAllocDriver extends AllocationDriverBase {
|
|
25
25
|
const adbNamePattern = deviceConfig.device.adbName;
|
26
26
|
const adbName = await this._deviceRegistry.allocateDevice(() => this._freeDeviceFinder.findFreeDevice(adbNamePattern));
|
27
27
|
|
28
|
+
return new AttachedAndroidDeviceCookie(adbName);
|
29
|
+
}
|
30
|
+
|
31
|
+
/**
|
32
|
+
* @param {AttachedAndroidDeviceCookie} deviceCookie
|
33
|
+
* @returns {Promise<void>}
|
34
|
+
*/
|
35
|
+
async postAllocate(deviceCookie) {
|
36
|
+
const { adbName } = deviceCookie;
|
37
|
+
|
28
38
|
// TODO Also disable native animations?
|
29
39
|
await this._adb.apiLevel(adbName);
|
30
40
|
await this._adb.unlockScreen(adbName);
|
31
41
|
await this._attachedAndroidLauncher.notifyLaunchCompleted(adbName);
|
32
|
-
return new AttachedAndroidDeviceCookie(adbName);
|
33
42
|
}
|
34
43
|
|
35
44
|
/**
|
@@ -1,8 +1,6 @@
|
|
1
1
|
// @ts-nocheck
|
2
2
|
const _ = require('lodash');
|
3
3
|
|
4
|
-
const log = require('../../../../../utils/logger').child({ cat: 'device' });
|
5
|
-
const traceMethods = require('../../../../../utils/traceMethods');
|
6
4
|
const AndroidEmulatorCookie = require('../../../../cookies/AndroidEmulatorCookie');
|
7
5
|
const AllocationDriverBase = require('../../AllocationDriverBase');
|
8
6
|
|
@@ -23,8 +21,7 @@ class EmulatorAllocDriver extends AllocationDriverBase {
|
|
23
21
|
this._emulatorVersionResolver = emulatorVersionResolver;
|
24
22
|
this._emulatorLauncher = emulatorLauncher;
|
25
23
|
this._allocationHelper = allocationHelper;
|
26
|
-
|
27
|
-
traceMethods(log, this, ['_launchEmulator']);
|
24
|
+
this._launchInfo = {};
|
28
25
|
}
|
29
26
|
|
30
27
|
/**
|
@@ -38,21 +35,37 @@ class EmulatorAllocDriver extends AllocationDriverBase {
|
|
38
35
|
await this._fixAvdConfigIniSkinNameIfNeeded(avdName, deviceConfig.headless);
|
39
36
|
|
40
37
|
const allocResult = await this._allocationHelper.allocateDevice(avdName);
|
41
|
-
const { adbName
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
38
|
+
const { adbName } = allocResult;
|
39
|
+
|
40
|
+
this._launchInfo[adbName] = {
|
41
|
+
avdName,
|
42
|
+
isRunning: allocResult.isRunning,
|
43
|
+
launchOptions: {
|
44
|
+
bootArgs: deviceConfig.bootArgs,
|
45
|
+
gpuMode: deviceConfig.gpuMode,
|
46
|
+
headless: deviceConfig.headless,
|
47
|
+
readonly: deviceConfig.readonly,
|
48
|
+
port: allocResult.placeholderPort,
|
49
|
+
},
|
48
50
|
};
|
49
51
|
|
50
|
-
await this._launchEmulator(avdName, adbName, isRunning, launchOptions);
|
51
|
-
await this._prepareEmulator(adbName);
|
52
|
-
|
53
52
|
return new AndroidEmulatorCookie(adbName);
|
54
53
|
}
|
55
54
|
|
55
|
+
/**
|
56
|
+
* @param {AndroidEmulatorCookie} deviceCookie
|
57
|
+
* @returns {Promise<void>}
|
58
|
+
*/
|
59
|
+
async postAllocate(deviceCookie) {
|
60
|
+
const { adbName } = deviceCookie;
|
61
|
+
const { avdName, isRunning, launchOptions } = this._launchInfo[adbName];
|
62
|
+
|
63
|
+
await this._emulatorLauncher.launch(avdName, adbName, isRunning, launchOptions);
|
64
|
+
await this._adb.apiLevel(adbName);
|
65
|
+
await this._adb.disableAndroidAnimations(adbName);
|
66
|
+
await this._adb.unlockScreen(adbName);
|
67
|
+
}
|
68
|
+
|
56
69
|
/**
|
57
70
|
* @param cookie { AndroidEmulatorCookie }
|
58
71
|
* @param options { DeallocOptions }
|
@@ -73,21 +86,6 @@ class EmulatorAllocDriver extends AllocationDriverBase {
|
|
73
86
|
const binaryVersion = _.get(rawBinaryVersion, 'major');
|
74
87
|
return await patchAvdSkinConfig(avdName, binaryVersion);
|
75
88
|
}
|
76
|
-
|
77
|
-
async _launchEmulator(avdName, adbName, isRunning, options) {
|
78
|
-
try {
|
79
|
-
await this._emulatorLauncher.launch(avdName, adbName, isRunning, options);
|
80
|
-
} catch (e) {
|
81
|
-
await this._allocationHelper.deallocateDevice(adbName);
|
82
|
-
throw e;
|
83
|
-
}
|
84
|
-
}
|
85
|
-
|
86
|
-
async _prepareEmulator(adbName) {
|
87
|
-
await this._adb.apiLevel(adbName);
|
88
|
-
await this._adb.disableAndroidAnimations(adbName);
|
89
|
-
await this._adb.unlockScreen(adbName);
|
90
|
-
}
|
91
89
|
}
|
92
90
|
|
93
91
|
module.exports = EmulatorAllocDriver;
|
@@ -1,22 +1,25 @@
|
|
1
|
-
|
2
|
-
const
|
1
|
+
const { DetoxRuntimeError } = require('../../../../../errors');
|
2
|
+
const Timer = require('../../../../../utils/Timer');
|
3
3
|
const GenycloudEmulatorCookie = require('../../../../cookies/GenycloudEmulatorCookie');
|
4
4
|
const AllocationDriverBase = require('../../AllocationDriverBase');
|
5
5
|
|
6
6
|
class GenyAllocDriver extends AllocationDriverBase {
|
7
7
|
|
8
8
|
/**
|
9
|
-
* @param
|
10
|
-
* @param
|
11
|
-
* @param
|
12
|
-
* @param
|
9
|
+
* @param {object} options
|
10
|
+
* @param {import('../../../../common/drivers/android/exec/ADB')} options.adb
|
11
|
+
* @param {import('./GenyRecipeQuerying')} options.recipeQuerying
|
12
|
+
* @param {import('./GenyInstanceAllocationHelper')} options.allocationHelper
|
13
|
+
* @param {import('./GenyInstanceLauncher')} options.instanceLauncher
|
13
14
|
*/
|
14
15
|
constructor({ adb, recipeQuerying, allocationHelper, instanceLauncher }) {
|
15
16
|
super();
|
17
|
+
|
16
18
|
this._adb = adb;
|
17
19
|
this._recipeQuerying = recipeQuerying;
|
18
20
|
this._instanceLauncher = instanceLauncher;
|
19
21
|
this._instanceAllocationHelper = allocationHelper;
|
22
|
+
this._launchInfo = {};
|
20
23
|
}
|
21
24
|
|
22
25
|
/**
|
@@ -28,21 +31,26 @@ class GenyAllocDriver extends AllocationDriverBase {
|
|
28
31
|
const recipe = await this._recipeQuerying.getRecipeFromQuery(deviceQuery);
|
29
32
|
this._assertRecipe(deviceQuery, recipe);
|
30
33
|
|
31
|
-
const
|
32
|
-
|
34
|
+
const { instance, isNew } = await this._instanceAllocationHelper.allocateDevice(recipe);
|
35
|
+
this._launchInfo[instance.uuid] = { isNew };
|
36
|
+
return new GenycloudEmulatorCookie(instance);
|
37
|
+
}
|
33
38
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
}
|
40
|
-
const {
|
39
|
+
/**
|
40
|
+
* @param {GenycloudEmulatorCookie} cookie
|
41
|
+
* @returns {Promise<void>}
|
42
|
+
*/
|
43
|
+
async postAllocate(cookie) {
|
44
|
+
const { instance } = cookie;
|
45
|
+
const { isNew } = this._launchInfo[instance.uuid];
|
46
|
+
const readyInstance = cookie.instance = await this._instanceLauncher.launch(instance, isNew);
|
41
47
|
|
42
|
-
|
43
|
-
await
|
44
|
-
|
45
|
-
|
48
|
+
const { adbName } = readyInstance;
|
49
|
+
await Timer.run(20000, 'waiting for device to respond', async () => {
|
50
|
+
await this._adb.disableAndroidAnimations(adbName);
|
51
|
+
await this._adb.setWiFiToggle(adbName, true);
|
52
|
+
await this._adb.apiLevel(adbName);
|
53
|
+
});
|
46
54
|
}
|
47
55
|
|
48
56
|
/**
|
@@ -16,6 +16,7 @@ class SimulatorAllocDriver extends AllocationDriverBase {
|
|
16
16
|
this._deviceRegistry = deviceRegistry;
|
17
17
|
this._applesimutils = applesimutils;
|
18
18
|
this._simulatorLauncher = simulatorLauncher;
|
19
|
+
this._launchInfo = {};
|
19
20
|
}
|
20
21
|
|
21
22
|
/**
|
@@ -35,16 +36,20 @@ class SimulatorAllocDriver extends AllocationDriverBase {
|
|
35
36
|
throw new DetoxRuntimeError(`Failed to find device matching ${deviceComment}`);
|
36
37
|
}
|
37
38
|
|
38
|
-
|
39
|
-
await this._simulatorLauncher.launch(udid, deviceConfig.type, deviceConfig.bootArgs, deviceConfig.headless);
|
40
|
-
} catch (e) {
|
41
|
-
await this._deviceRegistry.disposeDevice(udid);
|
42
|
-
throw e;
|
43
|
-
}
|
44
|
-
|
39
|
+
this._launchInfo[udid] = { deviceConfig };
|
45
40
|
return new IosSimulatorCookie(udid);
|
46
41
|
}
|
47
42
|
|
43
|
+
/**
|
44
|
+
* @param {IosSimulatorCookie} deviceCookie
|
45
|
+
* @returns {Promise<void>}
|
46
|
+
*/
|
47
|
+
async postAllocate(deviceCookie) {
|
48
|
+
const { udid } = deviceCookie;
|
49
|
+
const { deviceConfig } = this._launchInfo[udid];
|
50
|
+
await this._simulatorLauncher.launch(udid, deviceConfig.type, deviceConfig.bootArgs, deviceConfig.headless);
|
51
|
+
}
|
52
|
+
|
48
53
|
/**
|
49
54
|
* @param cookie { IosSimulatorCookie }
|
50
55
|
* @param options { DeallocOptions }
|
@@ -310,19 +310,19 @@ class DetoxLogger {
|
|
310
310
|
/** @internal */
|
311
311
|
static defaultOptions({ level }) {
|
312
312
|
const ph = level === 'trace' || level === 'debug'
|
313
|
-
? value => require('chalk').
|
314
|
-
: value => require('chalk').
|
313
|
+
? value => require('chalk').grey(value) + ' '
|
314
|
+
: value => require('chalk').grey(value);
|
315
315
|
|
316
316
|
const id = level === 'trace'
|
317
|
-
? value => require('chalk').
|
317
|
+
? value => require('chalk').yellow(`@${value}`)
|
318
318
|
: undefined;
|
319
319
|
|
320
320
|
const cat = level === 'trace' || level === 'debug'
|
321
|
-
? (value) => require('chalk').
|
321
|
+
? (value) => require('chalk').yellow(`${value}`.split(',', 1)[0])
|
322
322
|
: undefined;
|
323
323
|
|
324
324
|
const event = level === 'trace' || level === 'debug'
|
325
|
-
? (value) => require('chalk').
|
325
|
+
? (value) => require('chalk').grey(`:${value}`)
|
326
326
|
: undefined;
|
327
327
|
|
328
328
|
const identity = x => x;
|
package/src/utils/Timer.js
CHANGED
@@ -1 +0,0 @@
|
|
1
|
-
312ca2e6d26a005375c7d31f7a489b60
|
@@ -1 +0,0 @@
|
|
1
|
-
80debe0a3a1ed317173f56f92cbfd1f108f3fb6c
|
@@ -1 +0,0 @@
|
|
1
|
-
ceeb582236c99040fb116be37c6eb97c67ecdf91f8cdd1d5dade614a3180375c
|
@@ -1 +0,0 @@
|
|
1
|
-
08dcd64b4e65c486a7fe0d4a59b23e4b9ffc037b4afad03db9cb820b97867e22693d929b7e896f41c11c8a4e3e0d0dbbc11799a69f438d46aeeb8ebb8b157056
|
@@ -1 +0,0 @@
|
|
1
|
-
643850c6dad298f22d9ce657af17dd5c
|
@@ -1 +0,0 @@
|
|
1
|
-
e483f7ab88c2b2d5882d1c825155856c61ba059f
|
@@ -1 +0,0 @@
|
|
1
|
-
2ee1a851c254668619e44df167d0b1d86bd63edb9414a239f1fcea380c39a077
|
@@ -1 +0,0 @@
|
|
1
|
-
3446a90564f2af11a6b6221f114ccd8786044080e222ed698fef6a92d706faed154fdaa976c0dca162db58976170a60b7bc05d5e931a1b1bbf02ff8292d0c47c
|
Binary file
|
@@ -1 +0,0 @@
|
|
1
|
-
e8ad50ff5afce32b3cb728f837917c3a
|
@@ -1 +0,0 @@
|
|
1
|
-
8f1738000005765fd1d4746e5f2d85c766dd591f
|
@@ -1 +0,0 @@
|
|
1
|
-
da1efa26b94b52cdbb831470e5753e0982bbe5ec1c861f8965a7347199e67b8b
|
@@ -1 +0,0 @@
|
|
1
|
-
9940e5dd655923d057b095817edc22c952105b1518ff02f1316cfd50be45999127e8410eb9b22e95d155517f455a484b3a52f9533f6f40e3f1e9efbf49a5eaa9
|
@@ -1 +0,0 @@
|
|
1
|
-
e6f8b937b6573484515f78adcc830096
|
@@ -1 +0,0 @@
|
|
1
|
-
59e4b461ba168a526c1c629fb29ded508472e732
|
@@ -1 +0,0 @@
|
|
1
|
-
b455e110295c40221b1620cfe6ffc905fc9623b36a5f38fabf52316def1f9749
|
@@ -1 +0,0 @@
|
|
1
|
-
766004a843b60ee7b385f26324762d8f18d35064324fb7ab5c24e59816869745401dafbfc3517ff55948527fd0411a2b9382b3f29de8aa6756e61df58a3c416c
|