Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-29 16:28:48 +03:00
parent 557e8eceb6
commit 5fda020fc3
58 changed files with 587 additions and 557 deletions

View file

@ -186,6 +186,7 @@ abstract class BaseTestCase : TestCase(
"VISA_ONBOARDING_ENABLED" to true,
"AND_15101_TANGEM_PAY_HOT_WALLET_ONBOARDING" to true,
"AND_15310_ADD_FUNDS_STAGE1" to true,
"APP_REDESIGN_ENABLED" to true,
)
)
}

View file

@ -115,5 +115,13 @@ private fun extractText(node: SemanticsNode): String? {
private fun parseVolume(node: SemanticsNode): Double? {
val text = extractText(node) ?: return null
return text.replace("[^0-9.]".toRegex(), "").toDoubleOrNull()
val multiplier = when {
text.contains('T', ignoreCase = true) -> 1_000_000_000_000.0
text.contains('B', ignoreCase = true) -> 1_000_000_000.0
text.contains('M', ignoreCase = true) -> 1_000_000.0
text.contains('K', ignoreCase = true) -> 1_000.0
else -> 1.0
}
val number = text.replace("[^0-9.]".toRegex(), "").toDoubleOrNull() ?: return null
return number * multiplier
}

View file

@ -11,6 +11,11 @@ fun KNode.clickWithAssertion() {
performClick()
}
fun KNode.clickWhenEnabled() {
assertIsEnabled()
performClick()
}
fun KNode.assertTextContainsSafe(
text: String,
substring: Boolean = false,

View file

@ -4,8 +4,6 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.wallet.R
import io.github.kakaocup.kakao.common.utilities.getResourceString
fun BaseTestCase.swipeVertical(
direction: SwipeDirection,
@ -31,21 +29,6 @@ fun BaseTestCase.pullToRefresh(steps: Int = 1000) {
)
}
fun BaseTestCase.swipeMarketsBlock(direction: SwipeDirection) {
val searchBarText = device.uiDevice
.findObject(By.textContains(getResourceString(R.string.markets_search_header_title)))
val bounds = searchBarText.visibleBounds
val centerX = bounds.centerX()
val startY = bounds.centerY()
val endY = when (direction) {
SwipeDirection.UP -> 50
SwipeDirection.DOWN -> device.uiDevice.displayHeight - 100
}
device.uiDevice.swipe(centerX, startY, centerX, endY, 100)
}
fun BaseTestCase.openTheAppFromRecents() {
device.uiDevice.waitForIdle()

View file

@ -6,97 +6,33 @@ import com.tangem.common.extensions.swipeVertical
import com.tangem.screens.onMainScreen
import io.qameta.allure.kotlin.Allure.step
fun BaseTestCase.checkSingleCurrencyMainScreen(
cardBlockchain: String,
cardTitle: String,
withTransactions: Boolean = false,
withWalletImage: Boolean = true
) {
fun BaseTestCase.checkSingleCurrencyMainScreen(cardTitle: String) {
step("Assert card title equal '$cardTitle'") {
onMainScreen { walletNameText.assertTextEquals(cardTitle) }
}
if (withWalletImage) {
step("Assert card image is displayed") { //TODO: create assertion method for checking images
onMainScreen { walletImage.assertIsDisplayed() }
}
} else {
step("Assert card image is not displayed") {
onMainScreen { walletImage.assertIsNotDisplayed() }
}
}
step("Assert 'Receive' button is displayed") {
onMainScreen { receiveButton.assertIsDisplayed() }
}
step("Assert 'Buy' button is displayed") {
onMainScreen { buyButton.assertIsDisplayed() }
}
step("Assert 'Send' button is displayed") {
onMainScreen { sendButton.assertIsDisplayed() }
}
step("Assert 'Sell' button is displayed") {
onMainScreen { sellButton.assertIsDisplayed() }
}
step("Assert 'Swap' button is not displayed") {
onMainScreen { swapButton.assertIsNotDisplayed() }
}
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("Swipe up") {
swipeVertical(SwipeDirection.UP)
}
if (withTransactions) {
step("Assert 'Transactions' block is displayed") {
onMainScreen { transactionsExplorerText.assertIsDisplayed() }
}
step("Assert 'Transactions' title is displayed") {
onMainScreen { transactionsTitle.assertIsDisplayed() }
}
step("Assert 'Explorer' icon is displayed") {
onMainScreen { transactionsExplorerIcon.assertIsDisplayed() }
}
} else {
step("Assert empty 'Transactions' block is displayed") {
onMainScreen { emptyTransactionBlock.assertIsDisplayed() }
}
step("Assert empty 'Transactions' block icon is displayed") {
onMainScreen { emptyTransactionBlockIcon.assertIsDisplayed() }
}
step("Assert empty 'Transactions' block text is displayed") {
onMainScreen { emptyTransactionBlockText.assertIsDisplayed() }
}
step("Assert empty 'Transactions' block 'Explore' button is displayed") {
onMainScreen { emptyTransactionBlockExploreButton.assertIsDisplayed() }
}
}
step("Assert 'Add & Manage' button is not displayed") {
onMainScreen { addAndManageButtonWithoutLazySearch.assertIsNotDisplayed() }
}
}
fun BaseTestCase.checkMultiCurrencyMainScreen(
devicesCount: String,
cardTitle: String,
withWalletImage: Boolean = true
) {
step("Assert card title equal '$cardTitle'") {
onMainScreen { walletNameText.assertTextEquals(cardTitle) }
}
if (withWalletImage) {
step("Assert card image is displayed") {
onMainScreen { walletImage.assertIsDisplayed() }
}
} else {
step("Assert card image is not displayed") {
onMainScreen { walletImage.assertIsNotDisplayed() }
}
}
step("Assert devices count equal to '$devicesCount'") {
onMainScreen { walletDevicesCount.assertTextContains(devicesCount) }
}
step("Assert 'Buy' button is displayed") {
onMainScreen { buyButton.assertIsDisplayed() }
}

View file

@ -1,28 +1,28 @@
package com.tangem.scenarios
import androidx.compose.ui.test.ExperimentalTestApi
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.SwipeDirection
import com.tangem.common.extensions.clickWithAssertion
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
fun BaseTestCase.openMarketTokenDetailsScreen(blockchainName: String, tokenName: String) {
fun BaseTestCase.openTokenDetailsFromMarketsScreen(blockchainName: String, tokenName: String) {
step("Open 'Markets' screen") {
onMainScreen { searchThroughMarketPlaceholder.performClick() }
waitForIdle()
}
step("Click on 'Search' placeholder") {
onMarketsScreen { searchThroughMarketPlaceholder.performClick() }
}
step("Click on $blockchainName blockchain") {
waitForIdle()
onMarketsScreen { tokenWithTitle(blockchainName).clickWithAssertion() }
}
step("Click on $tokenName token") {
step("Click on 'In your portfolio' block") {
waitForIdle()
onMarketsTokenDetailsScreen { inYourPortfolioBlock.clickWithAssertion() }
}
step("Click on $tokenName token in 'Your portfolio' bottom sheet") {
waitForIdle()
onMarketsTokenDetailsScreen { tokenWithTitle(tokenName).clickWithAssertion() }
}
@ -59,6 +59,7 @@ fun BaseTestCase.openMarketsScreen() {
}
}
@OptIn(ExperimentalTestApi::class)
fun BaseTestCase.openMarketsExchangesScreen(tokenName: String, shouldClickSeeAllButton: Boolean = false) {
openMarketsScreen()
if (shouldClickSeeAllButton)
@ -69,9 +70,8 @@ fun BaseTestCase.openMarketsExchangesScreen(tokenName: String, shouldClickSeeAll
onMarketsScreen { tokenWithTitle(tokenName).clickWithAssertion() }
waitForIdle()
}
step("Scroll down") {
swipeVertical(SwipeDirection.UP)
swipeVertical(SwipeDirection.UP)
step("Scroll to 'Listed on exchanges' block") {
onMarketsScreen { scrollToListedOnBlock() }
}
step("Click on 'Listed on exchanges' block") {
onMarketsScreen { listedOnBlockContainer.performClick() }

View file

@ -6,7 +6,6 @@ import com.tangem.common.constants.TestConstants.HOLD_DURATION_MS
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.extensions.assertIsDimmed
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.screens.*
@ -34,8 +33,11 @@ fun BaseTestCase.openSendScreen(
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
step("Click on 'Send' button") {
onTokenDetailsScreen { sendButton().performClick() }
step("Click on 'Transfer' button") {
onTokenDetailsScreen { transferButton.clickWithAssertion() }
}
step("Click on 'Send' button in bottom sheet") {
onTransferBottomSheet { sendButton.clickWithAssertion() }
}
}
@ -91,11 +93,11 @@ fun BaseTestCase.openSendAddressScreen(
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
step("Assert 'Send' button is not dimmed") {
onTokenDetailsScreen { sendButton().assertIsDimmed(false) }
step("Click on 'Transfer' button") {
onTokenDetailsScreen { transferButton.clickWithAssertion() }
}
step("Click on 'Send' button") {
onTokenDetailsScreen { sendButton().performClick() }
step("Click on 'Send' button in bottom sheet") {
onTransferBottomSheet { sendButton.clickWithAssertion() }
}
step("Type '$inputAmount' in input text field") {
onSendScreen {
@ -109,6 +111,13 @@ fun BaseTestCase.openSendAddressScreen(
step("Assert 'Send Address' container is displayed") {
onSendAddressScreen { container.assertIsDisplayed() }
}
step("Wait for recipient list to load") {
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) {
runCatching {
onSendAddressScreen { addressesShimmer.assertIsNotDisplayed() }
}.isSuccess
}
}
}
fun BaseTestCase.checkScanQrScreen(emptyClipboard: Boolean = true) {
@ -244,8 +253,11 @@ fun BaseTestCase.selectTokenToSendViaSwap(
networkName: String,
networkType: String? = null,
) {
step("Click on 'Send' button") {
onTokenDetailsScreen { sendButton().performClick() }
step("Click on 'Transfer' button") {
onTokenDetailsScreen { transferButton.clickWithAssertion() }
}
step("Click on 'Send' button in bottom sheet") {
onTransferBottomSheet { sendButton.clickWithAssertion() }
}
step("Click on 'Swap to another token' button") {
onSendScreen { swapToAnotherTokenButton.performClick() }

View file

@ -10,6 +10,7 @@ import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.HOLD_DURATION_MS
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.extensions.assertVisibility
import com.tangem.common.extensions.clickWhenEnabled
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.isDisplayedSafely
import com.tangem.core.ui.R as CoreUiR
@ -43,7 +44,7 @@ fun BaseTestCase.openSwapScreen(
}
SwapEntryPoint.TokenDetails -> step("Click on 'Swap' button on 'Token details' screen") {
onTokenDetailsScreen { swapButton().performClick() }
onTokenDetailsScreen { swapButton.clickWhenEnabled() }
}
SwapEntryPoint.MarketsTokenDetails -> step("Click on 'Swap' button on 'Markets' token details screen") {

View file

@ -0,0 +1,41 @@
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.BaseBottomSheetTestTags
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 AddFundsBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<AddFundsBottomSheetPageObject>(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(BaseBottomSheetTestTags.CONTAINER) },
) {
val buyButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_buy)))
useUnmergedTree = true
}
val swapButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_swap)))
useUnmergedTree = true
}
val receiveButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_receive)))
useUnmergedTree = true
}
val closeButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_close)))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onAddFundsBottomSheet(function: AddFundsBottomSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -11,7 +11,10 @@ import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
class AddTokenBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<AddTokenBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
ComposeScreen<AddTokenBottomSheetPageObject>(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(BaseBottomSheetTestTags.CONTAINER) },
) {
val title: KNode = child {
hasTestTag(BaseBottomSheetTestTags.TITLE)
@ -23,6 +26,12 @@ class AddTokenBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractions
hasText(getResourceString(R.string.common_add))
useUnmergedTree = true
}
val laterButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_later))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onAddTokenBottomSheet(function: AddTokenBottomSheetPageObject.() -> Unit) =

View file

@ -5,6 +5,7 @@ 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 androidx.compose.ui.test.swipeUp
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.getQuantityString
import com.tangem.common.extensions.hasLazyListItemPosition
@ -49,27 +50,32 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
val buyButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_buy))
hasAnyDescendant(withText(getResourceString(R.string.common_buy)))
useUnmergedTree = true
}
val sendButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_send))
hasAnyDescendant(withText(getResourceString(R.string.common_send)))
useUnmergedTree = true
}
val receiveButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_receive))
hasAnyDescendant(withText(getResourceString(R.string.common_receive)))
useUnmergedTree = true
}
val sellButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_sell))
hasAnyDescendant(withText(getResourceString(R.string.common_sell)))
useUnmergedTree = true
}
val swapButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_swap))
hasAnyDescendant(withText(getResourceString(R.string.common_swap)))
useUnmergedTree = true
}
val walletNameText: KNode = child {
@ -82,13 +88,21 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
useUnmergedTree = true
}
val walletDevicesCount: KNode = child {
hasTestTag(MainScreenTestTags.DEVICES_COUNT)
useUnmergedTree = true
/**
* Collapses the collapsing header via a touch-based swipe so that items near the bottom
* of the lazy list fall within screen bounds before programmatic childWith scroll.
* Required because TangemCollapsingTopBar places the body at y=collapsingHeight, which
* pushes lower list items off-screen when the header is expanded.
*/
private fun collapseHeader() {
screenContainer {
performTouchInput { swipeUp(startY = visibleSize.height * 0.6f, endY = visibleSize.height * 0.1f) }
}
}
@OptIn(ExperimentalTestApi::class)
fun marketPriceBlock(): LazyListItemNode {
collapseHeader()
return lazyList.childWith<LazyListItemNode> {
hasTestTag(MarketPriceBlockTestTags.BLOCK)
useUnmergedTree = true
@ -231,6 +245,7 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
*/
@OptIn(ExperimentalTestApi::class)
fun accountWithName(name: String): LazyListItemNode {
collapseHeader()
return lazyList.childWith<LazyListItemNode> {
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
hasAnyDescendant(withText(name))
@ -243,6 +258,7 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
*/
@OptIn(ExperimentalTestApi::class)
fun tokenWithTitleAndAddress(tokenTitle: String): KNode {
collapseHeader()
return lazyList.childWith<LazyListItemNode> {
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
hasText(tokenTitle)
@ -255,6 +271,7 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
@OptIn(ExperimentalTestApi::class)
fun tokenWithCustomDerivationIcon(tokenTitle: String): KNode {
collapseHeader()
return lazyList.childWith<LazyListItemNode> {
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
hasText(tokenTitle)
@ -267,6 +284,7 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
@OptIn(ExperimentalTestApi::class)
fun addAndManageButton(): KNode {
collapseHeader()
return lazyList.childWith<LazyListItemNode> {
hasTestTag(MainScreenTestTags.ADD_AND_MANAGE_BUTTON)
}.child<KNode> {
@ -282,11 +300,12 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
}
val searchThroughMarketPlaceholder: KNode = child {
hasText(getResourceString(R.string.markets_search_header_title))
hasText(getResourceString(R.string.markets_search_title_placeholder))
useUnmergedTree = true
}
fun tokenNetworkGroupTitle(tokenNetwork: String): KNode {
collapseHeader()
return lazyList.child {
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
hasAnyChild(withText(tokenNetwork))
@ -296,6 +315,7 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
@OptIn(ExperimentalTestApi::class)
fun tokenWithTitleAndPosition(tokenTitle: String, index: Int): KNode {
collapseHeader()
return lazyList.childWith<LazyListItemNode> {
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
hasText(tokenTitle)

View file

@ -2,11 +2,9 @@ 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
@ -23,16 +21,15 @@ class MarketsExchangesPageObject(private val provider: SemanticsNodeInteractions
fun allExchangeTypeNodes(): List<SemanticsNode> =
provider
.onAllNodes(hasParent(hasParent(hasTestTag(TokenElementsTestTags.TOKEN_PRICE))))
.onAllNodes(hasTestTag(TokenElementsTestTags.TOKEN_PRICE))
.fetchSemanticsNodes()
fun allTrustScoreNodes(): List<SemanticsNode> =
provider
.onAllNodes(hasParent(hasTestTag(TokenElementsTestTags.TOKEN_CRYPTO_AMOUNT)))
.onAllNodes(hasTestTag(TokenElementsTestTags.TOKEN_CRYPTO_AMOUNT))
.fetchSemanticsNodes()
val exchangesTitle: KNode = child {
hasTestTag(TopAppBarTestTags.TITLE)
hasText(getResourceString(R.string.markets_token_details_exchanges_title))
useUnmergedTree = true
}

View file

@ -1,6 +1,8 @@
package com.tangem.screens
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import androidx.compose.ui.test.hasTestTag
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.MARKETS_MAIN_NETWORK_SUFFIX
import com.tangem.core.ui.test.BaseButtonTestTags
@ -15,9 +17,9 @@ import io.github.kakaocup.kakao.common.utilities.getResourceString
class MarketsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<MarketsPageObject>(semanticsProvider = semanticsProvider) {
val addToPortfolioButton: KNode = child {
val addButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_add_to_portfolio))
hasText(getResourceString(R.string.common_add))
useUnmergedTree = true
}
@ -31,7 +33,12 @@ class MarketsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
}
val searchThroughMarketPlaceholder: KNode = child {
hasText(getResourceString(R.string.markets_search_header_title))
hasText(getResourceString(R.string.markets_search_title_placeholder))
useUnmergedTree = true
}
val tokenDetailsContent: KNode = child {
hasTestTag(MarketsTestTags.TOKEN_DETAILS_CONTENT)
useUnmergedTree = true
}
@ -41,7 +48,8 @@ class MarketsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
}
val listedOnBlockContainer: KNode = child {
hasText(getResourceString(R.string.markets_token_details_listed_on), substring = true)
hasTestTag(MarketsTestTags.LISTED_ON_BLOCK)
useUnmergedTree = true
}
val listedOnEmptyText: KNode = child {
@ -60,6 +68,13 @@ class MarketsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
hasText(title)
}
}
@ExperimentalTestApi
fun scrollToListedOnBlock() {
tokenDetailsContent {
performScrollToNode(hasTestTag(MarketsTestTags.LISTED_ON_BLOCK))
}
}
}
internal fun BaseTestCase.onMarketsScreen(function: MarketsPageObject.() -> Unit) =

View file

@ -3,14 +3,13 @@ package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.MarketTokenDetailsBottomSheetTestTags
import com.tangem.core.ui.test.TokenElementsTestTags
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
import androidx.compose.ui.test.hasTestTag as withTestTag
import androidx.compose.ui.test.hasText as withText
import com.tangem.core.ui.R as CoreUiR
class MarketsTokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<MarketsTokenDetailsPageObject>(semanticsProvider = semanticsProvider) {
@ -20,11 +19,14 @@ class MarketsTokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractions
hasText(getResourceString(R.string.common_swap), substring = true)
}
val inYourPortfolioBlock: KNode = child {
hasText(getResourceString(CoreUiR.string.markets_portfolio_block_subtitle), substring = true)
useUnmergedTree = true
}
fun tokenWithTitle(title: String): KNode = child {
hasAnyAncestor(withTestTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_TOKEN_ITEM))
hasTestTag(TokenElementsTestTags.TOKEN_TITLE)
hasAnySibling(withTestTag(TokenElementsTestTags.TOKEN_ICON))
hasAnyChild(withText(title))
hasClickAction()
useUnmergedTree = true
}
}

View file

@ -28,23 +28,18 @@ class OrganizeTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvi
useUnmergedTree = true
}
private val topBarGroupButton: KNode = child {
hasTestTag(OrganizeTokensScreenTestTags.GROUP_BUTTON)
val organizeMenuButton: KNode = child {
hasTestTag(OrganizeTokensScreenTestTags.MENU_BUTTON)
useUnmergedTree = true
}
val groupButton: KNode = topBarGroupButton.child {
val groupButton: KNode = child {
hasText(getResourceString(R.string.organize_tokens_group))
useUnmergedTree = true
}
val ungroupButton: KNode = topBarGroupButton.child {
hasText(getResourceString(R.string.organize_tokens_ungroup))
useUnmergedTree = true
}
val sortByBalanceButton: KNode = child {
hasTestTag(OrganizeTokensScreenTestTags.SORT_BY_BALANCE_BUTTON)
hasText(getResourceString(R.string.organize_tokens_sort_by_balance))
useUnmergedTree = true
}
// endregion TopBar
@ -84,7 +79,7 @@ class OrganizeTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvi
fun tokenNetworkGroupTitle(tokenNetwork: String): KNode {
return lazyList.child {
hasTestTag(OrganizeTokensScreenTestTags.GROUP_TITLE_ITEM)
hasAnyChild(withText(tokenNetwork))
hasAnyDescendant(withText(tokenNetwork))
useUnmergedTree = true
}
}

View file

@ -97,7 +97,7 @@ class SendAddressPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
): KNode = child {
hasTestTag(SendAddressScreenTestTags.RECENT_ADDRESS_ITEM)
hasAnyChild(withTestTag(SendAddressScreenTestTags.RECENT_ADDRESS_ICON))
hasAnyDescendant(withText(recipientAddress))
hasAnyDescendant(withText(recipientAddress, substring = true))
hasAnyDescendant(withTestTag(SendAddressScreenTestTags.RECENT_ADDRESS_TEXT))
useUnmergedTree = true
if (description != null) {

View file

@ -1,20 +1,15 @@
package com.tangem.screens
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.common.utils.LazyListItemNode
import com.tangem.core.ui.test.BaseActionButtonsBlockTestTags
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.NotificationTestTags
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
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.compose.node.element.lazylist.KLazyListNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
import androidx.compose.ui.test.hasTestTag as withTestTag
import androidx.compose.ui.test.hasText as withText
@ -36,18 +31,8 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
useUnmergedTree = true
}
val availableStakingBlockTitle: KNode = child {
hasTestTag(TokenDetailsScreenTestTags.STAKING_SERVICE_TITLE)
useUnmergedTree = true
}
val availableStakingBlockText: KNode = child {
hasTestTag(TokenDetailsScreenTestTags.STAKING_SERVICE_TEXT)
useUnmergedTree = true
}
val availableStakingBlockCurrencyIcon: KNode = child {
hasTestTag(TokenDetailsScreenTestTags.STAKING_CURRENCY_ICON)
fun availableStakingBlockText(apy: String): KNode = child {
hasText(getResourceString(R.string.token_details_earn_staking_subtitle, apy))
useUnmergedTree = true
}
@ -62,69 +47,39 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
useUnmergedTree = true
}
val stakingDot: KNode = child {
hasTestTag(TokenDetailsScreenTestTags.STAKING_DOT)
useUnmergedTree = true
}
val stakingTokenAmount: KNode = child {
hasTestTag(TokenDetailsScreenTestTags.STAKING_TOKEN_AMOUNT)
useUnmergedTree = true
}
val stakingChevronIcon: KNode = child {
hasTestTag(TokenDetailsScreenTestTags.STAKING_CHEVRON_ICON)
useUnmergedTree = true
val stakingTitle: KNode = child {
hasText(getResourceString(R.string.common_staking))
}
val stakingTitle: KNode = child {
hasText(getResourceString(R.string.staking_native))
val stakingEnabledTitle: KNode = child {
hasText(getResourceString(R.string.staking_enabled))
}
val title: KNode = child {
hasTestTag(TokenDetailsScreenTestTags.TOKEN_TITLE)
}
private val horizontalActionChips = KLazyListNode(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(BaseActionButtonsBlockTestTags.HORIZONTAL_ACTION_CHIPS) },
itemTypeBuilder = { itemType(::LazyListItemNode) },
positionMatcher = { position ->
SemanticsMatcher.expectValue(
LazyListItemPositionSemantics,
position
)
}
)
@OptIn(ExperimentalTestApi::class)
fun receiveButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
val addFundsButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_receive))
hasAnyDescendant(withText(getResourceString(R.string.tangempay_card_details_add_funds)))
useUnmergedTree = true
}
@OptIn(ExperimentalTestApi::class)
fun swapButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
val swapButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_swap))
hasAnyDescendant(withText(getResourceString(R.string.common_swap)))
useUnmergedTree = true
}
@OptIn(ExperimentalTestApi::class)
fun sellButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
val transferButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_sell))
}
@OptIn(ExperimentalTestApi::class)
fun buyButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_buy))
}
@OptIn(ExperimentalTestApi::class)
fun sendButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_send))
hasAnyDescendant(withText(getResourceString(R.string.common_transfer)))
useUnmergedTree = true
}
fun networkFeeNotificationIcon(feeCurrencyName: String): KNode = child {
@ -204,7 +159,6 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
hasAnyDescendant(withTestTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_SWAP_ICON))
hasAnyDescendant(withTestTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_TO_ICON))
hasAnyDescendant(withTestTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_TO_AMOUNT))
hasAnyDescendant(withTestTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_CHEVRON_ICON))
useUnmergedTree = true
}
}

