Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-31 16:15:55 +03:00
parent d465cafc8d
commit b517fe39dd
32 changed files with 790 additions and 61 deletions

View file

@ -167,6 +167,7 @@ abstract class BaseTestCase : TestCase(
"SWAP_REDESIGN_ENABLED" to false,
"ACCOUNTS_FEATURE_ENABLED" to true,
"GASLESS_APPROVAL_ENABLED" to true,
"MAIN_SCREEN_QR_SCANNING_ENABLED" to true,
)
)
}

View file

@ -1,9 +1,17 @@
package com.tangem.common.extensions
import android.support.annotation.PluralsRes
import androidx.compose.ui.test.SemanticsMatcher
import androidx.test.platform.app.InstrumentationRegistry
import com.tangem.core.ui.utils.LazyListItemPositionSemantics
import io.github.kakaocup.compose.node.builder.ViewBuilder
fun ViewBuilder.hasLazyListItemPosition(position: Int) = apply {
addSemanticsMatcher(SemanticsMatcher.expectValue(LazyListItemPositionSemantics, position))
}
}
fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any): String =
InstrumentationRegistry.getInstrumentation()
.targetContext
.resources
.getQuantityString(resId, quantity, *formatArgs)

View file

@ -89,7 +89,7 @@ fun BaseTestCase.synchronizeAddresses(
fun BaseTestCase.openDeviceSettingsScreen() {
step("Open wallet details") {
waitForIdle()
onTopBar { moreButton.clickWithAssertion() }
onMainScreenTopBar { moreButton.clickWithAssertion() }
}
step("Open 'Wallet settings' screen") {
onDetailsScreen { walletNameButton.performClick() }
@ -101,7 +101,7 @@ fun BaseTestCase.openDeviceSettingsScreen() {
fun BaseTestCase.openWalletConnectScreen() {
step("Click 'More' button on TopBar") {
onTopBar { moreButton.clickWithAssertion() }
onMainScreenTopBar { moreButton.clickWithAssertion() }
}
step("Click on 'Wallet Connect' button") {
onDetailsScreen { walletConnectButton.clickWithAssertion() }

View file

@ -0,0 +1,24 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.AccountDetailsScreenTestTags
import com.tangem.core.ui.test.TopAppBarTestTags
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
class AccountDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<AccountDetailsPageObject>(semanticsProvider = semanticsProvider) {
val topAppBarBackButton: KNode = child {
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
}
val manageTokensButton: KNode = child {
hasTestTag(AccountDetailsScreenTestTags.MANAGE_TOKENS_BUTTON)
}
}
internal fun BaseTestCase.onAccountDetails(function: AccountDetailsPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -3,6 +3,7 @@ package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.DetailsScreenTestTags
import com.tangem.core.ui.test.TopAppBarTestTags
import com.tangem.wallet.R
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
@ -10,10 +11,11 @@ import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
class DetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<DetailsPageObject>(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(DetailsScreenTestTags.SCREEN_CONTAINER) }
) {
ComposeScreen<DetailsPageObject>(semanticsProvider = semanticsProvider) {
val topAppBarBackButton: KNode = child {
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
}
val walletConnectButton: KNode = child {
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)

View file

@ -6,6 +6,7 @@ import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import androidx.compose.ui.test.hasAnyAncestor
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.getQuantityString
import com.tangem.common.extensions.hasLazyListItemPosition
import com.tangem.common.utils.LazyListItemNode
import com.tangem.core.ui.test.*
@ -154,6 +155,30 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
useUnmergedTree = true
}
val missingAddressNotificationIcon: KNode = child {
hasAnySibling(withText(getResourceString(R.string.warning_missing_derivation_title)))
hasTestTag(NotificationTestTags.ICON)
useUnmergedTree = true
}
val missingAddressNotificationTitle: KNode = child {
hasTestTag(NotificationTestTags.TITLE)
hasText(getResourceString(R.string.warning_missing_derivation_title))
useUnmergedTree = true
}
fun missingAddressNotificationMessage(networkCount: Int): KNode = child {
hasTestTag(NotificationTestTags.MESSAGE)
hasText(
getQuantityString(
R.plurals.warning_missing_derivation_message,
networkCount,
networkCount
)
)
useUnmergedTree = true
}
val totalBalanceContainer: KNode = child {
hasTestTag(MainScreenTestTags.WALLET_LIST_ITEM)
}
@ -189,6 +214,11 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
hasText(getResourceString(CoreUiR.string.wallet_notification_address_copied))
}
val organizeTokensButtonNode: KNode = child {
hasTestTag(MainScreenTestTags.ORGANIZE_TOKENS_BUTTON)
useUnmergedTree = true
}
/**
* Find token list item with title and address
*/
@ -203,6 +233,17 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
}
}
@OptIn(ExperimentalTestApi::class)
fun tokenWithCustomDerivationIcon(tokenTitle: String): KNode {
return lazyList.childWith<LazyListItemNode> {
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
hasText(tokenTitle)
}.child<KNode> {
hasTestTag(TokenElementsTestTags.TOKEN_CUSTOM_DERIVATION_ICON)
useUnmergedTree = true
}
}
@OptIn(ExperimentalTestApi::class)
fun organizeTokensButton(): KNode {
return lazyList.childWith<LazyListItemNode> {

View file

@ -7,16 +7,18 @@ 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
class TopBarPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<TopBarPageObject>(
class MainScreenTopBarPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<MainScreenTopBarPageObject>(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(MainScreenTestTags.TOP_BAR) }
) {
val moreButton: KNode = child {
hasTestTag(MainScreenTestTags.MORE_BUTTON)
hasPosition(1)
useUnmergedTree = true
}
}
internal fun BaseTestCase.onTopBar(function: TopBarPageObject.() -> Unit) =
internal fun BaseTestCase.onMainScreenTopBar(function: MainScreenTopBarPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -0,0 +1,55 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
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.SwitchTestTags
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 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
class ManageTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<ManageTokensPageObject>(semanticsProvider = semanticsProvider) {
val searchField: KNode = child {
hasTestTag(BaseSearchBarTestTags.SEARCH_BAR)
}
fun tokenItem(tokenName: String): KNode = child {
hasTestTag(ManageTokensScreenTestTags.TOKEN_ITEM)
hasText(tokenName)
}
fun networkSwitch(networkName: String): KNode = child {
useUnmergedTree = true
addSemanticsMatcher(
withTestTag(SwitchTestTags.SWITCH)
.and(
withAnyAncestor(
withAnySibling(
withTestTag(ManageTokensScreenTestTags.NETWORK_NAME)
.and(withAnyDescendant(withText(networkName)))
)
)
)
)
}
val saveButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_save))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onManageTokensScreen(function: ManageTokensPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -0,0 +1,42 @@
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.BaseButtonTestTags
import com.tangem.core.ui.test.BaseDialogTestTags
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 UnableToHideDialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<UnableToHideDialogPageObject>(semanticsProvider = semanticsProvider) {
fun unableToHideTokenTitle(tokenName: String): KNode = child {
hasTestTag(BaseDialogTestTags.TITLE)
hasText(getResourceString(
R.string.token_details_unable_hide_alert_title,
tokenName))
}
fun unableToHideTokenMessage(tokenName: String, tokenSymbol: String, networkName: String): KNode = child {
hasTestTag(BaseDialogTestTags.TEXT)
hasText(
getResourceString(
R.string.token_details_unable_hide_alert_message,
tokenName,
tokenSymbol,
networkName
)
)
}
val okButton: KNode = child {
hasTestTag(BaseButtonTestTags.BUTTON)
hasText(getResourceString(R.string.common_ok))
}
}
internal fun BaseTestCase.onUnableToHideDialog(function: UnableToHideDialogPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -2,18 +2,22 @@ package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.TopAppBarTestTags
import com.tangem.core.ui.test.WalletSettingsScreenTestTags
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
import androidx.compose.ui.test.hasText as withText
class WalletSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<WalletSettingsPageObject>(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(WalletSettingsScreenTestTags.SCREEN_CONTAINER) }
) {
ComposeScreen<WalletSettingsPageObject>(semanticsProvider = semanticsProvider) {
val topAppBarBackButton: KNode = child {
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
}
private val walletSettingsItem: KNode = child {
hasTestTag(WalletSettingsScreenTestTags.SCREEN_ITEM)
}
@ -21,15 +25,24 @@ class WalletSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvi
val linkMoreCardsButton: KNode = walletSettingsItem.child {
hasText(getResourceString(R.string.details_row_title_create_backup))
}
val deviceSettingsButton: KNode = walletSettingsItem.child {
hasText(getResourceString(R.string.card_settings_title))
}
val referralProgramButton: KNode = walletSettingsItem.child {
hasText(getResourceString(R.string.details_referral_title))
}
val forgetWalletButton: KNode = walletSettingsItem.child {
hasText(getResourceString(R.string.settings_forget_wallet))
}
fun accountItem(accountName: String): KNode = walletSettingsItem.child {
hasTestTag(WalletSettingsScreenTestTags.USER_ACCOUNT_ITEM)
hasAnyDescendant(withText(accountName))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onWalletSettingsScreen(function: WalletSettingsPageObject.() -> Unit) =

View file

@ -19,7 +19,7 @@ class DetailsTest : BaseTestCase() {
step("Open 'Main Screen'") {
openMainScreen()
}
onTopBar {
onMainScreenTopBar {
step("Open wallet details") {
moreButton.clickWithAssertion()
}
@ -66,7 +66,7 @@ class DetailsTest : BaseTestCase() {
step("Open 'Main Screen'") {
openMainScreen(productType = ProductType.Wallet2)
}
onTopBar {
onMainScreenTopBar {
step("Open wallet details") {
moreButton.clickWithAssertion()
}
@ -116,7 +116,7 @@ class DetailsTest : BaseTestCase() {
step("Open 'Main Screen'") {
openMainScreen(ProductType.Note)
}
onTopBar {
onMainScreenTopBar {
step("Open wallet details") {
moreButton.clickWithAssertion()
}
@ -163,7 +163,7 @@ class DetailsTest : BaseTestCase() {
openMainScreen()
}
step("Open wallet details") {
onTopBar { moreButton.clickWithAssertion() }
onMainScreenTopBar { moreButton.clickWithAssertion() }
}
step("Open 'Wallet settings' screen") {
onDetailsScreen { walletNameButton.clickWithAssertion() }

View file

@ -26,7 +26,7 @@ import com.tangem.screens.onSendConfirmScreen
import com.tangem.screens.onSendScreen
import com.tangem.screens.onStoriesScreen
import com.tangem.screens.onTokenDetailsScreen
import com.tangem.screens.onTopBar
import com.tangem.screens.onMainScreenTopBar
import com.tangem.tap.domain.sdk.mocks.MockProvider
import com.tangem.tap.store
import dagger.hilt.android.testing.HiltAndroidTest
@ -57,7 +57,7 @@ class FeedbackTest : BaseTestCase() {
}
step("Click 'More' button on TopBar") {
waitForIdle()
onTopBar { moreButton.clickWithAssertion() }
onMainScreenTopBar { moreButton.clickWithAssertion() }
}
step("Click 'Contact support' button") {
waitForIdle()

View file

@ -1,20 +0,0 @@
package com.tangem.tests
import com.tangem.common.BaseTestCase
import com.tangem.scenarios.openMainScreen
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Test
@HiltAndroidTest
class MainScreenTest : BaseTestCase() {
@Test
fun goToMain() {
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
}
}
}

View file

@ -14,7 +14,6 @@ import com.tangem.tap.domain.sdk.mocks.content.*
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Ignore
import org.junit.Test
@HiltAndroidTest
@ -86,7 +85,6 @@ class ScanCardTest : BaseTestCase() {
}
}
@Ignore("TODO: [REDACTED_JIRA]")
@AllureId("870")
@DisplayName("Scan: Card with Ed25519 curve")
@Test

View file

@ -0,0 +1,312 @@
package com.tangem.tests.main
import androidx.compose.ui.test.longClick
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.USER_TOKENS_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.synchronizeAddresses
import com.tangem.screens.*
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 HideTokenTest : BaseTestCase() {
@AllureId("3638")
@DisplayName("Main: hide token by long tap")
@Test
fun hideTokenByLongTapTest() {
val tokenTitle = "Polygon"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Click on 'Hide token' button") {
onTokenActionsBottomSheet { hideTokenButton.performClick() }
}
step("Click 'Hide' button in dialog") {
onDialog {
dialogContainer.assertIsDisplayed()
okButton.clickWithAssertion()
}
}
step("Assert token: '$tokenTitle' is not displayed") {
onMainScreen { assertTokenDoesNotExist(tokenTitle) }
}
}
}
@AllureId("3627")
@DisplayName("Main: hide token via Manage tokens")
@Test
fun hideTokenViaManageTokensTest() {
val tokenTitle = "Tether"
val networkTitle = "ETHEREUM"
val scenarioState = "USDT"
val accountName = "Main account"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
}
).run {
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$scenarioState'") {
setWireMockScenarioState(USER_TOKENS_API_SCENARIO, scenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Open wallet details") {
waitForIdle()
onMainScreenTopBar { moreButton.clickWithAssertion() }
}
step("Open 'Wallet settings' screen") {
onDetailsScreen { walletNameButton.performClick() }
}
step("Click on account: '$accountName'") {
onWalletSettingsScreen { accountItem(accountName).performClick() }
}
step("Click on 'Manage tokens' button") {
onAccountDetails { manageTokensButton.performClick() }
}
step("Click on token: '$tokenTitle'") {
onManageTokensScreen { tokenItem(tokenTitle).performClick() }
}
step("Assert switch is on") {
onManageTokensScreen { networkSwitch(networkTitle).assertIsOn() }
}
step("Click on '$networkTitle' switch") {
onManageTokensScreen { networkSwitch(networkTitle).performClick() }
}
step("Click 'Hide' button in dialog") {
onDialog {
dialogContainer.assertIsDisplayed()
hideButton.clickWithAssertion()
}
}
step("Assert switch is off") {
onManageTokensScreen { networkSwitch(networkTitle).assertIsOff() }
}
step("Click on 'Save' button") {
onManageTokensScreen { saveButton.performClick() }
}
step("Click on 'Account details' screen 'Back' button") {
waitForIdle()
onAccountDetails { 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() }
}
step("Assert token: '$tokenTitle' is not displayed") {
onMainScreen { assertTokenDoesNotExist(tokenTitle) }
}
}
}
@AllureId("3626")
@DisplayName("Main: hide main coin via manage tokens")
@Test
fun hideMainCoinViaManageTokensTest() {
val tokenTitle = "POL (ex-MATIC)"
val networkTitle = "POLYGON"
val polygonTitle = "Polygon"
val accountName = "Main account"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Open wallet details") {
waitForIdle()
onMainScreenTopBar { moreButton.clickWithAssertion() }
}
step("Open 'Wallet settings' screen") {
onDetailsScreen { walletNameButton.performClick() }
}
step("Click on account: '$accountName'") {
onWalletSettingsScreen { accountItem(accountName).performClick() }
}
step("Click on 'Manage tokens' button") {
onAccountDetails { manageTokensButton.performClick() }
}
step("Click on token: '$tokenTitle'") {
onManageTokensScreen { tokenItem(tokenTitle).performClick() }
}
step("Assert switch is on") {
onManageTokensScreen { networkSwitch(networkTitle).assertIsOn() }
}
step("Click on '$networkTitle' switch") {
onManageTokensScreen { networkSwitch(networkTitle).performClick() }
}
step("Click 'Hide' button in dialog") {
onDialog {
dialogContainer.assertIsDisplayed()
hideButton.clickWithAssertion()
}
}
step("Assert switch is off") {
onManageTokensScreen { networkSwitch(networkTitle).assertIsOff() }
}
step("Click on 'Save' button") {
onManageTokensScreen { saveButton.performClick() }
}
step("Click on 'Account details' screen 'Back' button") {
waitForIdle()
onAccountDetails { 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() }
}
step("Assert token: '$polygonTitle' is not displayed") {
onMainScreen { assertTokenDoesNotExist(polygonTitle) }
}
}
}
@AllureId("3610")
@DisplayName("Main: check 'Unable to hide token' warning")
@Test
fun checkUnableToHideTokenWarningTest() {
val tokenTitle = "Ethereum"
val tokenSymbol = "ETH"
val scenarioState = "USDT"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
}
).run {
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$scenarioState'") {
setWireMockScenarioState(USER_TOKENS_API_SCENARIO, scenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Click on 'Hide token' button") {
onTokenActionsBottomSheet { hideTokenButton.performClick() }
}
step("Assert 'Unable to hide $tokenTitle' alert title is displayed") {
onUnableToHideDialog {
unableToHideTokenTitle(tokenName = tokenTitle).assertIsDisplayed()
}
}
step("Assert 'Unable to hide $tokenTitle' alert message is displayed") {
onUnableToHideDialog {
unableToHideTokenMessage(
tokenName = tokenTitle,
tokenSymbol = tokenSymbol,
networkName = tokenTitle
).assertIsDisplayed()
}
}
step("Click on 'Ok' button") {
onUnableToHideDialog {
okButton.performClick()
}
}
step("Press 'Back' button") {
device.uiDevice.pressBack()
}
step("Assert token: '$tokenTitle' is displayed") {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).assertIsDisplayed() }
}
step("Click on token: '$tokenTitle'") {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
}
step("Assert 'Token details screen' open") {
onTokenDetailsScreen { screenContainer.assertIsDisplayed() }
}
step("Click 'More' button") {
onTokenDetailsTopBar { moreButton.clickWithAssertion() }
}
step("Click 'Hide token' button") {
onPopUpMenu {
popUpContainer.assertIsDisplayed()
hideTokenButton.clickWithAssertion()
}
}
step("Assert 'Unable to hide $tokenSymbol' alert title is displayed") {
onUnableToHideDialog {
unableToHideTokenTitle(tokenName = tokenSymbol).assertIsDisplayed()
}
}
step("Assert 'Unable to hide $tokenTitle' alert message is displayed") {
onUnableToHideDialog {
unableToHideTokenMessage(
tokenName = tokenTitle,
tokenSymbol = tokenSymbol,
networkName = tokenTitle
).assertIsDisplayed()
}
}
step("Click on 'Ok' button") {
onUnableToHideDialog {
okButton.performClick()
}
}
step("Click 'Back' button") {
onTokenDetailsTopBar { backButton.clickWithAssertion() }
}
step("Assert token: '$tokenTitle' is displayed") {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).assertIsDisplayed() }
}
}
}
}

View file

@ -0,0 +1,114 @@
package com.tangem.tests.main
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
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 MainScreenTest : BaseTestCase() {
@AllureId("66")
@DisplayName("Main: check 'Organize tokens' button with multiple tokens no accounts")
@Test
fun checkOrganizeTokensButtonWithMultipleTokensNoAccountsTest() {
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Assert 'Organize tokens' button is displayed") {
onMainScreen { organizeTokensButton().assertIsDisplayed() }
}
}
}
@AllureId("8748")
@DisplayName("Main: check 'Organize tokens' button with single token no accounts")
@Test
fun checkOrganizeTokensButtonWithSingleTokenNoAccountsTest() {
val scenarioState = "Cardano"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
}
).run {
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$scenarioState'") {
setWireMockScenarioState(USER_TOKENS_API_SCENARIO, scenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Assert 'Organize tokens' button is not displayed") {
onMainScreen { organizeTokensButtonNode.assertIsNotDisplayed()}
}
}
}
@AllureId("8749")
@DisplayName("Main: check 'Organize tokens' button with single token two accounts")
@Test
fun checkOrganizeTokensButtonWithSingleTokenMultiAccountsTest() {
val scenarioState = "TwoAccountsSingleTokenEach"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
}
).run {
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$scenarioState'") {
setWireMockScenarioState(USER_TOKENS_API_SCENARIO, scenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Assert 'Organize tokens' button is not displayed") {
onMainScreen { organizeTokensButtonNode.assertIsNotDisplayed()}
}
}
}
@AllureId("8750")
@DisplayName("Main: check 'Organize tokens' button with multiple tokens two accounts")
@Test
fun checkOrganizeTokensButtonWithMultipleTokensMultiAccountsTest() {
val scenarioState = "TwoAccountsMixed"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
}
).run {
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$scenarioState'") {
setWireMockScenarioState(USER_TOKENS_API_SCENARIO, scenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Assert 'Organize tokens' button is not displayed") {
onMainScreen { organizeTokensButtonNode.assertIsDisplayed()}
}
}
}
}

