From 61145f7cc5c3674d50d983504f0244d64c0f8878 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 4 Mar 2026 19:54:48 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../com/tangem/common/extensions/KNode.kt | 7 + .../tangem/scenarios/SendWarningScenarios.kt | 10 +- .../com/tangem/scenarios/SwapScenarios.kt | 39 +++ .../com/tangem/screens/SwapTokenPageObject.kt | 18 ++ .../tests/swap/SwapTokenScreenWarningsTest.kt | 261 ++++++++++++++++++ 5 files changed, 326 insertions(+), 9 deletions(-) diff --git a/app/src/androidTest/kotlin/com/tangem/common/extensions/KNode.kt b/app/src/androidTest/kotlin/com/tangem/common/extensions/KNode.kt index d26878e4f4..b91b4767eb 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/extensions/KNode.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/extensions/KNode.kt @@ -27,3 +27,10 @@ fun KNode.isDisplayedSafely(): Boolean { } } +fun KNode.assertVisibility(shouldBeDisplayed: Boolean) { + if (shouldBeDisplayed) { + assertIsDisplayed() + } else { + assertIsNotDisplayed() + } +} diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/SendWarningScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/SendWarningScenarios.kt index f90275a934..7abb486367 100644 --- a/app/src/androidTest/kotlin/com/tangem/scenarios/SendWarningScenarios.kt +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/SendWarningScenarios.kt @@ -1,8 +1,8 @@ package com.tangem.scenarios import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.assertVisibility import com.tangem.screens.onSendConfirmScreen -import io.github.kakaocup.compose.node.element.KNode import io.qameta.allure.kotlin.Allure.step fun BaseTestCase.checkSendWarning( @@ -45,12 +45,4 @@ fun BaseTestCase.checkSendWarning( sendButton.assertIsEnabled() } } -} - -private fun KNode.assertVisibility(shouldBeDisplayed: Boolean) { - if (shouldBeDisplayed) { - assertIsDisplayed() - } else { - assertIsNotDisplayed() - } } \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/SwapScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/SwapScenarios.kt index e239b87144..358b29b5b3 100644 --- a/app/src/androidTest/kotlin/com/tangem/scenarios/SwapScenarios.kt +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/SwapScenarios.kt @@ -2,6 +2,7 @@ package com.tangem.scenarios import androidx.compose.ui.test.click import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.assertVisibility import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.extensions.isDisplayedSafely import com.tangem.screens.* @@ -258,6 +259,44 @@ fun BaseTestCase.chackUnableToCoverFeeNotification(networkName: String, currency } } +fun BaseTestCase.checkSwapWarning( + title: String, + message: String, + isDisplayed: Boolean = true, + swapButtonIsDisabled: Boolean = isDisplayed, +) { + val assertDisplay = if (isDisplayed) "displayed" else "not displayed" + + step("Assert warning title is $assertDisplay") { + onSwapTokenScreen { + warningTitle(title).assertVisibility(isDisplayed) + } + } + step("Assert warning icon is $assertDisplay") { + onSwapTokenScreen { + warningIcon(message).assertVisibility(isDisplayed) + } + } + step("Assert warning message is $assertDisplay") { + onSwapTokenScreen { + warningMessage(message).assertVisibility(isDisplayed) + } + } + + if (swapButtonIsDisabled) + step("Assert 'Swap' button is disabled") { + onSwapTokenScreen { + swapButton.assertIsNotEnabled() + } + } + else + step("Assert 'Swap' button is enabled") { + onSwapTokenScreen { + swapButton.assertIsEnabled() + } + } +} + sealed class SwapEntryPoint { object MainScreen : SwapEntryPoint() object TokenDetails : SwapEntryPoint() diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt index 2d705445e0..30fe6823bd 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt @@ -113,6 +113,24 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) useUnmergedTree = true } + fun warningTitle(title: String): KNode = child { + hasTestTag(NotificationTestTags.TITLE) + hasText(title) + useUnmergedTree = true + } + + fun warningMessage(message: String): KNode = child { + hasTestTag(NotificationTestTags.MESSAGE) + hasText(message) + useUnmergedTree = true + } + + fun warningIcon(message: String): KNode = child { + hasTestTag(NotificationTestTags.ICON) + hasAnySibling(withText(message)) + useUnmergedTree = true + } + val refreshButton: KNode = child { hasTestTag(BaseButtonTestTags.BUTTON) hasText(getResourceString(R.string.warning_button_refresh)) diff --git a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenWarningsTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenWarningsTest.kt index a64fd32341..5b98eafb34 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenWarningsTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenWarningsTest.kt @@ -14,6 +14,7 @@ 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.chackUnableToCoverFeeNotification +import com.tangem.scenarios.checkSwapWarning import com.tangem.scenarios.openMainScreen import com.tangem.scenarios.openSwapScreen import com.tangem.scenarios.synchronizeAddresses @@ -316,4 +317,264 @@ class SwapTokenScreenWarningsTest : BaseTestCase() { } } } + + @AllureId("2831") + @DisplayName("Swap: warning is not displayed, if remaining balance is equal to 0") + @Test + fun solanaRemainingBalanceEqualToZeroWarningTest() { + val tokenTitle = "Solana" + val inputAmount = "0.00168933" + val tokensScenarioState = "SolanaUSDC" + val rentAmount = "SOL 0.00089088" + val notificationTitle = getResourceString(R.string.send_notification_invalid_amount_title) + val notificationMessage = getResourceString( + R.string.send_notification_invalid_amount_rent_fee, + rentAmount + ) + + setupHooks( + additionalAfterSection = { + resetWireMockScenarioState(USER_TOKENS_API_SCENARIO) + resetWireMockScenarioState(QUOTES_API_SCENARIO) + } + ).run { + + step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$tokensScenarioState'") { + setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = tokensScenarioState) + } + step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: $tokensScenarioState") { + setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = tokensScenarioState) + } + + 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 'You swap' block is displayed") { + onSwapTokenScreen { youSwapBlock.assertIsDisplayed() } + } + step("Input swap amount = '$inputAmount'") { + waitForIdle() + onSwapTokenScreen { + textInput.clickWithAssertion() + textInput.performTextReplacement(inputAmount) + } + } + step("Assert 'Invalid amount' warning is not displayed") { + flakySafely(WAIT_UNTIL_TIMEOUT_LONG) { + checkSwapWarning( + title = notificationTitle, + message = notificationMessage, + isDisplayed = false + ) + } + } + } + } + + @AllureId("2832") + @DisplayName("Swap: warning is not displayed, if remaining balance is equal to rent amount") + @Test + fun solanaRemainingBalanceEqualToRentAmountTest() { + val tokenTitle = "Solana" + val inputAmount = "0.001689338" + val tokensScenarioState = "SolanaUSDC" + val rentAmount = "SOL 0.00089088" + val notificationTitle = getResourceString(R.string.send_notification_invalid_amount_title) + val notificationMessage = getResourceString( + R.string.send_notification_invalid_amount_rent_fee, + rentAmount + ) + + setupHooks( + additionalAfterSection = { + resetWireMockScenarioState(USER_TOKENS_API_SCENARIO) + resetWireMockScenarioState(QUOTES_API_SCENARIO) + } + ).run { + + step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$tokensScenarioState'") { + setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = tokensScenarioState) + } + step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: $tokensScenarioState") { + setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = tokensScenarioState) + } + + 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 'You swap' block is displayed") { + onSwapTokenScreen { youSwapBlock.assertIsDisplayed() } + } + step("Input swap amount = '$inputAmount'") { + waitForIdle() + onSwapTokenScreen { + textInput.clickWithAssertion() + textInput.performTextReplacement(inputAmount) + } + } + step("Assert 'Invalid amount' warning is not displayed") { + flakySafely(WAIT_UNTIL_TIMEOUT_LONG) { + checkSwapWarning( + title = notificationTitle, + message = notificationMessage, + isDisplayed = false + ) + } + + } + } + } + + @AllureId("2833") + @DisplayName("Swap: warning is not displayed, if remaining balance more than rent amount") + @Test + fun solanaRemainingBalanceMoreThanRentAmountTest() { + val tokenTitle = "Solana" + val inputAmount = "0.0000941" + val tokensScenarioState = "SolanaUSDC" + val rentAmount = "SOL 0.00089088" + val notificationTitle = getResourceString(R.string.send_notification_invalid_amount_title) + val notificationMessage = getResourceString( + R.string.send_notification_invalid_amount_rent_fee, + rentAmount + ) + + setupHooks( + additionalAfterSection = { + resetWireMockScenarioState(USER_TOKENS_API_SCENARIO) + resetWireMockScenarioState(QUOTES_API_SCENARIO) + } + ).run { + + step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$tokensScenarioState'") { + setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = tokensScenarioState) + } + step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: $tokensScenarioState") { + setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = tokensScenarioState) + } + + 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 'You swap' block is displayed") { + onSwapTokenScreen { youSwapBlock.assertIsDisplayed() } + } + step("Input swap amount = '$inputAmount'") { + waitForIdle() + onSwapTokenScreen { + textInput.clickWithAssertion() + textInput.performTextReplacement(inputAmount) + } + } + step("Assert 'Invalid amount' warning is not displayed") { + flakySafely(WAIT_UNTIL_TIMEOUT_LONG) { + checkSwapWarning( + title = notificationTitle, + message = notificationMessage, + isDisplayed = false + ) + } + } + } + } + + @AllureId("2830") + @DisplayName("Swap: warning is displayed, if remaining balance less than rent amount") + @Test + fun solanaRemainingBalanceLessThanRentAmountTest() { + val tokenTitle = "Solana" + val inputAmount = "0.0016941" + val tokensScenarioState = "SolanaUSDC" + val rentAmount = "SOL 0.00089088" + val notificationTitle = getResourceString(R.string.send_notification_invalid_amount_title) + val notificationMessage = getResourceString( + R.string.send_notification_invalid_amount_rent_fee, + rentAmount + ) + + setupHooks( + additionalAfterSection = { + resetWireMockScenarioState(USER_TOKENS_API_SCENARIO) + resetWireMockScenarioState(QUOTES_API_SCENARIO) + } + ).run { + + step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$tokensScenarioState'") { + setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = tokensScenarioState) + } + step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: $tokensScenarioState") { + setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = tokensScenarioState) + } + + 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 'You swap' block is displayed") { + onSwapTokenScreen { youSwapBlock.assertIsDisplayed() } + } + step("Input swap amount = '$inputAmount'") { + waitForIdle() + onSwapTokenScreen { + textInput.clickWithAssertion() + textInput.performTextReplacement(inputAmount) + } + } + step("Assert 'Invalid amount' warning is displayed") { + flakySafely(WAIT_UNTIL_TIMEOUT_LONG) { + checkSwapWarning( + title = notificationTitle, + message = notificationMessage + ) + } + } + } + } } \ No newline at end of file