diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/SendScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/SendScenarios.kt index 343231d121..5277c76ba8 100644 --- a/app/src/androidTest/kotlin/com/tangem/scenarios/SendScenarios.kt +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/SendScenarios.kt @@ -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() diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/SwapScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/SwapScenarios.kt index 358b29b5b3..43fdc8264d 100644 --- a/app/src/androidTest/kotlin/com/tangem/scenarios/SwapScenarios.kt +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/SwapScenarios.kt @@ -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() diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SwapChooseTokenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SwapChooseTokenPageObject.kt deleted file mode 100644 index 77b3e18ad1..0000000000 --- a/app/src/androidTest/kotlin/com/tangem/screens/SwapChooseTokenPageObject.kt +++ /dev/null @@ -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(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) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SwapSelectTokenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SwapSelectTokenPageObject.kt index b07573e5da..56075f5436 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/SwapSelectTokenPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/SwapSelectTokenPageObject.kt @@ -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 } diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt index e313cac282..73c09bf564 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt @@ -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) = diff --git a/app/src/androidTest/kotlin/com/tangem/tests/send/sendViaSwap/SendViaSwapTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/send/sendViaSwap/SendViaSwapTest.kt index 1280e102da..81e533d007 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/send/sendViaSwap/SendViaSwapTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/send/sendViaSwap/SendViaSwapTest.kt @@ -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) } diff --git a/app/src/androidTest/kotlin/com/tangem/tests/swap/SearchAndSwapTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/swap/SearchAndSwapTest.kt index e1e79b96ad..5dba4d5d87 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/swap/SearchAndSwapTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/swap/SearchAndSwapTest.kt @@ -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") { diff --git a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapChooseTokenScreenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapChooseTokenScreenTest.kt index f84fdea929..def2f12334 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapChooseTokenScreenTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapChooseTokenScreenTest.kt @@ -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() } diff --git a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapSelectTokenScreenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapSelectTokenScreenTest.kt index e474212036..cc654f9b2c 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapSelectTokenScreenTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapSelectTokenScreenTest.kt @@ -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() } } 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 22046cdc8a..0336d3895d 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenTest.kt @@ -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() } 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 e61314adb3..3b220ce5d5 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenWarningsTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenWarningsTest.kt @@ -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( diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt index 59a5811c90..2e8dcfbea1 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2WithDerivationsMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2WithDerivationsMockContent.kt new file mode 100644 index 0000000000..bc976b765a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2WithDerivationsMockContent.kt @@ -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, + ): Map { + 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) } + } + } +} \ No newline at end of file