Updated on 2026-08-14
This commit is contained in:
parent
8b677a8e6b
commit
c176dc37c4
13 changed files with 362 additions and 19 deletions
|
|
@ -4,6 +4,8 @@ import android.content.ClipData
|
|||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
|
||||
fun getClipboardText(context: Context): String? {
|
||||
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
|
|
@ -27,12 +29,15 @@ fun clearClipboard(
|
|||
clipboard.clearPrimaryClip()
|
||||
}
|
||||
|
||||
// Kotlin's assert() is a no-op on device (JVM assertions disabled) — use JUnit asserts here.
|
||||
fun assertClipboardTextEquals(
|
||||
expected: String,
|
||||
context: Context = ApplicationProvider.getApplicationContext()
|
||||
) {
|
||||
val actual = getClipboardText(context)
|
||||
assert(actual == expected) {
|
||||
"Clipboard text mismatch.\nExpected: '$expected'\nActual: '$actual'"
|
||||
}
|
||||
assertEquals("Clipboard text mismatch", expected, getClipboardText(context))
|
||||
}
|
||||
|
||||
fun assertClipboardIsEmpty(context: Context = ApplicationProvider.getApplicationContext()) {
|
||||
val actual = getClipboardText(context)
|
||||
assertTrue("Expected empty clipboard but was: '$actual'", actual.isNullOrEmpty())
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.common.BaseTestCase
|
|||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.extensions.performTextInputInChunks
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.screens.accounts.onAccountDetailsScreen
|
||||
import com.tangem.screens.onAddCustomTokenScreen
|
||||
import com.tangem.screens.onDetailsScreen
|
||||
|
|
@ -12,6 +13,7 @@ import com.tangem.screens.onManageTokensScreen
|
|||
import com.tangem.screens.onWalletSettingsScreen
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
|
||||
private fun mainAccountName(): String = getResourceString(CoreResR.string.account_main_account_title)
|
||||
|
|
@ -66,7 +68,19 @@ fun BaseTestCase.addCustomTokenWithCustomDerivation(network: String, contract: S
|
|||
navigateBackToMainFromManageTokens()
|
||||
}
|
||||
|
||||
fun BaseTestCase.navigateBackToMainFromManageTokens() {
|
||||
private const val MAX_BACKS_TO_MANAGE_TOKENS = 5
|
||||
|
||||
// The device-backs to close the sheet vary — press back until Manage Tokens is shown, then fail loudly.
|
||||
private fun BaseTestCase.closeAddCustomTokenSheet() {
|
||||
repeat(MAX_BACKS_TO_MANAGE_TOKENS) {
|
||||
if (runCatching { onManageTokensScreen { searchField.assertIsDisplayed() } }.isSuccess) return
|
||||
device.uiDevice.pressBack()
|
||||
waitForIdle()
|
||||
}
|
||||
onManageTokensScreen { searchField.assertIsDisplayed() }
|
||||
}
|
||||
|
||||
private fun BaseTestCase.navigateBackToWalletSettings() {
|
||||
step("Click on 'Manage tokens' screen 'Back' button") {
|
||||
waitForIdle()
|
||||
onManageTokensScreen { topAppBarBackButton.performClick() }
|
||||
|
|
@ -75,6 +89,60 @@ fun BaseTestCase.navigateBackToMainFromManageTokens() {
|
|||
waitForIdle()
|
||||
onAccountDetailsScreen { topAppBarBackButton.performClick() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.forgetCurrentWalletAndReArmForNextScan() {
|
||||
step("Close 'Add custom token' bottom sheet") { closeAddCustomTokenSheet() }
|
||||
navigateBackToWalletSettings()
|
||||
step("Click on 'Forget wallet' button") {
|
||||
waitForIdle()
|
||||
onWalletSettingsScreen {
|
||||
scrollToForgetWallet()
|
||||
forgetWalletButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Confirm forgetting the wallet") {
|
||||
awaitSuccess { onDialog { forgetButton.assertIsDisplayed() } }
|
||||
onDialog { forgetButton.clickWithAssertion() }
|
||||
}
|
||||
// Markets tooltip is a one-time app-level flag; re-arm it so the next scan's openMainScreen can dismiss it.
|
||||
step("Re-arm the markets tooltip for the next scan") {
|
||||
runBlocking {
|
||||
appPreferencesStore.editData { it.set(PreferencesKeys.SHOULD_SHOW_MARKETS_TOOLTIP_KEY, value = true) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.assertDerivationPathsInSelector(networkToOpen: String, vararg expectedPaths: Pair<String, String>) {
|
||||
step("Click on network: '$networkToOpen'") {
|
||||
awaitSuccess { onAddCustomTokenScreen { scrollToNetwork(networkToOpen) } }
|
||||
onAddCustomTokenScreen { networkRow(networkToOpen).performClick() }
|
||||
}
|
||||
step("Click on 'Derivation path' field") {
|
||||
awaitSuccess { onAddCustomTokenScreen { derivationSelectorField.assertExists() } }
|
||||
onAddCustomTokenScreen { derivationSelectorField.performClick() }
|
||||
}
|
||||
expectedPaths.forEach { (networkId, path) ->
|
||||
step("Assert '$networkId' derivation path is '$path'") {
|
||||
awaitSuccess { onAddCustomTokenScreen { scrollToDerivationRow(networkId) } }
|
||||
onAddCustomTokenScreen { derivationPath(networkId, path).assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.toggleTokenNetworkInManageTokens(tokenTitle: String, networkTitle: String) {
|
||||
step("Click on token: '$tokenTitle'") {
|
||||
awaitSuccess { onManageTokensScreen { tokenItem(tokenTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen { tokenItem(tokenTitle).performClick() }
|
||||
}
|
||||
step("Click on '$networkTitle' switch") {
|
||||
awaitSuccess { onManageTokensScreen { networkSwitch(networkTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen { networkSwitch(networkTitle).performClick() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.navigateBackToMainFromManageTokens() {
|
||||
navigateBackToWalletSettings()
|
||||
step("Click on 'Wallet settings' screen 'Back' button") {
|
||||
waitForIdle()
|
||||
onWalletSettingsScreen { topAppBarBackButton.performClick() }
|
||||
|
|
|
|||
|
|
@ -11,12 +11,13 @@ import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onCompose
|
|||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||
import androidx.compose.ui.test.hasText as withText
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
|
||||
class AddCustomTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<AddCustomTokenPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
private val selectorList: KNode = child {
|
||||
val selectorList: KNode = child {
|
||||
hasTestTag(AddCustomTokenScreenTestTags.SELECTOR_LIST)
|
||||
}
|
||||
|
||||
|
|
@ -29,6 +30,17 @@ class AddCustomTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvi
|
|||
performScrollToNode(withTestTag(AddCustomTokenScreenTestTags.networkRow(networkName)))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun scrollToDerivationRow(networkId: String) = selectorList {
|
||||
performScrollToNode(withTestTag(AddCustomTokenScreenTestTags.derivationRow(networkId)))
|
||||
}
|
||||
|
||||
fun derivationPath(networkId: String, path: String): KNode = child {
|
||||
useUnmergedTree = true
|
||||
hasTestTag(AddCustomTokenScreenTestTags.derivationRow(networkId))
|
||||
hasAnyDescendant(withText(path))
|
||||
}
|
||||
|
||||
val contractAddressField: KNode = child {
|
||||
hasTestTag(AddCustomTokenScreenTestTags.CONTRACT_ADDRESS_FIELD)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.screens
|
|||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.BaseDialogTestTags
|
||||
|
|
@ -94,6 +95,11 @@ class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
hasText(getResourceString(R.string.common_ok))
|
||||
}
|
||||
|
||||
val forgetButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(CoreResR.string.common_forget))
|
||||
}
|
||||
|
||||
val changeButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_change))
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import androidx.compose.ui.test.hasText as withText
|
|||
import androidx.compose.ui.test.hasAnySibling as withAnySibling
|
||||
import androidx.compose.ui.test.hasAnyDescendant as withAnyDescendant
|
||||
import androidx.compose.ui.test.hasAnyAncestor as withAnyAncestor
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
|
||||
class ManageTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<ManageTokensPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
|
@ -61,6 +62,18 @@ class ManageTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
|
|||
)
|
||||
}
|
||||
|
||||
fun networkName(networkName: String): KNode = child {
|
||||
useUnmergedTree = true
|
||||
addSemanticsMatcher(
|
||||
withTestTag(ManageTokensScreenTestTags.NETWORK_NAME)
|
||||
.and(withAnyDescendant(withText(networkName))),
|
||||
)
|
||||
}
|
||||
|
||||
val contractAddressCopiedMessage: KNode = child {
|
||||
hasText(getResourceString(CoreResR.string.contract_address_copied_message))
|
||||
}
|
||||
|
||||
fun networkSwitch(networkName: String): KNode = child {
|
||||
useUnmergedTree = true
|
||||
addSemanticsMatcher(
|
||||
|
|
|
|||
|
|
@ -6,16 +6,22 @@ import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
|
|||
import com.tangem.common.extensions.performTextInputInChunks
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.scenarios.addCustomTokenWithCustomDerivation
|
||||
import com.tangem.scenarios.assertDerivationPathsInSelector
|
||||
import com.tangem.scenarios.forgetCurrentWalletAndReArmForNextScan
|
||||
import com.tangem.scenarios.navigateBackToMainFromManageTokens
|
||||
import com.tangem.scenarios.openAddCustomToken
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.synchronizeAddresses
|
||||
import com.tangem.screens.onAddCustomTokenScreen
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.tap.domain.sdk.mocks.content.Wallet1LegacyDerivationMockContent
|
||||
import com.tangem.tap.domain.sdk.mocks.content.Wallet2NoEd25519Slip0010MockContent
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
|
|
@ -25,6 +31,8 @@ class AddCustomTokenTest : BaseTestCase() {
|
|||
private val tokenTitle = "Tether"
|
||||
private val ethereumNetwork = "Ethereum"
|
||||
private val solanaNetwork = "Solana"
|
||||
private val bitcoinNetworkId = "bitcoin"
|
||||
private val ethereumClassicNetworkId = "ethereum-classic"
|
||||
private val ethContract = "0xdac17f958d2ee523a2206206994597c13d831ec7"
|
||||
private val solanaContract = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
||||
private val customDerivationPath = "m/44'/60'/0'/0/1"
|
||||
|
|
@ -173,4 +181,71 @@ class AddCustomTokenTest : BaseTestCase() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("777")
|
||||
@DisplayName("Add custom token: network with a missing curve is not offered")
|
||||
@Test
|
||||
fun missingCurveNetworkNotOfferedTest() {
|
||||
val bitcoinNetwork = "Bitcoin"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen(mockContent = Wallet2NoEd25519Slip0010MockContent) }
|
||||
step("Open 'Add custom token' screen") { openAddCustomToken() }
|
||||
step("Assert supported network '$bitcoinNetwork' is displayed") {
|
||||
flakySafely { onAddCustomTokenScreen { scrollToNetwork(bitcoinNetwork) } }
|
||||
}
|
||||
step("Assert '$solanaNetwork' network is not displayed for the missing-curve card") {
|
||||
onAddCustomTokenScreen { selectorList.assertIsDisplayed() }
|
||||
val solanaOffered = runCatching { onAddCustomTokenScreen { scrollToNetwork(solanaNetwork) } }.isSuccess
|
||||
assertFalse("'$solanaNetwork' should not be offered for a missing-curve card", solanaOffered)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("776")
|
||||
@DisplayName("Add custom token: derivation paths match the card version (V1/V2/V3)")
|
||||
@Test
|
||||
fun derivationPathsMatchCardVersionTest() {
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Assert derivation paths for a legacy-batch V1 Wallet card") {
|
||||
openMainScreen(mockContent = Wallet1LegacyDerivationMockContent)
|
||||
openAddCustomToken()
|
||||
assertDerivationPathsInSelector(
|
||||
ethereumNetwork,
|
||||
bitcoinNetworkId to "m/44'/0'/0'/0/0",
|
||||
ethereumClassicNetworkId to "m/44'/61'/0'/0/0",
|
||||
)
|
||||
forgetCurrentWalletAndReArmForNextScan()
|
||||
}
|
||||
step("Assert derivation paths for a V2 Wallet card") {
|
||||
openMainScreen()
|
||||
openAddCustomToken()
|
||||
assertDerivationPathsInSelector(
|
||||
ethereumNetwork,
|
||||
bitcoinNetworkId to "m/44'/0'/0'/0/0",
|
||||
ethereumClassicNetworkId to "m/44'/60'/0'/0/0",
|
||||
)
|
||||
forgetCurrentWalletAndReArmForNextScan()
|
||||
}
|
||||
step("Assert derivation paths for a V3 Wallet 2 card") {
|
||||
openMainScreen(productType = ProductType.Wallet2)
|
||||
openAddCustomToken()
|
||||
assertDerivationPathsInSelector(
|
||||
ethereumNetwork,
|
||||
bitcoinNetworkId to "m/84'/0'/0'/0/0",
|
||||
ethereumClassicNetworkId to "m/44'/61'/0'/0/0",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +1,48 @@
|
|||
package com.tangem.tests.main
|
||||
|
||||
import androidx.compose.ui.test.longClick
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.COINS_API_SCENARIO
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.assertClipboardIsEmpty
|
||||
import com.tangem.common.utils.assertClipboardTextEquals
|
||||
import com.tangem.common.utils.clearClipboard
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.openManageTokens
|
||||
import com.tangem.scenarios.toggleTokenNetworkInManageTokens
|
||||
import com.tangem.screens.onDialog
|
||||
import com.tangem.screens.onManageTokensScreen
|
||||
import com.tangem.tap.domain.sdk.mocks.content.Firmware451MockContent
|
||||
import com.tangem.tap.domain.sdk.mocks.content.Wallet2NoEd25519Slip0010MockContent
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
|
||||
@HiltAndroidTest
|
||||
class ManageTokensTest : BaseTestCase() {
|
||||
|
||||
private val richState = "ManageTokensRich"
|
||||
private val solanaTokenTitle = "USD Coin"
|
||||
private val solanaNetworkTitle = "SOLANA"
|
||||
private val solanaName = "Solana"
|
||||
|
||||
@AllureId("765")
|
||||
@DisplayName("Manage tokens: network standard labels are shown for token networks")
|
||||
@Test
|
||||
fun networkStandardLabelsTest() {
|
||||
val tokenTitle = "Tether"
|
||||
val ethereum = "Ethereum"
|
||||
val bnbSmartChain = "BNB Smart Chain"
|
||||
val tron = "Tron"
|
||||
val erc20 = "ERC20"
|
||||
val bep20 = "BEP20"
|
||||
val trc20 = "TRC20"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
|
|
@ -38,21 +56,21 @@ class ManageTokensTest : BaseTestCase() {
|
|||
flakySafely { onManageTokensScreen { tokenItem(tokenTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen { tokenItem(tokenTitle).performClick() }
|
||||
}
|
||||
step("Assert 'Ethereum' network standard 'ERC20' is displayed") {
|
||||
step("Assert '$ethereum' network standard '$erc20' is displayed") {
|
||||
flakySafely {
|
||||
onManageTokensScreen { networkStandard(networkName = "Ethereum", standard = "ERC20").assertIsDisplayed() }
|
||||
onManageTokensScreen { networkStandard(networkName = ethereum, standard = erc20).assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
step("Assert 'BNB Smart Chain' network standard 'BEP20' is displayed") {
|
||||
step("Assert '$bnbSmartChain' network standard '$bep20' is displayed") {
|
||||
flakySafely {
|
||||
onManageTokensScreen {
|
||||
networkStandard(networkName = "BNB Smart Chain", standard = "BEP20").assertIsDisplayed()
|
||||
networkStandard(networkName = bnbSmartChain, standard = bep20).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Assert 'Tron' network standard 'TRC20' is displayed") {
|
||||
step("Assert '$tron' network standard '$trc20' is displayed") {
|
||||
flakySafely {
|
||||
onManageTokensScreen { networkStandard(networkName = "Tron", standard = "TRC20").assertIsDisplayed() }
|
||||
onManageTokensScreen { networkStandard(networkName = tron, standard = trc20).assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -109,8 +127,33 @@ class ManageTokensTest : BaseTestCase() {
|
|||
@DisplayName("Manage tokens: enabling Solana network on a modern card shows no warning")
|
||||
@Test
|
||||
fun solanaNetworkNoWarningTest() {
|
||||
val tokenTitle = "USD Coin"
|
||||
val networkTitle = "SOLANA"
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Open 'Manage tokens' screen") { openManageTokens() }
|
||||
step("Enable '$solanaNetworkTitle' network for token: '$solanaTokenTitle'") {
|
||||
toggleTokenNetworkInManageTokens(tokenTitle = solanaTokenTitle, networkTitle = solanaNetworkTitle)
|
||||
}
|
||||
step("Assert hide-token alert is not displayed") {
|
||||
waitForIdle()
|
||||
onDialog { dialogContainer.assertDoesNotExist() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("719")
|
||||
@DisplayName("Manage tokens: long tap on a token network copies the contract address, main network does not")
|
||||
@Test
|
||||
fun copyContractAddressOnNetworkLongTapTest() {
|
||||
val tokenTitle = "Tether"
|
||||
val tokenNetworkTitle = "ETHEREUM"
|
||||
val coinTitle = "Bitcoin"
|
||||
val coinNetworkTitle = "BITCOIN"
|
||||
val contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
|
|
@ -124,13 +167,83 @@ class ManageTokensTest : BaseTestCase() {
|
|||
flakySafely { onManageTokensScreen { tokenItem(tokenTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen { tokenItem(tokenTitle).performClick() }
|
||||
}
|
||||
step("Click on '$networkTitle' switch") {
|
||||
flakySafely { onManageTokensScreen { networkSwitch(networkTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen { networkSwitch(networkTitle).performClick() }
|
||||
step("Long click on '$tokenNetworkTitle' network row") {
|
||||
flakySafely { onManageTokensScreen { networkName(tokenNetworkTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen {
|
||||
networkName(tokenNetworkTitle).performTouchInput {
|
||||
longClick(position = center, durationMillis = 1000L)
|
||||
}
|
||||
step("Assert hide-token alert is not displayed") {
|
||||
}
|
||||
}
|
||||
step("Assert contract-address-copied message is displayed") {
|
||||
flakySafely { onManageTokensScreen { contractAddressCopiedMessage.assertIsDisplayed() } }
|
||||
}
|
||||
step("Assert clipboard contains '$tokenTitle' contract address") {
|
||||
assertClipboardTextEquals(contractAddress)
|
||||
}
|
||||
step("Clear clipboard") { clearClipboard() }
|
||||
step("Click on token: '$coinTitle'") {
|
||||
flakySafely { onManageTokensScreen { tokenItem(coinTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen { tokenItem(coinTitle).performClick() }
|
||||
}
|
||||
step("Long click on '$coinNetworkTitle' main network row") {
|
||||
flakySafely { onManageTokensScreen { networkName(coinNetworkTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen {
|
||||
networkName(coinNetworkTitle).performTouchInput {
|
||||
longClick(position = center, durationMillis = 1000L)
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Assert clipboard is empty after long click on main network") {
|
||||
waitForIdle()
|
||||
onDialog { dialogContainer.assertDoesNotExist() }
|
||||
assertClipboardIsEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("737")
|
||||
@DisplayName("Manage tokens: enabling Solana on an old-firmware card shows firmware-limitation warning")
|
||||
@Test
|
||||
fun solanaFirmwareLimitationWarningTest() {
|
||||
val warningMessage = getResourceString(CoreResR.string.alert_manage_tokens_unsupported_message, solanaName)
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen(mockContent = Firmware451MockContent) }
|
||||
step("Open 'Manage tokens' screen") { openManageTokens() }
|
||||
step("Enable '$solanaNetworkTitle' network for token: '$solanaTokenTitle'") {
|
||||
toggleTokenNetworkInManageTokens(tokenTitle = solanaTokenTitle, networkTitle = solanaNetworkTitle)
|
||||
}
|
||||
step("Assert firmware-limitation warning is displayed") {
|
||||
flakySafely { onDialog { containerWithText(warningMessage).assertIsDisplayed() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("767")
|
||||
@DisplayName("Manage tokens: enabling Solana on a card missing its curve shows unsupported-curve warning")
|
||||
@Test
|
||||
fun solanaUnsupportedCurveWarningTest() {
|
||||
val warningMessage =
|
||||
getResourceString(CoreResR.string.alert_manage_tokens_unsupported_curve_message, solanaName)
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen(mockContent = Wallet2NoEd25519Slip0010MockContent) }
|
||||
step("Open 'Manage tokens' screen") { openManageTokens() }
|
||||
step("Enable '$solanaNetworkTitle' network for token: '$solanaTokenTitle'") {
|
||||
toggleTokenNetworkInManageTokens(tokenTitle = solanaTokenTitle, networkTitle = solanaNetworkTitle)
|
||||
}
|
||||
step("Assert unsupported-curve warning is displayed") {
|
||||
flakySafely { onDialog { containerWithText(warningMessage).assertIsDisplayed() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ object MockProvider {
|
|||
MockOption("Shiba (No Backup, No Wallets)") { ShibaNoBackupNoWalletsMockContent },
|
||||
MockOption("Ed25519 Curve") { EdCurveMockContent },
|
||||
MockOption("Secp256k1 Curve") { Secpk1CurveMockContent },
|
||||
MockOption("Wallet 2 (No ed25519_slip0010)") { Wallet2NoEd25519Slip0010MockContent },
|
||||
MockOption("Wallet 1 (Legacy derivation)") { Wallet1LegacyDerivationMockContent },
|
||||
MockOption("Firmware 4.51") { Firmware451MockContent },
|
||||
MockOption("Backup Wallet") { BackupWalletMockContent },
|
||||
MockOption("Dev Wallet") { DevWalletMockContent },
|
||||
MockOption("Firmware 4.12") { Firmware412MockContent },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.tap.domain.sdk.mocks.content
|
||||
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||
|
||||
// Firmware 4.51: HD-capable (>= 4.39) but below SolanaTokensAvailable (4.52), so Solana tokens are firmware-limited.
|
||||
object Firmware451MockContent : MockContent by WalletMockContent {
|
||||
|
||||
override val cardDto: CardDTO = WalletMockContent.cardDto.copy(
|
||||
firmwareVersion = WalletMockContent.cardDto.firmwareVersion.copy(minor = 51),
|
||||
)
|
||||
|
||||
override val scanResponse: ScanResponse = WalletMockContent.scanResponse.copy(card = cardDto)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.tap.domain.sdk.mocks.content
|
||||
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||
|
||||
// Wallet1 on a first-batch id (AC01) — resolves to the V1 (legacy) derivation style.
|
||||
object Wallet1LegacyDerivationMockContent : MockContent by WalletMockContent {
|
||||
|
||||
override val cardDto: CardDTO = WalletMockContent.cardDto.copy(batchId = "AC01")
|
||||
|
||||
override val scanResponse: ScanResponse = WalletMockContent.scanResponse.copy(card = cardDto)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.tap.domain.sdk.mocks.content
|
||||
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||
|
||||
// Wallet2 without its ed25519_slip0010 wallet, so adding Solana warns UnsupportedCurve (no wallet for its curve).
|
||||
object Wallet2NoEd25519Slip0010MockContent : MockContent by Wallet2WithSeedPhraseMockContent {
|
||||
|
||||
override val cardDto: CardDTO = Wallet2WithSeedPhraseMockContent.cardDto.copy(
|
||||
wallets = Wallet2WithSeedPhraseMockContent.cardDto.wallets.filterNot {
|
||||
it.curve == EllipticCurve.Ed25519Slip0010
|
||||
},
|
||||
)
|
||||
|
||||
override val scanResponse: ScanResponse = Wallet2WithSeedPhraseMockContent.scanResponse.copy(card = cardDto)
|
||||
}
|
||||
|
|
@ -8,4 +8,6 @@ object AddCustomTokenScreenTestTags {
|
|||
const val SELECTOR_LIST = "ADD_CUSTOM_TOKEN_SELECTOR_LIST"
|
||||
|
||||
fun networkRow(networkName: String): String = "ADD_CUSTOM_TOKEN_NETWORK_ROW_${networkName.lowercase()}"
|
||||
|
||||
fun derivationRow(networkId: String): String = "ADD_CUSTOM_TOKEN_DERIVATION_ROW_${networkId.lowercase()}"
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ internal fun CustomTokenSelectorContent(model: CustomTokenSelectorUM, modifier:
|
|||
}
|
||||
is DerivationPathUM -> {
|
||||
DerivationPathItem(
|
||||
modifier = itemModifier,
|
||||
modifier = itemModifier.testTag(AddCustomTokenScreenTestTags.derivationRow(item.id)),
|
||||
model = item,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue