Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-29 17:05:01 +03:00
parent 82506a1c9c
commit 01b51acd93
13 changed files with 238 additions and 143 deletions

View file

@ -10,9 +10,14 @@ import com.tangem.common.extensions.assertIsDimmed
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.screens.*
import com.tangem.tap.domain.sdk.mocks.MockContent
import io.qameta.allure.kotlin.Allure.step
fun BaseTestCase.openSendScreen(tokenName: String, mockState: String = "") {
fun BaseTestCase.openSendScreen(
tokenName: String,
mockState: String = "",
mockContent: MockContent? = null,
) {
val scenarioState = mockState.ifEmpty { tokenName }
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$scenarioState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = scenarioState)
@ -21,7 +26,7 @@ fun BaseTestCase.openSendScreen(tokenName: String, mockState: String = "") {
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = scenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
openMainScreen(mockContent = mockContent)
}
step("Synchronize addresses") {
synchronizeAddresses()

View file

@ -297,6 +297,15 @@ fun BaseTestCase.checkSwapWarning(
}
}
fun BaseTestCase.chooseReceiveToken(tokenName: String) {
step("Click on 'Choose token' button") {
onSwapTokenScreen { chooseTokenButton.performClick() }
}
step("Click on token with name '$tokenName'") {
onSwapSelectTokenScreen { tokenWithName(tokenName).performClick() }
}
}
sealed class SwapEntryPoint {
object MainScreen : SwapEntryPoint()
object TokenDetails : SwapEntryPoint()

View file

@ -1,60 +0,0 @@
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.AppBarWithSearchTestTags
import com.tangem.core.ui.test.BuyTokenScreenTestTags
import com.tangem.core.ui.test.MarketsTestTags
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<SwapChooseTokenPageObject>(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))
}
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
}
fun marketsTokenWithTitle(title: String): KNode {
return child {
hasTestTag(MarketsTestTags.TOKENS_LIST_ITEM)
hasText(title)
}
}
}
internal fun BaseTestCase.onSwapChooseTokenScreen(function: SwapChooseTokenPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -29,8 +29,8 @@ class SwapSelectTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProv
}
val youSwapBlock: KNode = child {
hasTestTag(SwapSelectTokenScreenTestTags.YOU_SWAP_BLOCK)
hasAnyDescendant(withText(getResourceString(R.string.action_buttons_you_want_to_swap)))
hasTestTag(SwapTokenScreenTestTags.SWAP_CARD)
hasAnyDescendant(withText(getResourceString(R.string.swapping_from_title)))
useUnmergedTree = true
}
@ -40,8 +40,8 @@ class SwapSelectTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProv
}
val youReceiveBlock: KNode = child {
hasTestTag(SwapSelectTokenScreenTestTags.YOU_SWAP_BLOCK)
hasAnyDescendant(withText(getResourceString(R.string.action_buttons_you_want_to_receive)))
hasTestTag(SwapTokenScreenTestTags.RECEIVE_CARD)
hasAnyDescendant(withText(getResourceString(R.string.swapping_to_title)))
useUnmergedTree = true
}

View file

