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
@@ -0,0 +1,73 @@
1
+ /*
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * See the NOTICE file distributed with this work for additional
5
+ * information regarding copyright ownership.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package io.appium.espressoserver.lib.compose
18
+
19
+ import io.appium.espressoserver.lib.handlers.exceptions.ComposeNotSupportedException
20
+ import io.appium.espressoserver.lib.model.AppiumParams
21
+ import io.appium.espressoserver.lib.model.BaseElement
22
+ import io.appium.espressoserver.lib.model.Locator
23
+ import io.appium.espressoserver.lib.model.Location
24
+ import io.appium.espressoserver.lib.model.MobileSwipeParams
25
+ import io.appium.espressoserver.lib.model.Rect
26
+ import io.appium.espressoserver.lib.model.Size
27
+ import io.appium.espressoserver.lib.model.TextValueParams
28
+
29
+ internal object ComposeHandlerBridge {
30
+ private fun unsupported(): Nothing = throw ComposeNotSupportedException()
31
+
32
+ fun findElement(params: Locator): BaseElement = unsupported()
33
+
34
+ fun findElements(params: Locator): List<BaseElement> = unsupported()
35
+
36
+ fun click(params: AppiumParams) {
37
+ unsupported()
38
+ }
39
+
40
+ fun clear(params: AppiumParams) {
41
+ unsupported()
42
+ }
43
+
44
+ fun elementValue(params: TextValueParams, isReplacing: Boolean) {
45
+ unsupported()
46
+ }
47
+
48
+ fun keys(params: TextValueParams) {
49
+ unsupported()
50
+ }
51
+
52
+ fun mobileSwipe(params: MobileSwipeParams): Void? = unsupported()
53
+
54
+ fun getDisplayed(params: AppiumParams): Boolean = unsupported()
55
+
56
+ fun elementScreenshot(elementId: String): String = unsupported()
57
+
58
+ fun getAttribute(elementId: String, attributeName: String): String? = unsupported()
59
+
60
+ fun getEnabled(params: AppiumParams): Boolean = unsupported()
61
+
62
+ fun getLocation(params: AppiumParams): Location = unsupported()
63
+
64
+ fun getName(params: AppiumParams): String? = unsupported()
65
+
66
+ fun getRect(params: AppiumParams): Rect = unsupported()
67
+
68
+ fun getSelected(params: AppiumParams): Boolean = unsupported()
69
+
70
+ fun getSize(params: AppiumParams): Size = unsupported()
71
+
72
+ fun text(params: AppiumParams): String = unsupported()
73
+ }
@@ -0,0 +1,29 @@
1
+ /*
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * See the NOTICE file distributed with this work for additional
5
+ * information regarding copyright ownership.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package io.appium.espressoserver.lib.helpers
18
+
19
+ /** No-op when Jetpack Compose is not included in this server build. */
20
+ object ComposeViewCache {
21
+ @Suppress("UNUSED_PARAMETER")
22
+ fun put(id: String, composeNode: Any) {
23
+ }
24
+
25
+ fun get(id: String?): Any? = null
26
+
27
+ fun reset() {
28
+ }
29
+ }
@@ -0,0 +1,21 @@
1
+ /*
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * See the NOTICE file distributed with this work for additional
5
+ * information regarding copyright ownership.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package io.appium.espressoserver.lib.http
18
+
19
+ class Server : ServerBase() {
20
+ override fun advanceComposeMainClockIfNeeded() = Unit
21
+ }
@@ -0,0 +1,26 @@
1
+ /*
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * See the NOTICE file distributed with this work for additional
5
+ * information regarding copyright ownership.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package io.appium.espressoserver.lib.model
18
+
19
+ import io.appium.espressoserver.lib.handlers.exceptions.ComposeNotSupportedException
20
+
21
+ internal object SourceDocumentComposeHooks {
22
+ @Suppress("UNUSED_PARAMETER")
23
+ fun appendComposeStrategy(doc: SourceDocument, root: Any?) {
24
+ throw ComposeNotSupportedException()
25
+ }
26
+ }
@@ -0,0 +1,180 @@
1
+ /*
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * See the NOTICE file distributed with this work for additional
5
+ * information regarding copyright ownership.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package io.appium.espressoserver.lib.compose
18
+
19
+ import androidx.compose.ui.test.assertIsDisplayed
20
+ import androidx.compose.ui.test.performClick
21
+ import androidx.compose.ui.test.performGesture
22
+ import androidx.compose.ui.test.performTextClearance
23
+ import androidx.compose.ui.test.performTextInput
24
+ import androidx.compose.ui.test.swipeDown
25
+ import androidx.compose.ui.test.swipeLeft
26
+ import androidx.compose.ui.test.swipeRight
27
+ import androidx.compose.ui.test.swipeUp
28
+ import io.appium.espressoserver.lib.handlers.exceptions.InvalidArgumentException
29
+ import io.appium.espressoserver.lib.handlers.exceptions.InvalidElementStateException
30
+ import io.appium.espressoserver.lib.handlers.exceptions.InvalidSelectorException
31
+ import io.appium.espressoserver.lib.handlers.exceptions.NoSuchElementException
32
+ import io.appium.espressoserver.lib.handlers.exceptions.StaleElementException
33
+ import io.appium.espressoserver.lib.helpers.AndroidLogger
34
+ import io.appium.espressoserver.lib.helpers.getNodeInteractionById
35
+ import io.appium.espressoserver.lib.helpers.getSemanticsNode
36
+ import io.appium.espressoserver.lib.helpers.toNodeInteractionsCollection
37
+ import io.appium.espressoserver.lib.model.AppiumParams
38
+ import io.appium.espressoserver.lib.model.BaseElement
39
+ import io.appium.espressoserver.lib.model.ComposeElement
40
+ import io.appium.espressoserver.lib.model.ComposeNodeElement
41
+ import io.appium.espressoserver.lib.model.Locator
42
+ import io.appium.espressoserver.lib.model.Location
43
+ import io.appium.espressoserver.lib.model.MobileSwipeParams
44
+ import io.appium.espressoserver.lib.model.Rect
45
+ import io.appium.espressoserver.lib.model.Size
46
+ import io.appium.espressoserver.lib.model.TextValueParams
47
+
48
+ internal object ComposeHandlerBridge {
49
+ fun findElement(params: Locator): BaseElement {
50
+ val nodeInteractions = toNodeInteractionsCollection(params)
51
+ if (nodeInteractions.fetchSemanticsNodes(false).isEmpty()) {
52
+ throw NoSuchElementException(
53
+ String.format(
54
+ "Could not find a compose element with strategy '%s' and selector '%s'",
55
+ params.using,
56
+ params.value,
57
+ ),
58
+ )
59
+ }
60
+ return ComposeElement(nodeInteractions[0])
61
+ }
62
+
63
+ fun findElements(params: Locator): List<BaseElement> {
64
+ val nodeInteractions = toNodeInteractionsCollection(params)
65
+ return List(nodeInteractions.fetchSemanticsNodes(false).size) { index ->
66
+ ComposeElement(nodeInteractions[index])
67
+ }
68
+ }
69
+
70
+ fun click(params: AppiumParams) {
71
+ try {
72
+ getNodeInteractionById(params.elementId).performClick()
73
+ } catch (e: AssertionError) {
74
+ throw StaleElementException(params.elementId!!)
75
+ } catch (e: IllegalArgumentException) {
76
+ throw InvalidElementStateException("Click", params.elementId!!, e)
77
+ }
78
+ }
79
+
80
+ fun clear(params: AppiumParams) {
81
+ try {
82
+ getNodeInteractionById(params.elementId).performTextClearance()
83
+ } catch (e: AssertionError) {
84
+ throw StaleElementException(params.elementId!!)
85
+ } catch (e: IllegalArgumentException) {
86
+ throw InvalidElementStateException("Clear", params.elementId!!, e)
87
+ }
88
+ }
89
+
90
+ fun elementValue(params: TextValueParams, isReplacing: Boolean) {
91
+ val value: String = extractTextToEnter(params)
92
+ try {
93
+ if (isReplacing) {
94
+ getNodeInteractionById(params.elementId).performTextClearance()
95
+ }
96
+ getNodeInteractionById(params.elementId).performTextInput(value)
97
+ } catch (e: IllegalArgumentException) {
98
+ throw InvalidElementStateException("Clear", params.elementId!!, e)
99
+ }
100
+ }
101
+
102
+ fun keys(params: TextValueParams) {
103
+ try {
104
+ val keys = params.value ?: return
105
+ keys.forEach {
106
+ getNodeInteractionById(params.elementId).performTextInput(it)
107
+ }
108
+ } catch (e: AssertionError) {
109
+ throw StaleElementException(params.elementId!!)
110
+ } catch (e: IllegalArgumentException) {
111
+ throw InvalidElementStateException("Keys", params.elementId!!, e)
112
+ }
113
+ }
114
+
115
+ fun mobileSwipe(params: MobileSwipeParams): Void? {
116
+ val nodeInteractions = getNodeInteractionById(params.elementId)
117
+ AndroidLogger.info("Performing swipe action with direction '${params.direction}'")
118
+ when (params.direction) {
119
+ MobileSwipeParams.Direction.UP -> nodeInteractions.performGesture { swipeUp() }
120
+ MobileSwipeParams.Direction.DOWN -> nodeInteractions.performGesture { swipeDown() }
121
+ MobileSwipeParams.Direction.LEFT -> nodeInteractions.performGesture { swipeLeft() }
122
+ MobileSwipeParams.Direction.RIGHT -> nodeInteractions.performGesture { swipeRight() }
123
+ else -> throw InvalidArgumentException(
124
+ "Unknown swipe direction '${params.direction}'. " +
125
+ "Only the following values are supported: " +
126
+ MobileSwipeParams.Direction.values().joinToString(",") { x -> x.name.lowercase() },
127
+ )
128
+ }
129
+ return null
130
+ }
131
+
132
+ fun getDisplayed(params: AppiumParams): Boolean =
133
+ try {
134
+ getNodeInteractionById(params.elementId).assertIsDisplayed()
135
+ true
136
+ } catch (e: AssertionError) {
137
+ false
138
+ }
139
+
140
+ fun elementScreenshot(elementId: String): String =
141
+ ComposeScreenshot.takeComposeNodeScreenshot(getNodeInteractionById(elementId))
142
+
143
+ fun getAttribute(elementId: String, attributeName: String): String? =
144
+ ComposeNodeElement(getSemanticsNode(elementId)).getAttribute(attributeName)
145
+
146
+ fun getEnabled(params: AppiumParams): Boolean =
147
+ ComposeNodeElement(getSemanticsNode(params.elementId!!)).isEnabled
148
+
149
+ fun getLocation(params: AppiumParams): Location {
150
+ val composeNodeElement = ComposeNodeElement(getSemanticsNode(params.elementId!!))
151
+ return Location(composeNodeElement.bounds.left, composeNodeElement.bounds.top)
152
+ }
153
+
154
+ fun getName(params: AppiumParams): String? {
155
+ val composeNodeElement = ComposeNodeElement(getSemanticsNode(params.elementId!!))
156
+ return composeNodeElement.contentDescription?.toString()
157
+ }
158
+
159
+ fun getRect(params: AppiumParams): Rect =
160
+ ComposeNodeElement(getSemanticsNode(params.elementId!!)).rect
161
+
162
+ fun getSelected(params: AppiumParams): Boolean =
163
+ ComposeNodeElement(getSemanticsNode(params.elementId!!)).isSelected
164
+
165
+ fun getSize(params: AppiumParams): Size {
166
+ val bounds = ComposeNodeElement(getSemanticsNode(params.elementId!!)).bounds
167
+ return Size(bounds.width(), bounds.height())
168
+ }
169
+
170
+ fun text(params: AppiumParams): String =
171
+ ComposeNodeElement(getSemanticsNode(params.elementId!!)).text.toString()
172
+
173
+ private fun extractTextToEnter(params: TextValueParams) =
174
+ when (Pair(params.value == null, params.text == null)) {
175
+ Pair(first = true, second = true) ->
176
+ throw InvalidArgumentException("Must provide 'value' or 'text' property")
177
+ Pair(first = false, second = true) -> params.value!!.joinToString(separator = "")
178
+ else -> params.text!!
179
+ }
180
+ }
@@ -0,0 +1,49 @@
1
+ /*
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * See the NOTICE file distributed with this work for additional
5
+ * information regarding copyright ownership.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package io.appium.espressoserver.lib.compose
18
+
19
+ import android.graphics.Bitmap
20
+ import android.util.Base64
21
+ import androidx.compose.ui.graphics.asAndroidBitmap
22
+ import androidx.compose.ui.test.SemanticsNodeInteraction
23
+ import androidx.compose.ui.test.captureToImage
24
+ import io.appium.espressoserver.lib.handlers.exceptions.ScreenCaptureException
25
+ import java.io.ByteArrayOutputStream
26
+
27
+ internal object ComposeScreenshot {
28
+ fun takeComposeNodeScreenshot(nodeInteraction: SemanticsNodeInteraction): String =
29
+ try {
30
+ encodeBitmap(
31
+ nodeInteraction.captureToImage().asAndroidBitmap(),
32
+ ) { nodeInteraction.fetchSemanticsNode().toString() }
33
+ } catch (e: RuntimeException) {
34
+ throw ScreenCaptureException("Cannot take a screenshot of a node", e)
35
+ }
36
+
37
+ private fun encodeBitmap(bitmap: Bitmap?, targetNameSupplier: () -> String): String {
38
+ if (bitmap == null || bitmap.height == 0 || bitmap.width == 0) {
39
+ throw ScreenCaptureException(
40
+ "Cannot capture a shot of the ${targetNameSupplier()}. " +
41
+ "Make sure none of the currently visible " +
42
+ "views have FLAG_SECURE set and that it is possible to take a screenshot manually",
43
+ )
44
+ }
45
+ val outputStream = ByteArrayOutputStream()
46
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
47
+ return Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP)
48
+ }
49
+ }
@@ -0,0 +1,29 @@
1
+ /*
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * See the NOTICE file distributed with this work for additional
5
+ * information regarding copyright ownership.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package io.appium.espressoserver.lib.drivers
18
+
19
+ import androidx.compose.ui.test.junit4.ComposeTestRule
20
+ import androidx.compose.ui.test.junit4.createEmptyComposeRule
21
+
22
+ private val emptyComposeRule: ComposeTestRule = createEmptyComposeRule()
23
+
24
+ /**
25
+ * Compose UI test rule for this build. Import [composeTestRule] when using from other packages
26
+ * (`import io.appium.espressoserver.lib.drivers.composeTestRule`).
27
+ */
28
+ val DriverContext.composeTestRule: ComposeTestRule
29
+ get() = emptyComposeRule
@@ -19,6 +19,7 @@ package io.appium.espressoserver.lib.helpers
19
19
  import androidx.compose.ui.semantics.SemanticsNode
