From 79aaeaf74f43cba29811fb743b8f1a28a8b1c183 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 12 Mar 2026 14:24:35 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../kotlin/com/tangem/common/BaseTestCase.kt | 19 ++ .../common/extensions/CustomAssertsExt.kt | 51 +++++ .../com/tangem/scenarios/MarketsScenarios.kt | 50 +++++ .../screens/MarketsExchangesPageObject.kt | 71 ++++++ .../com/tangem/screens/MarketsPageObject.kt | 19 ++ .../tests/markets/MarketsExchangesTest.kt | 209 ++++++++++++++++++ .../token/internal/TokenFiatAmount.kt | 5 +- .../tangem/core/ui/test/MarketsTestTags.kt | 1 + .../core/ui/test/TokenElementsTestTags.kt | 1 + .../detailed/components/ListedOnBlock.kt | 5 +- 10 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 app/src/androidTest/kotlin/com/tangem/screens/MarketsExchangesPageObject.kt create mode 100644 app/src/androidTest/kotlin/com/tangem/tests/markets/MarketsExchangesTest.kt diff --git a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt index cb51a8d6be..0f2fb8d3c6 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt @@ -30,6 +30,8 @@ import kotlinx.coroutines.runBlocking import org.junit.Rule import org.junit.rules.RuleChain import org.junit.rules.TestRule +import org.junit.rules.TestWatcher +import org.junit.runner.Description import javax.inject.Inject abstract class BaseTestCase : TestCase( @@ -68,6 +70,12 @@ abstract class BaseTestCase : TestCase( */ val composeTestRule = createEmptyComposeRule() + private val semanticTreePrinterRule = object : TestWatcher() { + override fun failed(e: Throwable?, description: Description?) { + runCatching { printAllRoots() } + } + } + @Rule @JvmField val ruleChain: TestRule = RuleChain @@ -76,6 +84,7 @@ abstract class BaseTestCase : TestCase( .around(permissionRule) .around(apiEnvironmentRule) .around(composeTestRule) + .around(semanticTreePrinterRule) /** * Initialization order is important: @@ -140,6 +149,16 @@ abstract class BaseTestCase : TestCase( .printToLog(tag, maxDepth) } + fun printAllRoots( + tag: String = "ComposeTree", + ) { + val roots = composeTestRule.onAllNodes(isRoot()) + val count = roots.fetchSemanticsNodes().size + repeat(count) { index -> + roots[index].printToLog("$tag[$index]") + } + } + fun waitForIdle() = composeTestRule.waitForIdle() private fun applicationInjectionRule(): ApplicationInjectionExecutionRule { diff --git a/app/src/androidTest/kotlin/com/tangem/common/extensions/CustomAssertsExt.kt b/app/src/androidTest/kotlin/com/tangem/common/extensions/CustomAssertsExt.kt index 827136c346..2815c2cc17 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/extensions/CustomAssertsExt.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/extensions/CustomAssertsExt.kt @@ -1,10 +1,14 @@ package com.tangem.common.extensions +import androidx.compose.ui.semantics.SemanticsNode +import androidx.compose.ui.semantics.SemanticsProperties import androidx.compose.ui.test.SemanticsMatcher import com.tangem.common.utils.LazyListItemNode import com.tangem.core.ui.components.buttons.actions.HasBadgeKey import com.tangem.core.ui.components.buttons.actions.IsDimmedKey import io.github.kakaocup.compose.node.element.KNode +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue fun assertElementDoesNotExist( elementProvider: () -> KNode, @@ -42,4 +46,51 @@ fun Any.assertHasBadge(expectedValue: Boolean = true) { is KNode, is LazyListItemNode -> this.assert(matcher) else -> throw IllegalArgumentException("Unsupported type: ${this::class}") } +} + +fun List.assertSortedByVolumeDescending() { + val volumes = this.mapNotNull { parseVolume(it) } + assertFalse("Trading volumes list should not be empty", volumes.isEmpty()) + assertTrue( + "Exchanges list should be sorted by volume in descending order", + volumes == volumes.sortedDescending(), + ) +} + +fun List.assertExchangeTypesAreCexOrDex() { + assertFalse("Exchange types list should not be empty", isEmpty()) + forEach { node -> + val text = extractText(node) + assertTrue( + "Exchange type should be 'CEX' or 'DEX', but found: $text", + text == "CEX" || text == "DEX", + ) + } +} + +fun List.assertTrustScoresValid() { + val validScores = setOf("Risky", "Caution", "Trusted") + assertFalse("Trust scores list should not be empty", isEmpty()) + forEach { node -> + val text = extractText(node) + assertTrue( + "Trust score should be one of $validScores, but found: $text", + text in validScores, + ) + } +} + +/** + * Extracts the text value from a semantic node's config. + */ +private fun extractText(node: SemanticsNode): String? { + if (SemanticsProperties.Text in node.config) { + return node.config[SemanticsProperties.Text].firstOrNull()?.text + } + return null +} + +private fun parseVolume(node: SemanticsNode): Double? { + val text = extractText(node) ?: return null + return text.replace("[^0-9.]".toRegex(), "").toDoubleOrNull() } \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/MarketsScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/MarketsScenarios.kt index 02142e077c..b4749cd5d3 100644 --- a/app/src/androidTest/kotlin/com/tangem/scenarios/MarketsScenarios.kt +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/MarketsScenarios.kt @@ -1,8 +1,12 @@ package com.tangem.scenarios import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.SwipeDirection import com.tangem.common.extensions.clickWithAssertion +import com.tangem.common.extensions.swipeMarketsBlock +import com.tangem.common.extensions.swipeVertical import com.tangem.screens.onMainScreen +import com.tangem.screens.onMarketsExchangesScreen import com.tangem.screens.onMarketsScreen import com.tangem.screens.onMarketsTokenDetailsScreen import io.qameta.allure.kotlin.Allure.step @@ -23,4 +27,50 @@ fun BaseTestCase.openMarketTokenDetailsScreen(blockchainName: String, tokenName: waitForIdle() onMarketsTokenDetailsScreen { tokenWithTitle(tokenName).clickWithAssertion() } } +} + +fun BaseTestCase.assertMarketsExchangesScreen() { + step("Assert 'Title' is displayed") { + onMarketsExchangesScreen { exchangesTitle.assertIsDisplayed() } + } + step("Assert 'Exchange name' is displayed") { + onMarketsExchangesScreen { exchangeName.assertIsDisplayed() } + } + step("Assert 'Logo' is displayed") { + onMarketsExchangesScreen { exchangeLogo.assertIsDisplayed() } + } + step("Assert 'Exchange type' is displayed") { + onMarketsExchangesScreen { exchangeType.assertIsDisplayed() } + } + step("Assert 'Trust score' is displayed") { + onMarketsExchangesScreen { trustScore.assertIsDisplayed() } + } +} + +fun BaseTestCase.openMarketsScreen() { + step("Open 'Main Screen'") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses() + } + step("Open 'Markets' screen") { + swipeMarketsBlock(SwipeDirection.UP) + waitForIdle() + } +} + +fun BaseTestCase.openMarketsExchangesScreen(tokenName: String) { + openMarketsScreen() + step("Click on '$tokenName' token") { + onMarketsScreen { tokenWithTitle(tokenName).clickWithAssertion() } + waitForIdle() + } + step("Scroll down") { + swipeVertical(SwipeDirection.UP) + } + step("Click on 'Listed on exchanges' block") { + onMarketsScreen { listedOnBlockContainer.performClick() } + waitForIdle() + } } \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/MarketsExchangesPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/MarketsExchangesPageObject.kt new file mode 100644 index 0000000000..00d7f17067 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/MarketsExchangesPageObject.kt @@ -0,0 +1,71 @@ +package com.tangem.screens + +import androidx.compose.ui.semantics.SemanticsNode +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import androidx.compose.ui.test.hasParent +import androidx.compose.ui.test.hasTestTag +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.test.TokenElementsTestTags +import com.tangem.core.ui.test.TopAppBarTestTags +import com.tangem.features.onramp.impl.R +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen +import io.github.kakaocup.compose.node.element.KNode +import io.github.kakaocup.kakao.common.utilities.getResourceString + +class MarketsExchangesPageObject(private val provider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = provider) { + + fun allTradingVolumeNodes(): List = + provider + .onAllNodes(hasTestTag(TokenElementsTestTags.TOKEN_FIAT_AMOUNT_TEXT)) + .fetchSemanticsNodes() + + fun allExchangeTypeNodes(): List = + provider + .onAllNodes(hasParent(hasParent(hasTestTag(TokenElementsTestTags.TOKEN_PRICE)))) + .fetchSemanticsNodes() + + fun allTrustScoreNodes(): List = + provider + .onAllNodes(hasParent(hasTestTag(TokenElementsTestTags.TOKEN_CRYPTO_AMOUNT))) + .fetchSemanticsNodes() + + val exchangesTitle: KNode = child { + hasTestTag(TopAppBarTestTags.TITLE) + hasText(getResourceString(R.string.markets_token_details_exchanges_title)) + useUnmergedTree = true + } + + val exchangeName: KNode = child { + hasTestTag(TokenElementsTestTags.TOKEN_TITLE) + } + + val exchangeLogo: KNode = child { + hasTestTag(TokenElementsTestTags.TOKEN_ICON) + } + + val tradingVolume: KNode = child { + hasTestTag(TokenElementsTestTags.TOKEN_FIAT_AMOUNT) + } + + val exchangeType: KNode = child { + hasTestTag(TokenElementsTestTags.TOKEN_PRICE) + } + + val trustScore: KNode = child { + hasTestTag(TokenElementsTestTags.TOKEN_CRYPTO_AMOUNT) + } + + val tryAgainButton: KNode = child { + hasText(getResourceString(R.string.alert_button_try_again)) + } + + val errorMessage: KNode = child { + hasText(getResourceString(R.string.markets_loading_error_title)) + useUnmergedTree = true + } +} + +internal fun BaseTestCase.onMarketsExchangesScreen(function: MarketsExchangesPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/MarketsPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/MarketsPageObject.kt index ff3a31ce16..1eefd12486 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/MarketsPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/MarketsPageObject.kt @@ -35,6 +35,25 @@ class MarketsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : useUnmergedTree = true } + val listedOnExchangesCount: KNode = child { + hasTestTag(MarketsTestTags.LISTED_ON_EXCHANGES_COUNT) + useUnmergedTree = true + } + + val listedOnBlockContainer: KNode = child { + hasText(getResourceString(R.string.markets_token_details_listed_on), substring = true) + } + + val listedOnEmptyText: KNode = child { + hasText(getResourceString(R.string.markets_token_details_empty_exchanges)) + useUnmergedTree = true + } + + val seeAllButton: KNode = child { + hasText(getResourceString(com.tangem.core.ui.R.string.common_see_all)) + useUnmergedTree = true + } + fun tokenWithTitle(title: String): KNode { return child { hasTestTag(MarketsTestTags.TOKENS_LIST_ITEM) diff --git a/app/src/androidTest/kotlin/com/tangem/tests/markets/MarketsExchangesTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/markets/MarketsExchangesTest.kt new file mode 100644 index 0000000000..86d9afdcee --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/markets/MarketsExchangesTest.kt @@ -0,0 +1,209 @@ +package com.tangem.tests.markets + +import com.tangem.common.BaseTestCase +import com.tangem.common.annotations.ApiEnv +import com.tangem.common.annotations.ApiEnvConfig +import com.tangem.common.extensions.* +import com.tangem.common.utils.resetWireMockScenarioState +import com.tangem.common.utils.setWireMockScenarioState +import com.tangem.datasource.api.common.config.ApiConfig +import com.tangem.datasource.api.common.config.ApiEnvironment +import com.tangem.scenarios.* +import com.tangem.screens.onMarketsExchangesScreen +import com.tangem.screens.onMarketsScreen +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 MarketsExchangesTest : BaseTestCase() { + + @Test + @ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD)) + @AllureId("58") + @DisplayName("Markets: verify exchanges list screen") + fun marketsExchangesListTest() { + val tokenName = "Solana" + setupHooks().run { + step("Open 'Markets' screen") { + openMarketsScreen() + } + step("Click on 'See all' button") { + onMarketsScreen { seeAllButton.clickWithAssertion() } + } + step("Click on '$tokenName' token") { + onMarketsScreen { tokenWithTitle(tokenName).clickWithAssertion() } + waitForIdle() + } + step("Scroll down") { + swipeVertical(SwipeDirection.UP) + } + step("Click on 'Listed on exchanges' block") { + onMarketsScreen { listedOnBlockContainer.performClick() } + } + step("Assert 'Exchanges' list screen is displayed") { + assertMarketsExchangesScreen() + } + } + } + + @Test + @AllureId("56") + @ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD)) + @DisplayName("Markets: verify exchanges block is displayed in token details") + fun marketsExchangesBlockDisplayedTest() { + val tokenName = "Bitcoin" + setupHooks().run { + step("Open 'Main Screen'") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses() + } + step("Open 'Markets' screen") { + swipeMarketsBlock(SwipeDirection.UP) + waitForIdle() + } + step("Click on '$tokenName' token") { + onMarketsScreen { tokenWithTitle(tokenName).clickWithAssertion() } + waitForIdle() + } + step("Scroll down") { + swipeVertical(SwipeDirection.UP) + } + step("Assert 'Listed on exchanges' block has title") { + onMarketsScreen { listedOnBlockContainer.assertIsDisplayed() } + } + step("Assert 'Listed on exchanges' block has exchanges count") { + onMarketsScreen { listedOnExchangesCount.assertIsDisplayed() } + } + step("Assert 'Listed on exchanges' block has arrow button") { + onMarketsScreen { listedOnBlockContainer.assertHasClickAction() } + } + step("Tap on 'Listed on exchanges' block and navigate to exchanges list") { + onMarketsScreen { listedOnBlockContainer.performClick() } + waitForIdle() + } + step("Assert 'Exchanges' list screen is displayed") { + assertMarketsExchangesScreen() + } + } + } + + @Test + @AllureId("60") + @ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD)) + @DisplayName("Markets: verify exchanges list is sorted by volume descending") + fun marketsExchangesListSortedByVolumeTest() { + val tokenName = "Bitcoin" + setupHooks().run { + step("Open 'Markets Exhanges Screen with token: $tokenName'") { + openMarketsExchangesScreen(tokenName) + } + step("Assert exchanges are sorted by trading volume in descending order") { + flakySafely { + onMarketsExchangesScreen { allTradingVolumeNodes().assertSortedByVolumeDescending() } + } + } + } + } + + @Test + @AllureId("61") + @ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD)) + @DisplayName("Markets: verify exchange types are CEX or DEX") + fun marketsExchangesTypeTest() { + val tokenName = "Bitcoin" + setupHooks().run { + step("Open 'Markets Exhanges Screen with token: $tokenName'") { + openMarketsExchangesScreen(tokenName) + } + step("Assert exchange types list is not empty and all types are 'CEX' or 'DEX'") { + flakySafely { + onMarketsExchangesScreen { allExchangeTypeNodes().assertExchangeTypesAreCexOrDex() } + } + } + } + } + + @Test + @AllureId("62") + @ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD)) + @DisplayName("Markets: verify exchange trust scores are valid") + fun marketsExchangesTrustScoreTest() { + val tokenName = "Bitcoin" + setupHooks().run { + step("Open 'Markets Exhanges Screen with token: $tokenName'") { + openMarketsExchangesScreen(tokenName) + } + step("Assert trust scores list is not empty and all scores are valid") { + flakySafely { + onMarketsExchangesScreen { allTrustScoreNodes().assertTrustScoresValid() } + } + } + } + } + + @Test + @AllureId("57") + @DisplayName("Markets: verify empty exchanges state") + fun marketsExchangesEmptyTest() { + val tokenName = "Bitcoin" + val scenarioName = "coins_bitcoin" + setupHooks( + additionalBeforeAppLaunchSection = { + setWireMockScenarioState(scenarioName = scenarioName, state = "EmptyExchanges") + }, + additionalAfterSection = { + resetWireMockScenarioState(scenarioName) + }, + ).run { + step("Open 'Markets Exhanges Screen with token: $tokenName'") { + openMarketsExchangesScreen(tokenName) + } + step("Assert 'Listed on exchanges' block shows no exchanges") { + onMarketsScreen { listedOnEmptyText.assertIsDisplayed() } + } + } + } + + @Test + @AllureId("59") + @DisplayName("Markets: verify exchanges error state and retry") + fun marketsExchangesErrorStateTest() { + val tokenName = "Bitcoin" + val scenarioName = "bitcoin_exchange" + setupHooks( + additionalBeforeAppLaunchSection = { + setWireMockScenarioState(scenarioName = scenarioName, state = "Unreachable") + }, + additionalAfterSection = { + resetWireMockScenarioState(scenarioName) + }, + ).run { + step("Open 'Markets Exhanges Screen with token: $tokenName'") { + openMarketsExchangesScreen(tokenName) + } + step("Assert 'Unable to load data' error state is displayed") { + step("Assert 'Error message' is displayed") { + onMarketsExchangesScreen { errorMessage.assertIsDisplayed() } + } + step("Assert 'Try again button' is displayed") { + onMarketsExchangesScreen { tryAgainButton.assertIsDisplayed() } + } + + } + step("Set WireMock scenario '$scenarioName' to state 'Started'") { + setWireMockScenarioState(scenarioName = scenarioName, state = "Started") + } + step("Tap 'Try again' button") { + onMarketsExchangesScreen { tryAgainButton.clickWithAssertion() } + waitForIdle() + } + step("Assert 'Exchanges' list screen is displayed after retry") { + assertMarketsExchangesScreen() + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenFiatAmount.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenFiatAmount.kt index 0651618f73..d821328360 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenFiatAmount.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenFiatAmount.kt @@ -13,6 +13,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTag +import com.tangem.core.ui.test.TokenElementsTestTags import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextOverflow @@ -121,7 +124,7 @@ private fun FiatAmountText( isFlickering: Boolean = false, ) { Text( - modifier = modifier, + modifier = modifier.semantics { testTag = TokenElementsTestTags.TOKEN_FIAT_AMOUNT_TEXT }, text = text, maxLines = 1, overflow = TextOverflow.Ellipsis, diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/MarketsTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/MarketsTestTags.kt index 0721a092cd..8ab6a276ae 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/MarketsTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/MarketsTestTags.kt @@ -4,4 +4,5 @@ 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" } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt index d513f7791c..70277a9e2c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt @@ -5,6 +5,7 @@ object TokenElementsTestTags { const val TOKEN_ICON = "TOKEN_ICON" const val TOKEN_PRICE = "TOKEN_PRICE" const val TOKEN_FIAT_AMOUNT = "TOKEN_FIAT_AMOUNT" + const val TOKEN_FIAT_AMOUNT_TEXT = "TOKEN_FIAT_AMOUNT_TEXT" 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" diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/ListedOnBlock.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/ListedOnBlock.kt index 77d5ba5239..7836a63094 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/ListedOnBlock.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/ListedOnBlock.kt @@ -21,12 +21,15 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTag import com.tangem.common.ui.R import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.components.block.information.InformationBlock import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.test.MarketsTestTags import com.tangem.features.feed.ui.market.detailed.state.ListedOnUM import kotlinx.coroutines.delay @@ -99,7 +102,7 @@ internal fun ListedOnBlockPlaceholder(modifier: Modifier = Modifier) { private fun Description(state: ListedOnUM, modifier: Modifier = Modifier) { Text( text = state.description.resolveReference(), - modifier = modifier, + modifier = modifier.semantics { testTag = MarketsTestTags.LISTED_ON_EXCHANGES_COUNT }, color = TangemTheme.colors.text.tertiary, overflow = TextOverflow.Ellipsis, maxLines = 1,