@ -174,8 +174,16 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
hasTestTag(SwapTokenScreenTestTags.SWAP_FIAT_AMOUNT)
}
val selectTokenIcon: KNode = child {
val swapSelectTokenIcon: KNode = child {
hasAnyAncestor(withTestTag(SwapTokenScreenTestTags.SWAP_CARD))
hasTestTag(SwapTokenScreenTestTags.SELECT_TOKEN_ICON)
useUnmergedTree = true
}
val receiveSelectTokenIcon: KNode = child {
hasAnyAncestor(withTestTag(SwapTokenScreenTestTags.RECEIVE_CARD))
hasTestTag(SwapTokenScreenTestTags.SELECT_TOKEN_ICON)
useUnmergedTree = true
}
fun swapTokenSymbol(symbol: String): KNode = child {
@ -191,6 +199,11 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
hasText(symbol)
useUnmergedTree = true
}
val chooseTokenButton: KNode = child {
hasText(getResourceString(R.string.common_choose_token))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onSwapTokenScreen(function: SwapTokenPageObject.() -> Unit) =

View file

@ -6,15 +6,16 @@ import com.tangem.common.constants.TestConstants.ETHEREUM_RECIPIENT_ADDRESS
import com.tangem.common.constants.TestConstants.POLYGON_RECIPIENT_ADDRESS
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
import com.tangem.common.constants.TestConstants.SOLANA_RECIPIENT_ADDRESS
import com.tangem.common.constants.TestConstants.SVS_SEED_PHRASE_12
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
import com.tangem.common.constants.TestConstants.SVS_SEED_PHRASE_12
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.extractText
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.*
import com.tangem.screens.*
import com.tangem.tap.domain.sdk.mocks.content.Wallet2WithDerivationsMockContent
import dagger.hilt.android.testing.HiltAndroidTest
import io.github.kakaocup.kakao.common.utilities.getResourceString
import io.qameta.allure.kotlin.AllureId
@ -34,6 +35,7 @@ class SendViaSwapTest : BaseTestCase() {
val bitcoinBalanceScenarioState = "Balance"
val assetsScenarioName = "express_api_assets"
val assetsScenarioState = "BitcoinExchangeEnabled"
val userTokensScenarioState = "Wallet2"
val warningTitle = getResourceString(R.string.express_swap_not_supported_title, stellar)
val warningMessage = getResourceString(R.string.express_swap_not_supported_text)
@ -41,6 +43,7 @@ class SendViaSwapTest : BaseTestCase() {
additionalAfterSection = {
resetWireMockScenarioState(bitcoinBalanceScenarioName)
resetWireMockScenarioState(assetsScenarioName)
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
}
).run {
@ -50,9 +53,11 @@ class SendViaSwapTest : BaseTestCase() {
step("Set WireMock scenario: '$assetsScenarioName' to state: '$assetsScenarioState'") {
setWireMockScenarioState(scenarioName = assetsScenarioName, state = assetsScenarioState)
}
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensScenarioState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensScenarioState)
}
step("Open 'Send' screen") {
openSendScreen(tokenName)
openSendScreen(tokenName, mockContent = Wallet2WithDerivationsMockContent)
}
step("Click on 'Swap to another token button'") {
onSendScreen { swapToAnotherTokenButton.performClick() }
@ -93,11 +98,13 @@ class SendViaSwapTest : BaseTestCase() {
val bitcoinBalanceScenarioState = "Balance"
val assetsScenarioName = "express_api_assets"
val assetsScenarioState = "BitcoinExchangeEnabled"
val userTokensScenarioState = "Wallet2"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(bitcoinBalanceScenarioName)
resetWireMockScenarioState(assetsScenarioName)
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
}
).run {
@ -107,9 +114,12 @@ class SendViaSwapTest : BaseTestCase() {
step("Set WireMock scenario: '$assetsScenarioName' to state: '$assetsScenarioState'") {
setWireMockScenarioState(scenarioName = assetsScenarioName, state = assetsScenarioState)
}
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensScenarioState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensScenarioState)
}
step("Open 'Send' screen") {
openSendScreen(tokenName)
openSendScreen(tokenName, mockContent = Wallet2WithDerivationsMockContent)
}
step("Type '$firstInputAmount' in text field") {
onSendScreen { amountInputTextField.performTextInput(firstInputAmount) }
@ -178,10 +188,13 @@ class SendViaSwapTest : BaseTestCase() {
step("Assert provider name is '$regularProviderName'") {
onSendConfirmScreen { providerName.assertTextContains(regularProviderName) }
}
step("Click on fee selector icon") {
onSendConfirmScreen { feeSelectorIcon.performClick() }
step("Open fee selector bottom sheet via click on fee selector icon") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSendConfirmScreen { feeSelectorIcon.performClick() }
onSendSelectNetworkFeeBottomSheet { regularFeeSelectorItem(fastSelectorItem).assertIsDisplayed() }
}
}
step("Click on '$fastSelectorItem' selector item is displayed") {
step("Click on '$fastSelectorItem' selector item") {
onSendSelectNetworkFeeBottomSheet { regularFeeSelectorItem(fastSelectorItem).performClick() }
}
step("Capture fast fee value") {
@ -232,6 +245,7 @@ class SendViaSwapTest : BaseTestCase() {
val bitcoinBalanceScenarioState = "Balance"
val assetsScenarioName = "express_api_assets"
val assetsScenarioState = "BitcoinExchangeEnabled"
val userTokensScenarioState = "Wallet2"
val dialogTitle = getResourceString(R.string.send_with_swap_change_token_alert_title)
val dialogMessage = getResourceString(R.string.send_with_swap_change_token_alert_message)
val addressHint = getResourceString(R.string.send_enter_address_field_ens)
@ -240,6 +254,7 @@ class SendViaSwapTest : BaseTestCase() {
additionalAfterSection = {
resetWireMockScenarioState(bitcoinBalanceScenarioName)
resetWireMockScenarioState(assetsScenarioName)
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
}
).run {
@ -249,9 +264,12 @@ class SendViaSwapTest : BaseTestCase() {
step("Set WireMock scenario: '$assetsScenarioName' to state: '$assetsScenarioState'") {
setWireMockScenarioState(scenarioName = assetsScenarioName, state = assetsScenarioState)
}
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensScenarioState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensScenarioState)
}
step("Open 'Send' screen") {
openSendScreen(tokenName)
openSendScreen(tokenName, mockContent = Wallet2WithDerivationsMockContent)
}
step("Type '$inputAmount' in text field") {
onSendScreen { amountInputTextField.performTextInput(inputAmount) }

View file

@ -238,20 +238,17 @@ class SearchAndSwapTest : BaseTestCase() {
onSwapTokenScreen { replaceTokensButton.performClick() }
}
step("Click on 'Select token' icon") {
onSwapTokenScreen { selectTokenIcon.performClick() }
onSwapTokenScreen { swapSelectTokenIcon.performClick() }
}
step("Click on 'Search' icon") {
onSwapChooseTokenScreen { searchIcon.performClick() }
}
step("Click on 'Search' text field") {
onSwapChooseTokenScreen { searchTextField.performClick() }
onSwapSelectTokenScreen { searchBarIcon.performClick() }
}
step("Type '$swapTokenSymbol' in 'Search' text field") {
onSwapChooseTokenScreen { searchTextField.performTextInputInChunks(swapTokenSymbol) }
onSwapSelectTokenScreen { searchBarBlock.performTextInputInChunks(swapTokenSymbol) }
}
step("Click on token with name: '$swapTokenName'") {
flakySafely(WAIT_UNTIL_TIMEOUT) {
onSwapChooseTokenScreen { marketsTokenWithTitle(swapTokenName).performClick() }
onSwapSelectTokenScreen { marketsTokenWithName(swapTokenName).performClick() }
}
}
step("Click on 'Add' button") {

View file

@ -4,7 +4,7 @@ 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.extensions.clickWithAssertion
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.datasource.api.common.config.ApiConfig
@ -74,22 +74,22 @@ class SwapChooseTokenScreenTest : BaseTestCase() {
}
}
step("Click on 'Select token' icon") {
onSwapTokenScreen { selectTokenIcon.performClick() }
onSwapTokenScreen { swapSelectTokenIcon.performClick() }
}
step("Assert '$ethereum' is displayed") {
onSwapChooseTokenScreen { tokenWithTitle(ethereum).assertIsDisplayed() }
onSwapSelectTokenScreen { tokenWithName(ethereum).assertIsDisplayed() }
}
step("Assert '$polExMatic' is displayed") {
onSwapChooseTokenScreen { tokenWithTitle(polExMatic).assertIsDisplayed() }
onSwapSelectTokenScreen { tokenWithName(polExMatic).assertIsDisplayed() }
}
step("Assert '$bitcoin' is not displayed") {
onSwapChooseTokenScreen { tokenWithTitle(bitcoin).assertIsNotDisplayed() }
onSwapSelectTokenScreen { tokenWithName(bitcoin).assertIsNotDisplayed() }
}
step("Assert '$jesusCoin' is displayed and unavailable for swap") {
onSwapChooseTokenScreen { tokenWithTitle(tokenTitle = jesusCoin).assertIsDisplayed() }
onSwapSelectTokenScreen { tokenWithName(jesusCoin).assertIsDisplayed() }
}
step("Assert custom token without backend id '$salam' is not displayed") {
onSwapChooseTokenScreen { tokenWithTitle(salam).assertIsNotDisplayed() }
onSwapSelectTokenScreen { tokenWithName(salam).assertIsNotDisplayed() }
}
}
}
@ -136,38 +136,35 @@ class SwapChooseTokenScreenTest : BaseTestCase() {
textInput.performTextReplacement(inputAmount)
}
}
step("Click on 'Select token' icon") {
onSwapTokenScreen { selectTokenIcon.performClick() }
step("Click on 'Choose token' button") {
onSwapTokenScreen { chooseTokenButton.performClick() }
}
step("Click on 'Search' icon") {
onSwapChooseTokenScreen { searchIcon.performClick() }
}
step("Click on 'Search' text field") {
onSwapChooseTokenScreen { searchTextField.performClick() }
onSwapSelectTokenScreen { searchBarIcon.performClick() }
}
step("Type invalid search text: '$invalidSearchText' in 'Search' text field") {
onSwapChooseTokenScreen { searchTextField.performTextReplacement(invalidSearchText) }
onSwapSelectTokenScreen { searchBarBlock.performTextReplacement(invalidSearchText) }
}
step("Assert '$ethereum' is not displayed") {
onSwapChooseTokenScreen { tokenWithTitle(ethereum).assertIsNotDisplayed() }
onSwapSelectTokenScreen { tokenWithName(ethereum).assertIsNotDisplayed() }
}
step("Assert '$polExMatic' is not displayed") {
onSwapChooseTokenScreen { tokenWithTitle(polExMatic).assertIsNotDisplayed() }
onSwapSelectTokenScreen { tokenWithName(polExMatic).assertIsNotDisplayed() }
}
step("Press 'Delete' button") {
device.uiDevice.pressDelete()
}
step("Type valid search text: '$validSearchText' in 'Search' text field") {
onSwapChooseTokenScreen { searchTextField.performTextReplacement(validSearchText) }
onSwapSelectTokenScreen { searchBarBlock.performTextReplacement(validSearchText) }
}
step("Assert '$ethereum' is not displayed") {
onSwapChooseTokenScreen { tokenWithTitle(ethereum).assertIsNotDisplayed() }
onSwapSelectTokenScreen { tokenWithName(ethereum).assertIsNotDisplayed() }
}
step("Assert '$polExMatic' is displayed") {
onSwapChooseTokenScreen { tokenWithTitle(polExMatic).assertIsDisplayed() }
onSwapSelectTokenScreen { tokenWithName(polExMatic).assertIsDisplayed() }
}
step("Select new receive token: $polExMatic") {
onSwapChooseTokenScreen { tokenWithTitle(polExMatic).performClick() }
onSwapSelectTokenScreen { tokenWithName(polExMatic).performClick() }
}
step("Assert new receive token symbol: '$polExMaticSymbol' is displayed") {
onSwapTokenScreen { receiveTokenSymbol(polExMaticSymbol).assertIsDisplayed() }

View file

@ -20,7 +20,6 @@ class SwapSelectTokenScreenTest : BaseTestCase() {
@DisplayName("Open 'Swap select token' screen from 'Main' screen")
@Test
fun openSwapSelectTokenScreenFromMainScreenTest() {
val swapTokenName = "Ethereum"
val receiveTokenName = "Polygon"
setupHooks().run {
@ -37,14 +36,11 @@ class SwapSelectTokenScreenTest : BaseTestCase() {
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
}
step("Assert 'Swap select token' screen title is displayed") {
onSwapSelectTokenScreen { title.assertIsDisplayed() }
step("Click on 'Choose token' button") {
onSwapTokenScreen { chooseTokenButton.performClick() }
}
step("Assert 'You swap' title is displayed") {
onSwapSelectTokenScreen { youSwapTitle.assertIsDisplayed() }
}
step("Assert 'You swap' block is displayed") {
onSwapSelectTokenScreen { youSwapBlock.assertIsDisplayed() }
step("Assert 'You receive' title is displayed") {
onSwapSelectTokenScreen { youReceiveTitle.assertIsDisplayed() }
}
step("Assert search icon is displayed") {
onSwapSelectTokenScreen { searchBarIcon.assertIsDisplayed() }
@ -52,15 +48,6 @@ class SwapSelectTokenScreenTest : BaseTestCase() {
step("Assert search placeholder is displayed") {
onSwapSelectTokenScreen { searchBarPlaceholderText.assertIsDisplayed() }
}
step("Click on token with name '$swapTokenName'") {
onSwapSelectTokenScreen { tokenWithName(swapTokenName).performClick() }
}
step("Assert 'You receive' title is displayed") {
onSwapSelectTokenScreen { youReceiveTitle.assertIsDisplayed() }
}
step("Assert 'You receive' block is displayed") {
onSwapSelectTokenScreen { youReceiveBlock.assertIsDisplayed() }
}
step("Click on token with name '$receiveTokenName'") {
onSwapSelectTokenScreen { tokenWithName(receiveTokenName).performClick() }
}

View file

@ -32,6 +32,7 @@ class SwapTokenScreenTest : BaseTestCase() {
fun networkFeeTest() {
val inputAmount = "400"
val tokenTitle = "Polygon"
val receiveTokenName = "Ethereum"
setupHooks().run {
@ -67,13 +68,6 @@ class SwapTokenScreenTest : BaseTestCase() {
}
}
}
step("Assert receive amount is displayed") {
onSwapTokenScreen {
flakySafely(WAIT_UNTIL_TIMEOUT) {
receiveAmount.assertIsDisplayed()
}
}
}
step("Input swap amount = '$inputAmount'") {
waitForIdle()
onSwapTokenScreen {
@ -81,9 +75,19 @@ class SwapTokenScreenTest : BaseTestCase() {
textInput.performTextReplacement(inputAmount)
}
}
step("Choose receive token") {
chooseReceiveToken(receiveTokenName)
}
step("Assert input amount = '$inputAmount'") {
onSwapTokenScreen { textInput.assertTextEquals(inputAmount) }
}
step("Assert receive amount is displayed") {
onSwapTokenScreen {
flakySafely(WAIT_UNTIL_TIMEOUT) {
receiveAmount.assertIsDisplayed()
}
}
}
step("Assert 'Providers' block is displayed") {
onSwapTokenScreen {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
@ -175,9 +179,10 @@ class SwapTokenScreenTest : BaseTestCase() {
@Test
fun changeNetworkFeeTest() {
val inputAmount = "400"
val tokenTitle = "Polygon"
val receiveTokenName = "Ethereum"
setupHooks().run {
val tokenTitle = "Polygon"
step("Open 'Main Screen'") {
openMainScreen()
@ -207,13 +212,6 @@ class SwapTokenScreenTest : BaseTestCase() {
}
}
}
step("Assert receive amount is displayed") {
onSwapTokenScreen {
flakySafely(WAIT_UNTIL_TIMEOUT) {
receiveAmount.assertIsDisplayed()
}
}
}
step("Input swap amount = '$inputAmount'") {
waitForIdle()
onSwapTokenScreen {
@ -224,6 +222,16 @@ class SwapTokenScreenTest : BaseTestCase() {
step("Assert input amount = '$inputAmount'") {
onSwapTokenScreen { textInput.assertTextEquals(inputAmount) }
}
step("Choose receive token") {
chooseReceiveToken(receiveTokenName)
}
step("Assert receive amount is displayed") {
onSwapTokenScreen {
flakySafely(WAIT_UNTIL_TIMEOUT) {
receiveAmount.assertIsDisplayed()
}
}
}
step("Assert 'Network fee' block is displayed") {
onSwapTokenScreen {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
@ -270,11 +278,10 @@ class SwapTokenScreenTest : BaseTestCase() {
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
)
@AllureId("2828")
@DisplayName("Swap: network fee")
@DisplayName("Swap: go to token swap")
@Test
fun goToTokenSwapTest() {
val swapTokenSymbol = "POL"
val receiveTokenSymbol = "ETH"
val tokenTitle = "Polygon"
setupHooks().run {
@ -314,8 +321,8 @@ class SwapTokenScreenTest : BaseTestCase() {
step("Assert token symbol: '$swapTokenSymbol' is displayed") {
onSwapTokenScreen { swapTokenSymbol(swapTokenSymbol).assertIsDisplayed() }
}
step("Assert token symbol: '$receiveTokenSymbol' is displayed") {
onSwapTokenScreen { receiveTokenSymbol(receiveTokenSymbol).assertIsDisplayed() }
step("Assert 'Choose token' button is displayed") {
onSwapTokenScreen { chooseTokenButton.assertIsDisplayed() }
}
}
}
@ -329,6 +336,7 @@ class SwapTokenScreenTest : BaseTestCase() {
fun checkSwapUiTest() {
val swapTokenSymbol = "POL"
val receiveTokenSymbol = "ETH"
val receiveTokenName = "Ethereum"
val newReceiveToken = "POL (ex-MATIC)"
val tokenTitle = "Polygon"
val inputAmount = "1"
@ -356,14 +364,17 @@ class SwapTokenScreenTest : BaseTestCase() {
step("Assert swap token symbol: '$swapTokenSymbol' is displayed") {
onSwapTokenScreen { swapTokenSymbol(swapTokenSymbol).assertIsDisplayed() }
}
step("Choose receive token") {
chooseReceiveToken(receiveTokenName)
}
step("Assert receive token symbol: '$receiveTokenSymbol' is displayed") {
onSwapTokenScreen { receiveTokenSymbol(receiveTokenSymbol).assertIsDisplayed() }
}
step("Click on 'Select token' icon") {
onSwapTokenScreen { selectTokenIcon.performClick() }
step("Click on receive 'Select token' icon") {
onSwapTokenScreen { receiveSelectTokenIcon.performClick() }
}
step("Select new receive token: $newReceiveToken") {
onSwapChooseTokenScreen { tokenWithTitle(newReceiveToken).performClick() }
onSwapSelectTokenScreen { tokenWithName(newReceiveToken).performClick() }
}
step("Assert new receive token symbol: '$swapTokenSymbol' is displayed") {
onSwapTokenScreen { receiveTokenSymbol(swapTokenSymbol).assertIsDisplayed() }
@ -392,7 +403,11 @@ class SwapTokenScreenTest : BaseTestCase() {
onSwapTokenScreen { swapFiatAmount.assertIsDisplayed() }
}
step("Assert receive token fiat amount is displayed") {
onSwapTokenScreen { receiveFiatAmount.assertIsDisplayed() }
onSwapTokenScreen {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
receiveFiatAmount.assertIsDisplayed()
}
}
}
step("Assert 'Swap tokens on screen' button is displayed") {
onSwapTokenScreen {
@ -416,6 +431,7 @@ class SwapTokenScreenTest : BaseTestCase() {
fun checkSwapTokensSwitchTest() {
val swapTokenSymbol = "POL"
val receiveTokenSymbol = "ETH"
val receiveTokenName = "Ethereum"
val tokenTitle = "Polygon"
setupHooks().run {
@ -438,6 +454,9 @@ class SwapTokenScreenTest : BaseTestCase() {
step("Assert swap token symbol: '$swapTokenSymbol' is displayed") {
onSwapTokenScreen { swapTokenSymbol(swapTokenSymbol).assertIsDisplayed() }
}
step("Choose receive token") {
chooseReceiveToken(receiveTokenName)
}
step("Assert receive token symbol: '$receiveTokenSymbol' is displayed") {
onSwapTokenScreen { receiveTokenSymbol(receiveTokenSymbol).assertIsDisplayed() }
}
@ -514,6 +533,7 @@ class SwapTokenScreenTest : BaseTestCase() {
@Test
fun enableToCoverMarketAndFastFeeTest() {
val tokenName = "Ethereum"
val receiveTokenName = "Polygon"
val inputAmount = "0.99"
val market = "Market"
val fast = "Fast"
@ -544,6 +564,9 @@ class SwapTokenScreenTest : BaseTestCase() {
textInput.performTextReplacement(inputAmount)
}
}
step("Choose receive token") {
chooseReceiveToken(receiveTokenName)
}
step("Select '$market' fee type") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
selectFeeType(FeeType.Market, selectedFeeAmount = marketFeeAmount)
@ -562,6 +585,7 @@ class SwapTokenScreenTest : BaseTestCase() {
@Test
fun unableToCoverMarketAndFastFeeTest() {
val tokenName = "POL (ex-MATIC)"
val receiveTokenName = "Ethereum"
val inputAmount = "0.0001"
val marketFeeType = "Market"
val fastFeeType = "Fast"
@ -587,6 +611,9 @@ class SwapTokenScreenTest : BaseTestCase() {
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Swipe up") {
swipeVertical(SwipeDirection.UP)
}
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
@ -603,6 +630,9 @@ class SwapTokenScreenTest : BaseTestCase() {
textInput.performTextReplacement(inputAmount)
}
}
step("Choose receive token") {
chooseReceiveToken(receiveTokenName)
}
step("Select '$marketFeeType' fee type") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
selectFeeType(feeType = FeeType.Market, feeAmount)
@ -633,6 +663,7 @@ class SwapTokenScreenTest : BaseTestCase() {
@Test
fun unableToCoverFastFeeTest() {
val tokenName = "POL (ex-MATIC)"
val receiveTokenName = "Ethereum"
val inputAmount = "3000"
val fastFeeType = "Fast"
val fastFeeAmount = "$2,"
@ -660,6 +691,9 @@ class SwapTokenScreenTest : BaseTestCase() {
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Swipe up") {
swipeVertical(SwipeDirection.UP)
}
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
@ -676,6 +710,9 @@ class SwapTokenScreenTest : BaseTestCase() {
textInput.performTextReplacement(inputAmount)
}
}
step("Choose receive token") {
chooseReceiveToken(receiveTokenName)
}
step("Assert 'Swap' button is enabled") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSwapTokenScreen { swapButton.assertIsEnabled() }

View file

@ -15,6 +15,7 @@ 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.chooseReceiveToken
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.openSwapScreen
import com.tangem.scenarios.synchronizeAddresses
@ -36,6 +37,7 @@ class SwapTokenScreenWarningsTest : BaseTestCase() {
@Test
fun checkSwapInsufficientFundsWarningTest() {
val tokenTitle = "Polygon"
val receiveTokenTitle = "Ethereum"
val inputAmount = "1000"
setupHooks().run {
@ -65,6 +67,9 @@ class SwapTokenScreenWarningsTest : BaseTestCase() {
textInput.performTextReplacement(inputAmount)
}
}
step("Choose receive token") {
chooseReceiveToken(receiveTokenTitle)
}
step("Assert 'Insufficient funds' error is displayed") {
waitForIdle()
onSwapTokenScreen { insufficientFundsErrorTitle.assertIsDisplayed() }
@ -127,6 +132,9 @@ class SwapTokenScreenWarningsTest : BaseTestCase() {
textInput.performTextReplacement(inputAmount)
}
}
step("Choose receive token") {
chooseReceiveToken(networkName)
}
step("Check 'Unable to cover '$networkName' fee notification") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
chackUnableToCoverFeeNotification(networkName = networkName, currencySymbol = currencySymbol)
@ -143,6 +151,7 @@ class SwapTokenScreenWarningsTest : BaseTestCase() {
@Test
fun checkHighPriceImpactWarningCEXTest() {
val tokenTitle = "USDC"
val receiveTokenName = "Solana"
val inputAmount = "100"
val currencySymbol = "SOL"
val slippagePercent = "5%"
@ -202,6 +211,9 @@ class SwapTokenScreenWarningsTest : BaseTestCase() {
textInput.performTextReplacement(inputAmount)
}
}
step("Choose receive token") {
chooseReceiveToken(receiveTokenName)
}
step("Assert fiat amount with warning is displayed") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSwapTokenScreen {
@ -236,6 +248,7 @@ class SwapTokenScreenWarningsTest : BaseTestCase() {
@Test
fun checkHighPriceImpactWarningDEXTest() {
val tokenTitle = "Polygon"
val receiveTokenName = "Ethereum"
val inputAmount = "1000"
val slippagePercent = "3.5%"
val dialogTitle = getResourceString(R.string.swapping_alert_title)
@ -287,6 +300,9 @@ class SwapTokenScreenWarningsTest : BaseTestCase() {
textInput.performTextReplacement(inputAmount)
}
}
step("Choose receive token") {
chooseReceiveToken(receiveTokenName)
}
step("Assert fiat amount with warning is displayed") {
onSwapTokenScreen { receiveFiatAmount.assertTextContains("%", substring = true) }
}
@ -323,6 +339,7 @@ class SwapTokenScreenWarningsTest : BaseTestCase() {
@Test
fun solanaRemainingBalanceEqualToZeroWarningTest() {
val tokenTitle = "Solana"
val receiveTokenName = "USDC"
val inputAmount = "0.00168933"
val tokensScenarioState = "SolanaUSDC"
val rentAmount = "SOL 0.00089088"
@ -371,6 +388,9 @@ class SwapTokenScreenWarningsTest : BaseTestCase() {
textInput.performTextReplacement(inputAmount)
}
}
step("Choose receive token") {
chooseReceiveToken(receiveTokenName)
}
step("Assert 'Invalid amount' warning is not displayed") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
checkSwapWarning(
@ -388,6 +408,7 @@ class SwapTokenScreenWarningsTest : BaseTestCase() {
@Test
fun solanaRemainingBalanceEqualToRentAmountTest() {
val tokenTitle = "Solana"
val receiveTokenName = "USDC"
val inputAmount = "0.001689338"
val tokensScenarioState = "SolanaUSDC"
val rentAmount = "SOL 0.00089088"
@ -436,6 +457,9 @@ class SwapTokenScreenWarningsTest : BaseTestCase() {
textInput.performTextReplacement(inputAmount)
}
}
step("Choose receive token") {
chooseReceiveToken(receiveTokenName)
}
step("Assert 'Invalid amount' warning is not displayed") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
checkSwapWarning(
@ -454,6 +478,7 @@ class SwapTokenScreenWarningsTest : BaseTestCase() {
@Test
fun solanaRemainingBalanceMoreThanRentAmountTest() {
val tokenTitle = "Solana"
val receiveTokenName = "USDC"
val inputAmount = "0.0000941"
val tokensScenarioState = "SolanaUSDC"
val rentAmount = "SOL 0.00089088"
@ -502,6 +527,9 @@ class SwapTokenScreenWarningsTest : BaseTestCase() {
textInput.performTextReplacement(inputAmount)
}
}
step("Choose receive token") {
chooseReceiveToken(receiveTokenName)
}
step("Assert 'Invalid amount' warning is not displayed") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
checkSwapWarning(
@ -519,6 +547,7 @@ class SwapTokenScreenWarningsTest : BaseTestCase() {
@Test
fun solanaRemainingBalanceLessThanRentAmountTest() {
val tokenTitle = "Solana"
val receiveTokenName = "USDC"
val inputAmount = "0.0016941"
val tokensScenarioState = "SolanaUSDC"
val rentAmount = "SOL 0.00089088"
@ -567,6 +596,9 @@ class SwapTokenScreenWarningsTest : BaseTestCase() {
textInput.performTextReplacement(inputAmount)
}
}
step("Choose receive token") {
chooseReceiveToken(receiveTokenName)
}
step("Assert 'Invalid amount' warning is displayed") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
checkSwapWarning(

View file

@ -27,6 +27,7 @@ object MockProvider {
"Wallet 2 (No Backup)" to Wallet2NoBackupMockContent,
"Wallet 2 (No Backup, No Wallets)" to Wallet2NoBackupNoWalletsMockContent,
"Wallet 2 (Seed Phrase)" to Wallet2WithSeedPhraseMockContent,
"Wallet 2 (With derivations)" to Wallet2WithDerivationsMockContent,
"Shiba" to ShibaMockContent,
"Shiba (No Backup)" to ShibaNoBackupMockContent,
"Shiba (No Backup, No Wallets)" to ShibaNoBackupNoWalletsMockContent,

View file

@ -0,0 +1,59 @@
package com.tangem.tap.domain.sdk.mocks.content
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.tap.domain.sdk.mocks.MockContent
/**
* Mock content for UI tests that need the Wallet 2 derivation style (V3) AND the full set of derivations
* provided by [WalletMockContent].
*
* Why it exists:
* - [WalletMockContent] is Wallet 1 (derivation style V2), so for Bitcoin the card's default path resolves to
* `m/44'/0'/0'/0/0`. WireMock `/user-tokens` stubs send `m/84'/0'/0'/0/0` the path does not match the card default
* `Network.DerivationPath.Custom` `CryptoCurrency.isCustom == true`. That hides the swap-to-another-token button
* introduced in the Send flow and causes the Swap receive card to stay in Empty/Loading state.
* - [Wallet2MockContent] is V3 (matches WireMock) but its `derivationTaskResponse` is keyed by the wrong wallet
* public key (inherited from [WalletMockContent]) and only contains a handful of derivation paths. As a result
* address synchronisation fails in tests that need more than BTC/ETH/BCH/DOGE.
*
* This mock combines both: Wallet 2 card DTO from [Wallet2MockContent] (V3 derivation style) + full derivation
* entries from [WalletMockContent] re-keyed to [Wallet2MockContent]'s own wallet public keys.
*/
object Wallet2WithDerivationsMockContent : MockContent by Wallet2MockContent {
private val secp256k1Pubkey: ByteArray =
Wallet2MockContent.cardDto.wallets.first { it.curve == EllipticCurve.Secp256k1 }.publicKey
private val ed25519Pubkey: ByteArray =
Wallet2MockContent.cardDto.wallets.first { it.curve == EllipticCurve.Ed25519 }.publicKey
override val derivationTaskResponse: DerivationTaskResponse = DerivationTaskResponse(
entries = rekey(WalletMockContent.derivationTaskResponse.entries),
)
override val createProductWalletTaskResponse: CreateProductWalletTaskResponse =
CreateProductWalletTaskResponse(
card = Wallet2MockContent.cardDto,
derivedKeys = rekey(WalletMockContent.createProductWalletTaskResponse.derivedKeys),
primaryCard = Wallet2MockContent.createProductWalletTaskResponse.primaryCard,
)
/**
* Takes an entry map keyed by [WalletMockContent]'s wallet public keys (Secp256k1 first, Ed25519 second in
* insertion order) and re-keys it to [Wallet2MockContent]'s own wallet public keys so that
* [com.tangem.data.wallets.derivations.DerivationsSource] lookups by card wallet pubkey succeed.
*/
private fun rekey(
sourceEntries: Map<ByteArrayKey, ExtendedPublicKeysMap>,
): Map<ByteArrayKey, ExtendedPublicKeysMap> {
val values = sourceEntries.values.toList()
return buildMap {
values.getOrNull(index = 0)?.let { put(ByteArrayKey(secp256k1Pubkey), it) }
values.getOrNull(index = 1)?.let { put(ByteArrayKey(ed25519Pubkey), it) }
}
}
}