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.
Files changed (122) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +5 -0
  3. package/build/lib/server-builder.d.ts +8 -3
  4. package/build/lib/server-builder.d.ts.map +1 -1
  5. package/build/lib/server-builder.js +8 -0
  6. package/build/lib/server-builder.js.map +1 -1
  7. package/espresso-server/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk +0 -0
  8. package/espresso-server/app/build.gradle.kts +20 -15
  9. package/espresso-server/build.gradle.kts +3 -18
  10. package/espresso-server/buildSrc/.gradle/9.4.1/executionHistory/executionHistory.bin +0 -0
  11. package/espresso-server/buildSrc/.gradle/9.4.1/executionHistory/executionHistory.lock +0 -0
  12. package/espresso-server/buildSrc/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  13. package/espresso-server/buildSrc/.gradle/buildOutputCleanup/cache.properties +1 -1
  14. package/espresso-server/buildSrc/.gradle/file-system.probe +0 -0
  15. package/espresso-server/buildSrc/build/classes/kotlin/main/META-INF/buildSrc.kotlin_module +0 -0
  16. package/espresso-server/buildSrc/build/classes/kotlin/main/io/appium/espressoserver/gradle/CapabilityVersionKt.class +0 -0
  17. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  18. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
  19. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
  20. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +0 -0
  21. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  22. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
  23. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
  24. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
  25. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
  26. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
  27. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
  28. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
  29. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab +0 -0
  30. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream +0 -0
  31. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream.len +0 -0
  32. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.len +0 -0
  33. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.values.at +0 -0
  34. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i +0 -0
  35. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i.len +0 -0
  36. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
  37. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
  38. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
  39. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
  40. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  41. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  42. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  43. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
  44. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
  45. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +0 -0
  46. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  47. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
  48. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/counters.tab +1 -1
  49. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
  50. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
  51. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
  52. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +0 -0
  53. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
  54. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
  55. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
  56. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
  57. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
  58. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
  59. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
  60. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
  61. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +0 -0
  62. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  63. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  64. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
  65. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
  66. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  67. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  68. package/espresso-server/buildSrc/build/kotlin/compileKotlin/cacheable/last-build.bin +0 -0
  69. package/espresso-server/buildSrc/build/kotlin/compileKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
  70. package/espresso-server/buildSrc/build/libs/buildSrc.jar +0 -0
  71. package/espresso-server/buildSrc/build.gradle.kts +2 -1
  72. package/espresso-server/buildSrc/src/main/kotlin/io/appium/espressoserver/gradle/CapabilityVersion.kt +12 -0
  73. package/espresso-server/gradle/libs.versions.toml +16 -0
  74. package/espresso-server/gradle.properties +1 -12
  75. package/espresso-server/library/build.gradle.kts +53 -32
  76. package/espresso-server/library/src/composeOff/java/io/appium/espressoserver/lib/compose/ComposeHandlerBridge.kt +73 -0
  77. package/espresso-server/library/src/composeOff/java/io/appium/espressoserver/lib/helpers/ComposeViewCache.kt +29 -0
  78. package/espresso-server/library/src/composeOff/java/io/appium/espressoserver/lib/http/Server.kt +21 -0
  79. package/espresso-server/library/src/composeOff/java/io/appium/espressoserver/lib/model/SourceDocumentComposeHooks.kt +26 -0
  80. package/espresso-server/library/src/composeOn/java/io/appium/espressoserver/lib/compose/ComposeHandlerBridge.kt +180 -0
  81. package/espresso-server/library/src/composeOn/java/io/appium/espressoserver/lib/compose/ComposeScreenshot.kt +49 -0
  82. package/espresso-server/library/src/composeOn/java/io/appium/espressoserver/lib/drivers/DriverContextComposeRule.kt +29 -0
  83. package/espresso-server/library/src/{main → composeOn}/java/io/appium/espressoserver/lib/helpers/ComposeNodeFinder.kt +1 -0
  84. package/espresso-server/library/src/composeOn/java/io/appium/espressoserver/lib/helpers/ComposeViewCache.kt +37 -0
  85. package/espresso-server/library/src/composeOn/java/io/appium/espressoserver/lib/http/Server.kt +30 -0
  86. package/espresso-server/library/src/composeOn/java/io/appium/espressoserver/lib/model/ComposeElement.kt +33 -0
  87. package/espresso-server/library/src/composeOn/java/io/appium/espressoserver/lib/model/SourceDocumentComposeHooks.kt +103 -0
  88. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/drivers/DriverContext.kt +3 -6
  89. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/Clear.kt +2 -12
  90. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/Click.kt +2 -12
  91. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/DeleteSession.kt +2 -2
  92. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/ElementScreenshot.kt +2 -3
  93. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/ElementValue.kt +2 -12
  94. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/FindElement.kt +3 -12
  95. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/FindElements.kt +3 -7
  96. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetAttribute.kt +3 -7
  97. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetDisplayed.kt +2 -9
  98. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetEnabled.kt +2 -4
  99. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetLocation.kt +2 -6
  100. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetName.kt +2 -6
  101. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetRect.kt +2 -4
  102. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetSelected.kt +2 -4
  103. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/GetSize.kt +2 -6
  104. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/Keys.kt +2 -16
  105. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/MobileSwipe.kt +2 -24
  106. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/RequestHandler.kt +8 -1
  107. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/Text.kt +2 -4
  108. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/exceptions/ComposeNotSupportedException.kt +28 -0
  109. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/handlers/exceptions/InvalidArgumentException.kt +1 -1
  110. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/helpers/EspressoViewsCache.kt +0 -17
  111. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/helpers/ScreenshotHelpers.kt +0 -13
  112. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/helpers/Version.kt +1 -1
  113. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/http/Router.kt +1 -1
  114. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/http/{Server.kt → ServerBase.kt} +32 -20
  115. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/model/Element.kt +0 -13
  116. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/model/SourceDocument.kt +4 -85
  117. package/espresso-server/library/src/main/java/io/appium/espressoserver/lib/model/settings/DriverSetting.kt +15 -4
  118. package/espresso-server/settings.gradle.kts +30 -0
  119. package/lib/server-builder.ts +15 -2
  120. package/npm-shrinkwrap.json +36 -14
  121. package/package.json +2 -1
  122. /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
