Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-16 14:32:40 +04:00
parent 9b1bf5813f
commit ef39279cb9
12 changed files with 187 additions and 20 deletions

View file

@ -0,0 +1,25 @@
package com.tangem.common.extensions
import io.github.kakaocup.compose.node.element.KNode
fun assertElementDoesNotExist(
elementProvider: () -> KNode,
elementDescription: String,
) {
try {
elementProvider().assertExists()
throw AssertionError("$elementDescription should not exist but was found")
} catch (e: AssertionError) {
val isNotFoundError = e.message?.let { message ->
message.contains("No node found") ||
message.contains("scrollable container") ||
message.contains("There are no existing nodes") ||
message.contains("There are no existing nodes for that selector")
} ?: false
if (isNotFoundError) {
return
} else {
throw e
}
}
}

View file

@ -7,10 +7,7 @@ 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.BaseButtonTestTags
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.core.ui.test.NotificationTestTags
import com.tangem.core.ui.test.TokenElementsTestTags
import com.tangem.core.ui.test.*
import com.tangem.core.ui.utils.LazyListItemPositionSemantics
import com.tangem.feature.wallet.impl.R
import io.github.kakaocup.compose.node.element.ComposeScreen
@ -51,6 +48,45 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
hasText(getResourceString(R.string.common_buy))
}
val walletNameText: KNode = child {
hasTestTag(MainScreenTestTags.CARD_TITLE)
useUnmergedTree = true
}
val walletImage: KNode = child {
hasTestTag(MainScreenTestTags.CARD_IMAGE)
useUnmergedTree = true
}
val marketPriceBlock: KNode = child {
hasTestTag(MarketPriceBlockTestTags.BLOCK)
useUnmergedTree = true
}
val marketPriceText: KNode = child {
hasTestTag(MarketPriceBlockTestTags.TEXT)
useUnmergedTree = true
}
val transactionsExplorerIcon: KNode = child {
hasTestTag(TransactionHistoryBlockTestTags.EXPLORER_ICON)
useUnmergedTree = true
}
val transactionsTitle: KNode = child {
hasTestTag(TransactionHistoryBlockTestTags.TITLE_TEXT)
hasText(getResourceString(R.string.common_transactions))
useUnmergedTree = true
}
fun transactionsExplorer(): KNode {
return child {
hasTestTag(TransactionHistoryBlockTestTags.EXPLORER_TEXT)
hasText(getResourceString(R.string.common_explorer))
useUnmergedTree = true
}
}
val notificationContainer: KNode = child {
hasTestTag(NotificationTestTags.CONTAINER)
useUnmergedTree = true
@ -146,6 +182,14 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
}
}
fun organizeTokensButtonWithoutLazySearch(): KNode {
return child {
hasTestTag(MainScreenTestTags.ORGANIZE_TOKENS_BUTTON)
hasText(getResourceString(R.string.organize_tokens_title))
useUnmergedTree = true
}
}
fun tokenNetworkGroupTitle(tokenNetwork: String): KNode {
return lazyList.child {
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)

View file

@ -5,8 +5,8 @@ import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
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.OrganizeTokensScreenTestTags
import com.tangem.core.ui.test.TokenElementsTestTags
import com.tangem.core.ui.utils.LazyListItemPositionSemantics
import com.tangem.feature.wallet.impl.R
import io.github.kakaocup.compose.node.element.ComposeScreen
@ -14,10 +14,10 @@ import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onCompose
import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
import androidx.compose.ui.test.hasText as withText
import androidx.compose.ui.test.hasTestTag as withTestTag
import androidx.compose.ui.test.hasAnySibling as withAnySibling
import androidx.compose.ui.test.hasAnyChild as withAnyChild
import androidx.compose.ui.test.hasAnySibling as withAnySibling
import androidx.compose.ui.test.hasTestTag as withTestTag
import androidx.compose.ui.test.hasText as withText
class OrganizeTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<OrganizeTokensPageObject>(semanticsProvider = semanticsProvider) {
@ -25,10 +25,12 @@ class OrganizeTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvi
// region TopBar
val title: KNode = child {
hasText(getResourceString(R.string.organize_tokens_title))
useUnmergedTree = true
}
private val topBarGroupButton: KNode = child {
hasTestTag(OrganizeTokensScreenTestTags.GROUP_BUTTON)
useUnmergedTree = true
}
val groupButton: KNode = topBarGroupButton.child {
@ -100,10 +102,14 @@ class OrganizeTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvi
return lazyList.child {
hasTestTag(OrganizeTokensScreenTestTags.DRAGGABLE_IMAGE)
useUnmergedTree = true
hasParent(withTestTag(TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK)
.and(withAnySibling(withTestTag(TokenElementsTestTags.TOKEN_TITLE)
.and(withAnyChild(withText(tokenTitle))))
)
hasParent(
withTestTag(TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK)
.and(
withAnySibling(
withTestTag(TokenElementsTestTags.TOKEN_TITLE)
.and(withAnyChild(withText(tokenTitle)))
)
)
)
}
}

View file

@ -4,17 +4,17 @@ import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.common.utils.LazyListItemNode
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.NotificationTestTags
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
import com.tangem.features.tokendetails.impl.R
import com.tangem.core.ui.utils.LazyListItemPositionSemantics
import com.tangem.features.tokendetails.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
import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
import androidx.compose.ui.test.hasText as withText
class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
@ -156,7 +156,6 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
hasText(getResourceString(R.string.common_buy_currency, feeCurrencySymbol))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onTokenDetailsScreen(function: TokenDetailsPageObject.() -> Unit) =

View file

@ -0,0 +1,35 @@
package com.tangem.steps
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.assertElementDoesNotExist
import com.tangem.screens.onMainScreen
import io.qameta.allure.kotlin.Allure.step
fun BaseTestCase.checkSingleCurrencyMainScreen(cardBlockchain: String, cardTitle: String) {
step("Assert card title equal '$cardTitle'") {
onMainScreen { walletNameText.assertTextEquals(cardTitle) }
}
step("Assert 'Transactions' block is displayed") {
onMainScreen { transactionsExplorer().assertExists() }
}
step("Assert 'Transactions' title is displayer") {
onMainScreen { transactionsTitle.assertIsDisplayed() }
}
step("Assert 'Explorer Icon' is displayed") {
onMainScreen { transactionsExplorerIcon.assertIsDisplayed() }
}
step("Assert card image is displayed") { //TODO: Придумать нормальную проверку / реализовать скриншот-тестинг
onMainScreen { walletImage.assertIsDisplayed() }
}
step("Assert 'Market Price' on single card main screen is displayed") {
onMainScreen { marketPriceBlock.assertIsDisplayed() }
}
step("Assert 'Market Price' title equals $cardBlockchain Market Price") {
onMainScreen { marketPriceText.assertTextContains("$cardBlockchain Market Price") }
}
step("Assert 'Organize tokens' button is not displayed") {
onMainScreen {
assertElementDoesNotExist({ organizeTokensButtonWithoutLazySearch() }, "Organize tokens button")
}
}
}

View file

@ -0,0 +1,31 @@
package com.tangem.tests
import com.tangem.common.BaseTestCase
import com.tangem.domain.models.scan.ProductType
import com.tangem.scenarios.openMainScreen
import com.tangem.steps.checkSingleCurrencyMainScreen
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 SingleCurrencyCardsScan : BaseTestCase() {
@AllureId("868")
@DisplayName("Scan: Scanning single-currency cards")
@Test
fun singleTokenNoteScanTest() {
val cardBlockchain = "DOGE"
val cardType: ProductType = ProductType.Note
setupHooks().run {
step("Open 'Main Screen' on ${cardType.name} card") {
openMainScreen(cardType)
}
step("Check 'Main' screen for ${cardType.name} $cardBlockchain card") {
checkSingleCurrencyMainScreen(cardBlockchain = cardBlockchain, cardTitle = cardType.name)
}
}
}
}

View file

@ -10,6 +10,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -20,6 +21,7 @@ import com.tangem.core.ui.components.RectangleShimmer
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.MarketPriceBlockTestTags
import com.tangem.utils.StringsSigns.DASH_SIGN
/**
@ -45,7 +47,8 @@ fun MarketPriceBlock(state: MarketPriceBlockState, modifier: Modifier = Modifier
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size72)
.padding(all = TangemTheme.dimens.spacing12)
.onSizeChanged { rootWidth = it.width },
.onSizeChanged { rootWidth = it.width }
.testTag(MarketPriceBlockTestTags.BLOCK),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
horizontalAlignment = Alignment.Start,
) {
@ -64,6 +67,7 @@ private fun Title(currencyName: String, modifier: Modifier = Modifier) {
text = stringResourceSafe(id = R.string.wallet_marketplace_block_title, currencyName),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
modifier = Modifier.testTag(MarketPriceBlockTestTags.TEXT),
)
}
}

View file

@ -9,12 +9,14 @@ 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.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
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.TransactionHistoryBlockTestTags
/**
* Transactions block title
@ -38,10 +40,13 @@ fun TxHistoryTitle(onExploreClick: () -> Unit, modifier: Modifier = Modifier) {
text = stringResourceSafe(id = R.string.common_transactions),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
modifier = Modifier.testTag(TransactionHistoryBlockTestTags.TITLE_TEXT),
)
Row(
modifier = Modifier.clickable(onClick = onExploreClick),
modifier = Modifier
.clickable(onClick = onExploreClick)
.testTag(TransactionHistoryBlockTestTags.EXPLORER_ICON),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing2),
) {
@ -55,6 +60,7 @@ fun TxHistoryTitle(onExploreClick: () -> Unit, modifier: Modifier = Modifier) {
text = stringResourceSafe(id = R.string.common_explorer),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
modifier = Modifier.testTag(TransactionHistoryBlockTestTags.EXPLORER_TEXT),
)
}
}

View file

@ -8,6 +8,8 @@ object MainScreenTestTags {
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 CARD_TITLE = "MAIN_SCREEN_CARD_TITLE"
const val CARD_IMAGE = "MAIN_SCREEN_CARD_IMAGE"
const val WALLET_BALANCE = "MAIN_SCREEN_WALLET_BALANCE"
const val TOTAL_BALANCE_MENU_ITEM = "MAIN_SCREEN_TOTAL_BALANCE_MENU_ITEM"

View file

@ -0,0 +1,6 @@
package com.tangem.core.ui.test
object MarketPriceBlockTestTags {
const val BLOCK = "MAIN_SCREEN_MARKET_PRICE_BLOCK"
const val TEXT = "MAIN_SCREEN_MARKET_PRICE_TEXT"
}

View file

@ -0,0 +1,7 @@
package com.tangem.core.ui.test
object TransactionHistoryBlockTestTags {
const val TITLE_TEXT = "TRANSACTION_HISTORY_TITLE_TEXT"
const val EXPLORER_TEXT = "TRANSACTION_HISTORY_EXPLORER_TEXT"
const val EXPLORER_ICON = "TRANSACTION_HISTORY_EXPLORER_ICON"
}

View file

@ -261,7 +261,7 @@ private fun MenuItem(text: TextReference, imageVector: ImageVector, onClick: ()
private fun TitleText(text: String, modifier: Modifier = Modifier) {
Text(
text = text,
modifier = modifier,
modifier = modifier.testTag(MainScreenTestTags.CARD_TITLE),
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
@ -375,7 +375,9 @@ private fun Image(@DrawableRes id: Int?, modifier: Modifier = Modifier) {
Image(
painter = painterResource(id = imageRes),
contentDescription = null,
modifier = Modifier.width(width = TangemTheme.dimens.size120),
modifier = Modifier
.width(width = TangemTheme.dimens.size120)
.testTag(MainScreenTestTags.CARD_IMAGE),
contentScale = ContentScale.FillWidth,
)
}