View file

@ -0,0 +1,41 @@
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.BaseBottomSheetTestTags
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 TransferBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<TransferBottomSheetPageObject>(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(BaseBottomSheetTestTags.CONTAINER) },
) {
val sendButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_send)))
useUnmergedTree = true
}
val swapButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_swap)))
useUnmergedTree = true
}
val sellButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_sell)))
useUnmergedTree = true
}
val closeButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_close)))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onTransferBottomSheet(function: TransferBottomSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -27,6 +27,7 @@ import com.tangem.screens.onSendScreen
import com.tangem.screens.onStoriesScreen
import com.tangem.screens.onTokenDetailsScreen
import com.tangem.screens.onMainScreenTopBar
import com.tangem.screens.onTransferBottomSheet
import com.tangem.tap.domain.sdk.mocks.MockProvider
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
@ -94,8 +95,11 @@ class FeedbackTest : BaseTestCase() {
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
step("Click 'Send' button") {
onTokenDetailsScreen { sendButton().performClick() }
step("Click on 'Transfer' button") {
onTokenDetailsScreen { transferButton.clickWithAssertion() }
}
step("Click on 'Send' button in bottom sheet") {
onTransferBottomSheet { sendButton.clickWithAssertion() }
}
step("Type '$sendAmount' in input text field") {
onSendScreen {

View file

@ -22,7 +22,8 @@ class OrganizeTokensTest : BaseTestCase() {
fun groupTokensTest() {
setupHooks().run {
val tokenTitle = "Ethereum"
val tokenNetwork = "Ethereum network"
val networkTitleOrganize = "Ethereum"
val networkTitleMain = "Ethereum network"
step("Open 'Main Screen'") {
openMainScreen()
@ -39,17 +40,20 @@ class OrganizeTokensTest : BaseTestCase() {
tokenWithTitle(tokenTitle).assertIsDisplayed()
}
}
step("Open organize menu") {
onOrganizeTokensScreen { organizeMenuButton.clickWithAssertion() }
}
step("Click 'Group' button") {
onOrganizeTokensScreen { groupButton.clickWithAssertion() }
}
step("Assert tokens were grouped on 'Organize tokens' screen") {
onOrganizeTokensScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsDisplayed() }
onOrganizeTokensScreen { tokenNetworkGroupTitle(networkTitleOrganize).assertIsDisplayed() }
}
step("Click 'Apply' button") {
onOrganizeTokensScreen { applyButton.clickWithAssertion() }
}
step("Assert tokens were grouped on 'Main screen'") {
onMainScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsDisplayed() }
onMainScreen { tokenNetworkGroupTitle(networkTitleMain).assertIsDisplayed() }
}
step("Open 'Organize tokens' screen") {
openOrganizeTokensScreen()
@ -60,17 +64,20 @@ class OrganizeTokensTest : BaseTestCase() {
tokenWithTitle(tokenTitle).assertIsDisplayed()
}
}
step("Click 'Ungroup' button") {
onOrganizeTokensScreen { ungroupButton.clickWithAssertion() }
step("Open organize menu") {
onOrganizeTokensScreen { organizeMenuButton.clickWithAssertion() }
}
step("Click 'Group' checkbox again to ungroup") {
onOrganizeTokensScreen { groupButton.clickWithAssertion() }
}
step("Assert tokens were ungrouped on 'Organize tokens' screen") {
onOrganizeTokensScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsNotDisplayed() }
onOrganizeTokensScreen { tokenNetworkGroupTitle(networkTitleOrganize).assertIsNotDisplayed() }
}
step("Click 'Apply' button") {
onOrganizeTokensScreen { applyButton.clickWithAssertion() }
}
step("Assert tokens were ungrouped on 'Main screen'") {
onMainScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsNotDisplayed() }
onMainScreen { tokenNetworkGroupTitle(networkTitleMain).assertIsNotDisplayed() }
}
}
}
@ -185,6 +192,9 @@ class OrganizeTokensTest : BaseTestCase() {
tokenWithTitleAndPosition(polExMaticTitle, 3).assertIsDisplayed()
}
}
step("Open organize menu") {
onOrganizeTokensScreen { organizeMenuButton.clickWithAssertion() }
}
step("Click 'By Balance' button") {
onOrganizeTokensScreen {
sortByBalanceButton.clickWithAssertion()

View file

@ -35,11 +35,7 @@ class ScanCardTest : BaseTestCase() {
openMainScreen(cardType)
}
step("Check 'Main' screen for '${cardType.name}' $cardBlockchain card") {
checkSingleCurrencyMainScreen(
cardBlockchain = cardBlockchain,
cardTitle = cardType.name,
withTransactions = true
)
checkSingleCurrencyMainScreen(cardTitle = cardType.name)
}
}
}
@ -57,7 +53,7 @@ class ScanCardTest : BaseTestCase() {
openMainScreen(mockContent = cardType, isTwinsCard = true)
}
step("Check 'Main' screen for '$cardName' $cardBlockchain card") {
checkSingleCurrencyMainScreen(cardBlockchain = cardBlockchain, cardTitle = cardName)
checkSingleCurrencyMainScreen(cardTitle = cardName)
}
}
}
@ -66,7 +62,6 @@ class ScanCardTest : BaseTestCase() {
@DisplayName("Scan: Card with Secp256k1 curve")
@Test
fun secpk1CurveCardScanTest() {
val devicesCount = "1 device"
val cardType: MockContent = Secpk1CurveMockContent
val cardName = "Wallet"
val card = "card with Secp256k1 curve"
@ -75,12 +70,8 @@ class ScanCardTest : BaseTestCase() {
step("Open 'Main Screen' on $card") {
openMainScreen(mockContent = cardType)
}
step("Check 'Main' screen for $card curve with devices count = '$devicesCount'") {
checkMultiCurrencyMainScreen(
devicesCount = devicesCount,
cardTitle = cardName,
withWalletImage = false
)
step("Check 'Main' screen for $card curve") {
checkMultiCurrencyMainScreen(cardTitle = cardName)
}
}
}
@ -99,11 +90,7 @@ class ScanCardTest : BaseTestCase() {
openMainScreen(mockContent = cardType)
}
step("Check 'Main' screen for $card with blockchain: '$cardBlockchain'") {
checkSingleCurrencyMainScreen(
cardBlockchain = cardBlockchain,
cardTitle = cardName,
withWalletImage = false
)
checkSingleCurrencyMainScreen(cardTitle = cardName)
}
}
}
@ -112,7 +99,6 @@ class ScanCardTest : BaseTestCase() {
@DisplayName("Scan: 'Shiba' card")
@Test
fun shibaCardScanTest() {
val devicesCount = "2 devices"
val cardType: MockContent = ShibaMockContent
val cardName = "Wallet"
val card = "Shiba"
@ -121,8 +107,8 @@ class ScanCardTest : BaseTestCase() {
step("Open 'Main Screen' on '$card' card") {
openMainScreen(mockContent = cardType)
}
step("Check 'Main' screen for '$card' card with devices count = '$devicesCount'") {
checkMultiCurrencyMainScreen(devicesCount, cardName)
step("Check 'Main' screen for '$card' card") {
checkMultiCurrencyMainScreen(cardName)
}
}
}
@ -131,7 +117,6 @@ class ScanCardTest : BaseTestCase() {
@DisplayName("Scan: 'Ring'")
@Test
fun ringScanTest() {
val devicesCount = "3 devices"
val cardType: ProductType = ProductType.Ring
val cardName = "Wallet"
val ring = "Ring"
@ -140,8 +125,8 @@ class ScanCardTest : BaseTestCase() {
step("Open 'Main Screen' on '$ring'") {
openMainScreen(productType = cardType)
}
step("Check 'Main' screen for '$ring' with devices count = '$devicesCount'") {
checkMultiCurrencyMainScreen(devicesCount, cardName)
step("Check 'Main' screen for '$ring'") {
checkMultiCurrencyMainScreen(cardName)
}
}
}
@ -150,7 +135,6 @@ class ScanCardTest : BaseTestCase() {
@DisplayName("Scan: 'Wallet' card")
@Test
fun walletCardScanTest() {
val devicesCount = "1 device"
val cardType: ProductType = ProductType.Wallet
val cardName = "Wallet"
@ -158,8 +142,8 @@ class ScanCardTest : BaseTestCase() {
step("Open 'Main Screen' on '$cardName' card") {
openMainScreen(productType = cardType)
}
step("Check 'Main' screen for '$cardName' card with devices count = '$devicesCount'") {
checkMultiCurrencyMainScreen(devicesCount, cardName)
step("Check 'Main' screen for '$cardName' card") {
checkMultiCurrencyMainScreen(cardName)
}
}
}
@ -168,7 +152,6 @@ class ScanCardTest : BaseTestCase() {
@DisplayName("Scan: 'Wallet 2' card")
@Test
fun wallet2ScanTest() {
val devicesCount = "2 devices"
val cardType: MockContent = Wallet2MockContent
val cardName = "Wallet"
val card = "Wallet 2"
@ -177,8 +160,8 @@ class ScanCardTest : BaseTestCase() {
step("Open 'Main Screen' on '$card' card") {
openMainScreen(mockContent = cardType)
}
step("Check 'Main' screen for '$card' card with devices count = '$devicesCount'") {
checkMultiCurrencyMainScreen(devicesCount, cardName)
step("Check 'Main' screen for '$card' card") {
checkMultiCurrencyMainScreen(cardName)
}
}
}
@ -187,7 +170,6 @@ class ScanCardTest : BaseTestCase() {
@DisplayName("Scan: Card with 4.12 firmware")
@Test
fun firmware412CardScanTest() {
val devicesCount = "1 device"
val cardType: MockContent = Firmware412MockContent
val cardName = "Tangem card"
val card = "card with 4.12 firmware"
@ -196,8 +178,8 @@ class ScanCardTest : BaseTestCase() {
step("Open 'Main Screen' on '$card'") {
openMainScreen(mockContent = cardType)
}
step("Check 'Main' screen for '$card' with devices count = '$devicesCount'") {
checkMultiCurrencyMainScreen(devicesCount, cardName)
step("Check 'Main' screen for '$card'") {
checkMultiCurrencyMainScreen(cardName)
}
}
}

View file

@ -56,20 +56,14 @@ class StakingTest : BaseTestCase() {
onTokenDetailsScreen { stakingBlock.assertIsDisplayed() }
}
step("Assert 'Staking title' is displayed") {
onTokenDetailsScreen { stakingTitle.assertIsDisplayed() }
onTokenDetailsScreen { stakingEnabledTitle.assertIsDisplayed() }
}
step("Assert 'Staking fiat amount' is displayed") {
onTokenDetailsScreen { stakingFiatAmount.assertIsDisplayed() }
}
step("Assert 'Staking dot' is displayed") {
onTokenDetailsScreen { stakingDot.assertIsDisplayed() }
}
step("Assert 'Staking token amount' is displayed") {
onTokenDetailsScreen { stakingTokenAmount.assertIsDisplayed() }
}
step("Assert 'Staking block chevron icon' is displayed") {
onTokenDetailsScreen { stakingChevronIcon.assertIsDisplayed() }
}
}
}
@ -139,6 +133,7 @@ class StakingTest : BaseTestCase() {
val scenarioName = "staking_eth_pol_balances_android"
val scenarioState = "Started"
val stakingAmount = "1"
val stakingApy = "2.84%"
setupHooks(
additionalAfterSection = {
@ -172,13 +167,10 @@ class StakingTest : BaseTestCase() {
onTokenDetailsScreen { availableStakingBlock.assertIsDisplayed() }
}
step("Assert 'Available staking block' title is displayed") {
onTokenDetailsScreen { availableStakingBlockTitle.assertIsDisplayed() }
onTokenDetailsScreen { stakingTitle.assertIsDisplayed() }
}
step("Assert 'Available staking block' text is displayed") {
onTokenDetailsScreen { availableStakingBlockText.assertIsDisplayed() }
}
step("Assert 'Available staking block' currency icon is displayed") {
onTokenDetailsScreen { availableStakingBlockCurrencyIcon.assertIsDisplayed() }
onTokenDetailsScreen { availableStakingBlockText(stakingApy).assertIsDisplayed() }
}
step("Click on 'Stake' button") {
onTokenDetailsScreen { stakeButton.clickWithAssertion() }

View file

@ -1,14 +1,10 @@
package com.tangem.tests
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.screens.onMainScreen
import com.tangem.tap.domain.sdk.mocks.content.DevWalletMockContent
import com.tangem.tap.domain.sdk.mocks.content.Wallet2WithSeedPhraseMockContent
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.Allure.step
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Test

View file

@ -389,6 +389,9 @@ class MainScreenActionButtonsTest : BaseTestCase() {
step("Click on 'Buy' button") {
onMainScreen { buyButton.performClick() }
}
step("Click on token: '$tokenTitle'") {
onBuyTokenScreen { tokenWithTitleAndFiatAmount(tokenTitle).performClick() }
}
step("Click on 'Confirm' button in 'Dialog'") {
waitForIdle()
onDialog { confirmButton.clickWithAssertion() }

View file

@ -2,16 +2,17 @@ package com.tangem.tests.actionButtons
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.extensions.assertIsDimmed
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.scenarios.checkQrCodeBottomSheetScenario
import com.tangem.scenarios.goToQrCodeBottomSheet
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.onAddFundsBottomSheet
import com.tangem.screens.onMainScreen
import com.tangem.screens.onSwapStoriesScreen
import com.tangem.screens.onSwapTokenScreen
import com.tangem.screens.onTokenDetailsScreen
import com.tangem.screens.onTransferBottomSheet
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
@ -37,20 +38,41 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
waitForIdle()
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
}
step("Assert 'Receive' button is displayed") {
onTokenDetailsScreen { receiveButton().assertIsDisplayed() }
}
step("Assert 'Buy' button is displayed") {
onTokenDetailsScreen { buyButton().assertIsDisplayed() }
}
step("Assert 'Send' button is displayed") {
onTokenDetailsScreen { sendButton().assertIsDisplayed() }
step("Assert 'Add funds' button is displayed") {
onTokenDetailsScreen { addFundsButton.assertIsDisplayed() }
}
step("Assert 'Swap' button is displayed") {
onTokenDetailsScreen { swapButton().assertIsDisplayed() }
onTokenDetailsScreen { swapButton.assertIsDisplayed() }
}
step("Assert 'Sell' button is displayed") {
onTokenDetailsScreen { sellButton().assertIsDisplayed() }
step("Assert 'Transfer' button is displayed") {
onTokenDetailsScreen { transferButton.assertIsDisplayed() }
}
step("Click on 'Add funds' button") {
onTokenDetailsScreen { addFundsButton.clickWithAssertion() }
}
step("Assert 'Buy' button in bottom sheet is displayed") {
onAddFundsBottomSheet { buyButton.assertIsDisplayed() }
}
step("Assert 'Swap' button in bottom sheet is displayed") {
onAddFundsBottomSheet { swapButton.assertIsDisplayed() }
}
step("Assert 'Receive' button in bottom sheet is displayed") {
onAddFundsBottomSheet { receiveButton.assertIsDisplayed() }
}
step("Click on 'Close' button in bottom sheet") {
onAddFundsBottomSheet { closeButton.clickWithAssertion() }
}
step("Click on 'Transfer' button") {
onTokenDetailsScreen { transferButton.clickWithAssertion() }
}
step("Assert 'Send' button in bottom sheet is displayed") {
onTransferBottomSheet { sendButton.assertIsDisplayed() }
}
step("Assert 'Swap' button in bottom sheet is displayed") {
onTransferBottomSheet { swapButton.assertIsDisplayed() }
}
step("Assert 'Sell' button in bottom sheet is displayed") {
onTransferBottomSheet { sellButton.assertIsDisplayed() }
}
}
}
@ -72,20 +94,41 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
waitForIdle()
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
}
step("Assert 'Receive' button is not dimmed") {
onTokenDetailsScreen { receiveButton().assertIsDimmed(false) }
step("Assert 'Add funds' button is enabled") {
onTokenDetailsScreen { addFundsButton.assertIsEnabled() }
}
step("Assert 'Buy' button is not dimmed") {
onTokenDetailsScreen { buyButton().assertIsDimmed(false) }
step("Assert 'Swap' button is disabled") {
onTokenDetailsScreen { swapButton.assertIsNotEnabled() }
}
step("Assert 'Send' button is not dimmed") {
onTokenDetailsScreen { sendButton().assertIsDimmed(false) }
step("Assert 'Transfer' button is enabled") {
onTokenDetailsScreen { transferButton.assertIsEnabled() }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertIsDimmed() }
step("Click on 'Add funds' button") {
onTokenDetailsScreen { addFundsButton.clickWithAssertion() }
}
step("Assert 'Sell' button is dimmed") {
onTokenDetailsScreen { sellButton().assertIsDimmed() }
step("Assert 'Buy' button in bottom sheet is enabled") {
onAddFundsBottomSheet { buyButton.assertIsEnabled() }
}
step("Assert 'Swap' button in bottom sheet is disabled") {
onAddFundsBottomSheet { swapButton.assertIsNotEnabled() }
}
step("Assert 'Receive' button in bottom sheet is enabled") {
onAddFundsBottomSheet { receiveButton.assertIsEnabled() }
}
step("Click on 'Close' button in bottom sheet") {
onAddFundsBottomSheet { closeButton.clickWithAssertion() }
}
step("Click on 'Transfer' button") {
onTokenDetailsScreen { transferButton.clickWithAssertion() }
}
step("Assert 'Send' button in bottom sheet is enabled") {
onTransferBottomSheet { sendButton.assertIsEnabled() }
}
step("Assert 'Swap' button in bottom sheet is disabled") {
onTransferBottomSheet { swapButton.assertIsNotEnabled() }
}
step("Assert 'Sell' button in bottom sheet is disabled") {
onTransferBottomSheet { sellButton.assertIsNotEnabled() }
}
}
}
@ -109,7 +152,7 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
}
step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() }
onTokenDetailsScreen { swapButton.performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
@ -140,8 +183,11 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
waitForIdle()
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
}
step("Click on 'Receive' button") {
onTokenDetailsScreen { receiveButton().performClick() }
step("Click on 'Add funds' button") {
onTokenDetailsScreen { addFundsButton.clickWithAssertion() }
}
step("Click on 'Receive' button in bottom sheet") {
onAddFundsBottomSheet { receiveButton.clickWithAssertion() }
}
step("Go to QR code bottom sheet") {
flakySafely(WAIT_UNTIL_TIMEOUT) {

View file

@ -1,42 +0,0 @@
package com.tangem.tests.balance
import androidx.compose.ui.test.longClick
import com.tangem.common.BaseTestCase
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()
}
step("Long tap on total balance block") {
onMainScreen {
totalBalanceContainer.performTouchInput {
longClick()
}
}
}
step("Assert 'Rename' button is displayed") {
onMainScreen { totalBalanceMenuRenameWallet.assertIsDisplayed() }
}
step("Assert 'Delete' button is not displayed") {
onMainScreen { totalBalanceMenuDeleteWallet.assertIsNotDisplayed() }
}
}
}
}

View file

@ -3,6 +3,7 @@ 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.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.extensions.*
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
@ -82,28 +83,27 @@ class TotalBalanceUpdateTest : BaseTestCase() {
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
}
step("Click on 'Add to portfolio' button") {
onMarketsScreen { addToPortfolioButton.clickWithAssertion() }
step("Click on 'Add' button in 'Markets' bottom sheet") {
onMarketsScreen { addButton.clickWithAssertion() }
}
step("Click on main network") {
onMarketsScreen { mainNetworkSuffix.performClick() }
step("Click on 'Add' button in 'Add token' bottom sheet") {
flakySafely(WAIT_UNTIL_TIMEOUT) {
onAddTokenBottomSheet {
addButton.performClick()
}
step("Click on 'Add' button") {
onDialog { addButton.clickWithAssertion() }
onAddTokenBottomSheet { laterButton.assertIsDisplayed() }
}
step("Assert 'Continue' is not displayed") {
onDialog { addButton.assertIsNotDisplayed() }
}
step("Click on 'Later' button") {
onDialog { laterButton.clickWithAssertion() }
onAddTokenBottomSheet { laterButton.performClick() }
}
step("Go back to 'Markets: tokens list'") {
step("Press 'Back' button") {
waitForIdle()
onMarketsScreen { topBarBackButton.clickWithAssertion() }
device.uiDevice.pressBack()
}
step("Close 'Markets screen'") {
onSearchBar { searchField.assertIsDisplayed() }
swipeMarketsBlock(SwipeDirection.DOWN)
step("Press 'Back' button") {
waitForIdle()
device.uiDevice.pressBack()
}
step("Assert $updatedBalance is displayed in total balance") {
onMainScreen { totalBalanceText.assertTextContains(updatedBalance) }

View file

@ -2,6 +2,8 @@ package com.tangem.tests.main
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
import com.tangem.common.extensions.SwipeDirection
import com.tangem.common.extensions.swipeVertical
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.openMainScreen
@ -35,7 +37,7 @@ class MainScreenTest : BaseTestCase() {
}
@AllureId("8748")
@DisplayName("Main: check 'Organize tokens' button with single token no accounts")
@DisplayName("Main: check 'Add & Manage' button with single token no accounts")
@Test
fun checkOrganizeTokensButtonWithSingleTokenNoAccountsTest() {
val scenarioState = "Cardano"
@ -56,43 +58,18 @@ class MainScreenTest : BaseTestCase() {
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Assert 'Add & Manage' button is not displayed") {
onMainScreen { addAndManageButtonNode.assertIsNotDisplayed()}
step("Assert 'Add & Manage' button is displayed") {
onMainScreen { addAndManageButtonNode.assertIsDisplayed() }
}
}
}
@AllureId("8749")
@DisplayName("Main: check 'Organize tokens' button with single token two accounts")
@DisplayName("Main: check 'Add & Manage' 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 'Add & Manage' button is not displayed") {
onMainScreen { addAndManageButtonNode.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)
@ -111,4 +88,32 @@ class MainScreenTest : BaseTestCase() {
}
}
}
@AllureId("8750")
@DisplayName("Main: check 'Add & Manage' 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("Swipe up") {
swipeVertical(SwipeDirection.UP, startHeightRatio = 0.6f, endHeightRatio = 0.1f)
}
step("Assert 'Add & Manage' button is displayed") {
onMainScreen { addAndManageButtonNode.assertIsDisplayed() }
}
}
}
}

View file

@ -16,7 +16,7 @@ import org.junit.Test
class WarningsTest : BaseTestCase() {
@AllureId("184")
@DisplayName("Token list: hide token by long tap")
@DisplayName("Warnings: missing address warning")
@Test
fun checkUnavailableNetworksWarningTest() {
val scenarioState = "MissingDerivation"
@ -38,9 +38,6 @@ class WarningsTest : BaseTestCase() {
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() }
}

View file

@ -1,5 +1,6 @@
package com.tangem.tests.markets
import androidx.compose.ui.test.ExperimentalTestApi
import com.tangem.common.BaseTestCase
import com.tangem.common.annotations.ApiEnv
import com.tangem.common.annotations.ApiEnvConfig
@ -39,6 +40,7 @@ class MarketsExchangesTest : BaseTestCase() {
}
}
@OptIn(ExperimentalTestApi::class)
@Test
@AllureId("56")
@ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD))
@ -60,9 +62,8 @@ class MarketsExchangesTest : BaseTestCase() {
onMarketsScreen { tokenWithTitle(tokenName).clickWithAssertion() }
waitForIdle()
}
step("Scroll down") {
swipeVertical(SwipeDirection.UP)
swipeVertical(SwipeDirection.UP)
step("Scroll to 'Listed on exchanges' block") {
onMarketsScreen { scrollToListedOnBlock() }
}
step("Assert 'Listed on exchanges' block has title") {
onMarketsScreen { listedOnBlockContainer.assertIsDisplayed() }

View file

@ -227,7 +227,7 @@ class RecentBlockTest : BaseTestCase() {
val sendAmount = "1"
val txHistoryScenarioState = "11OutgoingTransactions"
val recipientAddressBase = "DJ2TaZ5vvp3mBLugUpKjVM3pRBLi4uYaq"
val shortenedRecipientAddress = "DJ2TaZ5vvp3mBLugU...Li4uYaq123456789b"
val longRecipientAddress = recipientAddressBase + "123456789b"
setupHooks(
additionalAfterSection = {
@ -261,7 +261,7 @@ class RecentBlockTest : BaseTestCase() {
checkRecentAddressItem(address = DOGECOIN_ADDRESS, description = recentTransactionAmount1)
}
step("Check recent address item №2") {
checkRecentAddressItem(address = shortenedRecipientAddress, description = recentTransactionAmount2)
checkRecentAddressItem(address = longRecipientAddress, description = recentTransactionAmount2)
}
step("Check recent address item №3") {
checkRecentAddressItem(address = recipientAddressBase + "k", description = recentTransactionAmount2)

View file

@ -246,8 +246,11 @@ class SendAddressScreenTest : BaseTestCase() {
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() }
}
step("Click on 'Send' button") {
onTokenDetailsScreen { sendButton().performClick() }
step("Click on 'Transfer' button") {
onTokenDetailsScreen { transferButton.clickWithAssertion() }
}
step("Click on 'Send' button in bottom sheet") {
onTransferBottomSheet { sendButton.clickWithAssertion() }
}
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)

View file

@ -46,8 +46,11 @@ class SendConfirmScreenTest : BaseTestCase() {
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
step("Click on 'Send' button") {
onTokenDetailsScreen { sendButton().performClick() }
step("Click on 'Transfer' button") {
onTokenDetailsScreen { transferButton.clickWithAssertion() }
}
step("Click on 'Send' button in bottom sheet") {
onTransferBottomSheet { sendButton.clickWithAssertion() }
}
step("Type '$inputAmount' in input text field") {
onSendScreen {
@ -123,8 +126,11 @@ class SendConfirmScreenTest : BaseTestCase() {
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
step("Click on 'Send' button") {
onTokenDetailsScreen { sendButton().performClick() }
step("Click on 'Transfer' button") {
onTokenDetailsScreen { transferButton.clickWithAssertion() }
}
step("Click on 'Send' button in bottom sheet") {
onTransferBottomSheet { sendButton.clickWithAssertion() }
}
step("Type '$inputAmount' in input text field") {
onSendScreen {

View file

@ -281,13 +281,13 @@ class SendFeeScreenTest : BaseTestCase() {
fun checkNetworkFeeBottomSheetForBitcoinTest() {
val tokenName = "Bitcoin"
val tokenAmount = "0.00000001"
val feeAmount = "$2.86"
val feeAmount = "$0.48"
val fiatFeeAmount = "$0.24"
val marketSelectorItem = getResourceString(R.string.common_fee_selector_option_market)
val fastSelectorItem = getResourceString(R.string.common_fee_selector_option_fast)
val slowSelectorItem = getResourceString(R.string.common_fee_selector_option_slow)
val feeUpTo = getResourceString(R.string.send_max_fee)
val feeUpToValue = "0.0000264 BTC"
val feeUpToValue = "0.0000044 BTC"
val newFeeUpToValue = "0.0000022 BTC"
val satoshi = getResourceString(R.string.send_satoshi_per_byte_title)
val satoshiValue = "2"

View file

@ -4,10 +4,12 @@ import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.KASPA_RECIPIENT_ADDRESS
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.checkSendWarning
import com.tangem.scenarios.openSendConfirmScreenViaNextButton
import com.tangem.scenarios.openSendScreen
import com.tangem.screens.onSendAddressScreen
import com.tangem.screens.onSendScreen
@ -145,8 +147,10 @@ class KaspaWarningsTest : BaseTestCase() {
step("Type address in input text field") {
onSendAddressScreen { addressTextField.performTextReplacement(KASPA_RECIPIENT_ADDRESS) }
}
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
step("Click 'Next' button until 'Send Confirm' screen opens") {
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) {
runCatching { openSendConfirmScreenViaNextButton() }.isSuccess
}
}
step("Assert 'UTXO limit warning' is displayed") {
checkSendWarning(

View file

@ -3,8 +3,6 @@ package com.tangem.tests.swap
import androidx.compose.ui.test.longClick
import androidx.test.InstrumentationRegistry.getTargetContext
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.extensions.assertHasBadge
import com.tangem.common.extensions.restartApp
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
@ -18,101 +16,6 @@ import org.junit.Test
@HiltAndroidTest
class SwapStoriesTest : BaseTestCase() {
@AllureId("5453")
@DisplayName("Check 'Swap' button badge on 'Main' screen")
@Test
fun checkMainScreenSwapButtonBadgeTest() {
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Assert 'Swap' button has badge") {
onMainScreen { swapButton.assertHasBadge() }
}
step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.MainScreen)
}
step("Click on 'Close' button") {
onSwapTokenScreen { closeButton.performClick() }
}
step("Assert 'Swap' button has not badge") {
onMainScreen { swapButton.assertHasBadge(false) }
}
}
}
@AllureId("5454")
@DisplayName("Check 'Swap' button badge on token details screen")
@Test
fun checkTokenDetailsScreenSwapButtonTest() {
val tokenName = "Ethereum"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() }
}
step("Assert 'Swap' button has badge") {
onTokenDetailsScreen { swapButton().assertHasBadge() }
}
step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.TokenDetails)
}
step("Click on 'Close' button") {
onSwapTokenScreen { closeButton.performClick() }
}
step("Assert 'Swap' button has not badge") {
onTokenDetailsScreen { swapButton().assertHasBadge(false) }
}
}
}
@AllureId("5455")
@DisplayName("Check 'Swap' button badge on token details in 'Market' screen")
@Test
fun checkMarketTokenDetailsScreenSwapButtonTest() {
val tokenName = "Ethereum"
val badgeShown = "Badge shown"
val badgeHidden = "Badge hidden"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Open 'Markets' token details screen for token '$tokenName'") {
openMarketTokenDetailsScreen(blockchainName = tokenName, tokenName = tokenName)
}
step("Assert 'Swap' button has badge") {
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertIsDisplayed() }
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertContentDescriptionEquals(badgeShown) }
}
step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.MarketsTokenDetails)
}
step("Click on 'Close' button") {
onSwapTokenScreen { closeButton.performClick() }
}
step("Assert 'Swap' button has not badge") {
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertContentDescriptionEquals(badgeHidden) }
}
}
}
@AllureId("5469")
@DisplayName("Check unavailable swap stories on 'Main' screen")
@Test
@ -136,9 +39,6 @@ class SwapStoriesTest : BaseTestCase() {
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Assert 'Swap' button has not badge") {
onMainScreen { swapButton.assertHasBadge(false) }
}
step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.MainScreen, storiesExist = false)
}
@ -155,9 +55,6 @@ class SwapStoriesTest : BaseTestCase() {
waitForIdle()
onMainScreen { swapButton.assertIsDisplayed() }
}
step("Assert 'Swap' button has badge") {
onMainScreen { swapButton.assertHasBadge() }
}
step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.MainScreen, storiesExist = true)
}
@ -192,9 +89,6 @@ class SwapStoriesTest : BaseTestCase() {
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() }
}
step("Assert 'Swap' button has not badge") {
onTokenDetailsScreen { swapButton().assertHasBadge(false) }
}
step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.TokenDetails, storiesExist = false)
}
@ -207,13 +101,6 @@ class SwapStoriesTest : BaseTestCase() {
step("Restart app") {
restartApp(packageName)
}
step("Assert 'Swap' button has badge") {
waitForIdle()
flakySafely(WAIT_UNTIL_TIMEOUT) {
composeTestRule.mainClock.advanceTimeBy(500)
onMainScreen { swapButton.assertHasBadge() }
}
}
step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.TokenDetails, storiesExist = true)
}
@ -228,8 +115,6 @@ class SwapStoriesTest : BaseTestCase() {
val scenarioErrorState = "Error"
val packageName = getTargetContext().packageName
val tokenName = "Ethereum"
val badgeShown = "Badge shown"
val badgeHidden = "Badge hidden"
setupHooks(
additionalBeforeAppLaunchSection = {
@ -246,16 +131,15 @@ class SwapStoriesTest : BaseTestCase() {
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Open 'Markets' token details screen for token '$tokenName'") {
openMarketTokenDetailsScreen(blockchainName = tokenName, tokenName = tokenName)
step("Open 'Token details' from 'Markets' screen for token '$tokenName'") {
openTokenDetailsFromMarketsScreen(blockchainName = tokenName, tokenName = tokenName)
}
step("Assert 'Swap' button has not badge") {
step("Assert 'Swap' button is displayed") {
waitForIdle()
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertIsDisplayed() }
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertContentDescriptionEquals(badgeHidden) }
onTokenDetailsScreen { swapButton.assertIsDisplayed() }
}
step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.MarketsTokenDetails, storiesExist = false)
openSwapScreen(from = SwapEntryPoint.TokenDetails, storiesExist = false)
}
step("Click on 'Close' button") {
onSwapTokenScreen { closeButton.performClick() }
@ -266,16 +150,12 @@ class SwapStoriesTest : BaseTestCase() {
step("Restart app") {
restartApp(packageName)
}
step("Open 'Markets' token details screen for token '$tokenName'") {
openMarketTokenDetailsScreen(blockchainName = tokenName, tokenName = tokenName)
}
step("Assert 'Swap' button has badge") {
step("Assert 'Swap' button is displayed") {
waitForIdle()
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertIsDisplayed() }
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertContentDescriptionEquals(badgeShown) }
onTokenDetailsScreen { swapButton.assertIsDisplayed() }
}
step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.MarketsTokenDetails, storiesExist = true)
openSwapScreen(from = SwapEntryPoint.TokenDetails, storiesExist = true)
}
}
}
@ -331,11 +211,8 @@ class SwapStoriesTest : BaseTestCase() {
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() }
}
step("Assert 'Swap' button has badge") {
onTokenDetailsScreen { swapButton().assertHasBadge() }
}
step("Click on 'Swap' button on 'Token details' screen") {
onTokenDetailsScreen { swapButton().performClick() }
onTokenDetailsScreen { swapButton.performClick() }
}
step("Check stories changes") {
checkStoriesChanges()
@ -369,11 +246,11 @@ class SwapStoriesTest : BaseTestCase() {
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Open 'Markets' token details screen for token '$tokenName'") {
openMarketTokenDetailsScreen(blockchainName = tokenName, tokenName = tokenName)
step("Open 'Token details' from 'Markets' screen for token '$tokenName'") {
openTokenDetailsFromMarketsScreen(blockchainName = tokenName, tokenName = tokenName)
}
step("Click on 'Swap' button on 'Markets' token details screen") {
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.performClick() }
onTokenDetailsScreen { swapButton.performClick() }
}
step("Check stories changes") {
checkStoriesChanges()
@ -388,7 +265,7 @@ class SwapStoriesTest : BaseTestCase() {
onSwapTokenScreen { closeButton.performClick() }
}
step("Open 'Swap' screen without stories") {
openSwapScreen(from = SwapEntryPoint.MarketsTokenDetails, storiesExist = false)
openSwapScreen(from = SwapEntryPoint.TokenDetails, storiesExist = false)
}
}
}
@ -433,6 +310,17 @@ class SwapStoriesTest : BaseTestCase() {
step("Click on 'Close' button") {
onSwapTokenScreen { closeButton.performClick() }
}
step("Long click on token with name: '$tokenName' again to reopen actions menu") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenName).performTouchInput {
longClick(
position = center,
durationMillis = 1000L,
)
}
}
}
step("Open 'Swap' screen without stories") {
openSwapScreen(from = SwapEntryPoint.TokenActionsBottomSheet, storiesExist = false)
}

View file

@ -50,7 +50,7 @@ class SwapTokenScreenTest : BaseTestCase() {
onTokenDetailsScreen { title.assertIsDisplayed() }
}
step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() }
onTokenDetailsScreen { swapButton.performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
@ -147,7 +147,7 @@ class SwapTokenScreenTest : BaseTestCase() {
disableMobileData()
}
step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() }
onTokenDetailsScreen { swapButton.performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
@ -201,7 +201,7 @@ class SwapTokenScreenTest : BaseTestCase() {
onTokenDetailsScreen { title.assertIsDisplayed() }
}
step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() }
onTokenDetailsScreen { swapButton.performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
@ -304,7 +304,7 @@ class SwapTokenScreenTest : BaseTestCase() {
onTokenDetailsScreen { title.assertIsDisplayed() }
}
step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() }
onTokenDetailsScreen { swapButton.performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
@ -510,7 +510,7 @@ class SwapTokenScreenTest : BaseTestCase() {
onMainScreen { tokenWithTitleAndAddress(polygon).clickWithAssertion() }
}
step("Assert 'Swap' button is not dimmed. Swap available") {
onTokenDetailsScreen { swapButton().assertIsDimmed(false) }
onTokenDetailsScreen { swapButton.assertIsEnabled() }
}
step("Press 'Back' button") {
device.uiDevice.pressBack()
@ -519,7 +519,7 @@ class SwapTokenScreenTest : BaseTestCase() {
onMainScreen { tokenWithTitleAndAddress(bitcoin).clickWithAssertion() }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertIsDimmed(true) }
onTokenDetailsScreen { swapButton.assertIsNotEnabled() }
}
step("Press 'Back' button") {
device.uiDevice.pressBack()
@ -528,7 +528,7 @@ class SwapTokenScreenTest : BaseTestCase() {
onMainScreen { tokenWithTitleAndAddress(salam).clickWithAssertion() }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertIsDimmed(true) }
onTokenDetailsScreen { swapButton.assertIsNotEnabled() }
}
}
}

View file

@ -16,6 +16,7 @@ import androidx.compose.ui.draw.BlurredEdgeTreatment
import androidx.compose.ui.draw.blur
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.innerShadow
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.shadow.Shadow
@ -38,6 +39,7 @@ import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
import com.tangem.core.res.R as CoreResR
private const val TINTED_BACKGROUND_ALPHA = 0.1f
@ -179,17 +181,23 @@ private fun EarnBlockTrailing(type: Type, trailingUM: EarnBlockUM.TrailingUM?, o
}
is EarnBlockUM.TrailingUM.Balance -> {
if (!trailingUM.isBalanceHidden) {
val fiatModifier = Modifier.layoutId(TangemRowLayoutId.END_TOP).let {
if (type == Type.Staking) it.testTag(TokenDetailsScreenTestTags.STAKING_FIAT_AMOUNT) else it
}
val cryptoModifier = Modifier.layoutId(TangemRowLayoutId.END_BOTTOM).let {
if (type == Type.Staking) it.testTag(TokenDetailsScreenTestTags.STAKING_TOKEN_AMOUNT) else it
}
Text(
text = trailingUM.fiatValue.resolveAnnotatedReference(),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
modifier = Modifier.layoutId(TangemRowLayoutId.END_TOP),
modifier = fiatModifier,
)
Text(
text = trailingUM.cryptoValue.resolveReference(),
style = TangemTheme.typography2.captionMedium12,
color = TangemTheme.colors2.text.neutral.secondary,
modifier = Modifier.layoutId(TangemRowLayoutId.END_BOTTOM),
modifier = cryptoModifier,
)
}
}