20
20
  import androidx.compose.ui.test.*
21
21
  import io.appium.espressoserver.lib.drivers.DriverContext
22
+ import io.appium.espressoserver.lib.drivers.composeTestRule
22
23
  import io.appium.espressoserver.lib.handlers.exceptions.InvalidSelectorException
23
24
  import io.appium.espressoserver.lib.model.Strategy
24
25
  import io.appium.espressoserver.lib.handlers.exceptions.InvalidArgumentException
@@ -0,0 +1,37 @@
1
+ /*
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * See the NOTICE file distributed with this work for additional
5
+ * information regarding copyright ownership.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package io.appium.espressoserver.lib.helpers
18
+
19
+ import android.util.LruCache
20
+ import androidx.compose.ui.test.SemanticsNodeInteraction
21
+
22
+ const val COMPOSE_VIEW_CACHE_MAX_SIZE = 500
23
+
24
+ /** Compose does not have stable instances, use locator again. */
25
+ object ComposeViewCache {
26
+ private val cache = LruCache<String, SemanticsNodeInteraction>(COMPOSE_VIEW_CACHE_MAX_SIZE)
27
+
28
+ fun put(id: String, composeNode: SemanticsNodeInteraction) {
29
+ cache.put(id, composeNode)
30
+ }
31
+
32
+ fun get(id: String?): SemanticsNodeInteraction? = cache.get(id)
33
+
34
+ fun reset() {
35
+ cache.evictAll()
36
+ }
37
+ }
@@ -0,0 +1,30 @@
1
+ /*
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * See the NOTICE file distributed with this work for additional
5
+ * information regarding copyright ownership.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package io.appium.espressoserver.lib.http
18
+
19
+ import io.appium.espressoserver.lib.drivers.DriverContext
20
+ import io.appium.espressoserver.lib.drivers.composeTestRule
21
+ import org.junit.rules.TestRule
22
+
23
+ class Server : ServerBase(), TestRule by DriverContext.composeTestRule {
24
+
25
+ override fun advanceComposeMainClockIfNeeded() {
26
+ if (DriverContext.currentStrategyType == DriverContext.StrategyType.COMPOSE) {
27
+ DriverContext.composeTestRule.mainClock.advanceTimeByFrame()
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,33 @@
1
+ /*
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * See the NOTICE file distributed with this work for additional
5
+ * information regarding copyright ownership.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package io.appium.espressoserver.lib.model
18
+
19
+ import androidx.compose.ui.test.SemanticsNodeInteraction
20
+ import com.google.gson.annotations.SerializedName
21
+ import io.appium.espressoserver.lib.helpers.ComposeViewCache
22
+ import java.util.UUID
23
+
24
+ class ComposeElement(node: SemanticsNodeInteraction) : BaseElement {
25
+ @Suppress("JoinDeclarationAndAssignment")
26
+ @SerializedName(JSONWP_ELEMENT_KEY, alternate = [W3C_ELEMENT_KEY])
27
+ override val element: String
28
+
29
+ init {
30
+ element = UUID.randomUUID().toString()
31
+ ComposeViewCache.put(element, node)
32
+ }
33
+ }
@@ -0,0 +1,103 @@
1
+ /*
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * See the NOTICE file distributed with this work for additional
5
+ * information regarding copyright ownership.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package io.appium.espressoserver.lib.model
18
+
19
+ import androidx.compose.ui.semantics.SemanticsNode
20
+ import androidx.compose.ui.test.SelectionResult
21
+ import androidx.compose.ui.test.SemanticsNodeInteraction
22
+ import androidx.compose.ui.test.onRoot
23
+ import io.appium.espressoserver.lib.drivers.DriverContext
24
+ import io.appium.espressoserver.lib.drivers.composeTestRule
25
+ import io.appium.espressoserver.lib.helpers.AndroidLogger
26
+ import kotlin.reflect.full.declaredMemberFunctions
27
+ import kotlin.reflect.jvm.isAccessible
28
+
29
+ internal object SourceDocumentComposeHooks {
30
+ fun appendComposeStrategy(doc: SourceDocument, root: Any?) {
31
+ if (root != null) {
32
+ serializeComposeNode(doc, root as SemanticsNode, 0)
33
+ } else {
34
+ val rootNodes = rootSemanticNodes()
35
+ if (rootNodes.size == 1) {
36
+ serializeComposeNode(doc, rootNodes.first(), 0)
37
+ } else {
38
+ doc.serializer?.startTag(NAMESPACE, DEFAULT_TAG_NAME)
39
+ rootNodes.forEach { semanticsNode -> serializeComposeNode(doc, semanticsNode, 0) }
40
+ doc.serializer?.endTag(NAMESPACE, DEFAULT_TAG_NAME)
41
+ }
42
+ }
43
+ }
44
+
45
+ private fun serializeComposeNode(doc: SourceDocument, semanticsNode: SemanticsNode?, depth: Int) {
46
+ if (semanticsNode == null) {
47
+ return
48
+ }
49
+ val nodeElement = ComposeNodeElement(semanticsNode)
50
+ val className = nodeElement.className
51
+ val tagName = toXmlNodeName(className)
52
+ doc.serializer?.startTag(NAMESPACE, tagName)
53
+
54
+ linkedMapOf(
55
+ AttributesEnum.CLASS to { className },
56
+ AttributesEnum.INDEX to { nodeElement.index },
57
+ AttributesEnum.CLICKABLE to { nodeElement.isClickable },
58
+ AttributesEnum.ENABLED to { nodeElement.isEnabled },
59
+ AttributesEnum.FOCUSED to { nodeElement.isFocused },
60
+ AttributesEnum.SCROLLABLE to { nodeElement.isScrollable },
61
+ AttributesEnum.SELECTED to { nodeElement.isSelected },
62
+ AttributesEnum.CHECKED to { nodeElement.isChecked },
63
+ AttributesEnum.VIEW_TAG to { nodeElement.viewTag },
64
+ AttributesEnum.CONTENT_DESC to { nodeElement.contentDescription },
65
+ AttributesEnum.BOUNDS to { nodeElement.bounds.toShortString() },
66
+ AttributesEnum.TEXT to { nodeElement.text },
67
+ AttributesEnum.PASSWORD to { nodeElement.isPassword },
68
+ AttributesEnum.RESOURCE_ID to { nodeElement.resourceId },
69
+ ).forEach {
70
+ doc.setAttribute(it.key, it.value())
71
+ }
72
+
73
+ if (depth < MAX_TRAVERSAL_DEPTH) {
74
+ val children = semanticsNode.children
75
+ for (index in 0 until children.count()) {
76
+ serializeComposeNode(doc, children[index], depth + 1)
77
+ }
78
+ } else {
79
+ AndroidLogger.warn(
80
+ "Skipping traversal of ${semanticsNode.javaClass.name}'s children, since " +
81
+ "the current depth has reached its maximum allowed value of $depth",
82
+ )
83
+ }
84
+
85
+ doc.serializer?.endTag(NAMESPACE, tagName)
86
+ }
87
+
88
+ private fun rootSemanticNodes(): List<SemanticsNode> =
89
+ try {
90
+ listOf(DriverContext.composeTestRule.onRoot(useUnmergedTree = true).fetchSemanticsNode())
91
+ } catch (e: AssertionError) {
92
+ val result: SelectionResult =
93
+ SemanticsNodeInteraction::class.declaredMemberFunctions.find { it.name == "fetchSemanticsNodes" }?.let {
94
+ it.isAccessible = true
95
+ if (it.parameters.size == 4) {
96
+ it.call(DriverContext.composeTestRule.onRoot(useUnmergedTree = true), true, null, true)
97
+ } else {
98
+ it.call(DriverContext.composeTestRule.onRoot(useUnmergedTree = true), true, null)
99
+ }
100
+ } as SelectionResult
101
+ result.selectedNodes
102
+ }
103
+ }
@@ -16,19 +16,16 @@
16
16
 
17
17
  package io.appium.espressoserver.lib.drivers
18
18
 
19
- import androidx.compose.ui.test.junit4.createEmptyComposeRule
20
-
21
19
  object DriverContext {
22
20
  var currentStrategyType: StrategyType = StrategyType.ESPRESSO
23
21
  private set
24
22
 
25
- val composeTestRule = createEmptyComposeRule()
26
-
27
23
  fun setDriverStrategy(strategyType: StrategyType) {
28
24
  currentStrategyType = strategyType
29
25
  }
30
26
 
31
27
  enum class StrategyType {
32
- ESPRESSO, COMPOSE
28
+ ESPRESSO,
29
+ COMPOSE,
33
30
  }
34
- }
31
+ }