appium-espresso-driver 8.0.0 → 8.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +5 -0
- package/build/lib/server-builder.d.ts +8 -3
- package/build/lib/server-builder.d.ts.map +1 -1
- package/build/lib/server-builder.js +8 -0
- package/build/lib/server-builder.js.map +1 -1
- package/espresso-server/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk +0 -0
- package/espresso-server/app/build.gradle.kts +20 -15
- package/espresso-server/build.gradle.kts +3 -18
- package/espresso-server/buildSrc/.gradle/9.4.1/executionHistory/executionHistory.bin +0 -0
- package/espresso-server/buildSrc/.gradle/9.4.1/executionHistory/executionHistory.lock +0 -0
- package/espresso-server/buildSrc/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/espresso-server/buildSrc/.gradle/buildOutputCleanup/cache.properties +1 -1
- package/espresso-server/buildSrc/.gradle/file-system.probe +0 -0
- package/espresso-server/buildSrc/build/classes/kotlin/main/META-INF/buildSrc.kotlin_module +0 -0
- package/espresso-server/buildSrc/build/classes/kotlin/main/io/appium/espressoserver/gradle/CapabilityVersionKt.class +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.values.at +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/counters.tab +1 -1
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/last-build.bin +0 -0
- package/espresso-server/buildSrc/build/kotlin/compileKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
- package/espresso-server/buildSrc/build/libs/buildSrc.jar +0 -0
- package/espresso-server/buildSrc/build.gradle.kts +2 -1
- package/espresso-server/buildSrc/src/main/kotlin/io/appium/espressoserver/gradle/CapabilityVersion.kt +12 -0
- package/espresso-server/gradle/libs.versions.toml +16 -0
- package/espresso-server/gradle.properties +1 -12
- package/espresso-server/library/build.gradle.kts +53 -32
- package/espresso-server/library/src/composeOff/java/io/appium/espressoserver/lib/compose/ComposeHandlerBridge.kt +73 -0
- package/espresso-server/library/src/composeOff/java/io/appium/espressoserver/lib/helpers/ComposeViewCache.kt +29 -0
- package/espresso-server/library/src/composeOff/java/io/appium/espressoserver/lib/http/Server.kt +21 -0
- package/espresso-server/library/src/composeOff/java/io/appium/espressoserver/lib/model/SourceDocumentComposeHooks.kt +26 -0
- package/espresso-server/library/src/composeOn/java/io/appium/espressoserver/lib/compose/ComposeHandlerBridge.kt +180 -0
- package/espresso-server/library/src/composeOn/java/io/appium/espressoserver/lib/compose/ComposeScreenshot.kt +49 -0
- package/espresso-server/library/src/composeOn/java/io/appium/espressoserver/lib/drivers/DriverContextComposeRule.kt +29 -0
- package/espresso-server/library/src/{main → composeOn}/java/io/appium/espressoserver/lib/helpers/ComposeNodeFinder.kt +1 -0
- package/espresso-server/library/src/composeOn/java/io/appium/espressoserver/lib/helpers/ComposeViewCache.kt +37 -0
- package/espresso-server/library/src/composeOn/java/io/appium/espressoserver/lib/http/Server.kt +30 -0
- package/espresso-server/library/src/composeOn/java/io/appium/espressoserver/lib/model/ComposeElement.kt +33 -0
- package/espresso-server/library/src/composeOn/java/io/appium/espressoserver/lib/model/SourceDocumentComposeHooks.kt +103 -0
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/drivers/DriverContext.kt +3 -6
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/Clear.kt +2 -12
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/Click.kt +2 -12
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/DeleteSession.kt +2 -2
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/ElementScreenshot.kt +2 -3
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/ElementValue.kt +2 -12
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/FindElement.kt +3 -12
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/FindElements.kt +3 -7
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetAttribute.kt +3 -7
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetDisplayed.kt +2 -9
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetEnabled.kt +2 -4
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetLocation.kt +2 -6
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetName.kt +2 -6
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetRect.kt +2 -4
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetSelected.kt +2 -4
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetSize.kt +2 -6
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/Keys.kt +2 -16
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/MobileSwipe.kt +2 -24
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/RequestHandler.kt +8 -1
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/Text.kt +2 -4
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/exceptions/ComposeNotSupportedException.kt +28 -0
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/exceptions/InvalidArgumentException.kt +1 -1
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/helpers/EspressoViewsCache.kt +0 -17
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/helpers/ScreenshotHelpers.kt +0 -13
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/helpers/Version.kt +1 -1
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/http/Router.kt +1 -1
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/http/{Server.kt → ServerBase.kt} +32 -20
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/model/Element.kt +0 -13
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/model/SourceDocument.kt +4 -85
- package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/model/settings/DriverSetting.kt +15 -4
- package/espresso-server/settings.gradle.kts +30 -0
- package/lib/server-builder.ts +15 -2
- package/npm-shrinkwrap.json +36 -14
- package/package.json +2 -1
- /package/espresso-server/library/src/{main → composeOn}/java/io/appium/espressoserver/lib/model/ComposeNodeElement.kt +0 -0
|
@@ -20,42 +20,51 @@ import androidx.test.espresso.Espresso
|
|
|
20
20
|
import androidx.test.platform.app.InstrumentationRegistry
|
|
21
21
|
import com.google.gson.GsonBuilder
|
|
22
22
|
import fi.iki.elonen.NanoHTTPD
|
|
23
|
-
import io.appium.espressoserver.lib.drivers.DriverContext
|
|
24
23
|
import io.appium.espressoserver.lib.helpers.AndroidLogger
|
|
25
24
|
import io.appium.espressoserver.lib.helpers.CustomFailureHandler
|
|
26
25
|
import io.appium.espressoserver.lib.helpers.StringHelpers
|
|
27
26
|
import io.appium.espressoserver.lib.helpers.setAccessibilityServiceState
|
|
28
27
|
import io.appium.espressoserver.lib.http.response.AppiumResponse
|
|
29
28
|
import io.appium.espressoserver.lib.http.response.BaseResponse
|
|
30
|
-
import org.junit.rules.TestRule
|
|
31
29
|
import java.io.IOException
|
|
32
30
|
import java.net.SocketException
|
|
33
|
-
import java.util
|
|
31
|
+
import java.util.LinkedHashMap
|
|
34
32
|
|
|
35
33
|
const val DEFAULT_PORT = 6791
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Shared NanoHTTPD server logic; flavor modules supply [Server] subclasses that
|
|
37
|
+
* extend this type and optionally mix in JUnit [org.junit.rules.TestRule] for Compose.
|
|
38
|
+
*/
|
|
39
|
+
abstract class ServerBase : NanoHTTPD(DEFAULT_PORT) {
|
|
38
40
|
|
|
39
41
|
private var router: Router? = null
|
|
40
42
|
|
|
41
43
|
@Volatile
|
|
42
44
|
private var isStopRequestReceived: Boolean = false
|
|
43
45
|
|
|
46
|
+
/**
|
|
47
|
+
* No-op when Compose is not present; on Compose builds advances the main clock when the
|
|
48
|
+
* driver strategy is Compose (see original Server implementation).
|
|
49
|
+
*/
|
|
50
|
+
protected abstract fun advanceComposeMainClockIfNeeded()
|
|
51
|
+
|
|
44
52
|
private val syncComposeClock = Thread {
|
|
45
53
|
while (!isStopRequestReceived) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// Let Android run measure, draw and in general any other async operations. AndroidComposeTestRule.android.kt:325
|
|
54
|
+
advanceComposeMainClockIfNeeded()
|
|
55
|
+
// Let Android run measure, draw and in general any other async operations.
|
|
56
|
+
// AndroidComposeTestRule.android.kt:325
|
|
50
57
|
Thread.sleep(ANDROID_ASYNC_WAIT_TIME_MS)
|
|
51
58
|
}
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
private fun buildFixedLengthResponse(response: BaseResponse): Response {
|
|
55
|
-
val gsonBuilder = GsonBuilder()
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
val gsonBuilder = GsonBuilder().serializeNulls()
|
|
63
|
+
return newFixedLengthResponse(
|
|
64
|
+
response.httpStatus,
|
|
65
|
+
"application/json",
|
|
66
|
+
gsonBuilder.create().toJson(response),
|
|
67
|
+
)
|
|
59
68
|
}
|
|
60
69
|
|
|
61
70
|
fun run() {
|
|
@@ -86,8 +95,10 @@ class Server : NanoHTTPD(DEFAULT_PORT), TestRule by DriverContext.composeTestRul
|
|
|
86
95
|
|
|
87
96
|
if (response is AppiumResponse) {
|
|
88
97
|
if (response.httpStatus === Response.Status.OK) {
|
|
89
|
-
AndroidLogger.info(
|
|
90
|
-
|
|
98
|
+
AndroidLogger.info(
|
|
99
|
+
"Responding to server with value: " +
|
|
100
|
+
StringHelpers.abbreviate(response.value?.toString(), 300),
|
|
101
|
+
)
|
|
91
102
|
} else {
|
|
92
103
|
AndroidLogger.info("Responding to server with error: ${response.value}")
|
|
93
104
|
}
|
|
@@ -98,17 +109,15 @@ class Server : NanoHTTPD(DEFAULT_PORT), TestRule by DriverContext.composeTestRul
|
|
|
98
109
|
} catch (e: RuntimeException) {
|
|
99
110
|
buildFixedLengthResponse(AppiumResponse(e))
|
|
100
111
|
}
|
|
101
|
-
|
|
102
112
|
}
|
|
103
113
|
|
|
104
114
|
@Throws(IOException::class)
|
|
105
115
|
override fun start() {
|
|
106
116
|
if (super.isAlive()) {
|
|
107
|
-
//kill the server if its already running
|
|
108
117
|
try {
|
|
109
118
|
super.stop()
|
|
110
119
|
} catch (e: Exception) {
|
|
111
|
-
//ignore the exception
|
|
120
|
+
// ignore the exception
|
|
112
121
|
}
|
|
113
122
|
}
|
|
114
123
|
|
|
@@ -118,8 +127,11 @@ class Server : NanoHTTPD(DEFAULT_PORT), TestRule by DriverContext.composeTestRul
|
|
|
118
127
|
try {
|
|
119
128
|
super.start(SOCKET_READ_TIMEOUT, false)
|
|
120
129
|
} catch (e: SocketException) {
|
|
121
|
-
throw IllegalStateException(
|
|
122
|
-
|
|
130
|
+
throw IllegalStateException(
|
|
131
|
+
"The application under test must require android.permission.INTERNET " +
|
|
132
|
+
"permission in its manifest",
|
|
133
|
+
e,
|
|
134
|
+
)
|
|
123
135
|
}
|
|
124
136
|
|
|
125
137
|
AndroidLogger.info("\nRunning Appium Espresso Server at port $DEFAULT_PORT\n")
|
|
@@ -128,7 +140,7 @@ class Server : NanoHTTPD(DEFAULT_PORT), TestRule by DriverContext.composeTestRul
|
|
|
128
140
|
|
|
129
141
|
private fun setCustomFailureHandler() =
|
|
130
142
|
Espresso.setFailureHandler(
|
|
131
|
-
CustomFailureHandler(InstrumentationRegistry.getInstrumentation().targetContext)
|
|
143
|
+
CustomFailureHandler(InstrumentationRegistry.getInstrumentation().targetContext),
|
|
132
144
|
)
|
|
133
145
|
|
|
134
146
|
override fun stop() {
|
|
@@ -19,7 +19,6 @@ package io.appium.espressoserver.lib.model
|
|
|
19
19
|
import android.view.View
|
|
20
20
|
import android.view.ViewParent
|
|
21
21
|
import android.widget.AdapterView
|
|
22
|
-
import androidx.compose.ui.test.SemanticsNodeInteraction
|
|
23
22
|
import androidx.test.espresso.Espresso.onData
|
|
24
23
|
import androidx.test.espresso.Espresso.onView
|
|
25
24
|
import androidx.test.espresso.EspressoException
|
|
@@ -30,7 +29,6 @@ import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
|
|
30
29
|
import com.google.gson.annotations.SerializedName
|
|
31
30
|
import io.appium.espressoserver.lib.handlers.exceptions.InvalidArgumentException
|
|
32
31
|
import io.appium.espressoserver.lib.handlers.exceptions.StaleElementException
|
|
33
|
-
import io.appium.espressoserver.lib.helpers.ComposeViewCache
|
|
34
32
|
import io.appium.espressoserver.lib.helpers.StringHelpers.charSequenceToNullableString
|
|
35
33
|
import io.appium.espressoserver.lib.helpers.ViewState
|
|
36
34
|
import io.appium.espressoserver.lib.helpers.EspressoViewsCache
|
|
@@ -160,15 +158,4 @@ class EspressoElement(viewState: ViewState) : BaseElement {
|
|
|
160
158
|
return resultState
|
|
161
159
|
}
|
|
162
160
|
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
class ComposeElement(node: SemanticsNodeInteraction) : BaseElement {
|
|
166
|
-
@Suppress("JoinDeclarationAndAssignment")
|
|
167
|
-
@SerializedName(JSONWP_ELEMENT_KEY, alternate = [W3C_ELEMENT_KEY])
|
|
168
|
-
override val element: String
|
|
169
|
-
|
|
170
|
-
init {
|
|
171
|
-
element = UUID.randomUUID().toString()
|
|
172
|
-
ComposeViewCache.put(element, node)
|
|
173
|
-
}
|
|
174
161
|
}
|
package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/model/SourceDocument.kt
CHANGED
|
@@ -24,10 +24,6 @@ import android.util.Xml
|
|
|
24
24
|
import android.view.View
|
|
25
25
|
import android.view.ViewGroup
|
|
26
26
|
import android.widget.AdapterView
|
|
27
|
-
import androidx.compose.ui.semantics.SemanticsNode
|
|
28
|
-
import androidx.compose.ui.test.SelectionResult
|
|
29
|
-
import androidx.compose.ui.test.SemanticsNodeInteraction
|
|
30
|
-
import androidx.compose.ui.test.onRoot
|
|
31
27
|
import androidx.test.core.app.ApplicationProvider.getApplicationContext
|
|
32
28
|
import io.appium.espressoserver.lib.drivers.DriverContext
|
|
33
29
|
import io.appium.espressoserver.lib.handlers.exceptions.AppiumException
|
|
@@ -45,8 +41,6 @@ import java.io.*
|
|
|
45
41
|
import java.util.*
|
|
46
42
|
import java.util.concurrent.Semaphore
|
|
47
43
|
import javax.xml.xpath.*
|
|
48
|
-
import kotlin.reflect.full.declaredMemberFunctions
|
|
49
|
-
import kotlin.reflect.jvm.isAccessible
|
|
50
44
|
|
|
51
45
|
const val NON_XML_CHAR_REPLACEMENT = "?"
|
|
52
46
|
const val VIEW_INDEX = "viewIndex"
|
|
@@ -58,7 +52,7 @@ const val XML_ENCODING = "UTF-8"
|
|
|
58
52
|
val XPATH: XPath = XPathFactory.newInstance().newXPath()
|
|
59
53
|
|
|
60
54
|
|
|
61
|
-
|
|
55
|
+
internal fun toXmlNodeName(className: String?): String {
|
|
62
56
|
if (className == null || className.trim { it <= ' ' }.isEmpty()) {
|
|
63
57
|
return DEFAULT_VIEW_CLASS_NAME
|
|
64
58
|
}
|
|
@@ -92,10 +86,10 @@ class SourceDocument constructor(
|
|
|
92
86
|
private val RESOURCES_GUARD = Semaphore(1)
|
|
93
87
|
|
|
94
88
|
private val viewMap: SparseArray<View> = SparseArray()
|
|
95
|
-
|
|
89
|
+
internal var serializer: XmlSerializer? = null
|
|
96
90
|
private var tmpXmlName: String? = null
|
|
97
91
|
|
|
98
|
-
|
|
92
|
+
internal fun setAttribute(attrName: AttributesEnum, attrValue: Any?) {
|
|
99
93
|
// Do not write attributes, whose values equal to null
|
|
100
94
|
attrValue?.let {
|
|
101
95
|
// Cut off longer strings to avoid OOM errors
|
|
@@ -210,50 +204,6 @@ class SourceDocument constructor(
|
|
|
210
204
|
serializer?.endTag(NAMESPACE, tagName)
|
|
211
205
|
}
|
|
212
206
|
|
|
213
|
-
private fun serializeComposeNode(semanticsNode: SemanticsNode?, depth: Int) {
|
|
214
|
-
if (semanticsNode == null) {
|
|
215
|
-
return
|
|
216
|
-
}
|
|
217
|
-
val nodeElement = ComposeNodeElement(semanticsNode)
|
|
218
|
-
val className = nodeElement.className
|
|
219
|
-
val tagName = toXmlNodeName(className)
|
|
220
|
-
serializer?.startTag(NAMESPACE, tagName)
|
|
221
|
-
|
|
222
|
-
linkedMapOf(
|
|
223
|
-
AttributesEnum.CLASS to { className },
|
|
224
|
-
AttributesEnum.INDEX to { nodeElement.index },
|
|
225
|
-
AttributesEnum.CLICKABLE to { nodeElement.isClickable },
|
|
226
|
-
AttributesEnum.ENABLED to { nodeElement.isEnabled },
|
|
227
|
-
AttributesEnum.FOCUSED to { nodeElement.isFocused },
|
|
228
|
-
AttributesEnum.SCROLLABLE to { nodeElement.isScrollable },
|
|
229
|
-
AttributesEnum.SELECTED to { nodeElement.isSelected },
|
|
230
|
-
AttributesEnum.CHECKED to { nodeElement.isChecked },
|
|
231
|
-
AttributesEnum.VIEW_TAG to { nodeElement.viewTag },
|
|
232
|
-
AttributesEnum.CONTENT_DESC to { nodeElement.contentDescription },
|
|
233
|
-
AttributesEnum.BOUNDS to { nodeElement.bounds.toShortString() },
|
|
234
|
-
AttributesEnum.TEXT to { nodeElement.text },
|
|
235
|
-
AttributesEnum.PASSWORD to { nodeElement.isPassword },
|
|
236
|
-
AttributesEnum.RESOURCE_ID to { nodeElement.resourceId },
|
|
237
|
-
).forEach {
|
|
238
|
-
setAttribute(it.key, it.value())
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (depth < MAX_TRAVERSAL_DEPTH) {
|
|
242
|
-
// Visit the children and build them too
|
|
243
|
-
val children = semanticsNode.children
|
|
244
|
-
for (index in 0 until children.count()) {
|
|
245
|
-
serializeComposeNode(children[index], depth + 1)
|
|
246
|
-
}
|
|
247
|
-
} else {
|
|
248
|
-
AndroidLogger.warn(
|
|
249
|
-
"Skipping traversal of ${semanticsNode.javaClass.name}'s children, since " +
|
|
250
|
-
"the current depth has reached its maximum allowed value of $depth"
|
|
251
|
-
)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
serializer?.endTag(NAMESPACE, tagName)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
207
|
private fun toStream(): InputStream {
|
|
258
208
|
var lastError: Throwable? = null
|
|
259
209
|
// Try to serialize the xml into the memory first, since it is fast
|
|
@@ -281,18 +231,7 @@ class SourceDocument constructor(
|
|
|
281
231
|
val startTime = SystemClock.uptimeMillis()
|
|
282
232
|
when (DriverContext.currentStrategyType) {
|
|
283
233
|
DriverContext.StrategyType.COMPOSE -> {
|
|
284
|
-
|
|
285
|
-
serializeComposeNode(root as SemanticsNode, 0)
|
|
286
|
-
} else {
|
|
287
|
-
val rootNodes = rootSemanticNodes()
|
|
288
|
-
if (rootNodes.size == 1) {
|
|
289
|
-
serializeComposeNode(rootNodes.first(), 0)
|
|
290
|
-
} else {
|
|
291
|
-
serializer?.startTag(NAMESPACE, DEFAULT_TAG_NAME)
|
|
292
|
-
rootNodes.forEach { semanticsNode -> serializeComposeNode(semanticsNode, 0) }
|
|
293
|
-
serializer?.endTag(NAMESPACE, DEFAULT_TAG_NAME)
|
|
294
|
-
}
|
|
295
|
-
}
|
|
234
|
+
SourceDocumentComposeHooks.appendComposeStrategy(this, root)
|
|
296
235
|
}
|
|
297
236
|
DriverContext.StrategyType.ESPRESSO -> {
|
|
298
237
|
val rootView = root ?: ViewGetter().rootView
|
|
@@ -328,26 +267,6 @@ class SourceDocument constructor(
|
|
|
328
267
|
throw AppiumException(lastError!!)
|
|
329
268
|
}
|
|
330
269
|
|
|
331
|
-
private fun rootSemanticNodes(): List<SemanticsNode> =
|
|
332
|
-
try {
|
|
333
|
-
listOf(DriverContext.composeTestRule.onRoot(useUnmergedTree = true).fetchSemanticsNode())
|
|
334
|
-
} catch (e: AssertionError) {
|
|
335
|
-
// Ideally there should be on `root` node but on some cases e.g:overlays screen, there can be more than 1 root.
|
|
336
|
-
// Compose API not respecting such cases instead throws AssertionError, as a work around fetching all root nodes by relaying on internal API.
|
|
337
|
-
// e.g: "Reason: Expected exactly '1' node but found '2' nodes that satisfy: (isRoot)"
|
|
338
|
-
val result: SelectionResult = SemanticsNodeInteraction::class.declaredMemberFunctions.find { it.name == "fetchSemanticsNodes" }?.let {
|
|
339
|
-
it.isAccessible = true
|
|
340
|
-
// INFO: Compose API has added the method parameter in compose 1.6.x+
|
|
341
|
-
// https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/SemanticsNodeInteraction.kt;l=59;bpv=1;bpt=0;drc=78d5dc8c9b42c32b0c8518c72181c19620a71c05;dlc=7aed8c7ff9c9355d5dfeb28021f8725d58dc4c6f
|
|
342
|
-
if (it.parameters.size == 4) {
|
|
343
|
-
it.call(DriverContext.composeTestRule.onRoot(useUnmergedTree = true), true, null, true)
|
|
344
|
-
} else {
|
|
345
|
-
it.call(DriverContext.composeTestRule.onRoot(useUnmergedTree = true), true, null)
|
|
346
|
-
}
|
|
347
|
-
} as SelectionResult
|
|
348
|
-
result.selectedNodes
|
|
349
|
-
}
|
|
350
|
-
|
|
351
270
|
private fun performCleanup() {
|
|
352
271
|
tmpXmlName?.let {
|
|
353
272
|
getApplicationContext<Context>().deleteFile(it)
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
package io.appium.espressoserver.lib.model.settings
|
|
18
18
|
|
|
19
|
+
import io.appium.espressoserver.lib.BuildConfig
|
|
19
20
|
import io.appium.espressoserver.lib.drivers.DriverContext
|
|
21
|
+
import io.appium.espressoserver.lib.handlers.exceptions.ComposeNotSupportedException
|
|
20
22
|
import io.appium.espressoserver.lib.handlers.exceptions.InvalidArgumentException
|
|
21
23
|
|
|
22
24
|
class DriverSetting : AbstractSetting() {
|
|
@@ -27,13 +29,22 @@ class DriverSetting : AbstractSetting() {
|
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
override fun apply(value: Any?) {
|
|
32
|
+
val requested = value?.toString()?.trim()?.lowercase()
|
|
33
|
+
if (requested == DriverContext.StrategyType.COMPOSE.name.lowercase() && !BuildConfig.COMPOSE_SUPPORT) {
|
|
34
|
+
throw ComposeNotSupportedException()
|
|
35
|
+
}
|
|
30
36
|
val driverStrategy =
|
|
31
|
-
|
|
37
|
+
allowedStrategies().find { it.toString().lowercase() == requested }
|
|
32
38
|
?: throw InvalidArgumentException(
|
|
33
|
-
"driver type must be one of ${
|
|
34
|
-
DriverContext.StrategyType.values().map { it.toString().lowercase() }
|
|
35
|
-
}"
|
|
39
|
+
"driver type must be one of ${allowedStrategies().map { it.toString().lowercase() }}",
|
|
36
40
|
)
|
|
37
41
|
DriverContext.setDriverStrategy(driverStrategy)
|
|
38
42
|
}
|
|
43
|
+
|
|
44
|
+
private fun allowedStrategies(): List<DriverContext.StrategyType> =
|
|
45
|
+
if (BuildConfig.COMPOSE_SUPPORT) {
|
|
46
|
+
DriverContext.StrategyType.entries
|
|
47
|
+
} else {
|
|
48
|
+
listOf(DriverContext.StrategyType.ESPRESSO)
|
|
49
|
+
}
|
|
39
50
|
}
|
|
@@ -1,2 +1,32 @@
|
|
|
1
|
+
pluginManagement {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
gradlePluginPortal()
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// When Espresso driver passes -PappiumAndroidGradlePlugin, override the AGP version from the
|
|
9
|
+
// version catalog. Otherwise keep Gradle's catalog-resolved requested version (no regex/TOML parse).
|
|
10
|
+
resolutionStrategy {
|
|
11
|
+
eachPlugin {
|
|
12
|
+
if (requested.id.id == "com.android.application" || requested.id.id == "com.android.library") {
|
|
13
|
+
val fromCapability =
|
|
14
|
+
providers.gradleProperty("appiumAndroidGradlePlugin").orNull?.trim()?.takeIf { it.isNotEmpty() }
|
|
15
|
+
if (fromCapability != null) {
|
|
16
|
+
useVersion(fromCapability)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
dependencyResolutionManagement {
|
|
24
|
+
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
|
|
25
|
+
repositories {
|
|
26
|
+
google()
|
|
27
|
+
mavenCentral()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
1
31
|
include(":app")
|
|
2
32
|
include(":library")
|
package/lib/server-builder.ts
CHANGED
|
@@ -56,8 +56,13 @@ export function buildServerSigningConfig(args: BuildServerSigningConfigArgs): Se
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
interface
|
|
59
|
+
export interface EspressoBuildConfiguration {
|
|
60
60
|
toolsVersions?: Record<string, string>;
|
|
61
|
+
/**
|
|
62
|
+
* When false, the Espresso server is built without Jetpack Compose dependencies.
|
|
63
|
+
* Defaults to true when omitted.
|
|
64
|
+
*/
|
|
65
|
+
composeSupport?: boolean;
|
|
61
66
|
additionalAppDependencies?: string[];
|
|
62
67
|
additionalAndroidTestDependencies?: string[];
|
|
63
68
|
}
|
|
@@ -65,7 +70,7 @@ interface BuildConfiguration {
|
|
|
65
70
|
export interface ServerBuilderOptions {
|
|
66
71
|
serverPath: string;
|
|
67
72
|
showGradleLog?: boolean;
|
|
68
|
-
buildConfiguration?:
|
|
73
|
+
buildConfiguration?: EspressoBuildConfiguration;
|
|
69
74
|
testAppPackage?: string;
|
|
70
75
|
signingConfig?: ServerSigningConfig | null;
|
|
71
76
|
}
|
|
@@ -79,6 +84,7 @@ export class ServerBuilder {
|
|
|
79
84
|
private readonly signingConfig?: ServerSigningConfig | null;
|
|
80
85
|
private readonly additionalAppDependencies: string[];
|
|
81
86
|
private readonly additionalAndroidTestDependencies: string[];
|
|
87
|
+
private readonly composeSupport: boolean;
|
|
82
88
|
|
|
83
89
|
constructor(log: AppiumLogger, args: ServerBuilderOptions) {
|
|
84
90
|
this.log = log;
|
|
@@ -86,6 +92,7 @@ export class ServerBuilder {
|
|
|
86
92
|
this.showGradleLog = args.showGradleLog;
|
|
87
93
|
|
|
88
94
|
const buildConfiguration = args.buildConfiguration || {};
|
|
95
|
+
this.composeSupport = buildConfiguration.composeSupport !== false;
|
|
89
96
|
|
|
90
97
|
const versionConfiguration = buildConfiguration.toolsVersions || {};
|
|
91
98
|
this.serverVersions = _.reduce(
|
|
@@ -153,6 +160,12 @@ export class ServerBuilder {
|
|
|
153
160
|
args.push(targetPackageArg);
|
|
154
161
|
}
|
|
155
162
|
}
|
|
163
|
+
if (!this.composeSupport) {
|
|
164
|
+
const composeArg = buildProperty('appiumComposeSupport', 'false');
|
|
165
|
+
if (composeArg) {
|
|
166
|
+
args.push(composeArg);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
156
169
|
args.push('app:assembleAndroidTest');
|
|
157
170
|
|
|
158
171
|
return {cmd, args};
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appium-espresso-driver",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.2.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "appium-espresso-driver",
|
|
9
|
-
"version": "8.
|
|
9
|
+
"version": "8.2.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"appium-adb": "^14.0.0",
|
|
@@ -91,6 +91,17 @@
|
|
|
91
91
|
"spdy": "4.0.2"
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
|
+
"node_modules/@appium/base-driver/node_modules/axios": {
|
|
95
|
+
"version": "1.15.0",
|
|
96
|
+
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
|
|
97
|
+
"integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
|
|
98
|
+
"license": "MIT",
|
|
99
|
+
"dependencies": {
|
|
100
|
+
"follow-redirects": "^1.15.11",
|
|
101
|
+
"form-data": "^4.0.5",
|
|
102
|
+
"proxy-from-env": "^2.1.0"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
94
105
|
"node_modules/@appium/logger": {
|
|
95
106
|
"version": "2.0.6",
|
|
96
107
|
"resolved": "https://registry.npmjs.org/@appium/logger/-/logger-2.0.6.tgz",
|
|
@@ -169,6 +180,17 @@
|
|
|
169
180
|
"sharp": "0.34.5"
|
|
170
181
|
}
|
|
171
182
|
},
|
|
183
|
+
"node_modules/@appium/support/node_modules/axios": {
|
|
184
|
+
"version": "1.15.0",
|
|
185
|
+
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
|
|
186
|
+
"integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
|
|
187
|
+
"license": "MIT",
|
|
188
|
+
"dependencies": {
|
|
189
|
+
"follow-redirects": "^1.15.11",
|
|
190
|
+
"form-data": "^4.0.5",
|
|
191
|
+
"proxy-from-env": "^2.1.0"
|
|
192
|
+
}
|
|
193
|
+
},
|
|
172
194
|
"node_modules/@appium/support/node_modules/teen_process": {
|
|
173
195
|
"version": "4.1.0",
|
|
174
196
|
"resolved": "https://registry.npmjs.org/teen_process/-/teen_process-4.1.0.tgz",
|
|
@@ -421,9 +443,9 @@
|
|
|
421
443
|
"license": "MIT"
|
|
422
444
|
},
|
|
423
445
|
"node_modules/@xmldom/xmldom": {
|
|
424
|
-
"version": "0.9.
|
|
425
|
-
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.
|
|
426
|
-
"integrity": "sha512-
|
|
446
|
+
"version": "0.9.10",
|
|
447
|
+
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz",
|
|
448
|
+
"integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==",
|
|
427
449
|
"license": "MIT",
|
|
428
450
|
"engines": {
|
|
429
451
|
"node": ">=14.6"
|
|
@@ -699,9 +721,9 @@
|
|
|
699
721
|
"license": "MIT"
|
|
700
722
|
},
|
|
701
723
|
"node_modules/axios": {
|
|
702
|
-
"version": "1.15.
|
|
703
|
-
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.
|
|
704
|
-
"integrity": "sha512-
|
|
724
|
+
"version": "1.15.1",
|
|
725
|
+
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.1.tgz",
|
|
726
|
+
"integrity": "sha512-WOG+Jj8ZOvR0a3rAn+Tuf1UQJRxw5venr6DgdbJzngJE3qG7X0kL83CZGpdHMxEm+ZK3seAbvFsw4FfOfP9vxg==",
|
|
705
727
|
"license": "MIT",
|
|
706
728
|
"dependencies": {
|
|
707
729
|
"follow-redirects": "^1.15.11",
|
|
@@ -812,9 +834,9 @@
|
|
|
812
834
|
}
|
|
813
835
|
},
|
|
814
836
|
"node_modules/bare-url": {
|
|
815
|
-
"version": "2.4.
|
|
816
|
-
"resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.
|
|
817
|
-
"integrity": "sha512-
|
|
837
|
+
"version": "2.4.1",
|
|
838
|
+
"resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.1.tgz",
|
|
839
|
+
"integrity": "sha512-fZapLWNB25gS+etK27NV9KgBNXgo2yeYHuj+OyPblQd6GYAE3JVy6aKxszMV5jhGGFwraXQKA5fldvf3lMyEqw==",
|
|
818
840
|
"license": "Apache-2.0",
|
|
819
841
|
"dependencies": {
|
|
820
842
|
"bare-path": "^3.0.0"
|
|
@@ -2704,9 +2726,9 @@
|
|
|
2704
2726
|
}
|
|
2705
2727
|
},
|
|
2706
2728
|
"node_modules/plist/node_modules/@xmldom/xmldom": {
|
|
2707
|
-
"version": "0.8.
|
|
2708
|
-
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.
|
|
2709
|
-
"integrity": "sha512-
|
|
2729
|
+
"version": "0.8.13",
|
|
2730
|
+
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz",
|
|
2731
|
+
"integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==",
|
|
2710
2732
|
"license": "MIT",
|
|
2711
2733
|
"engines": {
|
|
2712
2734
|
"node": ">=10.0.0"
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"automated testing",
|
|
8
8
|
"android"
|
|
9
9
|
],
|
|
10
|
-
"version": "8.
|
|
10
|
+
"version": "8.2.0",
|
|
11
11
|
"author": "Appium Contributors",
|
|
12
12
|
"license": "Apache-2.0",
|
|
13
13
|
"repository": {
|
|
@@ -106,6 +106,7 @@
|
|
|
106
106
|
"test": "npm run test:node",
|
|
107
107
|
"test:node": "mocha --exit --timeout 1m \"./test/unit/**/*-specs.ts\"",
|
|
108
108
|
"test:server": "cd espresso-server && ./gradlew test --stacktrace || cd ..",
|
|
109
|
+
"assemble:server:compose-off": "cd espresso-server && ./gradlew :app:assembleAndroidTest -PappiumComposeSupport=false --no-daemon",
|
|
109
110
|
"version": "npm run sync-version && npm run build:server",
|
|
110
111
|
"e2e-test": "mocha --exit --timeout 5m \"./test/functional/**/*-specs.ts\""
|
|
111
112
|
},
|