Updated on 2026-08-14
This commit is contained in:
parent
e2af488b32
commit
8ffbfc27e7
9 changed files with 510 additions and 6 deletions
|
|
@ -46,6 +46,7 @@ object TestConstants {
|
|||
const val ALLURE_LABEL_VALUE = "Kaspresso"
|
||||
|
||||
const val USER_TOKENS_API_SCENARIO = "user_tokens_api"
|
||||
const val COINS_API_SCENARIO = "coins_api"
|
||||
const val REFERRAL_API_SCENARIO = "referral_api"
|
||||
const val QUOTES_API_SCENARIO = "quotes_api"
|
||||
const val CREATE_USER_WALLET_API_SCENARIO = "create_user_wallet_api"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
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.screens.accounts.onAccountDetailsScreen
|
||||
import com.tangem.screens.onAddCustomTokenScreen
|
||||
import com.tangem.screens.onDetailsScreen
|
||||
import com.tangem.screens.onDialog
|
||||
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 com.tangem.core.res.R as CoreResR
|
||||
|
||||
private fun mainAccountName(): String = getResourceString(CoreResR.string.account_main_account_title)
|
||||
|
||||
// flakySafely is unavailable in BaseTestCase extensions — wait until the assertion/action stops throwing.
|
||||
private fun BaseTestCase.awaitSuccess(block: () -> Unit) {
|
||||
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT) { runCatching(block).isSuccess }
|
||||
}
|
||||
|
||||
fun BaseTestCase.openManageTokens(accountName: String = mainAccountName()) {
|
||||
openWalletSettingsScreen()
|
||||
openAccountDetails(accountName)
|
||||
step("Click on 'Manage tokens' button") {
|
||||
onAccountDetailsScreen { manageTokensButton.clickWithAssertion() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.openAddCustomToken(accountName: String = mainAccountName()) {
|
||||
openManageTokens(accountName)
|
||||
step("Click on 'Add custom token' button") {
|
||||
onManageTokensScreen { addCustomTokenButton.clickWithAssertion() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.addCustomTokenWithCustomDerivation(network: String, contract: String, derivationPath: String) {
|
||||
step("Click on network: '$network'") {
|
||||
awaitSuccess { onAddCustomTokenScreen { scrollToNetwork(network) } }
|
||||
onAddCustomTokenScreen { networkRow(network).performClick() }
|
||||
}
|
||||
step("Enter contract address: '$contract'") {
|
||||
awaitSuccess { onAddCustomTokenScreen { contractAddressField.assertExists() } }
|
||||
onAddCustomTokenScreen { contractAddressField.performTextInputInChunks(contract) }
|
||||
}
|
||||
step("Click on 'Derivation path' field") {
|
||||
awaitSuccess { onAddCustomTokenScreen { derivationSelectorField.assertExists() } }
|
||||
onAddCustomTokenScreen { derivationSelectorField.performClick() }
|
||||
}
|
||||
step("Click on 'Custom derivation' button") {
|
||||
awaitSuccess { onAddCustomTokenScreen { customDerivationButton.assertIsDisplayed() } }
|
||||
onAddCustomTokenScreen { customDerivationButton.performClick() }
|
||||
}
|
||||
step("Enter custom derivation path: '$derivationPath'") {
|
||||
awaitSuccess { onDialog { inputField.assertIsDisplayed() } }
|
||||
onDialog { inputField.performTextInputInChunks(derivationPath) }
|
||||
awaitSuccess { onDialog { okButton.assertIsEnabled() } }
|
||||
onDialog { okButton.performClick() }
|
||||
}
|
||||
step("Click on 'Add token' button") {
|
||||
awaitSuccess { onAddCustomTokenScreen { addTokenButton.assertIsEnabled() } }
|
||||
onAddCustomTokenScreen { addTokenButton.performClick() }
|
||||
}
|
||||
navigateBackToMainFromManageTokens()
|
||||
}
|
||||
|
||||
fun BaseTestCase.navigateBackToMainFromManageTokens() {
|
||||
step("Click on 'Manage tokens' screen 'Back' button") {
|
||||
waitForIdle()
|
||||
onManageTokensScreen { topAppBarBackButton.performClick() }
|
||||
}
|
||||
step("Click on 'Account details' screen 'Back' button") {
|
||||
waitForIdle()
|
||||
onAccountDetailsScreen { topAppBarBackButton.performClick() }
|
||||
}
|
||||
step("Click on 'Wallet settings' screen 'Back' button") {
|
||||
waitForIdle()
|
||||
onWalletSettingsScreen { topAppBarBackButton.performClick() }
|
||||
}
|
||||
step("Click on 'Details' screen 'Back' button") {
|
||||
waitForIdle()
|
||||
onDetailsScreen { topAppBarBackButton.performClick() }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import androidx.compose.ui.test.performScrollToNode
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.AddCustomTokenScreenTestTags
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
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.hasTestTag as withTestTag
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
|
||||
class AddCustomTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<AddCustomTokenPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
private val selectorList: KNode = child {
|
||||
hasTestTag(AddCustomTokenScreenTestTags.SELECTOR_LIST)
|
||||
}
|
||||
|
||||
fun networkRow(networkName: String): KNode = child {
|
||||
hasTestTag(AddCustomTokenScreenTestTags.networkRow(networkName))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun scrollToNetwork(networkName: String) = selectorList {
|
||||
performScrollToNode(withTestTag(AddCustomTokenScreenTestTags.networkRow(networkName)))
|
||||
}
|
||||
|
||||
val contractAddressField: KNode = child {
|
||||
hasTestTag(AddCustomTokenScreenTestTags.CONTRACT_ADDRESS_FIELD)
|
||||
}
|
||||
|
||||
val derivationSelectorField: KNode = child {
|
||||
hasTestTag(AddCustomTokenScreenTestTags.DERIVATION_SELECTOR_FIELD)
|
||||
}
|
||||
|
||||
val customDerivationButton: KNode = child {
|
||||
hasTestTag(AddCustomTokenScreenTestTags.CUSTOM_DERIVATION_BUTTON)
|
||||
}
|
||||
|
||||
val warningNotification: KNode = child {
|
||||
hasTestTag(AddCustomTokenScreenTestTags.WARNING_NOTIFICATION)
|
||||
}
|
||||
|
||||
val addTokenButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(CoreResR.string.common_add_token))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onAddCustomTokenScreen(function: AddCustomTokenPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.common.R
|
|||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.BaseSearchBarTestTags
|
||||
import com.tangem.core.ui.test.ManageTokensScreenTestTags
|
||||
import com.tangem.core.ui.test.SearchBarTestTags
|
||||
import com.tangem.core.ui.test.SwitchTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
|
|
@ -35,11 +36,31 @@ class ManageTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
|
|||
hasTestTag(BaseSearchBarTestTags.SEARCH_BAR)
|
||||
}
|
||||
|
||||
val searchClearButton: KNode = child {
|
||||
hasTestTag(SearchBarTestTags.CLEAR_BUTTON)
|
||||
}
|
||||
|
||||
val addCustomTokenButton: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.MORE_BUTTON)
|
||||
}
|
||||
|
||||
fun tokenItem(tokenName: String): KNode = child {
|
||||
hasTestTag(ManageTokensScreenTestTags.TOKEN_ITEM)
|
||||
hasText(tokenName)
|
||||
}
|
||||
|
||||
fun networkStandard(networkName: String, standard: String): KNode = child {
|
||||
useUnmergedTree = true
|
||||
addSemanticsMatcher(
|
||||
withText(standard).and(
|
||||
withAnyAncestor(
|
||||
withTestTag(ManageTokensScreenTestTags.NETWORK_NAME)
|
||||
.and(withAnyDescendant(withText(networkName, ignoreCase = true))),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun networkSwitch(networkName: String): KNode = child {
|
||||
useUnmergedTree = true
|
||||
addSemanticsMatcher(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
package com.tangem.tests.main
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.COINS_API_SCENARIO
|
||||
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.scenarios.addCustomTokenWithCustomDerivation
|
||||
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 dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class AddCustomTokenTest : BaseTestCase() {
|
||||
|
||||
private val richState = "ManageTokensRich"
|
||||
private val tokenTitle = "Tether"
|
||||
private val ethereumNetwork = "Ethereum"
|
||||
private val solanaNetwork = "Solana"
|
||||
private val ethContract = "0xdac17f958d2ee523a2206206994597c13d831ec7"
|
||||
private val solanaContract = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
||||
private val customDerivationPath = "m/44'/60'/0'/0/1"
|
||||
|
||||
@AllureId("772")
|
||||
@DisplayName("Add custom token: added token appears on Main")
|
||||
@Test
|
||||
fun addCustomTokenAppearsOnMainTest() {
|
||||
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("Synchronize addresses") {
|
||||
flakySafely { onMainScreen { synchronizeAddressesButton.assertIsDisplayed() } }
|
||||
synchronizeAddresses(assertBalance = false)
|
||||
}
|
||||
step("Open 'Add custom token' screen") { openAddCustomToken() }
|
||||
step("Click on network: '$ethereumNetwork'") {
|
||||
flakySafely { onAddCustomTokenScreen { scrollToNetwork(ethereumNetwork) } }
|
||||
onAddCustomTokenScreen { networkRow(ethereumNetwork).performClick() }
|
||||
}
|
||||
step("Enter contract address: '$ethContract'") {
|
||||
flakySafely { onAddCustomTokenScreen { contractAddressField.assertExists() } }
|
||||
// Chunked input spans several validation ticks — the model drops the first sampled form change.
|
||||
onAddCustomTokenScreen { contractAddressField.performTextInputInChunks(ethContract) }
|
||||
}
|
||||
step("Click on 'Add token' button") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) { onAddCustomTokenScreen { addTokenButton.assertIsEnabled() } }
|
||||
onAddCustomTokenScreen { addTokenButton.performClick() }
|
||||
}
|
||||
step("Navigate back to 'Main Screen'") { navigateBackToMainFromManageTokens() }
|
||||
step("Assert token: '$tokenTitle' is displayed on Main") {
|
||||
flakySafely { onMainScreen { tokenWithTitleAndAddress(tokenTitle).assertIsDisplayed() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("775")
|
||||
@DisplayName("Add custom token: custom derivation path is accepted")
|
||||
@Test
|
||||
fun addCustomTokenWithCustomDerivationTest() {
|
||||
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("Synchronize addresses") {
|
||||
flakySafely { onMainScreen { synchronizeAddressesButton.assertIsDisplayed() } }
|
||||
synchronizeAddresses(assertBalance = false)
|
||||
}
|
||||
step("Open 'Add custom token' screen") { openAddCustomToken() }
|
||||
step("Add custom token with custom derivation on '$ethereumNetwork'") {
|
||||
addCustomTokenWithCustomDerivation(
|
||||
network = ethereumNetwork,
|
||||
contract = ethContract,
|
||||
derivationPath = customDerivationPath,
|
||||
)
|
||||
}
|
||||
step("Assert token: '$tokenTitle' is displayed on Main") {
|
||||
flakySafely { onMainScreen { tokenWithTitleAndAddress(tokenTitle).assertIsDisplayed() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("771")
|
||||
@DisplayName("Add custom token: custom derivation indicator is shown on Main")
|
||||
@Test
|
||||
fun customDerivationIndicatorOnMainTest() {
|
||||
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("Synchronize addresses") {
|
||||
flakySafely { onMainScreen { synchronizeAddressesButton.assertIsDisplayed() } }
|
||||
synchronizeAddresses(assertBalance = false)
|
||||
}
|
||||
step("Open 'Add custom token' screen") { openAddCustomToken() }
|
||||
step("Add custom token with custom derivation on '$ethereumNetwork'") {
|
||||
addCustomTokenWithCustomDerivation(
|
||||
network = ethereumNetwork,
|
||||
contract = ethContract,
|
||||
derivationPath = customDerivationPath,
|
||||
)
|
||||
}
|
||||
step("Assert custom derivation indicator for '$tokenTitle' is displayed on Main") {
|
||||
flakySafely { onMainScreen { tokenWithCustomDerivationIcon(tokenTitle).assertIsDisplayed() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("770")
|
||||
@DisplayName("Add custom token: derivation field is available for a non-EVM network")
|
||||
@Test
|
||||
fun derivationAvailableForNonEvmNetworkTest() {
|
||||
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 'Add custom token' screen") { openAddCustomToken() }
|
||||
step("Click on network: '$solanaNetwork'") {
|
||||
flakySafely { onAddCustomTokenScreen { scrollToNetwork(solanaNetwork) } }
|
||||
onAddCustomTokenScreen { networkRow(solanaNetwork).performClick() }
|
||||
}
|
||||
step("Assert 'Derivation path' field is available") {
|
||||
flakySafely { onAddCustomTokenScreen { derivationSelectorField.assertExists() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("769")
|
||||
@DisplayName("Add custom token: Solana token on a modern card shows no unsupported warning")
|
||||
@Test
|
||||
fun solanaTokenModernCardNoWarningTest() {
|
||||
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 'Add custom token' screen") { openAddCustomToken() }
|
||||
step("Click on network: '$solanaNetwork'") {
|
||||
flakySafely { onAddCustomTokenScreen { scrollToNetwork(solanaNetwork) } }
|
||||
onAddCustomTokenScreen { networkRow(solanaNetwork).performClick() }
|
||||
}
|
||||
step("Enter contract address: '$solanaContract'") {
|
||||
flakySafely { onAddCustomTokenScreen { contractAddressField.assertExists() } }
|
||||
onAddCustomTokenScreen { contractAddressField.performTextInputInChunks(solanaContract) }
|
||||
}
|
||||
step("Assert 'Add token' button is enabled") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) { onAddCustomTokenScreen { addTokenButton.assertIsEnabled() } }
|
||||
}
|
||||
step("Assert unsupported-token warning is not displayed") {
|
||||
onAddCustomTokenScreen { warningNotification.assertDoesNotExist() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
package com.tangem.tests.main
|
||||
|
||||
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.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.openManageTokens
|
||||
import com.tangem.screens.onDialog
|
||||
import com.tangem.screens.onManageTokensScreen
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class ManageTokensTest : BaseTestCase() {
|
||||
|
||||
private val richState = "ManageTokensRich"
|
||||
|
||||
@AllureId("765")
|
||||
@DisplayName("Manage tokens: network standard labels are shown for token networks")
|
||||
@Test
|
||||
fun networkStandardLabelsTest() {
|
||||
val tokenTitle = "Tether"
|
||||
|
||||
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("Click on token: '$tokenTitle'") {
|
||||
flakySafely { onManageTokensScreen { tokenItem(tokenTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen { tokenItem(tokenTitle).performClick() }
|
||||
}
|
||||
step("Assert 'Ethereum' network standard 'ERC20' is displayed") {
|
||||
flakySafely {
|
||||
onManageTokensScreen { networkStandard(networkName = "Ethereum", standard = "ERC20").assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
step("Assert 'BNB Smart Chain' network standard 'BEP20' is displayed") {
|
||||
flakySafely {
|
||||
onManageTokensScreen {
|
||||
networkStandard(networkName = "BNB Smart Chain", standard = "BEP20").assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Assert 'Tron' network standard 'TRC20' is displayed") {
|
||||
flakySafely {
|
||||
onManageTokensScreen { networkStandard(networkName = "Tron", standard = "TRC20").assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("667")
|
||||
@DisplayName("Manage tokens: search by name and ticker filters the list")
|
||||
@Test
|
||||
fun searchByNameAndTickerTest() {
|
||||
val tokenTitle = "Tether"
|
||||
val nameQuery = "Tether"
|
||||
val tickerQuery = "USDT"
|
||||
val emptyQuery = "Zzqnotoken"
|
||||
|
||||
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("Search by name: '$nameQuery'") {
|
||||
onManageTokensScreen {
|
||||
searchField.performClick()
|
||||
searchField.performTextInput(nameQuery)
|
||||
}
|
||||
}
|
||||
step("Assert token: '$tokenTitle' is displayed") {
|
||||
flakySafely { onManageTokensScreen { tokenItem(tokenTitle).assertIsDisplayed() } }
|
||||
}
|
||||
step("Clear search field") {
|
||||
onManageTokensScreen { searchClearButton.clickWithAssertion() }
|
||||
}
|
||||
step("Search by ticker: '$tickerQuery'") {
|
||||
onManageTokensScreen { searchField.performTextInput(tickerQuery) }
|
||||
}
|
||||
step("Assert token: '$tokenTitle' is displayed") {
|
||||
flakySafely { onManageTokensScreen { tokenItem(tokenTitle).assertIsDisplayed() } }
|
||||
}
|
||||
step("Clear search field") {
|
||||
onManageTokensScreen { searchClearButton.clickWithAssertion() }
|
||||
}
|
||||
step("Search by unknown query: '$emptyQuery'") {
|
||||
onManageTokensScreen { searchField.performTextInput(emptyQuery) }
|
||||
}
|
||||
step("Assert token: '$tokenTitle' is not displayed") {
|
||||
flakySafely { onManageTokensScreen { tokenItem(tokenTitle).assertDoesNotExist() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("763")
|
||||
@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("Click on token: '$tokenTitle'") {
|
||||
flakySafely { onManageTokensScreen { tokenItem(tokenTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen { tokenItem(tokenTitle).performClick() }
|
||||
}
|
||||
step("Click on '$networkTitle' switch") {
|
||||
flakySafely { onManageTokensScreen { networkSwitch(networkTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen { networkSwitch(networkTitle).performClick() }
|
||||
}
|
||||
step("Assert hide-token alert is not displayed") {
|
||||
waitForIdle()
|
||||
onDialog { dialogContainer.assertDoesNotExist() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.core.ui.test
|
||||
|
||||
object AddCustomTokenScreenTestTags {
|
||||
const val CONTRACT_ADDRESS_FIELD = "ADD_CUSTOM_TOKEN_CONTRACT_ADDRESS_FIELD"
|
||||
const val DERIVATION_SELECTOR_FIELD = "ADD_CUSTOM_TOKEN_DERIVATION_SELECTOR_FIELD"
|
||||
const val CUSTOM_DERIVATION_BUTTON = "ADD_CUSTOM_TOKEN_CUSTOM_DERIVATION_BUTTON"
|
||||
const val WARNING_NOTIFICATION = "ADD_CUSTOM_TOKEN_WARNING_NOTIFICATION"
|
||||
const val SELECTOR_LIST = "ADD_CUSTOM_TOKEN_SELECTOR_LIST"
|
||||
|
||||
fun networkRow(networkName: String): String = "ADD_CUSTOM_TOKEN_NETWORK_ROW_${networkName.lowercase()}"
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
|
|
@ -35,6 +36,7 @@ import com.tangem.core.ui.extensions.stringReference
|
|||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.AddCustomTokenScreenTestTags
|
||||
import com.tangem.features.managetokens.component.preview.PreviewCustomTokenFormComponent
|
||||
import com.tangem.features.managetokens.entity.customtoken.ClickableFieldUM
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM
|
||||
|
|
@ -122,11 +124,15 @@ private fun FormContent(model: CustomTokenFormUM, modifier: Modifier = Modifier)
|
|||
|
||||
val derivationPath = model.derivationPath
|
||||
if (derivationPath != null) {
|
||||
ClickableField(model = derivationPath)
|
||||
ClickableField(
|
||||
modifier = Modifier.testTag(AddCustomTokenScreenTestTags.DERIVATION_SELECTOR_FIELD),
|
||||
model = derivationPath,
|
||||
)
|
||||
}
|
||||
|
||||
model.notifications.fastForEach { notification ->
|
||||
Notification(
|
||||
modifier = Modifier.testTag(AddCustomTokenScreenTestTags.WARNING_NOTIFICATION),
|
||||
config = notification.config,
|
||||
containerColor = TangemTheme.colors.button.disabled,
|
||||
)
|
||||
|
|
@ -143,14 +149,19 @@ private fun TokenForm(tokenForm: CustomTokenFormUM.TokenFormUM, modifier: Modifi
|
|||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
),
|
||||
) {
|
||||
tokenForm.fields.values.forEach { field ->
|
||||
TextField(model = field)
|
||||
tokenForm.fields.forEach { (key, field) ->
|
||||
TextField(
|
||||
model = field,
|
||||
fieldTestTag = AddCustomTokenScreenTestTags.CONTRACT_ADDRESS_FIELD.takeIf {
|
||||
key == Field.CONTRACT_ADDRESS
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TextField(model: TextInputFieldUM, modifier: Modifier = Modifier) {
|
||||
private fun TextField(model: TextInputFieldUM, modifier: Modifier = Modifier, fieldTestTag: String? = null) {
|
||||
InformationBlock(
|
||||
modifier = modifier,
|
||||
title = {
|
||||
|
|
@ -192,6 +203,7 @@ private fun TextField(model: TextInputFieldUM, modifier: Modifier = Modifier) {
|
|||
modifier = Modifier
|
||||
.padding(bottom = TangemTheme.dimens.spacing12)
|
||||
.fillMaxWidth()
|
||||
.then(if (fieldTestTag != null) Modifier.testTag(fieldTestTag) else Modifier)
|
||||
.onFocusChanged {
|
||||
model.onFocusChange(it.isFocused)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
|
|
@ -30,6 +31,7 @@ import com.tangem.core.ui.extensions.resolveReference
|
|||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.AddCustomTokenScreenTestTags
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.managetokens.component.AddCustomTokenMode
|
||||
|
|
@ -50,6 +52,7 @@ internal fun CustomTokenSelectorContent(model: CustomTokenSelectorUM, modifier:
|
|||
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.testTag(AddCustomTokenScreenTestTags.SELECTOR_LIST)
|
||||
.background(color = TangemTheme.colors.background.secondary)
|
||||
.bottomFade(),
|
||||
contentPadding = PaddingValues(
|
||||
|
|
@ -92,7 +95,7 @@ internal fun CustomTokenSelectorContent(model: CustomTokenSelectorUM, modifier:
|
|||
when (item) {
|
||||
is CurrencyNetworkUM -> {
|
||||
NetworkItem(
|
||||
modifier = itemModifier,
|
||||
modifier = itemModifier.testTag(AddCustomTokenScreenTestTags.networkRow(item.name)),
|
||||
model = item,
|
||||
)
|
||||
}
|
||||
|
|
@ -112,7 +115,9 @@ private fun Header(header: CustomTokenSelectorUM.HeaderUM, modifier: Modifier =
|
|||
when (header) {
|
||||
is CustomTokenSelectorUM.HeaderUM.CustomDerivationButton -> {
|
||||
CustomDerivationButton(
|
||||
modifier = modifier.padding(vertical = TangemTheme.dimens.spacing16),
|
||||
modifier = modifier
|
||||
.padding(vertical = TangemTheme.dimens.spacing16)
|
||||
.testTag(AddCustomTokenScreenTestTags.CUSTOM_DERIVATION_BUTTON),
|
||||
enteredDerivationPath = header.value,
|
||||
isSelected = header.value != null,
|
||||
onClick = header.onClick,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue