Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-26 18:48:03 +03:00
parent bba915b9ec
commit c9c99ff6d3
11 changed files with 953 additions and 14 deletions

View file

@ -170,6 +170,45 @@ fun BaseTestCase.checkStoriesChanges() {
}
}
fun BaseTestCase.selectFeeType(feeType: FeeType, selectedFeeAmount: String) {
step("Click on 'Select fee' icon") {
onSwapTokenScreen { selectFeeIcon.performClick() }
}
when (feeType) {
FeeType.Market -> {
step("Click on 'Market' item") {
onSwapSelectNetworkFeeBottomSheet { marketSelectorItem.performClick() }
}
step("Assert fee amount is equal to 'Market' fee:'$selectedFeeAmount'") {
onSwapTokenScreen { feeAmount.assertTextContains(selectedFeeAmount, substring = true) }
}
}
FeeType.Fast -> {
step("Click on 'Fast' item") {
onSwapSelectNetworkFeeBottomSheet { fastSelectorItem.performClick() }
}
step("Assert fee amount is equal to 'Fast' fee:'$selectedFeeAmount'") {
onSwapTokenScreen { feeAmount.assertTextContains(selectedFeeAmount, substring = true) }
}
}
}
}
fun BaseTestCase.chackUnableToCoverFeeNotification(networkName: String, currencySymbol: String) {
step("Assert 'Unable to cover '$networkName' fee notification title is displayed'") {
onSwapTokenScreen { unableToCoverFeeNotificationTitle(networkName).assertIsDisplayed() }
}
step("Assert 'Unable to cover '$networkName' fee notification text is displayed'") {
onSwapTokenScreen {
unableToCoverFeeNotificationText(
currencyName = networkName,
currencySymbol = currencySymbol
).assertIsDisplayed()
}
}
}
sealed class SwapEntryPoint {
object MainScreen : SwapEntryPoint()
object TokenDetails : SwapEntryPoint()
@ -177,4 +216,9 @@ sealed class SwapEntryPoint {
object TokenActionsBottomSheet : SwapEntryPoint()
}
enum class FeeType {
Market,
Fast
}

View file

@ -3,7 +3,8 @@ 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 com.tangem.core.ui.test.AppBarWithSearchTestTags
import com.tangem.core.ui.test.BuyTokenScreenTestTags
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
@ -21,11 +22,30 @@ class SwapChooseTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProv
hasText(getResourceString(R.string.exchange_tokens_available_tokens_header))
}
fun tokenWithTitle(tokenTitle: String): KNode = child {
hasTestTag(TokenElementsTestTags.TOKEN_TITLE)
hasAnyDescendant(withText(tokenTitle))
useUnmergedTree = true
val searchIcon: KNode = child {
hasTestTag(AppBarWithSearchTestTags.SEARCH_ICON)
}
val searchTextField: KNode = child {
hasTestTag(AppBarWithSearchTestTags.TEXT_FIELD)
}
val noTokensFoundText: KNode = child {
hasText(getResourceString(R.string.express_token_list_empty_search))
}
fun tokenWithTitle(tokenTitle: String, availableForSwap: Boolean = true): KNode = child {
hasTestTag(BuyTokenScreenTestTags.LAZY_LIST_ITEM)
hasAnyDescendant(withText(tokenTitle))
if (!availableForSwap) {
hasAnyDescendant(
withText(
getResourceString(R.string.tokens_list_unavailable_to_swap_source_header)
)
)
}
useUnmergedTree = true
}
}
internal fun BaseTestCase.onSwapChooseTokenScreen(function: SwapChooseTokenPageObject.() -> Unit) =

View file

@ -2,6 +2,7 @@ package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.SelectNetworkFeeBottomSheetTestTags
import com.tangem.wallet.R
import io.github.kakaocup.compose.node.element.ComposeScreen
@ -33,6 +34,12 @@ class SwapSelectNetworkFeeBottomSheetPageObject(semanticsProvider: SemanticsNode
hasTestTag(SelectNetworkFeeBottomSheetTestTags.LEARN_MORE_TEXT)
useUnmergedTree = true
}
val applyButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_apply))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onSwapSelectNetworkFeeBottomSheet(function: SwapSelectNetworkFeeBottomSheetPageObject.() -> Unit) =

View file