View file

@ -18,6 +18,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.common.ui.R
@ -35,6 +36,7 @@ import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
@ -78,7 +80,8 @@ private fun ExpressTransactionItem(
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors2.surface.level3)
.clickable(onClick = info.onClick)
.padding(TangemTheme.dimens2.x4),
.padding(TangemTheme.dimens2.x4)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM),
) {
TitleRow(
title = info.title.resolveReference(),
@ -104,7 +107,9 @@ private fun TitleRow(title: String, infoIconRes: Int?, infoIconTint: Color?) {
text = title,
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors3.text.primary,
modifier = Modifier.weight(1f),
modifier = Modifier
.weight(1f)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_TITLE),
)
if (infoIconRes != null && infoIconTint != null) {
Icon(
@ -126,32 +131,42 @@ private fun AmountsRow(info: ExpressTransactionStateInfoUM) {
CurrencyIcon(
state = info.fromCurrencyIcon,
shouldDisplayNetwork = false,
modifier = Modifier.size(TangemTheme.dimens.size18),
modifier = Modifier
.size(TangemTheme.dimens.size18)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_FROM_ICON),
)
EllipsisText(
text = info.fromAmount.resolveReference(),
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors3.text.primary,
ellipsis = TextEllipsis.OffsetEnd(info.fromAmountSymbol.length),
modifier = Modifier.weight(weight = 1f, fill = false),
modifier = Modifier
.weight(weight = 1f, fill = false)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_FROM_AMOUNT),
)
Icon(
painter = painterResource(R.drawable.ic_forward_24),
contentDescription = null,
tint = TangemTheme.colors3.icon.tertiary,
modifier = Modifier.size(TangemTheme.dimens.size18),
modifier = Modifier
.size(TangemTheme.dimens.size18)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_SWAP_ICON),
)
CurrencyIcon(
state = info.toCurrencyIcon,
shouldDisplayNetwork = false,
modifier = Modifier.size(TangemTheme.dimens.size18),
modifier = Modifier
.size(TangemTheme.dimens.size18)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_TO_ICON),
)
EllipsisText(
text = info.toAmount.resolveReference(),
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors3.text.primary,
ellipsis = TextEllipsis.OffsetEnd(info.toAmountSymbol.length),
modifier = Modifier.weight(weight = 1f, fill = false),
modifier = Modifier
.weight(weight = 1f, fill = false)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_TO_AMOUNT),
)
}
}

