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, "VISA_ONBOARDING_ENABLED" to true,
"AND_15101_TANGEM_PAY_HOT_WALLET_ONBOARDING" to true, "AND_15101_TANGEM_PAY_HOT_WALLET_ONBOARDING" to true,
"AND_15310_ADD_FUNDS_STAGE1" 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? { private fun parseVolume(node: SemanticsNode): Double? {
val text = extractText(node) ?: return null 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() performClick()
} }
fun KNode.clickWhenEnabled() {
assertIsEnabled()
performClick()
}
fun KNode.assertTextContainsSafe( fun KNode.assertTextContainsSafe(
text: String, text: String,
substring: Boolean = false, substring: Boolean = false,

View file

@ -4,8 +4,6 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until import androidx.test.uiautomator.Until
import com.tangem.common.BaseTestCase import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG 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( fun BaseTestCase.swipeVertical(
direction: SwipeDirection, 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() { fun BaseTestCase.openTheAppFromRecents() {
device.uiDevice.waitForIdle() device.uiDevice.waitForIdle()

View file

@ -6,97 +6,33 @@ import com.tangem.common.extensions.swipeVertical
import com.tangem.screens.onMainScreen import com.tangem.screens.onMainScreen
import io.qameta.allure.kotlin.Allure.step import io.qameta.allure.kotlin.Allure.step
fun BaseTestCase.checkSingleCurrencyMainScreen( fun BaseTestCase.checkSingleCurrencyMainScreen(cardTitle: String) {
cardBlockchain: String,
cardTitle: String,
withTransactions: Boolean = false,
withWalletImage: Boolean = true
) {
step("Assert card title equal '$cardTitle'") { step("Assert card title equal '$cardTitle'") {
onMainScreen { walletNameText.assertTextEquals(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") { step("Assert 'Buy' button is displayed") {
onMainScreen { buyButton.assertIsDisplayed() } onMainScreen { buyButton.assertIsDisplayed() }
} }
step("Assert 'Send' button is displayed") {
onMainScreen { sendButton.assertIsDisplayed() }
}
step("Assert 'Sell' button is displayed") { step("Assert 'Sell' button is displayed") {
onMainScreen { sellButton.assertIsDisplayed() } onMainScreen { sellButton.assertIsDisplayed() }
} }
step("Assert 'Swap' button is not displayed") { step("Assert 'Swap' button is not displayed") {
onMainScreen { swapButton.assertIsNotDisplayed() } 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") { step("Swipe up") {
swipeVertical(SwipeDirection.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") { step("Assert 'Add & Manage' button is not displayed") {
onMainScreen { addAndManageButtonWithoutLazySearch.assertIsNotDisplayed() } onMainScreen { addAndManageButtonWithoutLazySearch.assertIsNotDisplayed() }
} }
} }
fun BaseTestCase.checkMultiCurrencyMainScreen( fun BaseTestCase.checkMultiCurrencyMainScreen(
devicesCount: String,
cardTitle: String, cardTitle: String,
withWalletImage: Boolean = true
) { ) {
step("Assert card title equal '$cardTitle'") { step("Assert card title equal '$cardTitle'") {
onMainScreen { walletNameText.assertTextEquals(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") { step("Assert 'Buy' button is displayed") {
onMainScreen { buyButton.assertIsDisplayed() } onMainScreen { buyButton.assertIsDisplayed() }
} }

View file

@ -1,28 +1,28 @@
package com.tangem.scenarios package com.tangem.scenarios
import androidx.compose.ui.test.ExperimentalTestApi
import com.tangem.common.BaseTestCase import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.SwipeDirection
import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.swipeVertical
import com.tangem.screens.onMainScreen import com.tangem.screens.onMainScreen
import com.tangem.screens.onMarketsExchangesScreen import com.tangem.screens.onMarketsExchangesScreen
import com.tangem.screens.onMarketsScreen import com.tangem.screens.onMarketsScreen
import com.tangem.screens.onMarketsTokenDetailsScreen import com.tangem.screens.onMarketsTokenDetailsScreen
import io.qameta.allure.kotlin.Allure.step 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") { step("Open 'Markets' screen") {
onMainScreen { searchThroughMarketPlaceholder.performClick() } onMainScreen { searchThroughMarketPlaceholder.performClick() }
waitForIdle() waitForIdle()
} }
step("Click on 'Search' placeholder") {
onMarketsScreen { searchThroughMarketPlaceholder.performClick() }
}
step("Click on $blockchainName blockchain") { step("Click on $blockchainName blockchain") {
waitForIdle() waitForIdle()
onMarketsScreen { tokenWithTitle(blockchainName).clickWithAssertion() } 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() waitForIdle()
onMarketsTokenDetailsScreen { tokenWithTitle(tokenName).clickWithAssertion() } onMarketsTokenDetailsScreen { tokenWithTitle(tokenName).clickWithAssertion() }
} }
@ -59,6 +59,7 @@ fun BaseTestCase.openMarketsScreen() {
} }
} }
@OptIn(ExperimentalTestApi::class)
fun BaseTestCase.openMarketsExchangesScreen(tokenName: String, shouldClickSeeAllButton: Boolean = false) { fun BaseTestCase.openMarketsExchangesScreen(tokenName: String, shouldClickSeeAllButton: Boolean = false) {
openMarketsScreen() openMarketsScreen()
if (shouldClickSeeAllButton) if (shouldClickSeeAllButton)
@ -69,9 +70,8 @@ fun BaseTestCase.openMarketsExchangesScreen(tokenName: String, shouldClickSeeAll
onMarketsScreen { tokenWithTitle(tokenName).clickWithAssertion() } onMarketsScreen { tokenWithTitle(tokenName).clickWithAssertion() }
waitForIdle() waitForIdle()
} }
step("Scroll down") { step("Scroll to 'Listed on exchanges' block") {
swipeVertical(SwipeDirection.UP) onMarketsScreen { scrollToListedOnBlock() }
swipeVertical(SwipeDirection.UP)
} }
step("Click on 'Listed on exchanges' block") { step("Click on 'Listed on exchanges' block") {
onMarketsScreen { listedOnBlockContainer.performClick() } 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.QUOTES_API_SCENARIO
import com.tangem.common.constants.TestConstants.USER_TOKENS_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.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.extensions.assertIsDimmed
import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.setWireMockScenarioState import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.screens.* import com.tangem.screens.*
@ -34,8 +33,11 @@ fun BaseTestCase.openSendScreen(
step("Click on token with name: '$tokenName'") { step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() } onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
} }
step("Click on 'Send' button") { step("Click on 'Transfer' button") {
onTokenDetailsScreen { sendButton().performClick() } 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'") { step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() } onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
} }
step("Assert 'Send' button is not dimmed") { step("Click on 'Transfer' button") {
onTokenDetailsScreen { sendButton().assertIsDimmed(false) } onTokenDetailsScreen { transferButton.clickWithAssertion() }
} }
step("Click on 'Send' button") { step("Click on 'Send' button in bottom sheet") {
onTokenDetailsScreen { sendButton().performClick() } onTransferBottomSheet { sendButton.clickWithAssertion() }
} }
step("Type '$inputAmount' in input text field") { step("Type '$inputAmount' in input text field") {
onSendScreen { onSendScreen {
@ -109,6 +111,13 @@ fun BaseTestCase.openSendAddressScreen(
step("Assert 'Send Address' container is displayed") { step("Assert 'Send Address' container is displayed") {
onSendAddressScreen { container.assertIsDisplayed() } 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) { fun BaseTestCase.checkScanQrScreen(emptyClipboard: Boolean = true) {
@ -244,8 +253,11 @@ fun BaseTestCase.selectTokenToSendViaSwap(
networkName: String, networkName: String,
networkType: String? = null, networkType: String? = null,
) { ) {
step("Click on 'Send' button") { step("Click on 'Transfer' button") {
onTokenDetailsScreen { sendButton().performClick() } onTokenDetailsScreen { transferButton.clickWithAssertion() }
}
step("Click on 'Send' button in bottom sheet") {
onTransferBottomSheet { sendButton.clickWithAssertion() }
} }
step("Click on 'Swap to another token' button") { step("Click on 'Swap to another token' button") {
onSendScreen { swapToAnotherTokenButton.performClick() } 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.HOLD_DURATION_MS
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.extensions.assertVisibility import com.tangem.common.extensions.assertVisibility
import com.tangem.common.extensions.clickWhenEnabled
import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.isDisplayedSafely import com.tangem.common.extensions.isDisplayedSafely
import com.tangem.core.ui.R as CoreUiR import com.tangem.core.ui.R as CoreUiR
@ -43,8 +44,8 @@ fun BaseTestCase.openSwapScreen(
} }
SwapEntryPoint.TokenDetails -> step("Click on 'Swap' button on 'Token details' screen") { 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") { SwapEntryPoint.MarketsTokenDetails -> step("Click on 'Swap' button on 'Markets' token details screen") {
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.performClick() } onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.performClick() }

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 import io.github.kakaocup.kakao.common.utilities.getResourceString
class AddTokenBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : class AddTokenBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<AddTokenBottomSheetPageObject>(semanticsProvider = semanticsProvider) { ComposeScreen<AddTokenBottomSheetPageObject>(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(BaseBottomSheetTestTags.CONTAINER) },
) {
val title: KNode = child { val title: KNode = child {
hasTestTag(BaseBottomSheetTestTags.TITLE) hasTestTag(BaseBottomSheetTestTags.TITLE)
@ -23,6 +26,12 @@ class AddTokenBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractions
hasText(getResourceString(R.string.common_add)) hasText(getResourceString(R.string.common_add))
useUnmergedTree = true useUnmergedTree = true
} }
val laterButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_later))
useUnmergedTree = true
}
} }
internal fun BaseTestCase.onAddTokenBottomSheet(function: AddTokenBottomSheetPageObject.() -> Unit) = 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.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import androidx.compose.ui.test.hasAnyAncestor import androidx.compose.ui.test.hasAnyAncestor
import androidx.compose.ui.test.swipeUp
import com.tangem.common.BaseTestCase import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.getQuantityString import com.tangem.common.extensions.getQuantityString
import com.tangem.common.extensions.hasLazyListItemPosition import com.tangem.common.extensions.hasLazyListItemPosition
@ -49,27 +50,32 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
val buyButton: KNode = child { val buyButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_buy)) hasAnyDescendant(withText(getResourceString(R.string.common_buy)))
useUnmergedTree = true
} }
val sendButton: KNode = child { val sendButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_send)) hasAnyDescendant(withText(getResourceString(R.string.common_send)))
useUnmergedTree = true
} }
val receiveButton: KNode = child { val receiveButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_receive)) hasAnyDescendant(withText(getResourceString(R.string.common_receive)))
useUnmergedTree = true
} }
val sellButton: KNode = child { val sellButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_sell)) hasAnyDescendant(withText(getResourceString(R.string.common_sell)))
useUnmergedTree = true
} }
val swapButton: KNode = child { val swapButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_swap)) hasAnyDescendant(withText(getResourceString(R.string.common_swap)))
useUnmergedTree = true
} }
val walletNameText: KNode = child { val walletNameText: KNode = child {
@ -82,13 +88,21 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
useUnmergedTree = true useUnmergedTree = true
} }
val walletDevicesCount: KNode = child { /**
hasTestTag(MainScreenTestTags.DEVICES_COUNT) * Collapses the collapsing header via a touch-based swipe so that items near the bottom
useUnmergedTree = true * 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) @OptIn(ExperimentalTestApi::class)
fun marketPriceBlock(): LazyListItemNode { fun marketPriceBlock(): LazyListItemNode {
collapseHeader()
return lazyList.childWith<LazyListItemNode> { return lazyList.childWith<LazyListItemNode> {
hasTestTag(MarketPriceBlockTestTags.BLOCK) hasTestTag(MarketPriceBlockTestTags.BLOCK)
useUnmergedTree = true useUnmergedTree = true
@ -231,6 +245,7 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
*/ */
@OptIn(ExperimentalTestApi::class) @OptIn(ExperimentalTestApi::class)
fun accountWithName(name: String): LazyListItemNode { fun accountWithName(name: String): LazyListItemNode {
collapseHeader()
return lazyList.childWith<LazyListItemNode> { return lazyList.childWith<LazyListItemNode> {
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM) hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
hasAnyDescendant(withText(name)) hasAnyDescendant(withText(name))
@ -243,6 +258,7 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
*/ */
@OptIn(ExperimentalTestApi::class) @OptIn(ExperimentalTestApi::class)
fun tokenWithTitleAndAddress(tokenTitle: String): KNode { fun tokenWithTitleAndAddress(tokenTitle: String): KNode {
collapseHeader()
return lazyList.childWith<LazyListItemNode> { return lazyList.childWith<LazyListItemNode> {
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM) hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
hasText(tokenTitle) hasText(tokenTitle)
@ -255,6 +271,7 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
@OptIn(ExperimentalTestApi::class) @OptIn(ExperimentalTestApi::class)
fun tokenWithCustomDerivationIcon(tokenTitle: String): KNode { fun tokenWithCustomDerivationIcon(tokenTitle: String): KNode {
collapseHeader()
return lazyList.childWith<LazyListItemNode> { return lazyList.childWith<LazyListItemNode> {
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM) hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
hasText(tokenTitle) hasText(tokenTitle)
@ -267,6 +284,7 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
@OptIn(ExperimentalTestApi::class) @OptIn(ExperimentalTestApi::class)
fun addAndManageButton(): KNode { fun addAndManageButton(): KNode {
collapseHeader()
return lazyList.childWith<LazyListItemNode> { return lazyList.childWith<LazyListItemNode> {
hasTestTag(MainScreenTestTags.ADD_AND_MANAGE_BUTTON) hasTestTag(MainScreenTestTags.ADD_AND_MANAGE_BUTTON)
}.child<KNode> { }.child<KNode> {
@ -282,11 +300,12 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
} }
val searchThroughMarketPlaceholder: KNode = child { val searchThroughMarketPlaceholder: KNode = child {
hasText(getResourceString(R.string.markets_search_header_title)) hasText(getResourceString(R.string.markets_search_title_placeholder))
useUnmergedTree = true useUnmergedTree = true
} }
fun tokenNetworkGroupTitle(tokenNetwork: String): KNode { fun tokenNetworkGroupTitle(tokenNetwork: String): KNode {
collapseHeader()
return lazyList.child { return lazyList.child {
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM) hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
hasAnyChild(withText(tokenNetwork)) hasAnyChild(withText(tokenNetwork))
@ -296,6 +315,7 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
@OptIn(ExperimentalTestApi::class) @OptIn(ExperimentalTestApi::class)
fun tokenWithTitleAndPosition(tokenTitle: String, index: Int): KNode { fun tokenWithTitleAndPosition(tokenTitle: String, index: Int): KNode {
collapseHeader()
return lazyList.childWith<LazyListItemNode> { return lazyList.childWith<LazyListItemNode> {
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM) hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
hasText(tokenTitle) hasText(tokenTitle)

View file

@ -2,11 +2,9 @@ package com.tangem.screens
import androidx.compose.ui.semantics.SemanticsNode import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import androidx.compose.ui.test.hasParent
import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.hasTestTag
import com.tangem.common.BaseTestCase import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.TokenElementsTestTags import com.tangem.core.ui.test.TokenElementsTestTags
import com.tangem.core.ui.test.TopAppBarTestTags
import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.impl.R
import io.github.kakaocup.compose.node.element.ComposeScreen 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.ComposeScreen.Companion.onComposeScreen
@ -23,16 +21,15 @@ class MarketsExchangesPageObject(private val provider: SemanticsNodeInteractions
fun allExchangeTypeNodes(): List<SemanticsNode> = fun allExchangeTypeNodes(): List<SemanticsNode> =
provider provider
.onAllNodes(hasParent(hasParent(hasTestTag(TokenElementsTestTags.TOKEN_PRICE)))) .onAllNodes(hasTestTag(TokenElementsTestTags.TOKEN_PRICE))
.fetchSemanticsNodes() .fetchSemanticsNodes()
fun allTrustScoreNodes(): List<SemanticsNode> = fun allTrustScoreNodes(): List<SemanticsNode> =
provider provider
.onAllNodes(hasParent(hasTestTag(TokenElementsTestTags.TOKEN_CRYPTO_AMOUNT))) .onAllNodes(hasTestTag(TokenElementsTestTags.TOKEN_CRYPTO_AMOUNT))
.fetchSemanticsNodes() .fetchSemanticsNodes()
val exchangesTitle: KNode = child { val exchangesTitle: KNode = child {
hasTestTag(TopAppBarTestTags.TITLE)
hasText(getResourceString(R.string.markets_token_details_exchanges_title)) hasText(getResourceString(R.string.markets_token_details_exchanges_title))
useUnmergedTree = true useUnmergedTree = true
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -1,20 +1,15 @@
package com.tangem.screens package com.tangem.screens
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase import com.tangem.common.BaseTestCase
import com.tangem.common.utils.LazyListItemNode
import com.tangem.core.ui.test.BaseActionButtonsBlockTestTags import com.tangem.core.ui.test.BaseActionButtonsBlockTestTags
import com.tangem.core.ui.test.BaseButtonTestTags import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.NotificationTestTags import com.tangem.core.ui.test.NotificationTestTags
import com.tangem.core.ui.test.TokenDetailsScreenTestTags import com.tangem.core.ui.test.TokenDetailsScreenTestTags
import com.tangem.core.ui.utils.LazyListItemPositionSemantics
import com.tangem.features.tokendetails.impl.R import com.tangem.features.tokendetails.impl.R
import io.github.kakaocup.compose.node.element.ComposeScreen 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.ComposeScreen.Companion.onComposeScreen
import io.github.kakaocup.compose.node.element.KNode 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 io.github.kakaocup.kakao.common.utilities.getResourceString
import androidx.compose.ui.test.hasTestTag as withTestTag import androidx.compose.ui.test.hasTestTag as withTestTag
import androidx.compose.ui.test.hasText as withText import androidx.compose.ui.test.hasText as withText
@ -36,18 +31,8 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
useUnmergedTree = true useUnmergedTree = true
} }
val availableStakingBlockTitle: KNode = child { fun availableStakingBlockText(apy: String): KNode = child {
hasTestTag(TokenDetailsScreenTestTags.STAKING_SERVICE_TITLE) hasText(getResourceString(R.string.token_details_earn_staking_subtitle, apy))
useUnmergedTree = true
}
val availableStakingBlockText: KNode = child {
hasTestTag(TokenDetailsScreenTestTags.STAKING_SERVICE_TEXT)
useUnmergedTree = true
}
val availableStakingBlockCurrencyIcon: KNode = child {
hasTestTag(TokenDetailsScreenTestTags.STAKING_CURRENCY_ICON)
useUnmergedTree = true useUnmergedTree = true
} }
@ -62,69 +47,39 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
useUnmergedTree = true useUnmergedTree = true
} }
val stakingDot: KNode = child {
hasTestTag(TokenDetailsScreenTestTags.STAKING_DOT)
useUnmergedTree = true
}
val stakingTokenAmount: KNode = child { val stakingTokenAmount: KNode = child {
hasTestTag(TokenDetailsScreenTestTags.STAKING_TOKEN_AMOUNT) hasTestTag(TokenDetailsScreenTestTags.STAKING_TOKEN_AMOUNT)
useUnmergedTree = true useUnmergedTree = true
} }
val stakingChevronIcon: KNode = child { val stakingTitle: KNode = child {
hasTestTag(TokenDetailsScreenTestTags.STAKING_CHEVRON_ICON) hasText(getResourceString(R.string.common_staking))
useUnmergedTree = true
} }
val stakingTitle: KNode = child { val stakingEnabledTitle: KNode = child {
hasText(getResourceString(R.string.staking_native)) hasText(getResourceString(R.string.staking_enabled))
} }
val title: KNode = child { val title: KNode = child {
hasTestTag(TokenDetailsScreenTestTags.TOKEN_TITLE) hasTestTag(TokenDetailsScreenTestTags.TOKEN_TITLE)
} }
private val horizontalActionChips = KLazyListNode( val addFundsButton: KNode = child {
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> {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) 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) val swapButton: KNode = child {
fun swapButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_swap)) hasAnyDescendant(withText(getResourceString(R.string.common_swap)))
useUnmergedTree = true
} }
@OptIn(ExperimentalTestApi::class) val transferButton: KNode = child {
fun sellButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_sell)) hasAnyDescendant(withText(getResourceString(R.string.common_transfer)))
} useUnmergedTree = true
@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))
} }
fun networkFeeNotificationIcon(feeCurrencyName: String): KNode = child { 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_SWAP_ICON))
hasAnyDescendant(withTestTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_TO_ICON)) hasAnyDescendant(withTestTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_TO_ICON))
hasAnyDescendant(withTestTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_TO_AMOUNT)) hasAnyDescendant(withTestTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_TO_AMOUNT))
hasAnyDescendant(withTestTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_CHEVRON_ICON))
useUnmergedTree = true 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.onStoriesScreen
import com.tangem.screens.onTokenDetailsScreen import com.tangem.screens.onTokenDetailsScreen
import com.tangem.screens.onMainScreenTopBar import com.tangem.screens.onMainScreenTopBar
import com.tangem.screens.onTransferBottomSheet
import com.tangem.tap.domain.sdk.mocks.MockProvider import com.tangem.tap.domain.sdk.mocks.MockProvider
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId import io.qameta.allure.kotlin.AllureId
@ -94,8 +95,11 @@ class FeedbackTest : BaseTestCase() {
step("Click on token with name: '$tokenName'") { step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() } onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
} }
step("Click 'Send' button") { step("Click on 'Transfer' button") {
onTokenDetailsScreen { sendButton().performClick() } onTokenDetailsScreen { transferButton.clickWithAssertion() }
}
step("Click on 'Send' button in bottom sheet") {
onTransferBottomSheet { sendButton.clickWithAssertion() }
} }
step("Type '$sendAmount' in input text field") { step("Type '$sendAmount' in input text field") {
onSendScreen { onSendScreen {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,16 +2,17 @@ package com.tangem.tests.actionButtons
import com.tangem.common.BaseTestCase import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.extensions.assertIsDimmed
import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.extensions.clickWithAssertion
import com.tangem.scenarios.checkQrCodeBottomSheetScenario import com.tangem.scenarios.checkQrCodeBottomSheetScenario
import com.tangem.scenarios.goToQrCodeBottomSheet import com.tangem.scenarios.goToQrCodeBottomSheet
import com.tangem.scenarios.openMainScreen import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.onAddFundsBottomSheet
import com.tangem.screens.onMainScreen import com.tangem.screens.onMainScreen
import com.tangem.screens.onSwapStoriesScreen import com.tangem.screens.onSwapStoriesScreen
import com.tangem.screens.onSwapTokenScreen import com.tangem.screens.onSwapTokenScreen
import com.tangem.screens.onTokenDetailsScreen import com.tangem.screens.onTokenDetailsScreen
import com.tangem.screens.onTransferBottomSheet
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName import io.qameta.allure.kotlin.junit4.DisplayName
@ -37,20 +38,41 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
waitForIdle() waitForIdle()
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() } onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
} }
step("Assert 'Receive' button is displayed") { step("Assert 'Add funds' button is displayed") {
onTokenDetailsScreen { receiveButton().assertIsDisplayed() } onTokenDetailsScreen { addFundsButton.assertIsDisplayed() }
}
step("Assert 'Buy' button is displayed") {
onTokenDetailsScreen { buyButton().assertIsDisplayed() }
}
step("Assert 'Send' button is displayed") {
onTokenDetailsScreen { sendButton().assertIsDisplayed() }
} }
step("Assert 'Swap' button is displayed") { step("Assert 'Swap' button is displayed") {
onTokenDetailsScreen { swapButton().assertIsDisplayed() } onTokenDetailsScreen { swapButton.assertIsDisplayed() }
} }
step("Assert 'Sell' button is displayed") { step("Assert 'Transfer' button is displayed") {
onTokenDetailsScreen { sellButton().assertIsDisplayed() } 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() waitForIdle()
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() } onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
} }
step("Assert 'Receive' button is not dimmed") { step("Assert 'Add funds' button is enabled") {
onTokenDetailsScreen { receiveButton().assertIsDimmed(false) } onTokenDetailsScreen { addFundsButton.assertIsEnabled() }
} }
step("Assert 'Buy' button is not dimmed") { step("Assert 'Swap' button is disabled") {
onTokenDetailsScreen { buyButton().assertIsDimmed(false) } onTokenDetailsScreen { swapButton.assertIsNotEnabled() }
} }
step("Assert 'Send' button is not dimmed") { step("Assert 'Transfer' button is enabled") {
onTokenDetailsScreen { sendButton().assertIsDimmed(false) } onTokenDetailsScreen { transferButton.assertIsEnabled() }
} }
step("Assert 'Swap' button is dimmed") { step("Click on 'Add funds' button") {
onTokenDetailsScreen { swapButton().assertIsDimmed() } onTokenDetailsScreen { addFundsButton.clickWithAssertion() }
} }
step("Assert 'Sell' button is dimmed") { step("Assert 'Buy' button in bottom sheet is enabled") {
onTokenDetailsScreen { sellButton().assertIsDimmed() } 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() } onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
} }
step("Click on 'Swap' button") { step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() } onTokenDetailsScreen { swapButton.performClick() }
} }
step("Close 'Stories' screen") { step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() } onSwapStoriesScreen { closeButton.clickWithAssertion() }
@ -140,8 +183,11 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
waitForIdle() waitForIdle()
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() } onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
} }
step("Click on 'Receive' button") { step("Click on 'Add funds' button") {
onTokenDetailsScreen { receiveButton().performClick() } onTokenDetailsScreen { addFundsButton.clickWithAssertion() }
}
step("Click on 'Receive' button in bottom sheet") {
onAddFundsBottomSheet { receiveButton.clickWithAssertion() }
} }
step("Go to QR code bottom sheet") { step("Go to QR code bottom sheet") {
flakySafely(WAIT_UNTIL_TIMEOUT) { 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 androidx.compose.ui.test.longClick
import com.tangem.common.BaseTestCase import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE 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.extensions.*
import com.tangem.common.utils.resetWireMockScenarioState import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState import com.tangem.common.utils.setWireMockScenarioState
@ -82,28 +83,27 @@ class TotalBalanceUpdateTest : BaseTestCase() {
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") { step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState) setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
} }
step("Click on 'Add to portfolio' button") { step("Click on 'Add' button in 'Markets' bottom sheet") {
onMarketsScreen { addToPortfolioButton.clickWithAssertion() } onMarketsScreen { addButton.clickWithAssertion() }
} }
step("Click on main network") { step("Click on 'Add' button in 'Add token' bottom sheet") {
onMarketsScreen { mainNetworkSuffix.performClick() } flakySafely(WAIT_UNTIL_TIMEOUT) {
} onAddTokenBottomSheet {
step("Click on 'Add' button") { addButton.performClick()
onDialog { addButton.clickWithAssertion() } }
} onAddTokenBottomSheet { laterButton.assertIsDisplayed() }
step("Assert 'Continue' is not displayed") { }
onDialog { addButton.assertIsNotDisplayed() }
} }
step("Click on 'Later' button") { step("Click on 'Later' button") {
onDialog { laterButton.clickWithAssertion() } onAddTokenBottomSheet { laterButton.performClick() }
} }
step("Go back to 'Markets: tokens list'") { step("Press 'Back' button") {
waitForIdle() waitForIdle()
onMarketsScreen { topBarBackButton.clickWithAssertion() } device.uiDevice.pressBack()
} }
step("Close 'Markets screen'") { step("Press 'Back' button") {
onSearchBar { searchField.assertIsDisplayed() } waitForIdle()
swipeMarketsBlock(SwipeDirection.DOWN) device.uiDevice.pressBack()
} }
step("Assert $updatedBalance is displayed in total balance") { step("Assert $updatedBalance is displayed in total balance") {
onMainScreen { totalBalanceText.assertTextContains(updatedBalance) } onMainScreen { totalBalanceText.assertTextContains(updatedBalance) }

View file

@ -2,6 +2,8 @@ package com.tangem.tests.main
import com.tangem.common.BaseTestCase import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO 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.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.openMainScreen import com.tangem.scenarios.openMainScreen
@ -35,7 +37,7 @@ class MainScreenTest : BaseTestCase() {
} }
@AllureId("8748") @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 @Test
fun checkOrganizeTokensButtonWithSingleTokenNoAccountsTest() { fun checkOrganizeTokensButtonWithSingleTokenNoAccountsTest() {
val scenarioState = "Cardano" val scenarioState = "Cardano"
@ -56,14 +58,14 @@ class MainScreenTest : BaseTestCase() {
step("Synchronize addresses") { step("Synchronize addresses") {
synchronizeAddresses() synchronizeAddresses()
} }
step("Assert 'Add & Manage' button is not displayed") { step("Assert 'Add & Manage' button is displayed") {
onMainScreen { addAndManageButtonNode.assertIsNotDisplayed()} onMainScreen { addAndManageButtonNode.assertIsDisplayed() }
} }
} }
} }
@AllureId("8749") @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 @Test
fun checkOrganizeTokensButtonWithSingleTokenMultiAccountsTest() { fun checkOrganizeTokensButtonWithSingleTokenMultiAccountsTest() {
val scenarioState = "TwoAccountsSingleTokenEach" val scenarioState = "TwoAccountsSingleTokenEach"
@ -81,14 +83,14 @@ class MainScreenTest : BaseTestCase() {
step("Open 'Main Screen'") { step("Open 'Main Screen'") {
openMainScreen() openMainScreen()
} }
step("Assert 'Add & Manage' button is not displayed") { step("Assert 'Add & Manage' button is displayed") {
onMainScreen { addAndManageButtonNode.assertIsNotDisplayed()} onMainScreen { addAndManageButtonNode.assertIsDisplayed() }
} }
} }
} }
@AllureId("8750") @AllureId("8750")
@DisplayName("Main: check 'Organize tokens' button with multiple tokens two accounts") @DisplayName("Main: check 'Add & Manage' button with multiple tokens two accounts")
@Test @Test
fun checkOrganizeTokensButtonWithMultipleTokensMultiAccountsTest() { fun checkOrganizeTokensButtonWithMultipleTokensMultiAccountsTest() {
val scenarioState = "TwoAccountsMixed" val scenarioState = "TwoAccountsMixed"
@ -106,8 +108,11 @@ class MainScreenTest : BaseTestCase() {
step("Open 'Main Screen'") { step("Open 'Main Screen'") {
openMainScreen() openMainScreen()
} }
step("Swipe up") {
swipeVertical(SwipeDirection.UP, startHeightRatio = 0.6f, endHeightRatio = 0.1f)
}
step("Assert 'Add & Manage' button is displayed") { step("Assert 'Add & Manage' button is displayed") {
onMainScreen { addAndManageButtonNode.assertIsDisplayed()} onMainScreen { addAndManageButtonNode.assertIsDisplayed() }
} }
} }
} }

