diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/MultiWalletScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/MultiWalletScenarios.kt index 8551ce9af5..dc3ef704c7 100644 --- a/app/src/androidTest/kotlin/com/tangem/scenarios/MultiWalletScenarios.kt +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/MultiWalletScenarios.kt @@ -1,47 +1,15 @@ package com.tangem.scenarios -import androidx.compose.ui.semantics.SemanticsActions -import androidx.compose.ui.test.SemanticsMatcher -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.hasAnyDescendant -import androidx.compose.ui.test.hasTestTag -import androidx.compose.ui.test.hasText -import androidx.compose.ui.test.performClick -import androidx.compose.ui.test.performSemanticsAction -import androidx.compose.ui.test.performTouchInput -import androidx.compose.ui.test.swipeLeft -import androidx.compose.ui.test.swipeRight import com.tangem.common.BaseTestCase import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_VERY_LONG import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.extensions.isDisplayedSafely -import com.tangem.core.ui.test.BuyTokenScreenTestTags -import com.tangem.core.ui.test.DetailsScreenTestTags -import com.tangem.core.ui.test.MainScreenTestTags import com.tangem.screens.* import com.tangem.tap.domain.sdk.mocks.MockContent import com.tangem.tap.domain.sdk.mocks.MockProvider import io.qameta.allure.kotlin.Allure.step -// Custom/nested clickables (Add-Wallet row, wallet tab) reject touch injection — drive OnClick directly. -private fun BaseTestCase.clickViaSemantics(matcher: SemanticsMatcher, useUnmergedTree: Boolean = false) { - composeTestRule.onNode(matcher, useUnmergedTree).performSemanticsAction(SemanticsActions.OnClick) -} - -// Both pager pages stay mounted; the off-screen one ignores swipes, so always swipe the on-screen wallet card. -private fun BaseTestCase.swipeDisplayedWallet(toPrevious: Boolean) { - val nodes = composeTestRule.onAllNodes(hasTestTag(MainScreenTestTags.WALLET_LIST_ITEM)) - for (i in 0 until nodes.fetchSemanticsNodes().size) { - val swiped = runCatching { - nodes[i].assertIsDisplayed() - nodes[i].performTouchInput { if (toPrevious) swipeRight() else swipeLeft() } - }.isSuccess - if (swiped) break - } - waitForIdle() -} - /** 'Add Wallet' scans a card immediately (no type chooser), so [mockContent] must be set before the click. */ fun BaseTestCase.addNewCardWallet(mockContent: MockContent) { step("Click 'More' button on TopBar") { @@ -49,7 +17,7 @@ fun BaseTestCase.addNewCardWallet(mockContent: MockContent) { } MockProvider.setMocks(mockContent) step("Click on 'Add Wallet' button (scans a new hardware wallet)") { - clickViaSemantics(hasTestTag(DetailsScreenTestTags.ADD_WALLET_BUTTON)) + onDetailsScreen { addWalletButton.clickWithAssertion() } } // Gate on the top-bar More button, not the container — the bottom Markets sheet can leave the container un-"displayed". step("Assert 'Main' screen is displayed with the new wallet") { @@ -62,7 +30,7 @@ fun BaseTestCase.addNewCardWallet(mockContent: MockContent) { composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) { var shown = false onMainScreen { shown = synchronizeAddressesButton.isDisplayedSafely() } - if (!shown) swipeDisplayedWallet(toPrevious = false) + if (!shown) onMainScreen { swipeToAdjacentWallet(toPrevious = false) } shown } // Let the pager fling settle — a click mid-animation is eaten by the button's clickableSingle debounce. @@ -77,25 +45,15 @@ fun BaseTestCase.addNewCardWallet(mockContent: MockContent) { } } -/** Both pager pages mount the same token; clicks the [tokenName] copy currently on-screen. */ fun BaseTestCase.clickDisplayedTokenOnMain(tokenName: String) { - val matcher = hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM) and hasAnyDescendant(hasText(tokenName)) step("Click on token '$tokenName' on the visible wallet") { - composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) { - val nodes = composeTestRule.onAllNodes(matcher, useUnmergedTree = true) - (0 until nodes.fetchSemanticsNodes().size).any { i -> - runCatching { - nodes[i].assertIsDisplayed() - nodes[i].performClick() - }.isSuccess - } - } + onMainScreen { clickDisplayedToken(tokenName) } } } fun BaseTestCase.switchToPreviousWallet() { step("Swipe wallet card to the previous wallet") { - swipeDisplayedWallet(toPrevious = true) + onMainScreen { swipeToAdjacentWallet(toPrevious = true) } } } @@ -104,18 +62,11 @@ fun BaseTestCase.selectReceiveTokenOnWallet(token: String, walletName: String) { step("Click on 'Choose token' button") { onSwapTokenScreen { chooseTokenButton.performClick() } } - // The wallet tab is a custom clickable Row that Kakao's performClick can't hit (autoscroll fails); invoke OnClick directly. step("Select wallet tab '$walletName'") { - clickViaSemantics( - hasTestTag(BuyTokenScreenTestTags.WALLET_TAB) and hasAnyDescendant(hasText(walletName)), - useUnmergedTree = true, - ) + onBuyTokenScreen { walletTab(walletName).performClick() } } step("Click on token with name '$token'") { - clickViaSemantics( - hasTestTag(BuyTokenScreenTestTags.LAZY_LIST_ITEM) and hasAnyDescendant(hasText(token)), - useUnmergedTree = true, - ) + onBuyTokenScreen { tokenWithTitle(token).performClick() } } } diff --git a/app/src/androidTest/kotlin/com/tangem/screens/BuyTokenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/BuyTokenPageObject.kt index 1dbe1d3ba3..c3b236391e 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/BuyTokenPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/BuyTokenPageObject.kt @@ -15,6 +15,7 @@ import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onCompose import io.github.kakaocup.compose.node.element.KNode import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode import io.github.kakaocup.kakao.common.utilities.getResourceString +import androidx.compose.ui.test.hasText as withText class BuyTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : ComposeScreen(semanticsProvider = semanticsProvider) { @@ -48,6 +49,19 @@ class BuyTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : useUnmergedTree = true } } + + fun walletTab(walletName: String): KNode = child { + hasTestTag(BuyTokenScreenTestTags.WALLET_TAB) + hasAnyDescendant(withText(walletName)) + useUnmergedTree = true + } + + @OptIn(ExperimentalTestApi::class) + fun tokenWithTitle(tokenTitle: String): LazyListItemNode = lazyList.childWith { + hasTestTag(BuyTokenScreenTestTags.LAZY_LIST_ITEM) + hasText(tokenTitle) + useUnmergedTree = true + } } internal fun BaseTestCase.onBuyTokenScreen(function: BuyTokenPageObject.() -> Unit) = diff --git a/app/src/androidTest/kotlin/com/tangem/screens/DetailsPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/DetailsPageObject.kt index ff937e632d..110188ecbc 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/DetailsPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/DetailsPageObject.kt @@ -28,6 +28,10 @@ class DetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : useUnmergedTree = true } + val addWalletButton: KNode = child { + hasTestTag(DetailsScreenTestTags.ADD_WALLET_BUTTON) + } + val buyTangemButton: KNode = child { hasTestTag(DetailsScreenTestTags.SCREEN_ITEM) hasText(getResourceString(R.string.details_buy_wallet)) diff --git a/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt index 8e83fc81ea..c6b343f37b 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt @@ -111,6 +111,28 @@ class MainScreenPageObject(private val semanticsProvider: SemanticsNodeInteracti ) } + // Wallet pager keeps the adjacent page composed (beyondViewportPageCount=1), so the token is mounted on two pages — click the displayed copy. + fun clickDisplayedToken(tokenName: String) { + val matcher = withTestTag(MainScreenTestTags.TOKEN_LIST_ITEM) and hasAnyDescendant(withText(tokenName)) + val nodes = semanticsProvider.onAllNodes(matcher, useUnmergedTree = true) + for (i in 0 until nodes.fetchSemanticsNodes().size) { + if (runCatching { nodes[i].assertIsDisplayed(); nodes[i].performClick() }.isSuccess) return + } + error("Token '$tokenName' is not displayed on the current wallet page") + } + + // Adjacent pager pages stay mounted; swipe the wallet card that's actually on-screen. + fun swipeToAdjacentWallet(toPrevious: Boolean) { + val nodes = semanticsProvider.onAllNodes(withTestTag(MainScreenTestTags.WALLET_LIST_ITEM)) + for (i in 0 until nodes.fetchSemanticsNodes().size) { + val swiped = runCatching { + nodes[i].assertIsDisplayed() + nodes[i].performTouchInput { if (toPrevious) swipeRight() else swipeLeft() } + }.isSuccess + if (swiped) return + } + } + val restoringProgressText: KNode = child { hasTestTag(MainScreenTestTags.SYNC_PROGRESS_TEXT) useUnmergedTree = true