From 49cf3b9cef0579abcf8f79656dd86ac8dac45ead Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Feb 2026 15:48:14 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../screens/SwapChooseTokenPageObject.kt | 32 +++++ .../com/tangem/screens/SwapTokenPageObject.kt | 41 +++++- .../MainScreenActionButtonsTest.kt | 2 +- .../TokenDetailsScreenActionButtonsTest.kt | 2 +- .../com/tangem/tests/swap/SwapStoriesTest.kt | 8 +- .../tangem/tests/swap/SwapTokenScreenTest.kt | 135 +++++++++++++++++- .../core/ui/test/SwapTokenScreenTestTags.kt | 7 +- .../tangem/feature/swap/ui/TransactionCard.kt | 19 ++- 8 files changed, 230 insertions(+), 16 deletions(-) create mode 100644 app/src/androidTest/kotlin/com/tangem/screens/SwapChooseTokenPageObject.kt diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SwapChooseTokenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SwapChooseTokenPageObject.kt new file mode 100644 index 0000000000..e8e256017c --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/SwapChooseTokenPageObject.kt @@ -0,0 +1,32 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.R +import com.tangem.core.ui.test.TokenElementsTestTags +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen +import io.github.kakaocup.compose.node.element.KNode +import io.github.kakaocup.kakao.common.utilities.getResourceString +import androidx.compose.ui.test.hasText as withText + +class SwapChooseTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + val title: KNode = child { + hasText(getResourceString(R.string.common_choose_token)) + } + + val myTokensTitle: KNode = child { + hasText(getResourceString(R.string.exchange_tokens_available_tokens_header)) + } + + fun tokenWithTitle(tokenTitle: String): KNode = child { + hasTestTag(TokenElementsTestTags.TOKEN_TITLE) + hasAnyDescendant(withText(tokenTitle)) + useUnmergedTree = true + } +} + +internal fun BaseTestCase.onSwapChooseTokenScreen(function: SwapChooseTokenPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt index f31becbd3f..15be0dee77 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt @@ -9,6 +9,7 @@ import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onCompose import io.github.kakaocup.compose.node.element.KNode import io.github.kakaocup.kakao.common.utilities.getResourceString import androidx.compose.ui.test.hasTestTag as withTestTag +import androidx.compose.ui.test.hasText as withText class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : ComposeScreen(semanticsProvider = semanticsProvider) { @@ -80,11 +81,45 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) hasText(getResourceString(R.string.common_swap)) } - fun tokenSymbol(symbol: String): KNode = child { - hasTestTag(SwapTokenScreenTestTags.TOKEN_SYMBOL) - hasText(symbol) + val youSwapBlock: KNode = child { + hasTestTag(SwapTokenScreenTestTags.SWAP_BLOCK_HEADER) + hasAnyDescendant(withText(getResourceString(R.string.swapping_from_title))) + hasAnyDescendant(withTestTag(SwapTokenScreenTestTags.BALANCE)) + useUnmergedTree = true } + val youReceiveBlock: KNode = child { + hasTestTag(SwapTokenScreenTestTags.SWAP_BLOCK_HEADER) + hasAnyDescendant(withText(getResourceString(R.string.swapping_to_title))) + hasAnyDescendant(withTestTag(SwapTokenScreenTestTags.BALANCE)) + useUnmergedTree = true + } + + val receiveFiatAmount: KNode = child { + hasTestTag(SwapTokenScreenTestTags.RECEIVE_FIAT_AMOUNT) + } + + val swapFiatAmount: KNode = child { + hasTestTag(SwapTokenScreenTestTags.SWAP_FIAT_AMOUNT) + } + + val changeTokenIcon: KNode = child { + hasTestTag(SwapTokenScreenTestTags.SELECT_TOKEN_ICON) + } + + fun swapTokenSymbol(symbol: String): KNode = child { + hasAnyAncestor(withTestTag(SwapTokenScreenTestTags.SWAP_CARD)) + hasTestTag(SwapTokenScreenTestTags.TOKEN_SYMBOL) + hasText(symbol) + useUnmergedTree = true + } + + fun receiveTokenSymbol(symbol: String): KNode = child { + hasAnyAncestor(withTestTag(SwapTokenScreenTestTags.RECEIVE_CARD)) + hasTestTag(SwapTokenScreenTestTags.TOKEN_SYMBOL) + hasText(symbol) + useUnmergedTree = true + } } internal fun BaseTestCase.onSwapTokenScreen(function: SwapTokenPageObject.() -> Unit) = diff --git a/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/MainScreenActionButtonsTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/MainScreenActionButtonsTest.kt index e807c84361..28fe6ef184 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/MainScreenActionButtonsTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/MainScreenActionButtonsTest.kt @@ -208,7 +208,7 @@ class MainScreenActionButtonsTest : BaseTestCase() { onSwapTokenScreen { title.assertIsDisplayed() } } step("Assert token symbol: '$tokenSymbol' is displayed") { - onSwapTokenScreen { tokenSymbol(tokenSymbol).assertIsDisplayed() } + onSwapTokenScreen { swapTokenSymbol(tokenSymbol).assertIsDisplayed() } } } } diff --git a/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/TokenDetailsScreenActionButtonsTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/TokenDetailsScreenActionButtonsTest.kt index 309a1e637c..5a158f6e07 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/TokenDetailsScreenActionButtonsTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/TokenDetailsScreenActionButtonsTest.kt @@ -119,7 +119,7 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() { onSwapTokenScreen { title.assertIsDisplayed() } } step("Assert token symbol: '$tokenSymbol' is displayed") { - onSwapTokenScreen { tokenSymbol(tokenSymbol).assertIsDisplayed() } + onSwapTokenScreen { swapTokenSymbol(tokenSymbol).assertIsDisplayed() } } } } diff --git a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapStoriesTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapStoriesTest.kt index 2f3f1a81d6..21e65feb9b 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapStoriesTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapStoriesTest.kt @@ -135,7 +135,7 @@ class SwapStoriesTest : BaseTestCase() { step("Synchronize addresses") { synchronizeAddresses() } - step("Assert 'Swap' button has badge") { + step("Assert 'Swap' button has not badge") { onMainScreen { swapButton.assertHasBadge(false) } } step("Open 'Swap' screen") { @@ -191,7 +191,7 @@ class SwapStoriesTest : BaseTestCase() { step("Click on token with name: '$tokenName'") { onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() } } - step("Assert 'Swap' button has badge") { + step("Assert 'Swap' button has not badge") { onTokenDetailsScreen { swapButton().assertHasBadge(false) } } step("Open 'Swap' screen") { @@ -216,7 +216,7 @@ class SwapStoriesTest : BaseTestCase() { } } - @AllureId("5471") + @AllureId("5470") @DisplayName("Check unavailable swap stories on 'Markets' token details screen") @Test fun checkUnavailableSwapStoriesOnMarketsTokenDetailsScreen() { @@ -245,7 +245,7 @@ class SwapStoriesTest : BaseTestCase() { step("Open 'Markets' token details screen for token '$tokenName'") { openMarketTokenDetailsScreen(blockchainName = tokenName, tokenName = tokenName) } - step("Assert 'Swap' button has badge") { + step("Assert 'Swap' button has not badge") { waitForIdle() onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertIsDisplayed() } onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertContentDescriptionEquals(badgeHidden) } diff --git a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenTest.kt index d0f574aa0b..60ec5a8154 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenTest.kt @@ -10,7 +10,9 @@ import com.tangem.common.extensions.* import com.tangem.common.utils.resetWireMockScenarios import com.tangem.datasource.api.common.config.ApiConfig import com.tangem.datasource.api.common.config.ApiEnvironment +import com.tangem.scenarios.SwapEntryPoint import com.tangem.scenarios.openMainScreen +import com.tangem.scenarios.openSwapScreen import com.tangem.scenarios.synchronizeAddresses import com.tangem.screens.* import dagger.hilt.android.testing.HiltAndroidTest @@ -218,7 +220,6 @@ class SwapTokenScreenTest : BaseTestCase() { onSwapSelectNetworkFeeBottomSheet { title.assertIsDisplayed() } } step("Assert 'Market' item is displayed") { - printSemanticTree(rootIndex = 1, useUnmergedTree = true) onSwapSelectNetworkFeeBottomSheet { marketSelectorItem.assertIsDisplayed() } } step("Assert 'Fast' item is displayed") { @@ -283,10 +284,138 @@ class SwapTokenScreenTest : BaseTestCase() { } } step("Assert token symbol: '$swapTokenSymbol' is displayed") { - onSwapTokenScreen { tokenSymbol(swapTokenSymbol).assertIsDisplayed() } + onSwapTokenScreen { swapTokenSymbol(swapTokenSymbol).assertIsDisplayed() } } step("Assert token symbol: '$receiveTokenSymbol' is displayed") { - onSwapTokenScreen { tokenSymbol(receiveTokenSymbol).assertIsDisplayed() } + onSwapTokenScreen { receiveTokenSymbol(receiveTokenSymbol).assertIsDisplayed() } + } + } + } + + @AllureId("575") + @DisplayName("Swap: check UI") + @Test + fun checkSwapUiTest() { + val swapTokenSymbol = "POL" + val receiveTokenSymbol = "ETH" + val newReceiveToken = "POL (ex-MATIC)" + val tokenTitle = "Polygon" + val inputAmount = "1" + + setupHooks().run { + + step("Open 'Main Screen'") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses() + } + step("Click on token with name: '$tokenTitle'") { + onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() } + } + step("Assert title: '$tokenTitle' is displayed") { + onTokenDetailsScreen { title.assertIsDisplayed() } + } + step("Open 'Swap' screen") { + openSwapScreen(from = SwapEntryPoint.TokenDetails) + } + step("Assert 'Close' button is displayed") { + onSwapTokenScreen { closeButton.assertIsDisplayed() } + } + step("Assert swap token symbol: '$swapTokenSymbol' is displayed") { + onSwapTokenScreen { swapTokenSymbol(swapTokenSymbol).assertIsDisplayed() } + } + step("Assert receive token symbol: '$receiveTokenSymbol' is displayed") { + onSwapTokenScreen { receiveTokenSymbol(receiveTokenSymbol).assertIsDisplayed() } + } + step("Click on 'Select token' icon") { + onSwapTokenScreen { changeTokenIcon.performClick() } + } + step("Select new receive token: $newReceiveToken") { + onSwapChooseTokenScreen { tokenWithTitle(newReceiveToken).performClick() } + } + step("Assert new receive token symbol: '$swapTokenSymbol' is displayed") { + onSwapTokenScreen { receiveTokenSymbol(swapTokenSymbol).assertIsDisplayed() } + } + step("Input swap amount = '$inputAmount'") { + waitForIdle() + onSwapTokenScreen { + textInput.clickWithAssertion() + textInput.performTextReplacement(inputAmount) + } + } + step("Assert input amount = '$inputAmount'") { + onSwapTokenScreen { textInput.assertTextEquals(inputAmount) } + } + step("Press 'Delete' button on keyboard") { + device.uiDevice.pressDelete() + waitForIdle() + } + step("Assert 'You swap' block is displayed") { + onSwapTokenScreen { youSwapBlock.assertIsDisplayed() } + } + step("Assert 'You receive' block is displayed") { + onSwapTokenScreen { youReceiveBlock } + } + step("Assert send token fiat amount is displayed") { + onSwapTokenScreen { swapFiatAmount.assertIsDisplayed() } + } + step("Assert receive token fiat amount is displayed") { + onSwapTokenScreen { receiveFiatAmount.assertIsDisplayed() } + } + step("Assert 'Swap tokens on screen' button is displayed") { + onSwapTokenScreen { + flakySafely(WAIT_UNTIL_TIMEOUT) { + swapTokensOnscreenButton.assertIsDisplayed() + } + } + } + step("Assert 'Swap' button is disabled") { + onSwapTokenScreen { swapButton.assertIsNotEnabled() } + } + } + } + + @AllureId("5162") + @DisplayName("Swap: check swap tokens switch") + @Test + fun checkSwapTokensSwitchTest() { + val swapTokenSymbol = "POL" + val receiveTokenSymbol = "ETH" + val tokenTitle = "Polygon" + + setupHooks().run { + + step("Open 'Main Screen'") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses() + } + step("Click on token with name: '$tokenTitle'") { + onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() } + } + step("Assert title: '$tokenTitle' is displayed") { + onTokenDetailsScreen { title.assertIsDisplayed() } + } + step("Open 'Swap' screen") { + openSwapScreen(from = SwapEntryPoint.TokenDetails) + } + step("Assert swap token symbol: '$swapTokenSymbol' is displayed") { + onSwapTokenScreen { swapTokenSymbol(swapTokenSymbol).assertIsDisplayed() } + } + step("Assert receive token symbol: '$receiveTokenSymbol' is displayed") { + onSwapTokenScreen { receiveTokenSymbol(receiveTokenSymbol).assertIsDisplayed() } + } + step("Click on 'Swap tokens on screen' button") { + onSwapTokenScreen { swapTokensOnscreenButton.performClick() } + waitForIdle() + } + step("Assert new swap token symbol: '$receiveTokenSymbol' is displayed") { + onSwapTokenScreen { swapTokenSymbol(receiveTokenSymbol).assertIsDisplayed() } + } + step("Assert new receive token symbol: '$swapTokenSymbol' is displayed") { + onSwapTokenScreen { receiveTokenSymbol(swapTokenSymbol).assertIsDisplayed() } } } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/SwapTokenScreenTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/SwapTokenScreenTestTags.kt index ec8883b577..3ddbefea69 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/SwapTokenScreenTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/SwapTokenScreenTestTags.kt @@ -2,14 +2,19 @@ package com.tangem.core.ui.test object SwapTokenScreenTestTags { const val CONTAINER = "SWAP_TOKEN_SCREEN_CONTAINER" - const val SWAP_BLOCK_HEADER = "SWAP_TOKEN_SCREEN_SWAP_BLOCK" + const val SWAP_BLOCK_HEADER = "SWAP_TOKEN_SCREEN_SWAP_BLOCK_HEADER" const val BALANCE = "SWAP_TOKEN_SCREEN_BALANCE" const val SWAP_TEXT_FIELD = "SWAP_TOKEN_SCREEN_SWAP_TEXT_FIELD" const val RECEIVE_TEXT_FIELD = "SWAP_TOKEN_SCREEN_RECEIVE_TEXT_FIELD" + const val SWAP_CARD = "SWAP_TOKEN_SCREEN_SWAP_CARD" + const val RECEIVE_CARD = "SWAP_TOKEN_SCREEN_RECEIVE_CARD" const val RECEIVE_AMOUNT_SHIMMER = "SWAP_TOKEN_SCREEN_RECEIVE_AMOUNT_SHIMMER" const val PROVIDERS_BLOCK = "SWAP_TOKEN_SCREEN_PROVIDERS_BLOCK" const val SWAP_BUTTON = "SWAP_TOKEN_SCREEN_SWAP_BUTTON" const val TOKEN = "SWAP_TOKEN_SCREEN_TOKEN" const val TOKEN_SYMBOL = "SWAP_TOKEN_SCREEN_TOKEN_SYMBOL" const val TOKEN_ICON = "SWAP_TOKEN_SCREEN_TOKEN_ICON" + const val SELECT_TOKEN_ICON = "SWAP_TOKEN_SCREEN_SELECT_TOKEN_ICON" + const val RECEIVE_FIAT_AMOUNT = "SWAP_TOKEN_SCREEN_RECEIVE_FIAT_AMOUNT" + const val SWAP_FIAT_AMOUNT = "SWAP_TOKEN_SCREEN_SWAP_FIAT_AMOUNT" } \ No newline at end of file diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index 373d309f0f..25aac0f046 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -67,13 +67,21 @@ fun TransactionCard( @DrawableRes networkIconRes: Int? = null, onChangeTokenClick: (() -> Unit)? = null, ) { + val cardTag = when (type) { + is TransactionCardType.Inputtable -> + SwapTokenScreenTestTags.SWAP_CARD + is TransactionCardType.ReadOnly -> + SwapTokenScreenTestTags.RECEIVE_CARD + } + Box( modifier = modifier .background( shape = RoundedCornerShape(TangemTheme.dimens.radius16), color = TangemTheme.colors.background.primary, ) - .fillMaxSize(), + .fillMaxSize() + .testTag(cardTag), ) { Column( modifier = Modifier @@ -327,6 +335,7 @@ private fun Content( text = amount, color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.body2, + modifier = Modifier.testTag(SwapTokenScreenTestTags.RECEIVE_FIAT_AMOUNT), ) } } @@ -357,7 +366,9 @@ private fun Content( text = amount, color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.body2, - modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20), + modifier = Modifier + .defaultMinSize(minHeight = TangemTheme.dimens.size20) + .testTag(SwapTokenScreenTestTags.SWAP_FIAT_AMOUNT), ) } } @@ -504,7 +515,9 @@ fun ChangeTokenSelector() { contentAlignment = Alignment.CenterEnd, ) { Icon( - modifier = Modifier.size(TangemTheme.dimens.size20), + modifier = Modifier + .size(TangemTheme.dimens.size20) + .testTag(SwapTokenScreenTestTags.SELECT_TOKEN_ICON), painter = painterResource(id = R.drawable.ic_chevron_24), tint = TangemTheme.colors.icon.secondary, contentDescription = null,