View file

@ -16,7 +16,7 @@ import org.junit.Test
class WarningsTest : BaseTestCase() { class WarningsTest : BaseTestCase() {
@AllureId("184") @AllureId("184")
@DisplayName("Token list: hide token by long tap") @DisplayName("Warnings: missing address warning")
@Test @Test
fun checkUnavailableNetworksWarningTest() { fun checkUnavailableNetworksWarningTest() {
val scenarioState = "MissingDerivation" val scenarioState = "MissingDerivation"
@ -38,9 +38,6 @@ class WarningsTest : BaseTestCase() {
step("Synchronize addresses") { step("Synchronize addresses") {
synchronizeAddresses(isBalanceAvailable = false) synchronizeAddresses(isBalanceAvailable = false)
} }
step("Assert 'Missing addresses' notification icon is displayed") {
onMainScreen { missingAddressNotificationIcon.assertIsDisplayed() }
}
step("Assert 'Missing addresses' notification title is displayed") { step("Assert 'Missing addresses' notification title is displayed") {
onMainScreen { missingAddressNotificationTitle.assertIsDisplayed() } onMainScreen { missingAddressNotificationTitle.assertIsDisplayed() }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -281,13 +281,13 @@ class SendFeeScreenTest : BaseTestCase() {
fun checkNetworkFeeBottomSheetForBitcoinTest() { fun checkNetworkFeeBottomSheetForBitcoinTest() {
val tokenName = "Bitcoin" val tokenName = "Bitcoin"
val tokenAmount = "0.00000001" val tokenAmount = "0.00000001"
val feeAmount = "$2.86" val feeAmount = "$0.48"
val fiatFeeAmount = "$0.24" val fiatFeeAmount = "$0.24"
val marketSelectorItem = getResourceString(R.string.common_fee_selector_option_market) val marketSelectorItem = getResourceString(R.string.common_fee_selector_option_market)
val fastSelectorItem = getResourceString(R.string.common_fee_selector_option_fast) val fastSelectorItem = getResourceString(R.string.common_fee_selector_option_fast)
val slowSelectorItem = getResourceString(R.string.common_fee_selector_option_slow) val slowSelectorItem = getResourceString(R.string.common_fee_selector_option_slow)
val feeUpTo = getResourceString(R.string.send_max_fee) val feeUpTo = getResourceString(R.string.send_max_fee)
val feeUpToValue = "0.0000264 BTC" val feeUpToValue = "0.0000044 BTC"
val newFeeUpToValue = "0.0000022 BTC" val newFeeUpToValue = "0.0000022 BTC"
val satoshi = getResourceString(R.string.send_satoshi_per_byte_title) val satoshi = getResourceString(R.string.send_satoshi_per_byte_title)
val satoshiValue = "2" 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.KASPA_RECIPIENT_ADDRESS
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO 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.USER_TOKENS_API_SCENARIO
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.resetWireMockScenarioState import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.checkSendWarning import com.tangem.scenarios.checkSendWarning
import com.tangem.scenarios.openSendConfirmScreenViaNextButton
import com.tangem.scenarios.openSendScreen import com.tangem.scenarios.openSendScreen
import com.tangem.screens.onSendAddressScreen import com.tangem.screens.onSendAddressScreen
import com.tangem.screens.onSendScreen import com.tangem.screens.onSendScreen
@ -145,8 +147,10 @@ class KaspaWarningsTest : BaseTestCase() {
step("Type address in input text field") { step("Type address in input text field") {
onSendAddressScreen { addressTextField.performTextReplacement(KASPA_RECIPIENT_ADDRESS) } onSendAddressScreen { addressTextField.performTextReplacement(KASPA_RECIPIENT_ADDRESS) }
} }
step("Click on 'Next' button") { step("Click 'Next' button until 'Send Confirm' screen opens") {
onSendAddressScreen { nextButton.clickWithAssertion() } composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) {
runCatching { openSendConfirmScreenViaNextButton() }.isSuccess
}
} }
step("Assert 'UTXO limit warning' is displayed") { step("Assert 'UTXO limit warning' is displayed") {
checkSendWarning( checkSendWarning(

View file

@ -3,8 +3,6 @@ package com.tangem.tests.swap
import androidx.compose.ui.test.longClick import androidx.compose.ui.test.longClick
import androidx.test.InstrumentationRegistry.getTargetContext import androidx.test.InstrumentationRegistry.getTargetContext
import com.tangem.common.BaseTestCase 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.extensions.restartApp
import com.tangem.common.utils.resetWireMockScenarioState import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState import com.tangem.common.utils.setWireMockScenarioState
@ -18,101 +16,6 @@ import org.junit.Test
@HiltAndroidTest @HiltAndroidTest
class SwapStoriesTest : BaseTestCase() { 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") @AllureId("5469")
@DisplayName("Check unavailable swap stories on 'Main' screen") @DisplayName("Check unavailable swap stories on 'Main' screen")
@Test @Test
@ -136,9 +39,6 @@ class SwapStoriesTest : BaseTestCase() {
step("Synchronize addresses") { step("Synchronize addresses") {
synchronizeAddresses() synchronizeAddresses()
} }
step("Assert 'Swap' button has not badge") {
onMainScreen { swapButton.assertHasBadge(false) }
}
step("Open 'Swap' screen") { step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.MainScreen, storiesExist = false) openSwapScreen(from = SwapEntryPoint.MainScreen, storiesExist = false)
} }
@ -155,9 +55,6 @@ class SwapStoriesTest : BaseTestCase() {
waitForIdle() waitForIdle()
onMainScreen { swapButton.assertIsDisplayed() } onMainScreen { swapButton.assertIsDisplayed() }
} }
step("Assert 'Swap' button has badge") {
onMainScreen { swapButton.assertHasBadge() }
}
step("Open 'Swap' screen") { step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.MainScreen, storiesExist = true) openSwapScreen(from = SwapEntryPoint.MainScreen, storiesExist = true)
} }
@ -192,9 +89,6 @@ class SwapStoriesTest : BaseTestCase() {
step("Click on token with name: '$tokenName'") { step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() } onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() }
} }
step("Assert 'Swap' button has not badge") {
onTokenDetailsScreen { swapButton().assertHasBadge(false) }
}
step("Open 'Swap' screen") { step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.TokenDetails, storiesExist = false) openSwapScreen(from = SwapEntryPoint.TokenDetails, storiesExist = false)
} }
@ -207,13 +101,6 @@ class SwapStoriesTest : BaseTestCase() {
step("Restart app") { step("Restart app") {
restartApp(packageName) restartApp(packageName)
} }
step("Assert 'Swap' button has badge") {
waitForIdle()
flakySafely(WAIT_UNTIL_TIMEOUT) {
composeTestRule.mainClock.advanceTimeBy(500)
onMainScreen { swapButton.assertHasBadge() }
}
}
step("Open 'Swap' screen") { step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.TokenDetails, storiesExist = true) openSwapScreen(from = SwapEntryPoint.TokenDetails, storiesExist = true)
} }
@ -228,8 +115,6 @@ class SwapStoriesTest : BaseTestCase() {
val scenarioErrorState = "Error" val scenarioErrorState = "Error"
val packageName = getTargetContext().packageName val packageName = getTargetContext().packageName
val tokenName = "Ethereum" val tokenName = "Ethereum"
val badgeShown = "Badge shown"
val badgeHidden = "Badge hidden"
setupHooks( setupHooks(
additionalBeforeAppLaunchSection = { additionalBeforeAppLaunchSection = {
@ -246,16 +131,15 @@ class SwapStoriesTest : BaseTestCase() {
step("Synchronize addresses") { step("Synchronize addresses") {
synchronizeAddresses() synchronizeAddresses()
} }
step("Open 'Markets' token details screen for token '$tokenName'") { step("Open 'Token details' from 'Markets' screen for token '$tokenName'") {
openMarketTokenDetailsScreen(blockchainName = tokenName, tokenName = tokenName) openTokenDetailsFromMarketsScreen(blockchainName = tokenName, tokenName = tokenName)
} }
step("Assert 'Swap' button has not badge") { step("Assert 'Swap' button is displayed") {
waitForIdle() waitForIdle()
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertIsDisplayed() } onTokenDetailsScreen { swapButton.assertIsDisplayed() }
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertContentDescriptionEquals(badgeHidden) }
} }
step("Open 'Swap' screen") { step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.MarketsTokenDetails, storiesExist = false) openSwapScreen(from = SwapEntryPoint.TokenDetails, storiesExist = false)
} }
step("Click on 'Close' button") { step("Click on 'Close' button") {
onSwapTokenScreen { closeButton.performClick() } onSwapTokenScreen { closeButton.performClick() }
@ -266,16 +150,12 @@ class SwapStoriesTest : BaseTestCase() {
step("Restart app") { step("Restart app") {
restartApp(packageName) restartApp(packageName)
} }
step("Open 'Markets' token details screen for token '$tokenName'") { step("Assert 'Swap' button is displayed") {
openMarketTokenDetailsScreen(blockchainName = tokenName, tokenName = tokenName)
}
step("Assert 'Swap' button has badge") {
waitForIdle() waitForIdle()
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertIsDisplayed() } onTokenDetailsScreen { swapButton.assertIsDisplayed() }
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertContentDescriptionEquals(badgeShown) }
} }
step("Open 'Swap' screen") { 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'") { step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() } onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() }
} }
step("Assert 'Swap' button has badge") {
onTokenDetailsScreen { swapButton().assertHasBadge() }
}
step("Click on 'Swap' button on 'Token details' screen") { step("Click on 'Swap' button on 'Token details' screen") {
onTokenDetailsScreen { swapButton().performClick() } onTokenDetailsScreen { swapButton.performClick() }
} }
step("Check stories changes") { step("Check stories changes") {
checkStoriesChanges() checkStoriesChanges()
@ -369,11 +246,11 @@ class SwapStoriesTest : BaseTestCase() {
step("Synchronize addresses") { step("Synchronize addresses") {
synchronizeAddresses() synchronizeAddresses()
} }
step("Open 'Markets' token details screen for token '$tokenName'") { step("Open 'Token details' from 'Markets' screen for token '$tokenName'") {
openMarketTokenDetailsScreen(blockchainName = tokenName, tokenName = tokenName) openTokenDetailsFromMarketsScreen(blockchainName = tokenName, tokenName = tokenName)
} }
step("Click on 'Swap' button on 'Markets' token details screen") { step("Click on 'Swap' button on 'Markets' token details screen") {
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.performClick() } onTokenDetailsScreen { swapButton.performClick() }
} }
step("Check stories changes") { step("Check stories changes") {
checkStoriesChanges() checkStoriesChanges()
@ -388,7 +265,7 @@ class SwapStoriesTest : BaseTestCase() {
onSwapTokenScreen { closeButton.performClick() } onSwapTokenScreen { closeButton.performClick() }
} }
step("Open 'Swap' screen without stories") { 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") { step("Click on 'Close' button") {
onSwapTokenScreen { closeButton.performClick() } 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") { step("Open 'Swap' screen without stories") {
openSwapScreen(from = SwapEntryPoint.TokenActionsBottomSheet, storiesExist = false) openSwapScreen(from = SwapEntryPoint.TokenActionsBottomSheet, storiesExist = false)
} }

View file

@ -50,7 +50,7 @@ class SwapTokenScreenTest : BaseTestCase() {
onTokenDetailsScreen { title.assertIsDisplayed() } onTokenDetailsScreen { title.assertIsDisplayed() }
} }
step("Click on 'Swap' button") { step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() } onTokenDetailsScreen { swapButton.performClick() }
} }
step("Close 'Stories' screen") { step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() } onSwapStoriesScreen { closeButton.clickWithAssertion() }
@ -147,7 +147,7 @@ class SwapTokenScreenTest : BaseTestCase() {
disableMobileData() disableMobileData()
} }
step("Click on 'Swap' button") { step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() } onTokenDetailsScreen { swapButton.performClick() }
} }
step("Close 'Stories' screen") { step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() } onSwapStoriesScreen { closeButton.clickWithAssertion() }
@ -201,7 +201,7 @@ class SwapTokenScreenTest : BaseTestCase() {
onTokenDetailsScreen { title.assertIsDisplayed() } onTokenDetailsScreen { title.assertIsDisplayed() }
} }
step("Click on 'Swap' button") { step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() } onTokenDetailsScreen { swapButton.performClick() }
} }
step("Close 'Stories' screen") { step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() } onSwapStoriesScreen { closeButton.clickWithAssertion() }
@ -304,7 +304,7 @@ class SwapTokenScreenTest : BaseTestCase() {
onTokenDetailsScreen { title.assertIsDisplayed() } onTokenDetailsScreen { title.assertIsDisplayed() }
} }
step("Click on 'Swap' button") { step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() } onTokenDetailsScreen { swapButton.performClick() }
} }
step("Close 'Stories' screen") { step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() } onSwapStoriesScreen { closeButton.clickWithAssertion() }
@ -510,7 +510,7 @@ class SwapTokenScreenTest : BaseTestCase() {
onMainScreen { tokenWithTitleAndAddress(polygon).clickWithAssertion() } onMainScreen { tokenWithTitleAndAddress(polygon).clickWithAssertion() }
} }
step("Assert 'Swap' button is not dimmed. Swap available") { step("Assert 'Swap' button is not dimmed. Swap available") {
onTokenDetailsScreen { swapButton().assertIsDimmed(false) } onTokenDetailsScreen { swapButton.assertIsEnabled() }
} }
step("Press 'Back' button") { step("Press 'Back' button") {
device.uiDevice.pressBack() device.uiDevice.pressBack()
@ -519,7 +519,7 @@ class SwapTokenScreenTest : BaseTestCase() {
onMainScreen { tokenWithTitleAndAddress(bitcoin).clickWithAssertion() } onMainScreen { tokenWithTitleAndAddress(bitcoin).clickWithAssertion() }
} }
step("Assert 'Swap' button is dimmed") { step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertIsDimmed(true) } onTokenDetailsScreen { swapButton.assertIsNotEnabled() }
} }
step("Press 'Back' button") { step("Press 'Back' button") {
device.uiDevice.pressBack() device.uiDevice.pressBack()
@ -528,7 +528,7 @@ class SwapTokenScreenTest : BaseTestCase() {
onMainScreen { tokenWithTitleAndAddress(salam).clickWithAssertion() } onMainScreen { tokenWithTitleAndAddress(salam).clickWithAssertion() }
} }
step("Assert 'Swap' button is dimmed") { 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.blur
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.innerShadow import androidx.compose.ui.draw.innerShadow
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.shadow.Shadow 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.extensions.*
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
import com.tangem.core.res.R as CoreResR import com.tangem.core.res.R as CoreResR
private const val TINTED_BACKGROUND_ALPHA = 0.1f 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 -> { is EarnBlockUM.TrailingUM.Balance -> {
if (!trailingUM.isBalanceHidden) { 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(
text = trailingUM.fiatValue.resolveAnnotatedReference(), text = trailingUM.fiatValue.resolveAnnotatedReference(),
style = TangemTheme.typography2.bodySemibold16, style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary, color = TangemTheme.colors2.text.neutral.primary,
modifier = Modifier.layoutId(TangemRowLayoutId.END_TOP), modifier = fiatModifier,
) )
Text( Text(
text = trailingUM.cryptoValue.resolveReference(), text = trailingUM.cryptoValue.resolveReference(),
style = TangemTheme.typography2.captionMedium12, style = TangemTheme.typography2.captionMedium12,
color = TangemTheme.colors2.text.neutral.secondary, 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.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.tangem.common.ui.R 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.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -78,7 +80,8 @@ private fun ExpressTransactionItem(
.clip(TangemTheme.shapes.roundedCornersXMedium) .clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors2.surface.level3) .background(TangemTheme.colors2.surface.level3)
.clickable(onClick = info.onClick) .clickable(onClick = info.onClick)
.padding(TangemTheme.dimens2.x4), .padding(TangemTheme.dimens2.x4)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM),
) { ) {
TitleRow( TitleRow(
title = info.title.resolveReference(), title = info.title.resolveReference(),
@ -104,7 +107,9 @@ private fun TitleRow(title: String, infoIconRes: Int?, infoIconTint: Color?) {
text = title, text = title,
style = TangemTheme.typography2.bodyMedium16, style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors3.text.primary, color = TangemTheme.colors3.text.primary,
modifier = Modifier.weight(1f), modifier = Modifier
.weight(1f)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_TITLE),
) )
if (infoIconRes != null && infoIconTint != null) { if (infoIconRes != null && infoIconTint != null) {
Icon( Icon(
@ -126,32 +131,42 @@ private fun AmountsRow(info: ExpressTransactionStateInfoUM) {
CurrencyIcon( CurrencyIcon(
state = info.fromCurrencyIcon, state = info.fromCurrencyIcon,
shouldDisplayNetwork = false, shouldDisplayNetwork = false,
modifier = Modifier.size(TangemTheme.dimens.size18), modifier = Modifier
.size(TangemTheme.dimens.size18)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_FROM_ICON),
) )
EllipsisText( EllipsisText(
text = info.fromAmount.resolveReference(), text = info.fromAmount.resolveReference(),
style = TangemTheme.typography2.bodyMedium16, style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors3.text.primary, color = TangemTheme.colors3.text.primary,
ellipsis = TextEllipsis.OffsetEnd(info.fromAmountSymbol.length), 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( Icon(
painter = painterResource(R.drawable.ic_forward_24), painter = painterResource(R.drawable.ic_forward_24),
contentDescription = null, contentDescription = null,
tint = TangemTheme.colors3.icon.tertiary, 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( CurrencyIcon(
state = info.toCurrencyIcon, state = info.toCurrencyIcon,
shouldDisplayNetwork = false, shouldDisplayNetwork = false,
modifier = Modifier.size(TangemTheme.dimens.size18), modifier = Modifier
.size(TangemTheme.dimens.size18)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_TO_ICON),
) )
EllipsisText( EllipsisText(
text = info.toAmount.resolveReference(), text = info.toAmount.resolveReference(),
style = TangemTheme.typography2.bodyMedium16, style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors3.text.primary, color = TangemTheme.colors3.text.primary,
ellipsis = TextEllipsis.OffsetEnd(info.toAmountSymbol.length), 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.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.vectorResource 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 androidx.compose.ui.unit.dp
import com.tangem.core.ui.R import com.tangem.core.ui.R
import com.tangem.core.ui.ds.row.TangemRowContainer import com.tangem.core.ui.ds.row.TangemRowContainer
@ -68,7 +70,8 @@ fun TokenActionRow(
onClick = onClick, onClick = onClick,
onLongClick = onLongClick, onLongClick = onLongClick,
hapticManager = hapticManager, hapticManager = hapticManager,
), )
.semantics { if (!isEnabled) disabled() },
) { ) {
LeadingIcon(iconRes = iconRes, accentColor = accentColor) LeadingIcon(iconRes = iconRes, accentColor = accentColor)
Text( 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.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview 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.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.BaseBottomSheetTestTags
import com.tangem.core.ui.utils.WindowInsetsZero import com.tangem.core.ui.utils.WindowInsetsZero
/** /**
@ -253,7 +255,8 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
Column( Column(
modifier = contentModifier modifier = contentModifier
.background(containerColor) .background(containerColor)
.heightIn(max = maxHeight), .heightIn(max = maxHeight)
.testTag(BaseBottomSheetTestTags.CONTAINER),
) { ) {
Box(modifier = Modifier.fillMaxWidth()) { Box(modifier = Modifier.fillMaxWidth()) {
title(model) 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.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview 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.TangemSheetValue
import com.tangem.core.ui.components.sheetscaffold.rememberSheetState import com.tangem.core.ui.components.sheetscaffold.rememberSheetState
import com.tangem.core.ui.res.* import com.tangem.core.ui.res.*
import com.tangem.core.ui.test.BaseBottomSheetTestTags
import com.tangem.core.ui.utils.WindowInsetsZero import com.tangem.core.ui.utils.WindowInsetsZero
const val MODAL_SHEET_MAX_HEIGHT = 0.8f const val MODAL_SHEET_MAX_HEIGHT = 0.8f
@ -205,7 +207,8 @@ inline fun <reified T : TangemBottomSheetConfigContent> BsContent(
.background(containerColor) .background(containerColor)
.heightIn(max = maxHeight.dp) .heightIn(max = maxHeight.dp)
.fillMaxWidth() .fillMaxWidth()
.nestedScroll(nestedScrollConnection), .nestedScroll(nestedScrollConnection)
.testTag(BaseBottomSheetTestTags.CONTAINER),
) { ) {
Box(modifier = Modifier.fillMaxWidth()) { Box(modifier = Modifier.fillMaxWidth()) {
title(model) title(model)

View file

@ -10,6 +10,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.key import androidx.compose.runtime.key
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider 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.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.BaseActionButtonsBlockTestTags
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -51,7 +55,10 @@ fun ActionButtons(buttons: ImmutableList<TangemButtonUM>, modifier: Modifier = M
TangemTheme.colors2.text.status.disabled TangemTheme.colors2.text.status.disabled
} }
Column( 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), verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
horizontalAlignment = Alignment.CenterHorizontally, 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.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEach
import com.tangem.core.ui.R 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
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
import com.tangem.core.ui.ds.button.* import com.tangem.core.ui.ds.button.*
@ -66,7 +68,8 @@ fun TangemMessage(
Alignment.Top Alignment.Top
}, },
) )
.size(messageUM.iconSize), .size(messageUM.iconSize)
.testTag(NotificationTestTags.ICON),
) )
} }
}, },
@ -115,14 +118,14 @@ fun TangemMessage(
Image( Image(
painter = painterResource(config.iconResId), painter = painterResource(config.iconResId),
contentDescription = null, contentDescription = null,
modifier = Modifier.size(config.iconSize), modifier = Modifier.size(config.iconSize).testTag(NotificationTestTags.ICON),
) )
} else { } else {
Icon( Icon(
imageVector = ImageVector.vectorResource(config.iconResId), imageVector = ImageVector.vectorResource(config.iconResId),
contentDescription = null, contentDescription = null,
tint = iconTint, tint = iconTint,
modifier = Modifier.size(config.iconSize), modifier = Modifier.size(config.iconSize).testTag(NotificationTestTags.ICON),
) )
} }
}, },
@ -166,7 +169,7 @@ fun TangemMessage(
} else { } else {
Alignment.Start Alignment.Start
} }
Box(modifier = modifier) { Box(modifier = modifier.testTag(NotificationTestTags.CONTAINER)) {
Box( Box(
modifier = Modifier modifier = Modifier
.matchParentSize() .matchParentSize()
@ -244,6 +247,7 @@ private fun TangemMessageContent(
color = TangemTheme.colors2.text.neutral.primary, color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1, maxLines = 1,
textAlign = textAlign, textAlign = textAlign,
modifier = Modifier.testTag(NotificationTestTags.TITLE),
) )
} }
if (subtitle != null) { if (subtitle != null) {
@ -252,6 +256,7 @@ private fun TangemMessageContent(
text = subtitle.resolveAnnotatedReference(), text = subtitle.resolveAnnotatedReference(),
style = TangemTheme.typography2.captionSemibold12, style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary, color = TangemTheme.colors2.text.neutral.secondary,
modifier = Modifier.testTag(NotificationTestTags.MESSAGE),
) )
} }
} }

View file

@ -1,6 +1,7 @@
package com.tangem.core.ui.test package com.tangem.core.ui.test
object BaseBottomSheetTestTags { object BaseBottomSheetTestTags {
const val CONTAINER = "BASE_BOTTOM_SHEET_CONTAINER"
const val ACTION_BUTTON = "BASE_BOTTOM_SHEET_ACTION_BUTTON" const val ACTION_BUTTON = "BASE_BOTTOM_SHEET_ACTION_BUTTON"
const val ACTION_ICON = "BASE_BOTTOM_SHEET_ACTION_ICON" const val ACTION_ICON = "BASE_BOTTOM_SHEET_ACTION_ICON"
const val TITLE = "BASE_BOTTOM_SHEET_TITLE" 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 = "MARKETS_TOKENS_LIST"
const val TOKENS_LIST_ITEM = "MARKETS_TOKENS_LIST_ITEM" const val TOKENS_LIST_ITEM = "MARKETS_TOKENS_LIST_ITEM"
const val LISTED_ON_EXCHANGES_COUNT = "MARKETS_LISTED_ON_EXCHANGES_COUNT" 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 { object OrganizeTokensScreenTestTags {
// region OrganizeTokensTopBar // region OrganizeTokensTopBar
const val MENU_BUTTON = "ORGANIZE_TOKENS_MENU_BUTTON"
const val GROUP_BUTTON = "ORGANIZE_TOKENS_GROUP_BUTTON" const val GROUP_BUTTON = "ORGANIZE_TOKENS_GROUP_BUTTON"
const val SORT_BY_BALANCE_BUTTON = "SORT_BY_BALANCE_BUTTON" const val SORT_BY_BALANCE_BUTTON = "SORT_BY_BALANCE_BUTTON"
// endregion OrganizeTokensTopBar // endregion OrganizeTokensTopBar

View file

@ -17,6 +17,8 @@ import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity 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.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@ -116,6 +118,7 @@ private fun Content(
LazyColumn( LazyColumn(
state = lazyListState, state = lazyListState,
contentPadding = PaddingValues(bottom = bottomBarHeight, top = contentPadding.calculateTopPadding()), contentPadding = PaddingValues(bottom = bottomBarHeight, top = contentPadding.calculateTopPadding()),
modifier = Modifier.testTag(MarketsTestTags.TOKEN_DETAILS_CONTENT),
) { ) {
item("header") { item("header") {
Header(state = state) 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.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow 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.ds.topbar.TangemTopBarType
import com.tangem.core.ui.extensions.* import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.LocalRedesignEnabled 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.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.core.ui.res.TangemThemePreviewRedesign
@ -314,13 +316,15 @@ private fun ExchangeItemRowContent(exchangeItemUM: ExchangeItemUM.Content, modif
tangemIconUM = exchangeItemUM.icon, tangemIconUM = exchangeItemUM.icon,
modifier = Modifier modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.HEAD) .layoutId(layoutId = TangemRowLayoutId.HEAD)
.size(TangemTheme.dimens2.x10), .size(TangemTheme.dimens2.x10)
.testTag(TokenElementsTestTags.TOKEN_ICON),
) )
Text( Text(
modifier = Modifier modifier = Modifier
.padding(start = TangemTheme.dimens2.x2) .padding(start = TangemTheme.dimens2.x2)
.layoutId(TangemRowLayoutId.START_TOP), .layoutId(TangemRowLayoutId.START_TOP)
.testTag(TokenElementsTestTags.TOKEN_TITLE),
text = exchangeItemUM.title.resolveReference(), text = exchangeItemUM.title.resolveReference(),
style = TangemTheme.typography2.bodySemibold16, style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary, color = TangemTheme.colors2.text.neutral.primary,
@ -329,7 +333,8 @@ private fun ExchangeItemRowContent(exchangeItemUM: ExchangeItemUM.Content, modif
Text( Text(
modifier = Modifier modifier = Modifier
.padding(start = TangemTheme.dimens2.x2) .padding(start = TangemTheme.dimens2.x2)
.layoutId(TangemRowLayoutId.START_BOTTOM), .layoutId(TangemRowLayoutId.START_BOTTOM)
.testTag(TokenElementsTestTags.TOKEN_PRICE),
text = exchangeItemUM.subTitle.resolveReference(), text = exchangeItemUM.subTitle.resolveReference(),
style = TangemTheme.typography2.captionSemibold12, style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary, color = TangemTheme.colors2.text.neutral.secondary,
@ -338,7 +343,8 @@ private fun ExchangeItemRowContent(exchangeItemUM: ExchangeItemUM.Content, modif
Text( Text(
modifier = Modifier modifier = Modifier
.padding(start = TangemTheme.dimens2.x2) .padding(start = TangemTheme.dimens2.x2)
.layoutId(TangemRowLayoutId.END_TOP), .layoutId(TangemRowLayoutId.END_TOP)
.testTag(TokenElementsTestTags.TOKEN_FIAT_AMOUNT_TEXT),
text = exchangeItemUM.volumeInUsd.resolveReference(), text = exchangeItemUM.volumeInUsd.resolveReference(),
style = TangemTheme.typography2.bodySemibold16, style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary, color = TangemTheme.colors2.text.neutral.primary,
@ -351,7 +357,8 @@ private fun ExchangeItemRowContent(exchangeItemUM: ExchangeItemUM.Content, modif
shape = CircleShape, shape = CircleShape,
) )
.padding(vertical = 2.dp, horizontal = 6.dp) .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(), text = exchangeItemUM.auditLabel.text.resolveReference(),
style = TangemTheme.typography2.captionSemibold11, style = TangemTheme.typography2.captionSemibold11,
color = getColorByTrustValue(exchangeItemUM.auditLabel.type), 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.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource 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.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
@ -65,7 +64,8 @@ private fun ListedOnBlockV1(state: ListedOnUM, modifier: Modifier = Modifier) {
.clip(shape = TangemTheme.shapes.roundedCornersXMedium) .clip(shape = TangemTheme.shapes.roundedCornersXMedium)
.clickable(enabled = state is ListedOnUM.Content) { .clickable(enabled = state is ListedOnUM.Content) {
(state as? ListedOnUM.Content)?.onClick?.invoke() (state as? ListedOnUM.Content)?.onClick?.invoke()
}, }
.testTag(MarketsTestTags.LISTED_ON_BLOCK),
) { ) {
Description( Description(
state = state, state = state,
@ -89,9 +89,11 @@ private fun ListedOnBlockV1(state: ListedOnUM, modifier: Modifier = Modifier) {
@Composable @Composable
private fun ListedOnBlockV2(state: ListedOnUM, modifier: Modifier = Modifier) { private fun ListedOnBlockV2(state: ListedOnUM, modifier: Modifier = Modifier) {
TokenMarketInformationBlock( TokenMarketInformationBlock(
modifier = modifier.clickable(enabled = state is ListedOnUM.Content) { modifier = modifier
(state as? ListedOnUM.Content)?.onClick?.invoke() .clickable(enabled = state is ListedOnUM.Content) {
}, (state as? ListedOnUM.Content)?.onClick?.invoke()
}
.testTag(MarketsTestTags.LISTED_ON_BLOCK),
title = { title = {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1)) { Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1)) {
@ -103,6 +105,7 @@ private fun ListedOnBlockV2(state: ListedOnUM, modifier: Modifier = Modifier) {
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
Text( Text(
modifier = Modifier.testTag(MarketsTestTags.LISTED_ON_EXCHANGES_COUNT),
text = state.description.resolveReference(), text = state.description.resolveReference(),
style = TangemTheme.typography2.headingSemibold20, style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary, color = TangemTheme.colors2.text.neutral.primary,
@ -178,7 +181,7 @@ internal fun ListedOnBlockPlaceholderV2(modifier: Modifier = Modifier) {
private fun Description(state: ListedOnUM, modifier: Modifier = Modifier) { private fun Description(state: ListedOnUM, modifier: Modifier = Modifier) {
Text( Text(
text = state.description.resolveReference(), 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, color = TangemTheme.colors.text.tertiary,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 1, maxLines = 1,

View file

@ -13,12 +13,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.ui.account.AccountIconUM import com.tangem.common.ui.account.AccountIconUM
import com.tangem.common.ui.earn.EarnBlock 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.ExpressTransactionStateUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionsBlockState import com.tangem.common.ui.expressStatus.state.ExpressTransactionsBlockState
import com.tangem.common.ui.notifications.notifications 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.stringReference
import com.tangem.core.ui.extensions.themedColor import com.tangem.core.ui.extensions.themedColor
import com.tangem.core.ui.res.LocalHazeState 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.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.domain.models.account.CryptoPortfolioIcon import com.tangem.domain.models.account.CryptoPortfolioIcon
@ -140,7 +143,7 @@ private fun BoxScope.TokenDetailsMarketBlockOverlay(
) )
} }
@Suppress("LongParameterList") @Suppress("LongParameterList", "LongMethod")
@Composable @Composable
private fun TokenDetailsBody( private fun TokenDetailsBody(
tokenDetailsUM: TokenDetailsUM, tokenDetailsUM: TokenDetailsUM,
@ -165,6 +168,7 @@ private fun TokenDetailsBody(
LazyColumn( LazyColumn(
modifier = modifier modifier = modifier
.testTag(TokenDetailsScreenTestTags.SCREEN_CONTAINER)
.hazeSourceTangem(state = LocalHazeState.current) .hazeSourceTangem(state = LocalHazeState.current)
.topFade( .topFade(
height = topContentPadding, height = topContentPadding,
@ -190,9 +194,18 @@ private fun TokenDetailsBody(
) )
tokenDetailsUM.earnBlockState?.let { earnBlock -> tokenDetailsUM.earnBlockState?.let { earnBlock ->
item(key = "staking_block") { 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( EarnBlock(
state = 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.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity 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.TextAlign
import androidx.compose.ui.text.style.TextOverflow 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.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@ -58,7 +67,9 @@ internal fun TokenDetailsTopBar(topAppBarUM: TokenDetailsTopAppBarUM, modifier:
.clip(CircleShape) .clip(CircleShape)
.hazeEffectTangem { blurRadius = ACTION_BLUR_RADIUS } .hazeEffectTangem { blurRadius = ACTION_BLUR_RADIUS }
TangemTopBar( TangemTopBar(
modifier = modifier.statusBarsPadding(), modifier = modifier
.statusBarsPadding()
.testTag(TokenDetailsTopBarTestTags.BACK_BUTTON),
startContent = { startContent = {
TangemTopBarActionContent( TangemTopBarActionContent(
modifier = actionModifier, modifier = actionModifier,
@ -73,7 +84,7 @@ internal fun TokenDetailsTopBar(topAppBarUM: TokenDetailsTopAppBarUM, modifier:
var isDropdownMenuShown by rememberSaveable { mutableStateOf(false) } var isDropdownMenuShown by rememberSaveable { mutableStateOf(false) }
Box { Box {
TangemTopBarActionContent( TangemTopBarActionContent(
modifier = actionModifier, modifier = actionModifier.testTag(TokenDetailsTopBarTestTags.MORE_BUTTON),
actionUM = TangemTopBarActionUM( actionUM = TangemTopBarActionUM(
iconRes = CoreUiR.drawable.ic_more_default_24, iconRes = CoreUiR.drawable.ic_more_default_24,
onClick = { isDropdownMenuShown = true }, onClick = { isDropdownMenuShown = true },
@ -105,7 +116,9 @@ internal fun TokenDetailsTopBar(topAppBarUM: TokenDetailsTopAppBarUM, modifier:
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x0_5), verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x0_5),
) { ) {
TokenDetailsTitle(titleState = topAppBarUM.titleState) Box(modifier = Modifier.testTag(TokenDetailsScreenTestTags.TOKEN_TITLE)) {
TokenDetailsTitle(titleState = topAppBarUM.titleState)
}
Text( Text(
text = topAppBarUM.subtitle.resolveAnnotatedReference(), text = topAppBarUM.subtitle.resolveAnnotatedReference(),
color = TangemTheme.colors2.text.neutral.tertiary, color = TangemTheme.colors2.text.neutral.tertiary,

View file

@ -96,6 +96,7 @@ internal fun OrganizeTokensContent(
onClick = { isShowDropdownMenu = true }, onClick = { isShowDropdownMenu = true },
ghostModeProgress = 0f, ghostModeProgress = 0f,
), ),
modifier = Modifier.testTag(OrganizeTokensScreenTestTags.MENU_BUTTON),
type = TangemTopBarType.BottomSheet, type = TangemTopBarType.BottomSheet,
) )
OrganizeDropDownMenu( OrganizeDropDownMenu(
@ -161,8 +162,7 @@ private fun TokenList(
modifier = Modifier modifier = Modifier
.align(Alignment.TopCenter) .align(Alignment.TopCenter)
.reorderable(reorderableListState) .reorderable(reorderableListState)
.testTag(OrganizeTokensScreenTestTags.TOKENS_LAZY_LIST) .testTag(OrganizeTokensScreenTestTags.TOKENS_LAZY_LIST),
.hazeSourceTangem(zIndex = 1f),
state = reorderableListState.listState, state = reorderableListState.listState,
contentPadding = listContentPadding, contentPadding = listContentPadding,
) { ) {
@ -221,7 +221,7 @@ private fun LazyItemScope.DraggableItem(
when (item) { when (item) {
is OrganizeRowItemUM.Network -> TangemHeaderRow( is OrganizeRowItemUM.Network -> TangemHeaderRow(
modifier = modifierWithBackground, modifier = modifierWithBackground.testTag(OrganizeTokensScreenTestTags.GROUP_TITLE_ITEM),
reorderableState = reorderableState, reorderableState = reorderableState,
headerRowUM = item.headerRowUM, headerRowUM = item.headerRowUM,
) )
@ -231,7 +231,7 @@ private fun LazyItemScope.DraggableItem(
isBalanceHidden = isBalanceHidden, isBalanceHidden = isBalanceHidden,
) )
is OrganizeRowItemUM.Token -> OrganizeTokenRow( is OrganizeRowItemUM.Token -> OrganizeTokenRow(
modifier = modifierWithBackground, modifier = modifierWithBackground.testTag(OrganizeTokensScreenTestTags.TOKEN_LIST_ITEM),
tokenRowUM = item.tokenRowUM, tokenRowUM = item.tokenRowUM,
reorderableState = reorderableState, reorderableState = reorderableState,
isBalanceHidden = isBalanceHidden, isBalanceHidden = isBalanceHidden,
@ -262,11 +262,15 @@ private fun BoxScope.BottomButtons(organizeTokensUM: OrganizeTokensUM) {
) { ) {
TangemButton( TangemButton(
buttonUM = organizeTokensUM.cancelButton, buttonUM = organizeTokensUM.cancelButton,
modifier = Modifier.weight(1f), modifier = Modifier
.weight(1f)
.testTag(OrganizeTokensScreenTestTags.CANCEL_BUTTON),
) )
TangemButton( TangemButton(
buttonUM = organizeTokensUM.applyButton, 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.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalDensity 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.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpOffset
@ -101,6 +103,7 @@ private fun TokenActionContextMenuContent(actions: ImmutableList<TokenActionButt
Row( Row(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2), horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
modifier = Modifier modifier = Modifier
.testTag(BaseBottomSheetTestTags.ACTION_BUTTON)
.clickable( .clickable(
enabled = item.isEnabled, enabled = item.isEnabled,
onClick = { onClick = {
@ -123,7 +126,9 @@ private fun TokenActionContextMenuContent(actions: ImmutableList<TokenActionButt
} else { } else {
TangemTheme.colors2.graphic.neutral.primary TangemTheme.colors2.graphic.neutral.primary
}, },
modifier = Modifier.size(TangemTheme.dimens2.x5), modifier = Modifier
.size(TangemTheme.dimens2.x5)
.testTag(BaseBottomSheetTestTags.ACTION_ICON),
) )
Text( Text(
text = item.text.resolveReference(), 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.foundation.lazy.LazyListScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import com.tangem.core.ui.ds.button.TangemButton 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.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.features.tangempay.component.TangemPayMainBlockComponent import com.tangem.features.tangempay.component.TangemPayMainBlockComponent
import com.tangem.features.tangempay.entity.TangemPayMainUM import com.tangem.features.tangempay.entity.TangemPayMainUM
@ -25,9 +29,14 @@ internal fun LazyListScope.organizeTokens2(state: WalletUM, itemModifier: Modifi
key = "OrganizeTokensButton", key = "OrganizeTokensButton",
contentType = "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( TangemButton(
buttonUM = organizeButton, buttonUM = organizeButton,
modifier = itemModifier, modifier = itemModifier.testTag(testTag),
) )
} }
} }

View file

@ -68,6 +68,7 @@ internal fun WalletBalance(
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier modifier = modifier
.testTag(MainScreenTestTags.WALLET_LIST_ITEM)
.alpha(alpha) .alpha(alpha)
.scale(scale) .scale(scale)
.snapToExitUntilCollapsed(behavior) .snapToExitUntilCollapsed(behavior)
@ -150,8 +151,12 @@ private fun SubtitleRow(walletBalanceUM: WalletBalanceUM, modifier: Modifier = M
text = walletBalanceUM.name, text = walletBalanceUM.name,
style = TangemTheme.typography2.bodyRegular14, style = TangemTheme.typography2.bodyRegular14,
color = TangemTheme.colors2.text.neutral.tertiary, 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.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.LazyPagingItems
import com.tangem.common.ui.notifications.notifications import com.tangem.common.ui.notifications.notifications
import com.tangem.common.ui.notifications.notificationsCarousel import com.tangem.common.ui.notifications.notificationsCarousel
import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.components.transactions.txHistoryItems import com.tangem.core.ui.components.transactions.txHistoryItems
import com.tangem.core.ui.res.TangemTheme 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.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.tokensListItems 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) val itemModifier = movableItemModifier.padding(top = TangemTheme.dimens2.x3)
LazyColumn( LazyColumn(
modifier = modifier, modifier = modifier.testTag(MainScreenTestTags.SCREEN_CONTAINER),
state = listState, state = listState,
contentPadding = contentPadding, contentPadding = contentPadding,
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,

View file

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