Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-29 09:42:11 +02:00
parent 3472f48de7
commit 9b6ed88d78
8 changed files with 581 additions and 1 deletions

View file

@ -0,0 +1,156 @@
package com.tangem.scenarios
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasAnyDescendant
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performSemanticsAction
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeLeft
import androidx.compose.ui.test.swipeRight
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_VERY_LONG
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.isDisplayedSafely
import com.tangem.core.ui.test.BuyTokenScreenTestTags
import com.tangem.core.ui.test.DetailsScreenTestTags
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.screens.*
import com.tangem.tap.domain.sdk.mocks.MockContent
import com.tangem.tap.domain.sdk.mocks.MockProvider
import io.qameta.allure.kotlin.Allure.step
// The 'Add Wallet' button sits in a nested LazyColumn and rejects touch injection, so invoke OnClick directly.
private fun BaseTestCase.clickViaSemantics(matcher: SemanticsMatcher, useUnmergedTree: Boolean = false) {
composeTestRule.onNode(matcher, useUnmergedTree).performSemanticsAction(SemanticsActions.OnClick)
}
// Both pager pages stay mounted; the off-screen one ignores swipes, so always swipe the on-screen wallet card.
private fun BaseTestCase.swipeDisplayedWallet(toPrevious: Boolean) {
val nodes = composeTestRule.onAllNodes(hasTestTag(MainScreenTestTags.WALLET_LIST_ITEM))
for (i in 0 until nodes.fetchSemanticsNodes().size) {
val swiped = runCatching {
nodes[i].assertIsDisplayed()
nodes[i].performTouchInput { if (toPrevious) swipeRight() else swipeLeft() }
}.isSuccess
if (swiped) break
}
waitForIdle()
}
/**
* Adds a second hardware (card) wallet from a wallet-restricted state (a hot wallet already exists):
* 'Add Wallet' scans a card immediately there is no wallet-type chooser so the scan mock must be
* set before the click. After saving, the app returns to Main; the added card lands as an off-screen
* pager page, so this swipes to it and syncs its missing addresses before returning.
*/
fun BaseTestCase.addNewCardWallet(mockContent: MockContent) {
step("Click 'More' button on TopBar") {
onMainScreenTopBar { moreButton.clickWithAssertion() }
}
MockProvider.setMocks(mockContent)
step("Click on 'Add Wallet' button (scans a new hardware wallet)") {
clickViaSemantics(hasTestTag(DetailsScreenTestTags.ADD_WALLET_BUTTON))
}
// Details pops back to Main only after the freshly scanned wallet finishes its initial load over many (some failing) RPCs.
// Gate on the top-bar More button, not the screen container — the bottom Markets sheet can leave the container un-"displayed".
step("Assert 'Main' screen is displayed with the new wallet") {
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_VERY_LONG) {
runCatching { onMainScreenTopBar { moreButton.assertIsDisplayed() } }.isSuccess
}
}
// The added card is the newest pager page; its "Synchronize addresses" prompt is off-screen until swiped to.
step("Synchronize the new card wallet's addresses") {
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) {
var shown = false
onMainScreen { shown = synchronizeAddressesButton.isDisplayedSafely() }
if (!shown) swipeDisplayedWallet(toPrevious = false)
shown
}
// Let the pager fling settle before clicking — the wait above releases as soon as the prompt has bounds, and a
// click landing mid-animation is eaten by the button's clickableSingle debounce, so the prompt never clears.
waitForIdle()
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
// The prompt clears once the card's addresses are derived (re-scan + reload over many, some failing, RPCs).
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_VERY_LONG) {
var generated = false
onMainScreen { generated = !synchronizeAddressesButton.isDisplayedSafely() }
generated
}
}
}
/** Taps the on-screen token row by [tokenName]; the wallet pager keeps both pages mounted, so click the displayed copy. */
fun BaseTestCase.clickDisplayedTokenOnMain(tokenName: String) {
val matcher = hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM) and hasAnyDescendant(hasText(tokenName))
step("Click on token '$tokenName' on the visible wallet") {
// Both pager pages mount the same token; click the one currently on-screen once the swipe has settled.
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) {
val nodes = composeTestRule.onAllNodes(matcher, useUnmergedTree = true)
(0 until nodes.fetchSemanticsNodes().size).any { i ->
runCatching {
nodes[i].assertIsDisplayed()
nodes[i].performClick()
}.isSuccess
}
}
}
}
/** Swipes the wallet card to the previous wallet (the one added before the current page). */
fun BaseTestCase.switchToPreviousWallet() {
step("Swipe wallet card to the previous wallet") {
swipeDisplayedWallet(toPrevious = true)
}
}
/** Opens the receive selector and picks identical [token] located on a different wallet [walletName] via its wallet tab. */
fun BaseTestCase.selectReceiveTokenOnWallet(token: String, walletName: String) {
step("Click on 'Choose token' button") {
onSwapTokenScreen { chooseTokenButton.performClick() }
}
// The wallet tab is a custom clickable Row that Kakao's performClick can't hit (autoscroll fails); invoke OnClick directly.
step("Select wallet tab '$walletName'") {
clickViaSemantics(
hasTestTag(BuyTokenScreenTestTags.WALLET_TAB) and hasAnyDescendant(hasText(walletName)),
useUnmergedTree = true,
)
}
step("Click on token with name '$token'") {
clickViaSemantics(
hasTestTag(BuyTokenScreenTestTags.LAZY_LIST_ITEM) and hasAnyDescendant(hasText(token)),
useUnmergedTree = true,
)
}
}
/** Searches the receive selector for [token], adds it to recipient wallet [recipientWalletName] via the market result. */
fun BaseTestCase.addMissingReceiveTokenToWallet(token: String, recipientWalletName: String) {
step("Click on 'Choose token' button") {
onSwapTokenScreen { chooseTokenButton.performClick() }
}
step("Type '$token' in search field") {
onSwapSelectTokenScreen {
searchBarBlock.performClick()
searchBarBlock.performTextInput(token)
}
}
step("Click on market token '$token'") {
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) {
runCatching { onSwapSelectTokenScreen { marketsTokenWithName(token).performClick() } }.isSuccess
}
}
// The 'Add token' sheet pre-selects the recipient (the only wallet missing the token, since the source already holds it).
step("Assert recipient wallet '$recipientWalletName' is selected") {
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) {
runCatching { onAddToPortfolioScreen { walletName(recipientWalletName).assertIsDisplayed() } }.isSuccess
}
}
step("Click on 'Add' button") {
onAddToPortfolioScreen { addButton.performClick() }
}
}

View file

@ -0,0 +1,27 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import androidx.compose.ui.test.hasClickAction
import com.tangem.common.BaseTestCase
import com.tangem.wallet.R
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
class AddToPortfolioPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<AddToPortfolioPageObject>(semanticsProvider = semanticsProvider) {
fun walletName(walletName: String): KNode = child {
hasText(walletName)
useUnmergedTree = true
}
val addButton: KNode = child {
hasText(getResourceString(R.string.common_add))
hasClickAction()
}
}
internal fun BaseTestCase.onAddToPortfolioScreen(function: AddToPortfolioPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -5,14 +5,18 @@ import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.extractText
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
import com.tangem.common.constants.TestConstants.SVS_SEED_PHRASE_12
import com.tangem.common.constants.TestConstants.TANGEM_PAY_ELIGIBILITY_SCENARIO
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_VERY_LONG
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.core.ui.R as CoreUiR
import com.tangem.scenarios.*
import com.tangem.screens.*
import com.tangem.screens.tangempay.*
import com.tangem.tap.domain.sdk.mocks.content.Wallet2WithDerivationsMockContent
import com.tangem.tap.domain.sdk.mocks.content.WalletMockContent
import dagger.hilt.android.testing.HiltAndroidTest
import io.github.kakaocup.kakao.common.utilities.getResourceString
import io.qameta.allure.kotlin.Allure.step
@ -808,6 +812,334 @@ class AppTransfersTest : BaseTestCase() {
}
}
@AllureId("10005")
@DisplayName("App transfers: Tron network fee")
@Test
fun tronNetworkFeeTest() {
val token = "Tron"
val amount = "0.001"
val userTokensState = "TwoAccountsSameTron"
val networksProvidersScenario = "networks_providers"
val appTransfersNetworksState = "AppTransfersNetworks"
setupHooks(
additionalBeforeAppLaunchSection = {
setWireMockScenarioState(storiesScenario, storiesErrorState)
// networks_providers configures SDK RPC hosts at launch — must be set before the activity starts.
setWireMockScenarioState(networksProvidersScenario, appTransfersNetworksState)
},
additionalAfterSection = {
resetWireMockScenarioState(storiesScenario)
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(networksProvidersScenario)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
}
).run {
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
}
// Non-zero prices keep total fiat > 0 so the empty-wallet banner doesn't push the account list under the Markets sheet.
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$appTransfersNetworksState'") {
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = appTransfersNetworksState)
}
step("Open Swap in Transfer mode for '$token'") { openSwapInTransferMode(token) }
step("Enter amount '$amount'") { inputAmount(amount) }
step("Assert Transfer mode is ready") { assertTransferReady() }
step("Assert network fee is displayed") { waitForFeeDisplayed() }
}
}
// [REDACTED_TASK_KEY]: blockchain SDK TonProvidersBuilder drops public providers, so TON has no provider in the mocked build.
@Ignore("[REDACTED_JIRA]")
@AllureId("10012")
@DisplayName("App transfers: TON network fee")
@Test
fun tonNetworkFeeTest() {
// The SDK names TON's coin "Gram" (Blockchain.TON.getCoinName), so the portfolio row shows "Gram", not "Toncoin".
val token = "Gram"
val amount = "0.001"
val userTokensState = "TwoAccountsSameTON"
val networksProvidersScenario = "networks_providers"
val appTransfersNetworksState = "AppTransfersNetworks"
setupHooks(
additionalBeforeAppLaunchSection = {
setWireMockScenarioState(storiesScenario, storiesErrorState)
// networks_providers configures SDK RPC hosts at launch — must be set before the activity starts.
setWireMockScenarioState(networksProvidersScenario, appTransfersNetworksState)
},
additionalAfterSection = {
resetWireMockScenarioState(storiesScenario)
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(networksProvidersScenario)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
}
).run {
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
}
// Non-zero prices keep total fiat > 0 so the empty-wallet banner doesn't push the account list under the Markets sheet.
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$appTransfersNetworksState'") {
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = appTransfersNetworksState)
}
step("Open Swap in Transfer mode for '$token'") { openSwapInTransferMode(token) }
step("Enter amount '$amount'") { inputAmount(amount) }
step("Assert Transfer mode is ready") { assertTransferReady() }
step("Assert network fee is displayed") { waitForFeeDisplayed() }
}
}
@AllureId("10013")
@DisplayName("App transfers: Cosmos network fee")
@Test
fun cosmosNetworkFeeTest() {
val token = "Cosmos"
val amount = "0.001"
val userTokensState = "TwoAccountsSameCosmos"
val networksProvidersScenario = "networks_providers"
val appTransfersNetworksState = "AppTransfersNetworks"
setupHooks(
additionalBeforeAppLaunchSection = {
setWireMockScenarioState(storiesScenario, storiesErrorState)
// networks_providers configures SDK RPC hosts at launch — must be set before the activity starts.
setWireMockScenarioState(networksProvidersScenario, appTransfersNetworksState)
},
additionalAfterSection = {
resetWireMockScenarioState(storiesScenario)
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(networksProvidersScenario)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
}
).run {
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
}
// Non-zero prices keep total fiat > 0 so the empty-wallet banner doesn't push the account list under the Markets sheet.
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$appTransfersNetworksState'") {
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = appTransfersNetworksState)
}
step("Open Swap in Transfer mode for '$token'") { openSwapInTransferMode(token) }
step("Enter amount '$amount'") { inputAmount(amount) }
step("Assert Transfer mode is ready") { assertTransferReady() }
step("Assert network fee is displayed") { waitForFeeDisplayed() }
}
}
@AllureId("10015")
@DisplayName("App transfers: Aptos network fee")
@Test
fun aptosNetworkFeeTest() {
val token = "Aptos"
val amount = "0.001"
val userTokensState = "TwoAccountsSameAptos"
val networksProvidersScenario = "networks_providers"
val appTransfersNetworksState = "AppTransfersNetworks"
setupHooks(
additionalBeforeAppLaunchSection = {
setWireMockScenarioState(storiesScenario, storiesErrorState)
// networks_providers configures SDK RPC hosts at launch — must be set before the activity starts.
setWireMockScenarioState(networksProvidersScenario, appTransfersNetworksState)
},
additionalAfterSection = {
resetWireMockScenarioState(storiesScenario)
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(networksProvidersScenario)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
}
).run {
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
}
// Non-zero prices keep total fiat > 0 so the empty-wallet banner doesn't push the account list under the Markets sheet.
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$appTransfersNetworksState'") {
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = appTransfersNetworksState)
}
step("Open Swap in Transfer mode for '$token'") { openSwapInTransferMode(token) }
step("Enter amount '$amount'") { inputAmount(amount) }
step("Assert Transfer mode is ready") { assertTransferReady() }
step("Assert network fee is displayed") { waitForFeeDisplayed() }
}
}
@AllureId("9856")
@DisplayName("App transfers: Transfer mode is available from a Tangem Pay account")
@Test
fun transferModeAvailableFromTangemPayAccountTest() {
val token = "USDC"
val receiveAccountName = "Main account"
val eligibilityState = "PaeraCustomer"
val balanceScenario = "tangem_pay_balance_update"
val balanceInitialState = "InitialBalance"
val historyScenario = "tangem_pay_transaction_history"
val historyInitialState = "InitialEmpty"
val userTokensState = "TangemPayTransferUsdc"
setupHooks(
additionalBeforeAppLaunchSection = { setWireMockScenarioState(storiesScenario, storiesErrorState) },
additionalAfterSection = {
resetWireMockScenarioState(storiesScenario)
resetWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO)
resetWireMockScenarioState(balanceScenario)
resetWireMockScenarioState(historyScenario)
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
}
).run {
step("Set WireMock scenario: '$TANGEM_PAY_ELIGIBILITY_SCENARIO' to state: '$eligibilityState'") {
setWireMockScenarioState(scenarioName = TANGEM_PAY_ELIGIBILITY_SCENARIO, state = eligibilityState)
}
step("Set WireMock scenario: '$balanceScenario' to state: '$balanceInitialState'") {
setWireMockScenarioState(scenarioName = balanceScenario, state = balanceInitialState)
}
step("Set WireMock scenario: '$historyScenario' to state: '$historyInitialState'") {
setWireMockScenarioState(scenarioName = historyScenario, state = historyInitialState)
}
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
}
step("Open Tangem Pay") { openTangemPay() }
step("Click on 'Withdraw' button") {
onTangemPayMainScreen { withdrawButton.clickWithAssertion() }
}
step("Acknowledge withdrawal note sheet") {
onTangemPayWithdrawNoteSheet {
title.assertIsDisplayed()
gotItButton.clickWithAssertion()
}
}
step("Choose identical receive token '$token' from '$receiveAccountName'") {
chooseIdenticalReceiveToken(tokenName = token, receiveAccountName = receiveAccountName)
}
// Assert the top-bar 'Transfer' title (unambiguous transfer-mode signal) + no provider block; the
// withdraw-entry swap keeps recalculating, so use flakySafely rather than assertTransferReady's waitUntil.
step("Assert Transfer mode is ready") {
flakySafely(WAIT_UNTIL_TIMEOUT_VERY_LONG) {
onSwapTokenScreen { transferTitle.assertIsDisplayed() }
}
onSwapTokenScreen { providersBlock.assertIsNotDisplayed() }
}
}
}
@AllureId("9995")
@DisplayName("App transfers: transfer between different wallets reaches 'Transfer in progress' screen")
@Test
fun transferBetweenDifferentWalletsReachesFinishTest() {
val token = "Ethereum"
val amount = "0.001"
val secondWalletName = "Wallet 2"
val userTokensState = "EthereumWithSecondToken"
setupHooks(
additionalBeforeAppLaunchSection = { setWireMockScenarioState(storiesScenario, storiesErrorState) },
additionalAfterSection = {
resetWireMockScenarioState(storiesScenario)
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(ethCallScenario)
resetWireMockScenarioState(ethBalanceScenario)
}
).run {
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
}
step("Set WireMock scenario: '$ethCallScenario' to state: '$started'") {
setWireMockScenarioState(scenarioName = ethCallScenario, state = started)
}
step("Set WireMock scenario: '$ethBalanceScenario' to state: '$started'") {
setWireMockScenarioState(scenarioName = ethBalanceScenario, state = started)
}
step("Open 'Main' screen with existing hot wallet") {
openMainScreenWithExistingHotWallet(SVS_SEED_PHRASE_12)
}
step("Generate missing addresses") { generateMissingHotWalletAddresses() }
step("Wait for addresses to be generated") { waitForAddressesGenerated() }
step("Add a second card wallet '$secondWalletName'") {
addNewCardWallet(WalletMockContent)
}
step("Switch back to the hot wallet") { switchToPreviousWallet() }
step("Click on token with name: '$token'") { clickDisplayedTokenOnMain(token) }
step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.TokenDetails, storiesExist = false)
}
step("Select identical receive token '$token' on '$secondWalletName'") {
selectReceiveTokenOnWallet(token = token, walletName = secondWalletName)
}
step("Enter amount '$amount'") { inputAmount(amount) }
step("Assert Transfer mode is ready") { assertTransferReady() }
step("Assert network fee is displayed") { waitForFeeDisplayed() }
step("Hold to confirm the transfer") { holdToConfirmTransfer() }
step("Assert 'Transfer in progress' screen is displayed") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSwapSuccessScreen { transferInProgressTitle.assertIsDisplayed() }
}
}
}
}
@AllureId("9996")
@DisplayName("App transfers: adding a missing token to the recipient wallet enables Transfer")
@Test
fun addMissingTokenToRecipientWalletEnablesTransferTest() {
val token = "Ethereum"
val bitcoinToken = "Bitcoin"
val recipientWalletName = "Wallet"
val recipientWithoutEthereumState = "RecipientWithoutEthereum"
val ethereumWithSecondTokenState = "EthereumWithSecondToken"
setupHooks(
additionalBeforeAppLaunchSection = { setWireMockScenarioState(storiesScenario, storiesErrorState) },
additionalAfterSection = {
resetWireMockScenarioState(storiesScenario)
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(ethCallScenario)
resetWireMockScenarioState(ethBalanceScenario)
}
).run {
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$recipientWithoutEthereumState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = recipientWithoutEthereumState)
}
step("Set WireMock scenario: '$ethCallScenario' to state: '$started'") {
setWireMockScenarioState(scenarioName = ethCallScenario, state = started)
}
step("Set WireMock scenario: '$ethBalanceScenario' to state: '$started'") {
setWireMockScenarioState(scenarioName = ethBalanceScenario, state = started)
}
step("Open 'Main' screen with existing hot wallet") {
openMainScreenWithExistingHotWallet(SVS_SEED_PHRASE_12)
}
step("Generate missing addresses") { generateMissingHotWalletAddresses() }
step("Wait for addresses to be generated") { waitForAddressesGenerated() }
step("Assert token '$bitcoinToken' is displayed") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onMainScreen { tokenWithTitleAndAddress(bitcoinToken).assertIsDisplayed() }
}
}
// Switch the user-tokens mock so the second wallet loads with Ethereum while the recipient stays Ethereum-less.
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$ethereumWithSecondTokenState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = ethereumWithSecondTokenState)
}
step("Add a second card wallet") {
addNewCardWallet(WalletMockContent)
}
step("Click on token with name: '$token'") { clickDisplayedTokenOnMain(token) }
step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.TokenDetails, storiesExist = false)
}
step("Add missing token '$token' to recipient wallet '$recipientWalletName'") {
addMissingReceiveTokenToWallet(token = token, recipientWalletName = recipientWalletName)
}
step("Assert Transfer mode is ready") { assertTransferReady() }
}
}
// [REDACTED_TASK_KEY]: transfer mode never runs tx validation, so the destination rent-exemption notification never shows.
@Ignore("[REDACTED_JIRA]")
@AllureId("9852")