View file

@ -0,0 +1,51 @@
package com.tangem.tests.main
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
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 TokenListTest : BaseTestCase() {
@AllureId("180")
@DisplayName("Token list: hide token by long tap")
@Test
fun checkCustomDerivationIconOnTokenAndNetworkTest() {
val networkTitle = "Ethereum"
val customTokenTitle = "Myria"
val scenarioState = "CustomDerivation"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
}
).run {
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$scenarioState'") {
setWireMockScenarioState(USER_TOKENS_API_SCENARIO, scenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Assert token: '$networkTitle' is displayed") {
onMainScreen { tokenWithTitleAndAddress(networkTitle).assertIsDisplayed() }
}
step("Assert token with custom derivation icon: '$customTokenTitle' is displayed") {
onMainScreen { tokenWithCustomDerivationIcon(customTokenTitle).assertIsDisplayed() }
}
}
}
}

View file

@ -0,0 +1,52 @@
package com.tangem.tests.main
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
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 WarningsTest : BaseTestCase() {
@AllureId("184")
@DisplayName("Token list: hide token by long tap")
@Test
fun checkUnavailableNetworksWarningTest() {
val scenarioState = "MissingDerivation"
val networkCount = 1
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
}
).run {
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$scenarioState'") {
setWireMockScenarioState(USER_TOKENS_API_SCENARIO, scenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(isBalanceAvailable = false)
}
step("Assert 'Missing addresses' notification icon is displayed") {
onMainScreen { missingAddressNotificationIcon.assertIsDisplayed() }
}
step("Assert 'Missing addresses' notification title is displayed") {
onMainScreen { missingAddressNotificationTitle.assertIsDisplayed() }
}
step("Assert 'Missing addresses' notification message is displayed") {
onMainScreen { missingAddressNotificationMessage(networkCount).assertIsDisplayed() }
}
}
}
}