@ -43,6 +43,11 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
useUnmergedTree = true
}
val feeAmount: KNode = child {
hasParent(withTestTag(FeeSelectorBlockTestTags.FEE_AMOUNT))
useUnmergedTree = true
}
val receiveAmountShimmer: KNode = child {
hasTestTag(SwapTokenScreenTestTags.RECEIVE_AMOUNT_SHIMMER)
}
@ -71,6 +76,43 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
useUnmergedTree = true
}
fun unableToCoverFeeNotificationTitle(networkName: String): KNode = child {
hasTestTag(NotificationTestTags.TITLE)
hasText(
getResourceString(
R.string.warning_express_not_enough_fee_for_token_tx_title,
networkName
)
)
useUnmergedTree = true
}
fun unableToCoverFeeNotificationText(currencyName: String, currencySymbol: String): KNode = child {
hasTestTag(NotificationTestTags.MESSAGE)
hasText(
getResourceString(
R.string.warning_express_not_enough_fee_for_token_tx_description,
currencyName,
currencySymbol
)
)
useUnmergedTree = true
}
fun unableToCoverFeeNotificationIcon(networkName: String): KNode = child {
hasTestTag(NotificationTestTags.ICON)
hasAnySibling(withTestTag(NotificationTestTags.TITLE))
hasAnySibling(
withText(
getResourceString(
R.string.warning_express_not_enough_fee_for_token_tx_title,
networkName,
)
)
)
useUnmergedTree = true
}
val refreshButton: KNode = child {
hasTestTag(BaseButtonTestTags.BUTTON)
hasText(getResourceString(R.string.warning_button_refresh))
@ -95,15 +137,30 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
useUnmergedTree = true
}
val insufficientFundsErrorTitle: KNode = child {
hasTestTag(SendScreenTestTags.AMOUNT_CONTAINER_TITLE)
hasText(getResourceString(R.string.swapping_insufficient_funds))
useUnmergedTree = true
}
val receiveFiatAmount: KNode = child {
hasTestTag(SwapTokenScreenTestTags.RECEIVE_FIAT_AMOUNT)
}
val receiveFiatAmountWithPriceImpactWarning: KNode = child {
hasTestTag(SwapTokenScreenTestTags.RECEIVE_FIAT_AMOUNT_WITH_PRICE_IMPACT_WARNING)
}
val receiveFiatAmountInformationIcon: KNode = child {
hasTestTag(SwapTokenScreenTestTags.RECEIVE_FIAT_AMOUNT_INFORMATION_ICON)
useUnmergedTree = true
}
val swapFiatAmount: KNode = child {
hasTestTag(SwapTokenScreenTestTags.SWAP_FIAT_AMOUNT)
}
val changeTokenIcon: KNode = child {
val selectTokenIcon: KNode = child {
hasTestTag(SwapTokenScreenTestTags.SELECT_TOKEN_ICON)
}

View file

@ -0,0 +1,182 @@
package com.tangem.tests.swap
import com.tangem.common.BaseTestCase
import com.tangem.common.annotations.ApiEnv
import com.tangem.common.annotations.ApiEnvConfig
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
import com.tangem.common.extensions.*
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
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
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Test
@HiltAndroidTest
class SwapChooseTokenScreenTest : BaseTestCase() {
@ApiEnv(
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
)
@AllureId("8505")
@DisplayName("Swap: check available to swap tokens list")
@Test
fun checkAvailableToSwapTokensListTest() {
val tokenTitle = "Polygon"
val inputAmount = "100"
val ethereum = "Ethereum"
val polExMatic = "POL (ex-MATIC)"
val bitcoin = "Bitcoin"
val scenarioState = "CustomTokenAndJesusAdded"
val jesusCoin = "Jesus Coin"
val salam = "Salam"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
}
).run {
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$scenarioState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = scenarioState)
}
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("Click on 'Select token' icon") {
onSwapTokenScreen { selectTokenIcon.performClick() }
}
step("Assert '$ethereum' is displayed") {
onSwapChooseTokenScreen { tokenWithTitle(ethereum).assertIsDisplayed() }
}
step("Assert '$polExMatic' is displayed") {
onSwapChooseTokenScreen { tokenWithTitle(polExMatic).assertIsDisplayed() }
}
step("Assert '$bitcoin' is not displayed") {
onSwapChooseTokenScreen { tokenWithTitle(bitcoin).assertIsNotDisplayed() }
}
step("Assert '$jesusCoin' is displayed and unavailable for swap") {
onSwapChooseTokenScreen {
tokenWithTitle(
tokenTitle = jesusCoin,
availableForSwap = false
).assertIsDisplayed()
}
}
step("Assert custom token without backend id '$salam' is not displayed") {
onSwapChooseTokenScreen { tokenWithTitle(salam).assertIsNotDisplayed() }
}
}
}
@ApiEnv(
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
)
@AllureId("8506")
@DisplayName("Swap: check search on choose swap token screen")
@Test
fun checkSearchOnSwapChooseTokenScreenTest() {
val tokenTitle = "Polygon"
val inputAmount = "100"
val ethereum = "Ethereum"
val polExMatic = "POL (ex-MATIC)"
val polExMaticSymbol = "POL"
val invalidSearchText = "f"
val validSearchText = "pol"
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 'You swap' block is displayed") {
onSwapTokenScreen { youSwapBlock.assertIsDisplayed() }
}
step("Input swap amount = '$inputAmount'") {
waitForIdle()
onSwapTokenScreen {
textInput.clickWithAssertion()
textInput.performTextReplacement(inputAmount)
}
}
step("Click on 'Select token' icon") {
onSwapTokenScreen { selectTokenIcon.performClick() }
}
step("Click on 'Search' icon") {
onSwapChooseTokenScreen { searchIcon.performClick() }
}
step("Click on 'Search' text field") {
onSwapChooseTokenScreen { searchTextField.performClick() }
}
step("Type invalid search text: '$invalidSearchText' in 'Search' text field") {
onSwapChooseTokenScreen { searchTextField.performTextReplacement(invalidSearchText) }
}
step("Assert '$ethereum' is not displayed") {
onSwapChooseTokenScreen { tokenWithTitle(ethereum).assertIsNotDisplayed() }
}
step("Assert '$polExMatic' is not displayed") {
onSwapChooseTokenScreen { tokenWithTitle(polExMatic).assertIsNotDisplayed() }
}
step("Press 'Delete' button") {
device.uiDevice.pressDelete()
}
step("Type valid search text: '$validSearchText' in 'Search' text field") {
onSwapChooseTokenScreen { searchTextField.performTextReplacement(validSearchText) }
}
step("Assert '$ethereum' is not displayed") {
onSwapChooseTokenScreen { tokenWithTitle(ethereum).assertIsNotDisplayed() }
}
step("Assert '$polExMatic' is displayed") {
onSwapChooseTokenScreen { tokenWithTitle(polExMatic).assertIsDisplayed() }
}
step("Select new receive token: $polExMatic") {
onSwapChooseTokenScreen { tokenWithTitle(polExMatic).performClick() }
}
step("Assert new receive token symbol: '$polExMaticSymbol' is displayed") {
onSwapTokenScreen { receiveTokenSymbol(polExMaticSymbol).assertIsDisplayed() }
}
}
}
}

