From 410253dc5acdfc0efe5bb6e81a7744e3e9ada257 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Sep 2025 14:46:14 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../com/tangem/scenarios/BaseScenarios.kt | 6 +- .../tangem/screens/MainScreenPageObject.kt | 40 +++-- .../tests/balance/TotalBalanceLongTapTest.kt | 43 ++++++ .../balance/TotalBalanceUnavailableTest.kt | 145 ++++++++++++++++++ .../tangem/core/ui/test/MainScreenTestTags.kt | 5 +- .../wallet/ui/components/common/WalletCard.kt | 11 +- 6 files changed, 225 insertions(+), 25 deletions(-) create mode 100644 app/src/androidTest/kotlin/com/tangem/tests/balance/TotalBalanceLongTapTest.kt create mode 100644 app/src/androidTest/kotlin/com/tangem/tests/balance/TotalBalanceUnavailableTest.kt diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/BaseScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/BaseScenarios.kt index 272707ea7c..69e061f616 100644 --- a/app/src/androidTest/kotlin/com/tangem/scenarios/BaseScenarios.kt +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/BaseScenarios.kt @@ -4,10 +4,6 @@ import com.tangem.common.BaseTestCase import com.tangem.common.extensions.clickWithAssertion import com.tangem.domain.models.scan.ProductType import com.tangem.screens.* -import com.tangem.screens.onDisclaimerScreen -import com.tangem.screens.onMainScreen -import com.tangem.screens.onMarketsTooltipScreen -import com.tangem.screens.onStoriesScreen import com.tangem.tap.domain.sdk.mocks.MockContent import com.tangem.tap.domain.sdk.mocks.MockProvider import io.qameta.allure.kotlin.Allure.step @@ -47,7 +43,7 @@ fun BaseTestCase.synchronizeAddresses(balance: String) { onMainScreen { synchronizeAddressesButton.clickWithAssertion() } } step("Assert wallet balance = '$balance'") { - onMainScreen { walletBalance().assertTextContains(balance) } + onMainScreen { totalBalanceText.assertTextContains(balance) } } } diff --git a/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt index 43ad397162..7c145b6ff8 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt @@ -3,11 +3,12 @@ package com.tangem.screens import androidx.compose.ui.test.ExperimentalTestApi 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.hasLazyListItemPosition import com.tangem.common.utils.LazyListItemNode -import com.tangem.core.ui.test.TokenElementsTestTags import com.tangem.core.ui.test.MainScreenTestTags +import com.tangem.core.ui.test.TokenElementsTestTags import com.tangem.core.ui.test.NotificationTestTags import com.tangem.core.ui.utils.LazyListItemPositionSemantics import com.tangem.feature.wallet.impl.R @@ -72,6 +73,24 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) useUnmergedTree = true } + val totalBalanceContainer: KNode = child { + hasTestTag(MainScreenTestTags.WALLET_LIST_ITEM) + } + + val totalBalanceMenuRenameWallet: KNode = child { + hasTestTag(MainScreenTestTags.TOTAL_BALANCE_MENU_ITEM) + hasText(getResourceString(R.string.common_rename)) + } + + val totalBalanceMenuDeleteWallet: KNode = child { + hasTestTag(MainScreenTestTags.TOTAL_BALANCE_MENU_ITEM) + hasText(getResourceString(R.string.common_delete)) + } + + val totalBalanceText: KNode = child { + hasParent(withTestTag(MainScreenTestTags.WALLET_BALANCE)) + } + /** * Find token list item with title and address */ @@ -86,19 +105,6 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) } } - /** - * Find node with wallet balance using lazyList. This construction doesn't affect next step with lazyList. - */ - @OptIn(ExperimentalTestApi::class) - fun walletBalance(): KNode { - return lazyList.childWith { - hasAnyDescendant(withTestTag(MainScreenTestTags.WALLET_LIST_ITEM)) - }.child { - hasTestTag(MainScreenTestTags.WALLET_BALANCE) - useUnmergedTree = true - } - } - @OptIn(ExperimentalTestApi::class) fun organizeTokensButton(): KNode { return lazyList.childWith { @@ -129,6 +135,12 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) } } + fun KNode.assertIsUnreachable() { + this { + hasAnyAncestor(withText(getResourceString(R.string.common_unreachable))) + assertIsDisplayed() + } + } /** * This assertion is required to properly verify the token's absence in the semantic tree. diff --git a/app/src/androidTest/kotlin/com/tangem/tests/balance/TotalBalanceLongTapTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/balance/TotalBalanceLongTapTest.kt new file mode 100644 index 0000000000..05e052bdd4 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/balance/TotalBalanceLongTapTest.kt @@ -0,0 +1,43 @@ +package com.tangem.tests.balance + +import androidx.compose.ui.test.longClick +import com.tangem.common.BaseTestCase +import com.tangem.common.constants.TestConstants.TOTAL_BALANCE +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 TotalBalanceLongTapTest : BaseTestCase() { + + @Test + @AllureId("3965") + @DisplayName("Total balance: check long tap on block without biometry") + fun whenBiometryIsOffTest() { + setupHooks().run { + step("Open 'Main Screen'") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses(TOTAL_BALANCE) + } + step("Long tap on total balance block") { + onMainScreen { + totalBalanceContainer.performTouchInput { + longClick() + } + } + } + step("Assert 'Rename' button is not displayed") { + onMainScreen { totalBalanceMenuRenameWallet.assertIsNotDisplayed() } + } + step("Assert 'Delete' button is not displayed") { + onMainScreen { totalBalanceMenuDeleteWallet.assertIsNotDisplayed() } + } + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/tests/balance/TotalBalanceUnavailableTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/balance/TotalBalanceUnavailableTest.kt new file mode 100644 index 0000000000..f44bf4ffa4 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/balance/TotalBalanceUnavailableTest.kt @@ -0,0 +1,145 @@ +package com.tangem.tests.balance + +import com.tangem.common.BaseTestCase +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 com.tangem.utils.StringsSigns.DASH_SIGN +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 TotalBalanceUnavailableTest : BaseTestCase() { + + @Test + @AllureId("148") + @DisplayName("Total balance: check dash sign when addresses are not derived") + fun whenDerivationsDoesNotSyncedTest() { + setupHooks().run { + step("Open 'Main Screen'") { + openMainScreen() + } + step("Do not synchronize addresses") { + onMainScreen { synchronizeAddressesButton.assertIsDisplayed() } + } + step("Assert dash sign is displayed in total balance") { + onMainScreen { totalBalanceText.assertTextContains(DASH_SIGN) } + } + } + } + + @Test + @AllureId("3993") + @DisplayName("Total balance: check dash sign when network is unreachable") + fun whenNetworkIsUnreachableTest() { + val scenarioName = "eth_network_balance" + val scenarioState = "Unreachable" + val tokenTitle = "Ethereum" + setupHooks( + additionalAfterSection = { + resetWireMockScenarioState(scenarioName) + } + ).run { + step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") { + setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState) + } + step("Open 'Main Screen'") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses(DASH_SIGN) + } + step("Assert 'Synchronize addresses' button does not exist") { + onMainScreen { + flakySafely { + synchronizeAddressesButton.assertDoesNotExist() + } + } + } + step("Assert dash sign is displayed in total balance") { + onMainScreen { totalBalanceText.assertTextContains(DASH_SIGN) } + } + step("Assert $tokenTitle is unreachable") { + onMainScreen { tokenWithTitleAndPosition(tokenTitle, 1).assertIsUnreachable() } + } + } + } + + @Test + @AllureId("3994") + @DisplayName("Total balance: check dash sign when quotes are failed") + fun whenQuotesAreFailedTest() { + val scenarioName = "quotes_api" + val scenarioState = "Error" + val tokenTitle = "Ethereum" + setupHooks( + additionalAfterSection = { + resetWireMockScenarioState(scenarioName) + } + ).run { + step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") { + setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState) + } + step("Open 'Main Screen'") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses(DASH_SIGN) + } + step("Assert 'Synchronize addresses' button does not exist") { + onMainScreen { + flakySafely { + synchronizeAddressesButton.assertDoesNotExist() + } + } + } + step("Assert dash sign is displayed in total balance") { + onMainScreen { totalBalanceText.assertTextContains(DASH_SIGN) } + } + step("Assert $tokenTitle is unreachable") { + onMainScreen { tokenWithTitleAndPosition(tokenTitle, 1).assertIsUnreachable() } + } + } + } + + @Test + @AllureId("3995") + @DisplayName("Total balance: check dash sign when added custom token without rate") + fun whenCustomTokenWithoutRateAddedTest() { + val scenarioName = "user_tokens_api" + val scenarioState = "CustomTokenAdded" + val tokenTitle = "Myria" + setupHooks( + additionalAfterSection = { + resetWireMockScenarioState(scenarioName) + } + ).run { + step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") { + setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState) + } + step("Open 'Main Screen'") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses(DASH_SIGN) + } + step("Assert 'Synchronize addresses' button does not exist") { + onMainScreen { + flakySafely { + synchronizeAddressesButton.assertDoesNotExist() + } + } + } + step("Assert dash sign is displayed in total balance") { + onMainScreen { totalBalanceText.assertTextContains(DASH_SIGN) } + } + step("Assert $tokenTitle is unreachable") { + onMainScreen { tokenWithTitleAndPosition(tokenTitle, 4).assertIsUnreachable() } + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/MainScreenTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/MainScreenTestTags.kt index dfef1326a7..117cfefc69 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/MainScreenTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/MainScreenTestTags.kt @@ -5,8 +5,11 @@ object MainScreenTestTags { const val MORE_BUTTON = "MAIN_SCREEN_MORE_BUTTON" const val TOP_BAR = "MAIN_SCREEN_TOP_BAR" const val TOKEN_LIST_ITEM = "MAIN_SCREEN_TOKEN_LIST_ITEM" - const val WALLET_BALANCE = "MAIN_SCREEN_WALLET_BALANCE" const val WALLET_LIST_ITEM = "MAIN_SCREEN_WALLET_LIST_ITEM" const val ORGANIZE_TOKENS_BUTTON = "MAIN_SCREEN_ORGANIZE_TOKENS_BUTTON" const val MULTI_CURRENCY_ACTION_BUTTON = "MAIN_SCREEN_MULTI_CURRENCY_ACTION_BUTTON" + + const val WALLET_BALANCE = "MAIN_SCREEN_WALLET_BALANCE" + const val TOTAL_BALANCE_MENU_ITEM = "MAIN_SCREEN_TOTAL_BALANCE_MENU_ITEM" + const val TOTAL_BALANCE_CONTAINER = "MAIN_SCREEN_TOTAL_BALANCE_CONTAINER" } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt index 9b0b6f2e1e..d953f8a8ba 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt @@ -164,6 +164,7 @@ private fun CardContainer( modifier = modifier .defaultMinSize(minHeight = TangemTheme.dimens.size108) .onSizeChanged { itemSize = it } + .testTag(MainScreenTestTags.TOTAL_BALANCE_CONTAINER) .then( if (isLockedState || dropDownItems.isEmpty()) { Modifier @@ -244,7 +245,9 @@ private fun ManageWalletContextMenu( private fun MenuItem(text: TextReference, imageVector: ImageVector, onClick: () -> Unit) { DropdownMenuItem( text = { Text(text = text.resolveReference(), style = TangemTheme.typography.subtitle2) }, - modifier = Modifier.background(color = TangemTheme.colors.background.secondary), + modifier = Modifier + .background(color = TangemTheme.colors.background.secondary) + .testTag(MainScreenTestTags.TOTAL_BALANCE_MENU_ITEM), trailingIcon = { Icon(imageVector = imageVector, contentDescription = null) }, onClick = onClick, colors = MenuDefaults.itemColors( @@ -271,7 +274,7 @@ private fun Balance(state: WalletCardState, isBalanceHidden: Boolean, modifier: AnimatedContent( targetState = (state as? WalletCardState.Content)?.balance?.orMaskWithStars(isBalanceHidden).orEmpty(), label = "Update the balance", - modifier = modifier, + modifier = modifier.testTag(MainScreenTestTags.WALLET_BALANCE), transitionSpec = { fadeIn(animationSpec = tween(durationMillis = 220, delayMillis = 90)) togetherWith fadeOut(animationSpec = tween(durationMillis = 90)) @@ -280,9 +283,7 @@ private fun Balance(state: WalletCardState, isBalanceHidden: Boolean, modifier: when (state) { is WalletCardState.Content -> { ResizableText( - modifier = Modifier - .defaultMinSize(minHeight = TangemTheme.dimens.size32) - .testTag(MainScreenTestTags.WALLET_BALANCE), + modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32), text = balance, fontSizeRange = FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize), overflow = TextOverflow.Ellipsis,