View file

@ -23,7 +23,7 @@ class SendAmountScreenTest : BaseTestCase() {
val manualSendAmount = "1"
val clipboardSendAmount = "0.5"
val invalidAmount = "2"
val errorText = getResourceString(R.string.send_validation_amount_exceeds_balance)
val errorText = getResourceString(R.string.common_insufficient_balance)
val context = device.context
setupHooks().run {

View file

@ -17,7 +17,6 @@ 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.Ignore
import org.junit.Test
@HiltAndroidTest
@ -33,7 +32,6 @@ class StellarWarningsTest : BaseTestCase() {
getResourceString(R.string.send_notification_invalid_reserve_amount_title, reserveAmount)
private val warningMessage = getResourceString(R.string.send_notification_invalid_reserve_amount_text)
@Ignore("TODO: [REDACTED_JIRA]")
@AllureId("4287")
@DisplayName("Warnings: check warning, when sending less than reserve")
@Test
@ -87,7 +85,6 @@ class StellarWarningsTest : BaseTestCase() {
}
}
@Ignore("TODO: [REDACTED_JIRA]")
@AllureId("4286")
@DisplayName("Warnings: check warning when sending amount equal to reserve")
@Test
@ -142,7 +139,6 @@ class StellarWarningsTest : BaseTestCase() {
}
}
@Ignore("TODO: [REDACTED_JIRA]")
@AllureId("4288")
@DisplayName("Warnings: check warning when sending greater than reserve")
@Test

View file

@ -19,6 +19,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@ -43,6 +44,7 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.WalletSettingsScreenTestTags
import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.StringsSigns.DOT
import com.tangem.utils.StringsSigns.THREE_STARS
@ -64,7 +66,8 @@ fun UserWalletItem(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size68)
.padding(all = TangemTheme.dimens.spacing12),
.padding(all = TangemTheme.dimens.spacing12)
.testTag(WalletSettingsScreenTestTags.USER_ACCOUNT_ITEM),
)
}
}