View file

@ -4,16 +4,16 @@ import androidx.compose.ui.test.hasText
import com.tangem.common.BaseTestCase
import com.tangem.common.annotations.ApiEnv
import com.tangem.common.annotations.ApiEnvConfig
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.extensions.*
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.resetWireMockScenarios
import com.tangem.common.utils.setWireMockScenarioState
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.scenarios.*
import com.tangem.screens.*
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
@ -266,6 +266,9 @@ class SwapTokenScreenTest : BaseTestCase() {
}
}
@ApiEnv(
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
)
@AllureId("2828")
@DisplayName("Swap: network fee")
@Test
@ -317,6 +320,9 @@ class SwapTokenScreenTest : BaseTestCase() {
}
}
@ApiEnv(
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
)
@AllureId("575")
@DisplayName("Swap: check UI")
@Test
@ -354,7 +360,7 @@ class SwapTokenScreenTest : BaseTestCase() {
onSwapTokenScreen { receiveTokenSymbol(receiveTokenSymbol).assertIsDisplayed() }
}
step("Click on 'Select token' icon") {
onSwapTokenScreen { changeTokenIcon.performClick() }
onSwapTokenScreen { selectTokenIcon.performClick() }
}
step("Select new receive token: $newReceiveToken") {
onSwapChooseTokenScreen { tokenWithTitle(newReceiveToken).performClick() }
@ -401,6 +407,9 @@ class SwapTokenScreenTest : BaseTestCase() {
}
}
@ApiEnv(
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
)
@AllureId("5162")
@DisplayName("Swap: check swap tokens switch")
@Test
@ -444,4 +453,293 @@ class SwapTokenScreenTest : BaseTestCase() {
}
}
}
@AllureId("573")
@DisplayName("Swap: check 'Swap' button availability")
@Test
fun checkSwapButtonAvailabilityTest() {
val polygon = "Polygon"
val bitcoin = "Bitcoin"
val salam = "Salam"
val jesusCoin = "Jesus Coin"
val myria = "Myria"
val scenarioState = "CustomTokenAndJesusAdded"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
}
).run {
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$scenarioState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = scenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$polygon'") {
onMainScreen { tokenWithTitleAndAddress(polygon).clickWithAssertion() }
}
step("Assert 'Swap' button is not dimmed. Swap available") {
onTokenDetailsScreen { swapButton().assertIsDimmed(false) }
}
step("Press 'Back' button") {
device.uiDevice.pressBack()
}
step("Click on token with name: '$bitcoin'. Swap unavailable") {
onMainScreen { tokenWithTitleAndAddress(bitcoin).clickWithAssertion() }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertIsDimmed(true) }
}
step("Press 'Back' button") {
device.uiDevice.pressBack()
}
step("Click on unknown custom token with name: '$salam'. Swap unavailable") {
onMainScreen { tokenWithTitleAndAddress(salam).clickWithAssertion() }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertIsDimmed(true) }
}
step("Press 'Back' button") {
device.uiDevice.pressBack()
}
step("Swipe up") {
onMainScreen { tokenWithTitleAndAddress(jesusCoin).assertIsDisplayed() }
waitForIdle()
swipeVertical(SwipeDirection.UP)
}
step("Click on token with name: '$jesusCoin' in 'Ethereum' network. Swap unavailable") {
waitForIdle()
onMainScreen { tokenWithTitleAndAddress(jesusCoin).clickWithAssertion() }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertIsDimmed(true) }
}
step("Press 'Back' button") {
device.uiDevice.pressBack()
}
step("Click on custom token with 'exchangeAvailable=true': '$myria'. Swap unavailable") {
onMainScreen { tokenWithTitleAndAddress(myria).clickWithAssertion() }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertIsDimmed(true) }
}
}
}
@ApiEnv(
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
)
@AllureId("583")
@DisplayName("Swap: check switch fee type (enable to cover 'Market' and 'Fast' fee)")
@Test
fun enableToCoverMarketAndFastFeeTest() {
val tokenName = "Ethereum"
val inputAmount = "0.99"
val market = "Market"
val fast = "Fast"
val marketFeeAmount = "$1.12"
val fastFeeAmount = "$1.43"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
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("Select '$market' fee type") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
selectFeeType(FeeType.Market, selectedFeeAmount = marketFeeAmount)
}
}
step("Select '$fast' fee type") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
selectFeeType(FeeType.Fast, selectedFeeAmount = fastFeeAmount)
}
}
}
}
@ApiEnv(
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
)
@AllureId("8536")
@DisplayName("Swap: check switch fee type (unable to cover 'Market' and 'Fast' fee)")
@Test
fun unableToCoverMarketAndFastFeeTest() {
val tokenName = "POL (ex-MATIC)"
val inputAmount = "0.0001"
val market = "Market"
val fast = "Fast"
val marketFeeAmount = "$18,932"
val fastFeeAmount = "$24,139"
val scenarioName = "eth_network_balance"
val scenarioState = "LessThanDollar"
val networkName = "Ethereum"
val currencySymbol = "ETH"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(scenarioName)
}
).run {
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
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("Select '$market' fee type") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
selectFeeType(FeeType.Market, selectedFeeAmount = marketFeeAmount)
}
}
step("Check 'Unable to cover '$networkName' fee notification") {
chackUnableToCoverFeeNotification(networkName = networkName, currencySymbol = currencySymbol)
}
step("Assert 'Swap' button is disabled") {
onSwapTokenScreen { swapButton.assertIsNotEnabled() }
}
step("Select '$fast' fee type") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
selectFeeType(FeeType.Fast, selectedFeeAmount = fastFeeAmount)
}
}
step("Check 'Unable to cover '$networkName' fee notification") {
chackUnableToCoverFeeNotification(networkName = networkName, currencySymbol = currencySymbol)
}
step("Assert 'Swap' button is disabled") {
onSwapTokenScreen { swapButton.assertIsNotEnabled() }
}
}
}
@ApiEnv(
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
)
@AllureId("8537")
@DisplayName("Swap: check switch fee type (unable to cover 'Fast' fee)")
@Test
fun unableToCoverFastFeeTest() {
val tokenName = "POL (ex-MATIC)"
val inputAmount = "3000"
val fastFeeType = "Fast"
val fastFeeAmount = "$2,"
val marketFeeType = "Market"
val marketFeeAmount = "$1."
val scenarioName = "eth_fee_history"
val scenarioState = "UnableToCoverFastFee"
val networkName = "Ethereum"
val currencySymbol = "ETH"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(scenarioName)
}
).run {
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
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 'Swap' button is enabled") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSwapTokenScreen { swapButton.assertIsEnabled() }
}
}
step("Select '$fastFeeType' fee type") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
selectFeeType(FeeType.Fast, selectedFeeAmount = fastFeeAmount)
}
}
step("Assert fee amount is equal to '$fastFeeType' fee:'$fastFeeAmount'") {
onSwapTokenScreen { feeAmount.assertTextContains(fastFeeAmount, substring = true) }
}
step("Check 'Unable to cover '$networkName' fee notification") {
chackUnableToCoverFeeNotification(networkName = networkName, currencySymbol = currencySymbol)
}
step("Assert 'Swap' button is disabled") {
onSwapTokenScreen { swapButton.assertIsNotEnabled() }
}
step("Select '$marketFeeType' fee type") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
selectFeeType(FeeType.Market, selectedFeeAmount = marketFeeAmount)
}
}
step("Assert fee amount is equal to '$marketFeeType' fee:'$marketFeeAmount'") {
onSwapTokenScreen { feeAmount.assertTextContains(marketFeeAmount, substring = true) }
}
step("Assert 'Swap' button is enabled") {
waitForIdle()
onSwapTokenScreen { swapButton.assertIsEnabled() }
}
}
}
}