- class Server : NanoHTTPD(DEFAULT_PORT), TestRule by DriverContext.composeTestRule {
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
- if (DriverContext.currentStrategyType == DriverContext.StrategyType.COMPOSE) {
47
- DriverContext.composeTestRule.mainClock.advanceTimeByFrame()
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
- .serializeNulls()
57
- return newFixedLengthResponse(response.httpStatus,
58
- "application/json", gsonBuilder.create().toJson(response))
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("Responding to server with value: " +
90
- StringHelpers.abbreviate(response.value?.toString(), 300))
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("The application under test must require android.permission.INTERNET " +
122
- "permission in its manifest", e)
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
  }
@@ -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
- private fun toXmlNodeName(className: String?): String {
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
- private var serializer: XmlSerializer? = null
89
+ internal var serializer: XmlSerializer? = null
96
90
  private var tmpXmlName: String? = null
97
91
 
98
- private fun setAttribute(attrName: AttributesEnum, attrValue: Any?) {
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
- if (root != null) {
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
- DriverContext.StrategyType.values().find { it.toString().lowercase() == value }
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")
@@ -56,8 +56,13 @@ export function buildServerSigningConfig(args: BuildServerSigningConfigArgs): Se
56
56
  };
57
57
  }
58
58
 
59
- interface BuildConfiguration {
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?: 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};
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "appium-espresso-driver",
3
- "version": "8.0.0",
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.0.0",
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.9",
425
- "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.9.tgz",
426
- "integrity": "sha512-qycIHAucxy/LXAYIjmLmtQ8q9GPnMbnjG1KXhWm9o5sCr6pOYDATkMPiTNa6/v8eELyqOQ2FsEqeoFYmgv/gJg==",
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.0",
703
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
704
- "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
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.0",
816
- "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz",
817
- "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==",
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.12",
2708
- "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz",
2709
- "integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==",
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.0.0",
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
  },