diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/Clear.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/Clear.kt index 9f9fa1533..645a8b081 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/Clear.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/Clear.kt @@ -23,13 +23,12 @@ import io.appium.espressoserver.lib.model.AppiumParams import io.appium.espressoserver.lib.model.EspressoElement import androidx.test.espresso.action.ViewActions.clearText -import io.appium.espressoserver.lib.handlers.exceptions.AppiumException import io.appium.espressoserver.lib.handlers.exceptions.StaleElementException import io.appium.espressoserver.lib.helpers.getNodeInteractionById class Clear : RequestHandler { - override fun handleEspresso(params: AppiumParams): Unit { + override fun handleEspresso(params: AppiumParams) { val viewInteraction = EspressoElement.getViewInteractionById(params.elementId) try { viewInteraction.perform(clearText()) @@ -38,7 +37,7 @@ class Clear : RequestHandler { } } - override fun handleCompose(params: AppiumParams): Unit { + override fun handleCompose(params: AppiumParams) { try { getNodeInteractionById(params.elementId).performTextClearance() } catch (e: AssertionError) { diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/ElementEquals.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/ElementEquals.kt index 4598efeb8..44b9d721c 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/ElementEquals.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/ElementEquals.kt @@ -12,8 +12,8 @@ class ElementEquals : RequestHandler { val elementId = params.elementId val otherElementId = params.getUriParameterValue("otherId") ?: throw InvalidArgumentException("'otherElementId' query parameter not found") - val viewOne = EspressoElement.getViewById(elementId) - val viewTwo = EspressoElement.getViewById(otherElementId) + val viewOne = EspressoElement.getCachedViewStateById(elementId).view + val viewTwo = EspressoElement.getCachedViewStateById(otherElementId).view return viewOne == viewTwo } } \ No newline at end of file diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/ElementScreenshot.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/ElementScreenshot.kt index 27abba08b..bae607515 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/ElementScreenshot.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/ElementScreenshot.kt @@ -25,7 +25,7 @@ class ElementScreenshot : RequestHandler { @Throws(AppiumException::class) override fun handleInternal(params: AppiumParams): String { - val view = EspressoElement.getViewById(params.elementId) + val view = EspressoElement.getCachedViewStateById(params.elementId).view return ScreenshotsHelper(view).screenshot } } diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/ElementValue.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/ElementValue.kt index 8d9f9e1a7..97d585ad6 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/ElementValue.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/ElementValue.kt @@ -35,11 +35,11 @@ import io.appium.espressoserver.lib.viewaction.ViewTextGetter class ElementValue(private val isReplacing: Boolean) : RequestHandler { - override fun handleEspresso(params: TextValueParams): Unit { + override fun handleEspresso(params: TextValueParams) { val value: String = extractTextToEnter(params) val elementId = params.elementId - val view = EspressoElement.getViewById(elementId) + val view = EspressoElement.getCachedViewStateById(elementId).view try { if (view is ProgressBar) { @@ -76,7 +76,7 @@ class ElementValue(private val isReplacing: Boolean) : RequestHandler { @Throws(AppiumException::class) override fun handleInternal(params: AppiumParams): EspressoElement { val view = findActive() ?: throw NoSuchElementException("No elements are currently focused") - return EspressoElement(view) + return EspressoElement(ViewState(view)) } } diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/FindElement.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/FindElement.kt index ad0c90416..479ec711d 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/FindElement.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/FindElement.kt @@ -31,7 +31,7 @@ class FindElement : RequestHandler { ViewGetter().getView(EspressoElement.getViewInteractionById(it)) } // Test the selector - val view = ViewFinder.findBy( + val viewState = ViewFinder.findBy( parentView, params.using ?: throw InvalidSelectorException("Locator strategy cannot be empty"), params.value ?: throw InvalidArgumentException() @@ -44,7 +44,7 @@ class FindElement : RequestHandler { ) // If we have a match, return success - return EspressoElement(view) + return EspressoElement(viewState) } override fun handleCompose(params: Locator): BaseElement { diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/FindElements.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/FindElements.kt index 9efb00a62..75cbd653c 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/FindElements.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/FindElements.kt @@ -37,13 +37,12 @@ class FindElements : RequestHandler> { parentView, params.using ?: throw InvalidSelectorException("Locator strategy cannot be empty"), params.value ?: throw InvalidArgumentException() - ) - .map { EspressoElement(it) } + ).map { EspressoElement(it) } } override fun handleCompose(params: Locator): List { val nodeInteractions = toNodeInteractionsCollection(params) - return nodeInteractions.fetchSemanticsNodes(false) - .mapIndexed { index, _ -> ComposeElement(nodeInteractions[index]) } + return List(nodeInteractions.fetchSemanticsNodes(false).size) + { index -> ComposeElement(nodeInteractions[index]) } } } diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetAttribute.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetAttribute.kt index beb81a59b..259ab3cd7 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetAttribute.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetAttribute.kt @@ -52,9 +52,9 @@ class GetAttribute : RequestHandler { private fun getEspressoAttribute(elementId: String, attributeName: String): String? { val viewElementGetter: () -> ViewElement = - { ViewElement(EspressoElement.getViewById(elementId)) } + { ViewElement(EspressoElement.getCachedViewStateById(elementId).view) } val uncheckedViewElementGetter: () -> ViewElement = - { ViewElement(EspressoElement.getViewById(elementId, false)) } + { ViewElement(EspressoElement.getCachedViewStateById(elementId, false).view) } val viewInteractionGetter: () -> ViewInteraction = { EspressoElement.getViewInteractionById(elementId) } val checkToAttributeValue: (() -> Unit) -> String = { diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetDisplayed.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetDisplayed.kt index 75235f569..d63181be4 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetDisplayed.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetDisplayed.kt @@ -25,7 +25,7 @@ import io.appium.espressoserver.lib.model.ViewElement class GetDisplayed : RequestHandler { override fun handleEspresso(params: AppiumParams): Boolean = - ViewElement(EspressoElement.getViewById(params.elementId, false)).isVisible + ViewElement(EspressoElement.getCachedViewStateById(params.elementId, false).view).isVisible override fun handleCompose(params: AppiumParams): Boolean = try { diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetEnabled.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetEnabled.kt index 7c5e62a69..62d35341f 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetEnabled.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetEnabled.kt @@ -25,7 +25,7 @@ import io.appium.espressoserver.lib.model.ComposeNodeElement class GetEnabled : RequestHandler { override fun handleEspresso(params: AppiumParams): Boolean = - ViewElement(EspressoElement.getViewById(params.elementId)).isEnabled + ViewElement(EspressoElement.getCachedViewStateById(params.elementId).view).isEnabled override fun handleCompose(params: AppiumParams): Boolean = ComposeNodeElement(getSemanticsNode(params.elementId!!)).isEnabled diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetLocation.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetLocation.kt index 3fa8d24b9..68104d1d1 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetLocation.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetLocation.kt @@ -26,7 +26,7 @@ import io.appium.espressoserver.lib.model.ComposeNodeElement class GetLocation : RequestHandler { override fun handleEspresso(params: AppiumParams): Location { - val viewElement = ViewElement(EspressoElement.getViewById(params.elementId)) + val viewElement = ViewElement(EspressoElement.getCachedViewStateById(params.elementId).view) return Location(viewElement.bounds.left, viewElement.bounds.top) } diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetLocationInView.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetLocationInView.kt index 87332de22..51b6f2a45 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetLocationInView.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetLocationInView.kt @@ -24,7 +24,7 @@ import io.appium.espressoserver.lib.model.ViewElement class GetLocationInView : RequestHandler { override fun handleEspresso(params: AppiumParams): Location { - val viewElement = ViewElement(EspressoElement.getViewById(params.elementId)) + val viewElement = ViewElement(EspressoElement.getCachedViewStateById(params.elementId).view) return Location(viewElement.relativeLeft, viewElement.relativeTop) } } diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetName.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetName.kt index 883adfb3f..6e07011aa 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetName.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetName.kt @@ -25,8 +25,8 @@ import io.appium.espressoserver.lib.model.ComposeNodeElement class GetName : RequestHandler { override fun handleEspresso(params: AppiumParams): String? { - val view = EspressoElement.getViewById(params.elementId) - return ViewElement(view).contentDescription?.toString() + val viewState = EspressoElement.getCachedViewStateById(params.elementId) + return ViewElement(viewState.view).contentDescription?.toString() } override fun handleCompose(params: AppiumParams): String? { diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetRect.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetRect.kt index d2912b868..a757a503a 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetRect.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetRect.kt @@ -26,7 +26,7 @@ import io.appium.espressoserver.lib.model.ViewElement class GetRect : RequestHandler { override fun handleEspresso(params: AppiumParams): Rect = - ViewElement(EspressoElement.getViewById(params.elementId)).rect + ViewElement(EspressoElement.getCachedViewStateById(params.elementId).view).rect override fun handleCompose(params: AppiumParams): Rect = ComposeNodeElement(getSemanticsNode(params.elementId!!)).rect diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetSelected.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetSelected.kt index d7ef3ab7c..bc953d557 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetSelected.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetSelected.kt @@ -26,7 +26,7 @@ import io.appium.espressoserver.lib.model.ViewElement class GetSelected : RequestHandler { override fun handleEspresso(params: AppiumParams): Boolean = - ViewElement(EspressoElement.getViewById(params.elementId)).isSelected + ViewElement(EspressoElement.getCachedViewStateById(params.elementId).view).isSelected override fun handleCompose(params: AppiumParams): Boolean = ComposeNodeElement(getSemanticsNode(params.elementId!!)).isSelected diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetSize.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetSize.kt index f8671f84a..8fdae9180 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetSize.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/GetSize.kt @@ -26,7 +26,7 @@ import io.appium.espressoserver.lib.model.ComposeNodeElement class GetSize : RequestHandler { override fun handleEspresso(params: AppiumParams): Size { - val bounds = ViewElement(EspressoElement.getViewById(params.elementId)).bounds + val bounds = ViewElement(EspressoElement.getCachedViewStateById(params.elementId).view).bounds return Size(bounds.width(), bounds.height()) } diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/Keys.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/Keys.kt index 488fe912e..cb67ce4eb 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/Keys.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/Keys.kt @@ -18,7 +18,6 @@ package io.appium.espressoserver.lib.handlers import androidx.compose.ui.test.performTextInput import androidx.test.espresso.UiController -import io.appium.espressoserver.lib.handlers.exceptions.AppiumException import io.appium.espressoserver.lib.handlers.exceptions.InvalidElementStateException import io.appium.espressoserver.lib.handlers.exceptions.StaleElementException import io.appium.espressoserver.lib.helpers.getNodeInteractionById @@ -37,7 +36,7 @@ import io.appium.espressoserver.lib.model.TextValueParams class Keys : RequestHandler { - override fun handleEspresso(params: TextValueParams): Unit { + override fun handleEspresso(params: TextValueParams) { val keys = params.value ?: emptyList() val runnable = object : UiControllerRunnable { @@ -81,7 +80,7 @@ class Keys : RequestHandler { UiControllerPerformer(runnable).run() } - override fun handleCompose(params: TextValueParams): Unit { + override fun handleCompose(params: TextValueParams) { try { val keys = params.value ?: return keys.forEach { diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileBackdoor.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileBackdoor.kt index 79830c38a..ac7628231 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileBackdoor.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileBackdoor.kt @@ -1,6 +1,5 @@ package io.appium.espressoserver.lib.handlers -import io.appium.espressoserver.lib.handlers.exceptions.AppiumException import io.appium.espressoserver.lib.handlers.exceptions.InvalidArgumentException import io.appium.espressoserver.lib.helpers.ActivityHelpers import io.appium.espressoserver.lib.helpers.InvocationOperation @@ -11,7 +10,6 @@ import io.appium.espressoserver.lib.model.MobileBackdoorParams class MobileBackdoor : RequestHandler { - @Throws(AppiumException::class) override fun handleInternal(params: MobileBackdoorParams): Any? { params.target?.let {target -> val activity = ActivityHelpers.currentActivity @@ -21,7 +19,9 @@ class MobileBackdoor : RequestHandler { val result = when (target) { InvocationTarget.ACTIVITY -> invokeBackdoorMethods(activity, ops) InvocationTarget.APPLICATION -> invokeBackdoorMethods(activity.application, ops) - InvocationTarget.ELEMENT -> invokeBackdoorMethods(EspressoElement.getViewById(params.targetElement), ops) + InvocationTarget.ELEMENT -> invokeBackdoorMethods( + EspressoElement.getCachedViewStateById(params.targetElement).view, ops + ) else -> throw InvalidArgumentException("target cannot be '$target'") } ?: return null @@ -38,12 +38,11 @@ class MobileBackdoor : RequestHandler { throw InvalidArgumentException("Target must not be empty and must be of type: 'activity', 'application'") } - @Throws(AppiumException::class) + @Suppress("RedundantNullableReturnType") private fun invokeBackdoorMethods(invokeOn: Any, ops: List): Any? { return ops.fold(invokeOn) { invocationTarget, operation -> operation.apply(invocationTarget) } } - @Throws(InvalidArgumentException::class) private fun getBackdoorOperations(params: MobileBackdoorParams): List { return params.methods.map {method -> InvocationOperation(method.name, method.arguments, method.argumentTypes) diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileClickAction.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileClickAction.kt index 864be8d4a..744aa9370 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileClickAction.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileClickAction.kt @@ -23,6 +23,7 @@ import io.appium.espressoserver.lib.model.EspressoElement import io.appium.espressoserver.lib.model.MobileClickActionParams import io.appium.espressoserver.lib.viewaction.UiControllerPerformer import io.appium.espressoserver.lib.viewaction.UiControllerRunnable +import io.appium.espressoserver.lib.viewaction.ViewGetter class MobileClickAction : RequestHandler { @@ -37,7 +38,8 @@ class MobileClickAction : RequestHandler { params.inputDevice, params.buttonState ) - clickAction.perform(uiController, EspressoElement.getViewById(params.elementId)) + val viewInteraction = EspressoElement.getViewInteractionById(params.elementId) + clickAction.perform(uiController, ViewGetter().getView(viewInteraction)) return null } diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileSwipe.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileSwipe.kt index f662b5cbe..df6f0f0d3 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileSwipe.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileSwipe.kt @@ -27,6 +27,7 @@ import io.appium.espressoserver.lib.model.MobileSwipeParams import io.appium.espressoserver.lib.model.MobileSwipeParams.Direction.* import io.appium.espressoserver.lib.viewaction.UiControllerPerformer import io.appium.espressoserver.lib.viewaction.UiControllerRunnable +import io.appium.espressoserver.lib.viewaction.ViewGetter class MobileSwipe : RequestHandler { @@ -59,7 +60,7 @@ class MobileSwipe : RequestHandler { swiper=[${params.swiper}] startCoordinates=[${params.startCoordinates}] endCoordinates=[${params.endCoordinates}] precisionDescriber=[${params.precisionDescriber}] """.trimIndent()) - swipeAction.perform(uiController, EspressoElement.getViewById(params.elementId)) + swipeAction.perform(uiController, ViewGetter().getView(viewInteraction)) return null } } diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileViewFlash.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileViewFlash.kt index 0cc736cdb..643ea11d4 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileViewFlash.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/MobileViewFlash.kt @@ -17,7 +17,7 @@ class MobileViewFlash : RequestHandler { val duration = params.durationMillis val repeatCount = params.repeatCount - val view = EspressoElement.getViewById(params.elementId) + val view = EspressoElement.getCachedViewStateById(params.elementId).view val latch = CountDownLatch(1) InstrumentationRegistry.getInstrumentation().runOnMainSync { val animation = AlphaAnimation(1f, 0f) diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/PointerEventHandler.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/PointerEventHandler.kt index 9f68f9fe8..38adf464b 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/PointerEventHandler.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/PointerEventHandler.kt @@ -124,7 +124,7 @@ class PointerEventHandler(private val touchType: TouchType) : RequestHandler { // Initialize onWebView with web view matcher (if webviewEl provided) params.webviewElement.let{ webviewElement -> AndroidLogger.info("Initializing webView interaction on webview with el: '$webviewElement'") - val matcher = withView(EspressoElement.getViewById(webviewElement)) + val viewState = EspressoElement.getCachedViewStateById(webviewElement) + val matcher = withView(viewState.view) webViewInteraction = onWebView(matcher) } diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/EspressoViewsCache.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/EspressoViewsCache.kt index 73196946a..9e09fda3e 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/EspressoViewsCache.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/EspressoViewsCache.kt @@ -19,16 +19,22 @@ package io.appium.espressoserver.lib.helpers import android.util.LruCache import android.view.View import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.test.espresso.Root +import org.hamcrest.Matcher const val MAX_CACHE_SIZE = 500 -data class ViewState(val view: View, val initialContentDescription: CharSequence?) +data class ViewState( + val view: View, + val initialContentDescription: CharSequence? = view.contentDescription, + val rootMatcher: Matcher? = null +) object EspressoViewsCache { private val cache = LruCache(MAX_CACHE_SIZE) - fun put(id: String, view: View) { - cache.put(id, ViewState(view, view.contentDescription)) + fun put(id: String, viewState: ViewState) { + cache.put(id, viewState) } fun get(id: String): ViewState? = cache.get(id) diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/ViewFinder.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/ViewFinder.kt index 71df358a2..8783c488f 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/ViewFinder.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/ViewFinder.kt @@ -43,7 +43,6 @@ import androidx.test.espresso.Root import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA import androidx.test.espresso.matcher.ViewMatchers.isDisplayed -import androidx.test.espresso.matcher.ViewMatchers.withClassName import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withTagValue @@ -55,7 +54,6 @@ import io.appium.espressoserver.lib.viewmatcher.withXPath import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.`is` -import org.hamcrest.Matchers.endsWith import org.hamcrest.Matchers.hasEntry import org.hamcrest.Matchers.instanceOf @@ -78,9 +76,9 @@ object ViewFinder { * @throws InvalidSelectorException * @throws XPathLookupException */ - fun findBy(root: View?, strategy: Strategy, selector: String): View? { - val views = findAllBy(root, strategy, selector, true) - return if (views.isEmpty()) null else views[0] + fun findBy(root: View?, strategy: Strategy, selector: String): ViewState? { + val viewStates = findAllBy(root, strategy, selector, true) + return if (viewStates.isEmpty()) null else viewStates[0] } /** @@ -94,7 +92,7 @@ object ViewFinder { * @throws InvalidSelectorException * @throws XPathLookupException */ - fun findAllBy(root: View?, strategy: Strategy, selector: String): List { + fun findAllBy(root: View?, strategy: Strategy, selector: String): List { return findAllBy(root, strategy, selector, false) } @@ -125,53 +123,65 @@ object ViewFinder { strategy: Strategy, selector: String, findOne: Boolean - ): List { - @Suppress("NAME_SHADOWING") var selector = selector - var views: List + ): List { when (strategy) { - Strategy.ID // with ID - -> { - + Strategy.ID -> { + // with ID // find id from target context val context = getApplicationContext() + var patchedSelector = selector if (!selector.matches(ID_PATTERN.toRegex())) { - selector = "${context.packageName}:id/$selector" - AndroidLogger.info("Rewrote Id selector to '$selector'") + patchedSelector = "${context.packageName}:id/$selector" + AndroidLogger.info("Rewrote Id selector to '$patchedSelector'") + } + var id = context.resources.getIdentifier(patchedSelector, "Id", context.packageName) + if (id == 0 && patchedSelector != selector) { + id = context.resources.getIdentifier(selector, "Id", context.packageName) } - val id = context.resources.getIdentifier(selector, "Id", context.packageName) - views = getViews(root, withId(id), findOne) + return if (id == 0) emptyList() else getViews(root, withId(id), findOne).map { ViewState(it) } } - Strategy.CLASS_NAME -> + Strategy.CLASS_NAME -> { // with class name - // TODO: improve this finder with instanceOf - views = getViews(root, withClassName(endsWith(selector)), findOne) + val cls = try { + Class.forName(selector) + } catch (e: ClassNotFoundException) { + return emptyList() + } + return getViews(root, instanceOf(cls), findOne).map { ViewState(it) } + } Strategy.TEXT -> // with text - views = getViews(root, withText(selector), findOne) + return getViews(root, withText(selector), findOne).map { ViewState(it) } Strategy.ACCESSIBILITY_ID -> { - views = getViews(root, withContentDescription(selector), findOne) + val result = getViews(root, withContentDescription(selector), findOne) // If the item is not found on the screen, use 'onData' to try // to scroll it into view and then locate it again - if (views.isEmpty() && canScrollToViewWithContentDescription(root, selector)) { - views = getViews(root, withContentDescription(selector), findOne) - } + return if (result.isEmpty() && canScrollToViewWithContentDescription(root, selector)) + getViews(root, withContentDescription(selector), findOne).map { ViewState(it) } + else + result.map { ViewState(it) } } Strategy.XPATH -> // If we're only looking for one item that matches xpath, pass it index 0 or else // Espresso throws an AmbiguousMatcherException - views = if (findOne) { - getViews(root, withXPath(root, selector, 0), true) + return if (findOne) { + getViews(root, withXPath(root, selector, 0), true).map { ViewState(it) } } else { - getViews(root, withXPath(root, selector), false) + getViews(root, withXPath(root, selector), false).map { ViewState(it) } } - Strategy.VIEW_TAG -> views = getViews(root, withTagValue(allOf(instanceOf(String::class.java), - equalTo(selector as Any))), findOne) + Strategy.VIEW_TAG -> + return getViews( + root, + withTagValue(allOf(instanceOf(String::class.java), equalTo(selector as Any))), + findOne + ).map { ViewState(it) } Strategy.DATAMATCHER -> { val matcher = selector.toJsonMatcher() - views = try { + return try { getViewsFromDataInteraction(root, onData(matcher.query.matcher)) + .map { ViewState(it) } } catch (e: PerformException) { // Perform Exception means nothing was found. Return empty list emptyList() @@ -179,9 +189,14 @@ object ViewFinder { } Strategy.VIEWMATCHER -> { val matcherJson = selector.toJsonMatcher() - views = try { + return try { @Suppress("UNCHECKED_CAST") - getViews(root, matcherJson.query.matcher as Matcher, findOne, matcherJson.query.scope as Matcher) + getViews( + root, + matcherJson.query.matcher as Matcher, + findOne, + matcherJson.query.scope as Matcher? + ).map { ViewState(it, rootMatcher = matcherJson.query.scope) } } catch (e: PerformException) { // Perform Exception means nothing was found. Return empty list emptyList() @@ -189,8 +204,6 @@ object ViewFinder { } else -> throw InvalidSelectorException("Strategy is not implemented: ${strategy.strategyName}") } - - return views } /** diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/w3c/adapter/espresso/EspressoW3CActionAdapter.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/w3c/adapter/espresso/EspressoW3CActionAdapter.kt index aec642b4a..409dd9b01 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/w3c/adapter/espresso/EspressoW3CActionAdapter.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/w3c/adapter/espresso/EspressoW3CActionAdapter.kt @@ -126,10 +126,9 @@ class EspressoW3CActionAdapter(private val uiController: UiController) : BaseW3C return keyCodeToEvent(keyValue, location) } - @Throws(AppiumException::class) override fun getElementCenterPoint(elementId: String?): Point { - val view = EspressoElement.getViewById(elementId) - val coords = GeneralLocation.CENTER.calculateCoordinates(view) + val viewState = EspressoElement.getCachedViewStateById(elementId) + val coords = GeneralLocation.CENTER.calculateCoordinates(viewState.view) val point = Point() point.x = coords[0].roundToInt() point.y = coords[1].roundToInt() diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/model/Element.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/model/Element.kt index 50a9410ea..108c91fbf 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/model/Element.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/model/Element.kt @@ -28,7 +28,6 @@ import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import com.google.gson.annotations.SerializedName -import io.appium.espressoserver.lib.handlers.exceptions.AppiumException import io.appium.espressoserver.lib.handlers.exceptions.InvalidArgumentException import io.appium.espressoserver.lib.handlers.exceptions.StaleElementException import io.appium.espressoserver.lib.helpers.ComposeViewCache @@ -49,24 +48,26 @@ interface BaseElement { val element: String } -class EspressoElement(view: View) : BaseElement { +class EspressoElement(viewState: ViewState) : BaseElement { @Suppress("JoinDeclarationAndAssignment") @SerializedName(JSONWP_ELEMENT_KEY, alternate = [W3C_ELEMENT_KEY]) override val element: String init { element = UUID.randomUUID().toString() - EspressoViewsCache.put(element, view) + EspressoViewsCache.put(element, viewState) } companion object { /** * Retrieve cached view and return the ViewInteraction */ - @Throws(AppiumException::class) fun getViewInteractionById(elementId: String?): ViewInteraction { - val view = getViewById(elementId) - return onView(withView(view)) + val cachedView = getCachedViewStateById(elementId) + return if (cachedView.rootMatcher == null) + onView(withView(cachedView.view)) + else + onView(withView(cachedView.view)).inRoot(cachedView.rootMatcher) } /** @@ -107,7 +108,7 @@ class EspressoElement(view: View) : BaseElement { } @Throws(NoSuchElementException::class, StaleElementException::class) - fun getViewById(elementId: String?, checkStaleness: Boolean = true): View { + fun getCachedViewStateById(elementId: String?, checkStaleness: Boolean = true): ViewState { elementId ?: throw InvalidArgumentException("Cannot find 'null' element") if (!EspressoViewsCache.has(elementId)) { throw NoSuchElementException( @@ -116,43 +117,47 @@ class EspressoElement(view: View) : BaseElement { ) } - val (resultView, initialContentDescription1) = Objects.requireNonNull( + val resultState = Objects.requireNonNull( EspressoViewsCache.get(elementId) ) // If the cached view is gone, throw stale element exception - if (!resultView.isShown) { + if (!resultState.view.isShown) { if (checkStaleness) { throw StaleElementException(elementId) } else { - return resultView + return resultState } } - val initialContentDescription = charSequenceToNullableString(initialContentDescription1) - ?: return resultView + val initialContentDescription = + charSequenceToNullableString(resultState.initialContentDescription) + ?: return resultState val currentContentDescription = - charSequenceToNullableString(resultView.contentDescription) + charSequenceToNullableString(resultState.view.contentDescription) if (currentContentDescription == initialContentDescription) { - return resultView + return resultState } try { EspressoViewsCache.put( elementId, - lookupOffscreenView(resultView, initialContentDescription) + ViewState( + lookupOffscreenView(resultState.view, initialContentDescription), + rootMatcher = resultState.rootMatcher + ) ) } catch (e: Exception) { if (e is EspressoException) { if (!checkStaleness) { - return resultView + return resultState } throw StaleElementException(elementId) } throw e } - return resultView + return resultState } } }