View file

@ -19,6 +19,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.ds.row.TangemRowContainer
@ -68,7 +70,8 @@ fun TokenActionRow(
onClick = onClick,
onLongClick = onLongClick,
hapticManager = hapticManager,
),
)
.semantics { if (!isEnabled) disabled() },
) {
LeadingIcon(iconRes = iconRes, accentColor = accentColor)
Text(

View file

@ -16,6 +16,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
@ -39,6 +40,7 @@ import com.tangem.core.ui.res.LocalBottomSheetAlwaysVisible
import com.tangem.core.ui.res.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.BaseBottomSheetTestTags
import com.tangem.core.ui.utils.WindowInsetsZero
/**
@ -253,7 +255,8 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
Column(
modifier = contentModifier
.background(containerColor)
.heightIn(max = maxHeight),
.heightIn(max = maxHeight)
.testTag(BaseBottomSheetTestTags.CONTAINER),
) {
Box(modifier = Modifier.fillMaxWidth()) {
title(model)

View file

@ -21,6 +21,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
@ -40,6 +41,7 @@ import com.tangem.core.ui.components.sheetscaffold.TangemSheetState
import com.tangem.core.ui.components.sheetscaffold.TangemSheetValue
import com.tangem.core.ui.components.sheetscaffold.rememberSheetState
import com.tangem.core.ui.res.*
import com.tangem.core.ui.test.BaseBottomSheetTestTags
import com.tangem.core.ui.utils.WindowInsetsZero
const val MODAL_SHEET_MAX_HEIGHT = 0.8f
@ -205,7 +207,8 @@ inline fun <reified T : TangemBottomSheetConfigContent> BsContent(
.background(containerColor)
.heightIn(max = maxHeight.dp)
.fillMaxWidth()
.nestedScroll(nestedScrollConnection),
.nestedScroll(nestedScrollConnection)
.testTag(BaseBottomSheetTestTags.CONTAINER),
) {
Box(modifier = Modifier.fillMaxWidth()) {
title(model)

View file

@ -10,6 +10,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@ -25,6 +28,7 @@ import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.BaseActionButtonsBlockTestTags
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -51,7 +55,10 @@ fun ActionButtons(buttons: ImmutableList<TangemButtonUM>, modifier: Modifier = M
TangemTheme.colors2.text.status.disabled
}
Column(
modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x2_5),
modifier = Modifier
.padding(horizontal = TangemTheme.dimens2.x2_5)
.testTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
.semantics { if (!button.isEnabled) disabled() },
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
horizontalAlignment = Alignment.CenterHorizontally,
) {

View file

@ -19,9 +19,11 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import com.tangem.core.ui.R
import com.tangem.core.ui.test.NotificationTestTags
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
import com.tangem.core.ui.ds.button.*
@ -66,7 +68,8 @@ fun TangemMessage(
Alignment.Top
},
)
.size(messageUM.iconSize),
.size(messageUM.iconSize)
.testTag(NotificationTestTags.ICON),
)
}
},
@ -115,14 +118,14 @@ fun TangemMessage(
Image(
painter = painterResource(config.iconResId),
contentDescription = null,
modifier = Modifier.size(config.iconSize),
modifier = Modifier.size(config.iconSize).testTag(NotificationTestTags.ICON),
)
} else {
Icon(
imageVector = ImageVector.vectorResource(config.iconResId),
contentDescription = null,
tint = iconTint,
modifier = Modifier.size(config.iconSize),
modifier = Modifier.size(config.iconSize).testTag(NotificationTestTags.ICON),
)
}
},
@ -166,7 +169,7 @@ fun TangemMessage(
} else {
Alignment.Start
}
Box(modifier = modifier) {
Box(modifier = modifier.testTag(NotificationTestTags.CONTAINER)) {
Box(
modifier = Modifier
.matchParentSize()
@ -244,6 +247,7 @@ private fun TangemMessageContent(
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
textAlign = textAlign,
modifier = Modifier.testTag(NotificationTestTags.TITLE),
)
}
if (subtitle != null) {
@ -252,6 +256,7 @@ private fun TangemMessageContent(
text = subtitle.resolveAnnotatedReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary,
modifier = Modifier.testTag(NotificationTestTags.MESSAGE),
)
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.core.ui.test
object BaseBottomSheetTestTags {
const val CONTAINER = "BASE_BOTTOM_SHEET_CONTAINER"
const val ACTION_BUTTON = "BASE_BOTTOM_SHEET_ACTION_BUTTON"
const val ACTION_ICON = "BASE_BOTTOM_SHEET_ACTION_ICON"
const val TITLE = "BASE_BOTTOM_SHEET_TITLE"

View file

@ -4,4 +4,6 @@ object MarketsTestTags {
const val TOKENS_LIST = "MARKETS_TOKENS_LIST"
const val TOKENS_LIST_ITEM = "MARKETS_TOKENS_LIST_ITEM"
const val LISTED_ON_EXCHANGES_COUNT = "MARKETS_LISTED_ON_EXCHANGES_COUNT"
const val LISTED_ON_BLOCK = "MARKETS_LISTED_ON_BLOCK"
const val TOKEN_DETAILS_CONTENT = "MARKETS_TOKEN_DETAILS_CONTENT"
}

View file

@ -2,6 +2,7 @@ package com.tangem.core.ui.test
object OrganizeTokensScreenTestTags {
// region OrganizeTokensTopBar
const val MENU_BUTTON = "ORGANIZE_TOKENS_MENU_BUTTON"
const val GROUP_BUTTON = "ORGANIZE_TOKENS_GROUP_BUTTON"
const val SORT_BY_BALANCE_BUTTON = "SORT_BY_BALANCE_BUTTON"
// endregion OrganizeTokensTopBar

View file

@ -17,6 +17,8 @@ import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import com.tangem.core.ui.test.MarketsTestTags
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@ -116,6 +118,7 @@ private fun Content(
LazyColumn(
state = lazyListState,
contentPadding = PaddingValues(bottom = bottomBarHeight, top = contentPadding.calculateTopPadding()),
modifier = Modifier.testTag(MarketsTestTags.TOKEN_DETAILS_CONTENT),
) {
item("header") {
Header(state = state)

View file

@ -18,6 +18,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
@ -48,6 +49,7 @@ import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.test.TokenElementsTestTags
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@ -314,13 +316,15 @@ private fun ExchangeItemRowContent(exchangeItemUM: ExchangeItemUM.Content, modif
tangemIconUM = exchangeItemUM.icon,
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.HEAD)
.size(TangemTheme.dimens2.x10),
.size(TangemTheme.dimens2.x10)
.testTag(TokenElementsTestTags.TOKEN_ICON),
)
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens2.x2)
.layoutId(TangemRowLayoutId.START_TOP),
.layoutId(TangemRowLayoutId.START_TOP)
.testTag(TokenElementsTestTags.TOKEN_TITLE),
text = exchangeItemUM.title.resolveReference(),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
@ -329,7 +333,8 @@ private fun ExchangeItemRowContent(exchangeItemUM: ExchangeItemUM.Content, modif
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens2.x2)
.layoutId(TangemRowLayoutId.START_BOTTOM),
.layoutId(TangemRowLayoutId.START_BOTTOM)
.testTag(TokenElementsTestTags.TOKEN_PRICE),
text = exchangeItemUM.subTitle.resolveReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary,
@ -338,7 +343,8 @@ private fun ExchangeItemRowContent(exchangeItemUM: ExchangeItemUM.Content, modif
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens2.x2)
.layoutId(TangemRowLayoutId.END_TOP),
.layoutId(TangemRowLayoutId.END_TOP)
.testTag(TokenElementsTestTags.TOKEN_FIAT_AMOUNT_TEXT),
text = exchangeItemUM.volumeInUsd.resolveReference(),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
@ -351,7 +357,8 @@ private fun ExchangeItemRowContent(exchangeItemUM: ExchangeItemUM.Content, modif
shape = CircleShape,
)
.padding(vertical = 2.dp, horizontal = 6.dp)
.layoutId(TangemRowLayoutId.END_BOTTOM),
.layoutId(TangemRowLayoutId.END_BOTTOM)
.testTag(TokenElementsTestTags.TOKEN_CRYPTO_AMOUNT),
text = exchangeItemUM.auditLabel.text.resolveReference(),
style = TangemTheme.typography2.captionSemibold11,
color = getColorByTrustValue(exchangeItemUM.auditLabel.type),

View file

@ -10,10 +10,9 @@ 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.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -65,7 +64,8 @@ private fun ListedOnBlockV1(state: ListedOnUM, modifier: Modifier = Modifier) {
.clip(shape = TangemTheme.shapes.roundedCornersXMedium)
.clickable(enabled = state is ListedOnUM.Content) {
(state as? ListedOnUM.Content)?.onClick?.invoke()
},
}
.testTag(MarketsTestTags.LISTED_ON_BLOCK),
) {
Description(
state = state,
@ -89,9 +89,11 @@ private fun ListedOnBlockV1(state: ListedOnUM, modifier: Modifier = Modifier) {
@Composable
private fun ListedOnBlockV2(state: ListedOnUM, modifier: Modifier = Modifier) {
TokenMarketInformationBlock(
modifier = modifier.clickable(enabled = state is ListedOnUM.Content) {
modifier = modifier
.clickable(enabled = state is ListedOnUM.Content) {
(state as? ListedOnUM.Content)?.onClick?.invoke()
},
}
.testTag(MarketsTestTags.LISTED_ON_BLOCK),
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1)) {
@ -103,6 +105,7 @@ private fun ListedOnBlockV2(state: ListedOnUM, modifier: Modifier = Modifier) {
overflow = TextOverflow.Ellipsis,
)
Text(
modifier = Modifier.testTag(MarketsTestTags.LISTED_ON_EXCHANGES_COUNT),
text = state.description.resolveReference(),
style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary,
@ -178,7 +181,7 @@ internal fun ListedOnBlockPlaceholderV2(modifier: Modifier = Modifier) {
private fun Description(state: ListedOnUM, modifier: Modifier = Modifier) {
Text(
text = state.description.resolveReference(),
modifier = modifier.semantics { testTag = MarketsTestTags.LISTED_ON_EXCHANGES_COUNT },
modifier = modifier.testTag(MarketsTestTags.LISTED_ON_EXCHANGES_COUNT),
color = TangemTheme.colors.text.tertiary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,

View file

@ -13,12 +13,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.ui.account.AccountIconUM
import com.tangem.common.ui.earn.EarnBlock
import com.tangem.common.ui.earn.EarnBlockUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionsBlockState
import com.tangem.common.ui.notifications.notifications
@ -35,6 +37,7 @@ import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.themedColor
import com.tangem.core.ui.res.LocalHazeState
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.domain.models.account.CryptoPortfolioIcon
@ -140,7 +143,7 @@ private fun BoxScope.TokenDetailsMarketBlockOverlay(
)
}
@Suppress("LongParameterList")
@Suppress("LongParameterList", "LongMethod")
@Composable
private fun TokenDetailsBody(
tokenDetailsUM: TokenDetailsUM,
@ -165,6 +168,7 @@ private fun TokenDetailsBody(
LazyColumn(
modifier = modifier
.testTag(TokenDetailsScreenTestTags.SCREEN_CONTAINER)
.hazeSourceTangem(state = LocalHazeState.current)
.topFade(
height = topContentPadding,
@ -190,9 +194,18 @@ private fun TokenDetailsBody(
)
tokenDetailsUM.earnBlockState?.let { earnBlock ->
item(key = "staking_block") {
val stakingTag = when {
earnBlock is EarnBlockUM.Content &&
earnBlock.type == EarnBlockUM.Type.Staking &&
earnBlock.trailingUM is EarnBlockUM.TrailingUM.Button ->
TokenDetailsScreenTestTags.STAKING_AVAILABLE_BLOCK
else -> TokenDetailsScreenTestTags.STAKING_BLOCK
}
EarnBlock(
state = earnBlock,
modifier = itemModifier.padding(vertical = TangemTheme.dimens2.x3),
modifier = itemModifier
.padding(vertical = TangemTheme.dimens2.x3)
.testTag(stakingTag),
)
}
}

View file

@ -16,9 +16,18 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.*
import androidx.compose.ui.platform.testTag
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
import com.tangem.core.ui.test.TokenDetailsTopBarTestTags
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@ -58,7 +67,9 @@ internal fun TokenDetailsTopBar(topAppBarUM: TokenDetailsTopAppBarUM, modifier:
.clip(CircleShape)
.hazeEffectTangem { blurRadius = ACTION_BLUR_RADIUS }
TangemTopBar(
modifier = modifier.statusBarsPadding(),
modifier = modifier
.statusBarsPadding()
.testTag(TokenDetailsTopBarTestTags.BACK_BUTTON),
startContent = {
TangemTopBarActionContent(
modifier = actionModifier,
@ -73,7 +84,7 @@ internal fun TokenDetailsTopBar(topAppBarUM: TokenDetailsTopAppBarUM, modifier:
var isDropdownMenuShown by rememberSaveable { mutableStateOf(false) }
Box {
TangemTopBarActionContent(
modifier = actionModifier,
modifier = actionModifier.testTag(TokenDetailsTopBarTestTags.MORE_BUTTON),
actionUM = TangemTopBarActionUM(
iconRes = CoreUiR.drawable.ic_more_default_24,
onClick = { isDropdownMenuShown = true },
@ -105,7 +116,9 @@ internal fun TokenDetailsTopBar(topAppBarUM: TokenDetailsTopAppBarUM, modifier:
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x0_5),
) {
Box(modifier = Modifier.testTag(TokenDetailsScreenTestTags.TOKEN_TITLE)) {
TokenDetailsTitle(titleState = topAppBarUM.titleState)
}
Text(
text = topAppBarUM.subtitle.resolveAnnotatedReference(),
color = TangemTheme.colors2.text.neutral.tertiary,

View file

@ -96,6 +96,7 @@ internal fun OrganizeTokensContent(
onClick = { isShowDropdownMenu = true },
ghostModeProgress = 0f,
),
modifier = Modifier.testTag(OrganizeTokensScreenTestTags.MENU_BUTTON),
type = TangemTopBarType.BottomSheet,
)
OrganizeDropDownMenu(
@ -161,8 +162,7 @@ private fun TokenList(
modifier = Modifier
.align(Alignment.TopCenter)
.reorderable(reorderableListState)
.testTag(OrganizeTokensScreenTestTags.TOKENS_LAZY_LIST)
.hazeSourceTangem(zIndex = 1f),
.testTag(OrganizeTokensScreenTestTags.TOKENS_LAZY_LIST),
state = reorderableListState.listState,
contentPadding = listContentPadding,
) {
@ -221,7 +221,7 @@ private fun LazyItemScope.DraggableItem(
when (item) {
is OrganizeRowItemUM.Network -> TangemHeaderRow(
modifier = modifierWithBackground,
modifier = modifierWithBackground.testTag(OrganizeTokensScreenTestTags.GROUP_TITLE_ITEM),
reorderableState = reorderableState,
headerRowUM = item.headerRowUM,
)
@ -231,7 +231,7 @@ private fun LazyItemScope.DraggableItem(
isBalanceHidden = isBalanceHidden,
)
is OrganizeRowItemUM.Token -> OrganizeTokenRow(
modifier = modifierWithBackground,
modifier = modifierWithBackground.testTag(OrganizeTokensScreenTestTags.TOKEN_LIST_ITEM),
tokenRowUM = item.tokenRowUM,
reorderableState = reorderableState,
isBalanceHidden = isBalanceHidden,
@ -262,11 +262,15 @@ private fun BoxScope.BottomButtons(organizeTokensUM: OrganizeTokensUM) {
) {
TangemButton(
buttonUM = organizeTokensUM.cancelButton,
modifier = Modifier.weight(1f),
modifier = Modifier
.weight(1f)
.testTag(OrganizeTokensScreenTestTags.CANCEL_BUTTON),
)
TangemButton(
buttonUM = organizeTokensUM.applyButton,
modifier = Modifier.weight(1f),
modifier = Modifier
.weight(1f)
.testTag(OrganizeTokensScreenTestTags.APPLY_BUTTON),
)
}
}

View file

@ -14,6 +14,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import com.tangem.core.ui.test.BaseBottomSheetTestTags
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpOffset
@ -101,6 +103,7 @@ private fun TokenActionContextMenuContent(actions: ImmutableList<TokenActionButt
Row(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
modifier = Modifier
.testTag(BaseBottomSheetTestTags.ACTION_BUTTON)
.clickable(
enabled = item.isEnabled,
onClick = {
@ -123,7 +126,9 @@ private fun TokenActionContextMenuContent(actions: ImmutableList<TokenActionButt
} else {
TangemTheme.colors2.graphic.neutral.primary
},
modifier = Modifier.size(TangemTheme.dimens2.x5),
modifier = Modifier
.size(TangemTheme.dimens2.x5)
.testTag(BaseBottomSheetTestTags.ACTION_ICON),
)
Text(
text = item.text.resolveReference(),

View file

@ -2,7 +2,11 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import com.tangem.core.ui.ds.button.TangemButton
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.features.tangempay.component.TangemPayMainBlockComponent
import com.tangem.features.tangempay.entity.TangemPayMainUM
@ -25,9 +29,14 @@ internal fun LazyListScope.organizeTokens2(state: WalletUM, itemModifier: Modifi
key = "OrganizeTokensButton",
contentType = "OrganizeTokensButton",
) {
val testTag = if (organizeButton.text == resourceReference(R.string.main_add_and_manage_tokens)) {
MainScreenTestTags.ADD_AND_MANAGE_BUTTON
} else {
MainScreenTestTags.ORGANIZE_TOKENS_BUTTON
}
TangemButton(
buttonUM = organizeButton,
modifier = itemModifier,
modifier = itemModifier.testTag(testTag),
)
}
}

View file

@ -68,6 +68,7 @@ internal fun WalletBalance(
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.testTag(MainScreenTestTags.WALLET_LIST_ITEM)
.alpha(alpha)
.scale(scale)
.snapToExitUntilCollapsed(behavior)
@ -150,8 +151,12 @@ private fun SubtitleRow(walletBalanceUM: WalletBalanceUM, modifier: Modifier = M
text = walletBalanceUM.name,
style = TangemTheme.typography2.bodyRegular14,
color = TangemTheme.colors2.text.neutral.tertiary,
modifier = Modifier.testTag(MainScreenTestTags.CARD_TITLE),
)
TangemDeviceIcon(
state = walletBalanceUM.deviceIcon,
modifier = Modifier.testTag(MainScreenTestTags.CARD_IMAGE),
)
TangemDeviceIcon(state = walletBalanceUM.deviceIcon)
}
}
}

View file

@ -9,12 +9,14 @@ import androidx.compose.foundation.rememberOverscrollEffect
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.paging.compose.LazyPagingItems
import com.tangem.common.ui.notifications.notifications
import com.tangem.common.ui.notifications.notificationsCarousel
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.components.transactions.txHistoryItems
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.tokensListItems
@ -40,7 +42,7 @@ internal fun WalletListContent(
val itemModifier = movableItemModifier.padding(top = TangemTheme.dimens2.x3)
LazyColumn(
modifier = modifier,
modifier = modifier.testTag(MainScreenTestTags.SCREEN_CONTAINER),
state = listState,
contentPadding = contentPadding,
horizontalAlignment = Alignment.CenterHorizontally,

View file

@ -93,7 +93,10 @@ internal fun WalletTopBar(
),
) {
topBarConfig.endActions.forEach { action ->
TangemTopBarActionContent(action)
TangemTopBarActionContent(
action,
modifier = Modifier.testTag(MainScreenTestTags.MORE_BUTTON),
)
}
}
},