View file

@ -3,10 +3,10 @@ package com.tangem.core.ui.components
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
@ -20,7 +20,7 @@ import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.MarketsTestTags
import com.tangem.core.ui.test.SwitchTestTags
@Suppress("MagicNumber")
@Composable
@ -47,7 +47,8 @@ fun TangemSwitch(
Box(
modifier = modifier
.clickable(
.toggleable(
value = checked,
interactionSource = interactionSource,
indication = ripple(
bounded = false,
@ -55,10 +56,9 @@ fun TangemSwitch(
),
enabled = enabled,
role = Role.Switch,
onClick = {
onCheckedChange(!checked)
},
).testTag(MarketsTestTags.ADD_TO_PORTFOLIO_SWITCH),
onValueChange = onCheckedChange,
)
.testTag(SwitchTestTags.SWITCH),
) {
BoxWithConstraints(
modifier = Modifier

View file

@ -13,12 +13,14 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.TokenElementsTestTags
import com.tangem.core.ui.utils.getGreyScaleColorFilter
/**
@ -147,7 +149,9 @@ private fun BoxScope.ContentIconContainer(
if (icon.shouldShowCustomBadge) {
CurrencyIconBottomBadge(
modifier = Modifier.align(Alignment.BottomEnd),
modifier = Modifier
.align(Alignment.BottomEnd)
.testTag(TokenElementsTestTags.TOKEN_CUSTOM_DERIVATION_ICON),
)
}
}

View file

@ -5,11 +5,13 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.isNullOrEmpty
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.ManageTokensScreenTestTags
@Composable
inline fun RowContentContainer(
@ -27,18 +29,21 @@ inline fun RowContentContainer(
Box(
contentAlignment = Alignment.Center,
content = icon,
modifier = Modifier.testTag(ManageTokensScreenTestTags.NETWORK_ICON),
)
Box(
modifier = Modifier
.weight(1f)
.heightIn(min = TangemTheme.dimens.size22),
.heightIn(min = TangemTheme.dimens.size22)
.testTag(ManageTokensScreenTestTags.NETWORK_NAME),
contentAlignment = Alignment.CenterStart,
content = text,
)
Box(
modifier = Modifier
.requiredWidthIn(max = TangemTheme.dimens.size80)
.heightIn(min = TangemTheme.dimens.size24),
.heightIn(min = TangemTheme.dimens.size24)
.testTag(ManageTokensScreenTestTags.SWITCH),
contentAlignment = Alignment.CenterEnd,
content = action,
)

View file

@ -0,0 +1,5 @@
package com.tangem.core.ui.test
object AccountDetailsScreenTestTags {
const val MANAGE_TOKENS_BUTTON = "ACCOUNT_DETAILS_SCREEN_MANAGE_TOKENS_BUTTON"
}

View file

@ -0,0 +1,8 @@
package com.tangem.core.ui.test
object ManageTokensScreenTestTags {
const val TOKEN_ITEM = "MANAGE_TOKENS_SCREEN_TOKEN_ITEM"
const val NETWORK_ICON = "MANAGE_TOKENS_SCREEN_NETWORK_ICON"
const val NETWORK_NAME = "MANAGE_TOKENS_SCREEN_NETWORK_NAME"
const val SWITCH = "MANAGE_TOKENS_SCREEN_SWITCH"
}

View file

@ -3,6 +3,5 @@ package com.tangem.core.ui.test
object MarketsTestTags {
const val TOKENS_LIST = "MARKETS_TOKENS_LIST"
const val TOKENS_LIST_ITEM = "MARKETS_TOKENS_LIST_ITEM"
const val ADD_TO_PORTFOLIO_SWITCH = "MARKETS_ADD_TO_PORTFOLIO_SWITCH"
const val LISTED_ON_EXCHANGES_COUNT = "MARKETS_LISTED_ON_EXCHANGES_COUNT"
}

View file

@ -0,0 +1,5 @@
package com.tangem.core.ui.test
object SwitchTestTags {
const val SWITCH = "SWITCH"
}

View file

@ -9,4 +9,5 @@ object TokenElementsTestTags {
const val TOKEN_CRYPTO_AMOUNT = "TOKEN_CRYPTO_AMOUNT"
const val TOKEN_NON_FIAT_BLOCK = "TOKEN_NON_FIAT_BLOCK"
const val TOKEN_YIELD_PROMO_BANNER = "TOKEN_YIELD_PROMO_BANNER"
const val TOKEN_CUSTOM_DERIVATION_ICON = "TOKEN_CUSTOM_DERIVATION_ICON"
}

View file

@ -3,4 +3,5 @@ package com.tangem.core.ui.test
object WalletSettingsScreenTestTags {
const val SCREEN_CONTAINER = "WALLET_SETTINGS_SCREEN_CONTAINER"
const val SCREEN_ITEM = "WALLET_SETTINGS_SCREEN_ITEM"
const val USER_ACCOUNT_ITEM = "WALLET_SETTINGS_USER_ACCOUNT_ITEM"
}

View file

@ -13,6 +13,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@ -31,6 +32,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.AccountDetailsScreenTestTags
import com.tangem.features.account.details.entity.AccountDetailsUM
@Composable
@ -130,7 +132,8 @@ private fun ManageTokensRow(state: AccountDetailsUM) {
.clip(RoundedCornerShape(TangemTheme.dimens.radius12))
.background(TangemTheme.colors.background.primary)
.clickable(onClick = state.onManageTokensClick)
.padding(all = TangemTheme.dimens.spacing12),
.padding(all = TangemTheme.dimens.spacing12)
.testTag(AccountDetailsScreenTestTags.MANAGE_TOKENS_BUTTON),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {

View file

@ -22,6 +22,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.input.nestedscroll.nestedScroll
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
@ -53,6 +54,7 @@ import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.res.LocalHapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.ManageTokensScreenTestTags
import com.tangem.core.ui.utils.WindowInsetsZero
import com.tangem.core.ui.utils.rememberHideKeyboardNestedScrollConnection
import com.tangem.domain.models.wallet.UserWalletId
@ -311,7 +313,9 @@ private fun BasicCurrencyItem(item: CurrencyItemUM.Basic, isEditable: Boolean, m
Column(modifier = modifier) {
ChainRow(
modifier = Modifier.clickable(onClick = item.onExpandClick),
modifier = Modifier
.clickable(onClick = item.onExpandClick)
.testTag(ManageTokensScreenTestTags.TOKEN_ITEM),
model = with(item) {
ChainRowUM(
name = name,