View file

@ -0,0 +1,314 @@
package com.tangem.tests.swap
import com.tangem.common.BaseTestCase
import com.tangem.common.R
import com.tangem.common.annotations.ApiEnv
import com.tangem.common.annotations.ApiEnvConfig
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
import com.tangem.common.extensions.*
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
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.openMainScreen
import com.tangem.scenarios.openSwapScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.*
import dagger.hilt.android.testing.HiltAndroidTest
import io.github.kakaocup.kakao.common.utilities.getResourceString
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Test
@HiltAndroidTest
class SwapTokenScreenWarningsTest : BaseTestCase() {
@ApiEnv(
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
)
@AllureId("580")
@DisplayName("Swap: check 'Insufficient funds' warning")
@Test
fun checkSwapInsufficientFundsWarningTest() {
val tokenTitle = "Polygon"
val inputAmount = "1000"
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 'You swap' block is displayed") {
onSwapTokenScreen { youSwapBlock.assertIsDisplayed() }
}
step("Input swap amount = '$inputAmount'") {
waitForIdle()
onSwapTokenScreen {
textInput.clickWithAssertion()
textInput.performTextReplacement(inputAmount)
}
}
step("Assert 'Insufficient funds' error is displayed") {
waitForIdle()
onSwapTokenScreen { insufficientFundsErrorTitle.assertIsDisplayed() }
}
}
}
@AllureId("8502")
@DisplayName("Swap: check 'Unable to cover network fee' warning")
@Test
fun checkUnableToCoverBlockchainFeeWarningTest() {
val tokenTitle = "USDC"
val inputAmount = "1000"
val tokensScenarioState = "SolanaUSDC"
val balanceScenarioName = "solana_balance"
val balanceScenarioState = "Empty"
val networkName = "Solana"
val currencySymbol = "SOL"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
resetWireMockScenarioState(balanceScenarioState)
}
).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("Set WireMock scenario: '$balanceScenarioName' to state: $balanceScenarioState") {
setWireMockScenarioState(scenarioName = balanceScenarioName, state = balanceScenarioState)
}
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("Check 'Unable to cover '$networkName' fee notification") {
chackUnableToCoverFeeNotification(networkName = networkName, currencySymbol = currencySymbol)
}
step("Assert 'Unable to cover '$networkName' fee notification icon is displayed'") {
onSwapTokenScreen { unableToCoverFeeNotificationIcon(networkName).assertIsDisplayed() }
}
step("Assert 'Swap' button is disabled") {
onSwapTokenScreen { swapButton.assertIsNotEnabled() }
}
}
}
@AllureId("8503")
@DisplayName("Swap: check 'High price impact' warning on CEX")
@Test
fun checkHighPriceImpactWarningCEXTest() {
val tokenTitle = "USDC"
val inputAmount = "100"
val currencySymbol = "SOL"
val slippagePercent = "5%"
val tokensScenarioState = "SolanaUSDC"
val exchangeQuoteScenarioName = "exchange_quote_solana"
val exchangeQuoteScenarioState = "HighPriceImpact"
val dialogTitle = getResourceString(R.string.swapping_alert_title)
val dialogText = getResourceString(
R.string.swapping_alert_cex_description_with_slippage,
currencySymbol,
slippagePercent
)
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
resetWireMockScenarioState(exchangeQuoteScenarioName)
}
).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("Set WireMock scenario: '$exchangeQuoteScenarioName' to state: $exchangeQuoteScenarioState") {
setWireMockScenarioState(
scenarioName = exchangeQuoteScenarioName,
state = exchangeQuoteScenarioState
)
}
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 fiat amount with warning is displayed") {
onSwapTokenScreen { receiveFiatAmountWithPriceImpactWarning.assertTextContains("%", substring = true) }
}
step("Assert receive amount information icon is displayed") {
onSwapTokenScreen { receiveFiatAmountInformationIcon.assertIsDisplayed() }
}
step("Click on receive amount information icon") {
onSwapTokenScreen { receiveFiatAmountInformationIcon.performClick() }
}
step("Assert information dialog is displayed") {
onDialog { dialogContainer.assertIsDisplayed() }
}
step("Assert information dialog title is displayed") {
onDialog { title.assertTextEquals(dialogTitle) }
}
step("Assert information dialog text for CEX is displayed") {
onDialog { text.assertTextEquals(dialogText) }
}
step("Assert dialog 'OK' button is displayed") {
onDialog { okButton.assertIsDisplayed() }
}
}
}
@AllureId("8504")
@DisplayName("Swap: check 'High price impact' warning on DEX")
@Test
fun checkHighPriceImpactWarningDEXTest() {
val tokenTitle = "Polygon"
val inputAmount = "1000"
val slippagePercent = "3.5%"
val dialogTitle = getResourceString(R.string.swapping_alert_title)
val highPriceImpactDescription = getResourceString(R.string.swapping_high_price_impact_description)
val swappingAlertDEXDescription = getResourceString(R.string.swapping_alert_dex_description)
val swappingAlertDEXDescriptionWithSlippage = getResourceString(
R.string.swapping_alert_dex_description_with_slippage,
slippagePercent
)
val pairsToScenarioName = "polygon_pos_to_pairs"
val pairsFromScenarioName = "polygon_pos_from_pairs"
val scenarioState = "DexProvider"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(pairsToScenarioName)
resetWireMockScenarioState(pairsFromScenarioName)
}
).run {
step("Set WireMock scenario: '$pairsToScenarioName' to state: $scenarioState") {
setWireMockScenarioState(scenarioName = pairsToScenarioName, state = scenarioState)
}
step("Set WireMock scenario: '$pairsFromScenarioName' to state: $scenarioState") {
setWireMockScenarioState(scenarioName = pairsFromScenarioName, state = scenarioState)
}
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 fiat amount with warning is displayed") {
onSwapTokenScreen { receiveFiatAmountWithPriceImpactWarning.assertTextContains("%", substring = true) }
}
step("Assert receive amount information icon is displayed") {
onSwapTokenScreen { receiveFiatAmountInformationIcon.assertIsDisplayed() }
}
step("Click on receive amount information icon") {
onSwapTokenScreen { receiveFiatAmountInformationIcon.performClick() }
}
step("Assert information dialog is displayed") {
onDialog { dialogContainer.assertIsDisplayed() }
}
step("Assert information dialog title is displayed") {
onDialog { title.assertTextEquals(dialogTitle) }
}
step("Assert information dialog text for DEX is displayed") {
onDialog {
text.assertTextContains(highPriceImpactDescription, substring = true)
text.assertTextContains(swappingAlertDEXDescription, substring = true)
text.assertTextContains(swappingAlertDEXDescriptionWithSlippage, substring = true)
}
}
step("Assert dialog 'OK' button is displayed") {
onDialog { okButton.assertIsDisplayed() }
}
step("Click on 'OK' button") {
onDialog { okButton.performClick() }
}
}
}
}