Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-23 11:33:09 +03:00
parent 1157642722
commit 74e43f248e
17 changed files with 129 additions and 74 deletions

View file

@ -189,8 +189,12 @@ abstract class BaseTestCase : TestCase(
"ADD_AND_MANAGE_TOKENS_ENABLED" to true,
"ASSETS_DISCOVERY_ENABLED" to true,
"VISA_ONBOARDING_ENABLED" to true,
// Toggles released in 5.39+ — forced on so tests run against the actual build even when the
// app version resolves to 1.0.0-SNAPSHOT on CI (then 1.0.0 < 5.39 would disable them).
// Version-gated toggles released in versions <= 6.0 — forced on so tests run against the actual
// build even when the app version resolves to 1.0.0-SNAPSHOT on CI (then 1.0.0 < x.xx would
// disable them). On the releases/6.0 branch every toggle with version <= 6.0 ships enabled.
// 5.37
"HEDERA_ERC20_ENABLED" to true,
// 5.39
"STAKING_ETH_ENABLED" to true,
"DYNAMIC_ADDRESSES_ENABLED" to true,
"SOLANA_TX_HISTORY_ENABLED" to true,
@ -203,8 +207,22 @@ abstract class BaseTestCase : TestCase(
"AND_15103_SWAP_RATE_EXPERIENCE_ENABLED" to true,
"AND_15122_SWAP_PREDEFINED_BUTTONS_ENABLED" to true,
"TWI_1512_HIDE_STORIES_FOR_REFERRAL_ENABLED" to true,
// Toggles released in 5.40
// 5.39.2
"AND_15154_YIELD_PROMO_ENABLED" to true,
// 5.40
"TWI_1377_MANAGE_FUNDS" to true,
// 6.0
"APP_REDESIGN_ENABLED" to true,
"TWI_1326_YIELD_MODE_SWAP_ENABLED" to true,
"AND_15207_SWAP_SWITCH_TO_TRANSFER_ENABLED" to true,
"AND_15120_SWAP_INTEGRATED_APPROVE" to true,
"AND_15596_ONBOARDING_PUSH_NOTIFICATION_DOUBLE_ASK_AB_ENABLED" to true,
"AND_15258_QUICK_TOP_UP_ENABLED" to true,
"AND_15368_VISA_PAY_REDESIGN" to true,
"AND_15364_VISA_PAY_CARD_CLOSE" to true,
"AND_15489_EXPRESS_SHARE_BUTTON_ENABLED" to true,
"AND_15235_VISA_MULTIPLE_CARDS" to true,
"AND_15715_SWAP_BEST_DEX_RATE_ENABLED" to true,
)
)
}

View file

@ -13,8 +13,8 @@ fun BaseTestCase.checkSingleCurrencyMainScreen(cardTitle: String) {
step("Assert 'Add funds' button is displayed") {
onMainScreen { addFundsButton.assertIsDisplayed() }
}
step("Assert 'Sell' button is displayed") {
onMainScreen { sellButton.assertIsDisplayed() }
step("Assert 'Transfer' button is displayed") {
onMainScreen { transferButton.assertIsDisplayed() }
}
step("Assert 'Swap' button is not displayed") {
onMainScreen { swapButton.assertIsNotDisplayed() }
@ -39,8 +39,8 @@ fun BaseTestCase.checkMultiCurrencyMainScreen(
step("Assert 'Swap' button is displayed") {
onMainScreen { swapButton.assertIsDisplayed() }
}
step("Assert 'Sell' button is displayed") {
onMainScreen { sellButton.assertIsDisplayed() }
step("Assert 'Transfer' button is displayed") {
onMainScreen { transferButton.assertIsDisplayed() }
}
step("Assert 'Send' button is not displayed") {
onMainScreen { sendButton.assertIsNotDisplayed() }
@ -61,8 +61,8 @@ fun BaseTestCase.assertActionButtonsForMultiCurrencyWallet(isEnabled: Boolean =
step("Assert 'Swap' button is enabled") {
onMainScreen { swapButton.assertIsEnabled() }
}
step("Assert 'Sell' button is enabled") {
onMainScreen { sellButton.assertIsEnabled() }
step("Assert 'Transfer' button is enabled") {
onMainScreen { transferButton.assertIsEnabled() }
}
} else {
step("Assert 'Add funds' button is not enabled") {
@ -71,8 +71,8 @@ fun BaseTestCase.assertActionButtonsForMultiCurrencyWallet(isEnabled: Boolean =
step("Assert 'Swap' button is not enabled") {
onMainScreen { swapButton.assertIsNotEnabled() }
}
step("Assert 'Sell' button is not enabled") {
onMainScreen { sellButton.assertIsNotEnabled() }
step("Assert 'Transfer' button is not enabled") {
onMainScreen { transferButton.assertIsNotEnabled() }
}
}
}

View file

@ -13,6 +13,7 @@ import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_VERY_LONG
import com.tangem.common.extensions.assertVisibility
import com.tangem.common.extensions.clickWhenEnabled
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.extractText
import com.tangem.core.ui.R as CoreUiR
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.HotWalletAccessCodeTestTags
@ -185,6 +186,25 @@ fun BaseTestCase.selectFeeType(feeType: FeeType, selectedFeeAmount: String) {
}
}
fun BaseTestCase.selectFeeTypeAndReadFee(feeType: FeeType): String {
step("Click on 'Select fee' icon") {
onSwapTokenScreen { selectFeeIcon.performClick() }
}
step("Click on '$feeType' item") {
onSwapSelectNetworkFeeBottomSheet {
when (feeType) {
FeeType.Market -> marketSelectorItem.clickWithAssertion()
FeeType.Fast -> fastSelectorItem.clickWithAssertion()
}
}
}
var fee = ""
step("Read displayed '$feeType' fee amount") {
onSwapTokenScreen { fee = feeAmount.extractText() }
}
return fee
}
fun BaseTestCase.chackUnableToCoverFeeNotification(networkName: String, currencySymbol: String) {
step("Assert 'Unable to cover '$networkName' fee notification title is displayed'") {
onSwapTokenScreen { unableToCoverFeeNotificationTitle(networkName).assertIsDisplayed() }

View file

@ -20,14 +20,14 @@ import com.tangem.core.res.R as CoreResR
* After the onramp redesign this is a [BaseBottomSheetTestTags.CONTAINER] bottom sheet
* (centered title + close icon), not a full screen with a top app bar.
*/
class ChooseTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<ChooseTokenPageObject>(
class ChooseTokenBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<ChooseTokenBottomSheetPageObject>(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(BaseBottomSheetTestTags.CONTAINER) },
) {
val topAppBarTitle: KNode = child {
hasText(getResourceString(CoreResR.string.common_add_funds))
val title: KNode = child {
hasText(getResourceString(CoreResR.string.common_choose_token))
useUnmergedTree = true
}
@ -44,5 +44,5 @@ class ChooseTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
}
}
internal fun BaseTestCase.onChooseTokenScreen(function: ChooseTokenPageObject.() -> Unit) =
internal fun BaseTestCase.onChooseTokenBottomSheet(function: ChooseTokenBottomSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -68,9 +68,9 @@ class MainScreenPageObject(private val semanticsProvider: SemanticsNodeInteracti
useUnmergedTree = true
}
val sellButton: KNode = child {
val transferButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasAnyDescendant(withText(getResourceString(R.string.common_sell)))
hasAnyDescendant(withText(getResourceString(R.string.common_transfer)))
useUnmergedTree = true
}

View file

@ -26,7 +26,7 @@ class TangemPayCardPagePageObject(semanticsProvider: SemanticsNodeInteractionsPr
}
val showDetailsButton: KNode = child {
hasTestTag(TangemPayTestTags.CARD_DETAILS_SHOW_BUTTON)
hasTestTag(TangemPayTestTags.SHOW_DETAILS_ROW)
useUnmergedTree = true
}

View file

@ -44,8 +44,8 @@ class BuyTokenTest : BaseTestCase() {
onMainScreen { addFundsButton.clickWithAssertion() }
}
step("Click on token with name: '$tokenTitle'") {
onChooseTokenScreen {
topAppBarTitle.assertIsDisplayed()
onChooseTokenBottomSheet {
title.assertIsDisplayed()
tokenWithTitle(tokenTitle).clickWithAssertion()
}
}
@ -91,8 +91,8 @@ class BuyTokenTest : BaseTestCase() {
onMainScreen { addFundsButton.clickWithAssertion() }
}
step("Click on token with name: '$tokenTitle'") {
onChooseTokenScreen {
topAppBarTitle.assertIsDisplayed()
onChooseTokenBottomSheet {
title.assertIsDisplayed()
tokenWithTitle(tokenTitle).clickWithAssertion()
}
}
@ -165,8 +165,8 @@ class BuyTokenTest : BaseTestCase() {
onMainScreen { addFundsButton.clickWithAssertion() }
}
step("Click on token with name: '$tokenTitle'") {
onChooseTokenScreen {
topAppBarTitle.assertIsDisplayed()
onChooseTokenBottomSheet {
title.assertIsDisplayed()
tokenWithTitle(tokenTitle).clickWithAssertion()
}
}
@ -251,8 +251,8 @@ class BuyTokenTest : BaseTestCase() {
onMainScreen { addFundsButton.clickWithAssertion() }
}
step("Click on token with name: '$tokenTitle'") {
onChooseTokenScreen {
topAppBarTitle.assertIsDisplayed()
onChooseTokenBottomSheet {
title.assertIsDisplayed()
tokenWithTitle(tokenTitle).clickWithAssertion()
}
}
@ -336,8 +336,8 @@ class BuyTokenTest : BaseTestCase() {
onMainScreen { addFundsButton.clickWithAssertion() }
}
step("Click on token with name: '$tokenTitle'") {
onChooseTokenScreen {
topAppBarTitle.assertIsDisplayed()
onChooseTokenBottomSheet {
title.assertIsDisplayed()
tokenWithTitle(tokenTitle).clickWithAssertion()
}
}
@ -425,8 +425,8 @@ class BuyTokenTest : BaseTestCase() {
onMainScreen { addFundsButton.clickWithAssertion() }
}
step("Click on token with name: '$tokenTitle'") {
onChooseTokenScreen {
topAppBarTitle.assertIsDisplayed()
onChooseTokenBottomSheet {
title.assertIsDisplayed()
tokenWithTitle(tokenTitle).clickWithAssertion()
}
}

View file

@ -306,8 +306,8 @@ class DetailsTest : BaseTestCase() {
step("Assert 'Buy' button is not displayed") {
buyButton.assertIsNotDisplayed()
}
step("Assert 'Sell' button is not displayed") {
sellButton.assertIsNotDisplayed()
step("Assert 'Transfer' button is not displayed") {
transferButton.assertIsNotDisplayed()
}
step("Assert 'Swap' button is not displayed") {
swapButton.assertIsNotDisplayed()

View file

@ -389,8 +389,11 @@ class MainScreenActionButtonsTest : BaseTestCase() {
step("Click on 'Add funds' button") {
onMainScreen { addFundsButton.performClick() }
}
step("Click on '$tokenTitle'") {
onAddFundsBottomSheet { userTokenWithTitle(tokenTitle).clickWithAssertion() }
}
step("Click on 'Buy' button in bottom sheet") {
onAddFundsBottomSheet { buyButton.clickWithAssertion() }
onGetTokenBottomSheet { buyButton.performClick() }
}
step("Click on 'Confirm' button in 'Dialog'") {
waitForIdle()
@ -429,10 +432,10 @@ class MainScreenActionButtonsTest : BaseTestCase() {
onMainScreen { addFundsButton.performClick() }
}
step("Assert 'Choose token' screen title is displayed") {
onChooseTokenScreen { topAppBarTitle.assertIsDisplayed() }
onChooseTokenBottomSheet { title.assertIsDisplayed() }
}
step("Assert token with title: '$tokenTitle' is displayed") {
onChooseTokenScreen { tokenWithTitle(tokenTitle).assertIsDisplayed() }
onChooseTokenBottomSheet { tokenWithTitle(tokenTitle).assertIsDisplayed() }
}
step("Press 'Back' button") {
device.uiDevice.pressBack()
@ -452,14 +455,14 @@ class MainScreenActionButtonsTest : BaseTestCase() {
step("Press 'Back' button") {
device.uiDevice.pressBack()
}
step("Assert 'Sell' button is displayed") {
onMainScreen { sellButton.assertIsDisplayed() }
step("Assert 'Transfer' button is displayed") {
onMainScreen { transferButton.assertIsDisplayed() }
}
step("Click on 'Sell' button") {
onMainScreen { sellButton.performClick() }
step("Click on 'Transfer' button") {
onMainScreen { transferButton.performClick() }
}
step("Assert 'Sell' token screen title is displayed") {
onSellScreen { title.assertIsDisplayed() }
step("Assert 'Choose token' title is displayed") {
onChooseTokenBottomSheet { title.assertIsDisplayed() }
}
}
}
@ -488,7 +491,7 @@ class MainScreenActionButtonsTest : BaseTestCase() {
onMainScreen { addFundsButton.performClick() }
}
step("Assert 'Choose token' screen opens (Add funds is always available)") {
onChooseTokenScreen { topAppBarTitle.assertIsDisplayed() }
onChooseTokenBottomSheet { title.assertIsDisplayed() }
}
step("Press 'Back' to return to main screen") {
device.uiDevice.pressBack()
@ -508,17 +511,14 @@ class MainScreenActionButtonsTest : BaseTestCase() {
step("Click on 'Ok' button") {
onDialog { okButton.performClick() }
}
step("Assert 'Sell' button is displayed") {
onMainScreen { sellButton.assertIsDisplayed() }
step("Assert 'Transfer' button is displayed") {
onMainScreen { transferButton.assertIsDisplayed() }
}
step("Click on 'Sell' button") {
onMainScreen { sellButton.performClick() }
step("Click on 'Transfer' button") {
onMainScreen { transferButton.performClick() }
}
step("Check 'Action is unavailable' dialog") {
checkActionIsUnavailableDialog()
}
step("Click on 'Ok' button") {
onDialog { okButton.performClick() }
step("Assert 'Choose token' title is displayed") {
onChooseTokenBottomSheet { title.assertIsDisplayed() }
}
}
}
@ -548,7 +548,7 @@ class MainScreenActionButtonsTest : BaseTestCase() {
onMainScreen { addFundsButton.performClick() }
}
step("Assert 'Choose token' screen opens (Add funds is always available)") {
onChooseTokenScreen { topAppBarTitle.assertIsDisplayed() }
onChooseTokenBottomSheet { title.assertIsDisplayed() }
}
step("Press 'Back' to return to main screen") {
device.uiDevice.pressBack()
@ -566,17 +566,14 @@ class MainScreenActionButtonsTest : BaseTestCase() {
step("Click on 'Ok' button") {
onDialog { okButton.performClick() }
}
step("Assert 'Sell' button is displayed") {
onMainScreen { sellButton.assertIsDisplayed() }
step("Assert 'Transfer' button is displayed") {
onMainScreen { transferButton.assertIsDisplayed() }
}
step("Click on 'Sell' button") {
onMainScreen { sellButton.performClick() }
step("Click on 'Transfer' button") {
onMainScreen { transferButton.performClick() }
}
step("Check 'Action is unavailable' dialog") {
checkActionIsUnavailableDialog()
}
step("Click on 'Ok' button") {
onDialog { okButton.performClick() }
step("Assert 'Choose token' title is displayed") {
onChooseTokenBottomSheet { title.assertIsDisplayed() }
}
}
}

View file

@ -18,6 +18,7 @@ import com.tangem.screens.*
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Assert.assertTrue
import org.junit.Test
@HiltAndroidTest
@ -545,11 +546,11 @@ class SwapTokenScreenTest : BaseTestCase() {
val inputAmount = "0.99"
val market = "Market"
val fast = "Fast"
val marketFeeAmount = "$1.12"
val fastFeeAmount = "$1.43"
setupHooks().run {
var marketFee = 0.0
step("Open 'Main Screen'") {
openMainScreen()
}
@ -575,19 +576,28 @@ class SwapTokenScreenTest : BaseTestCase() {
textInput.performTextReplacement(inputAmount)
}
}
step("Select '$market' fee type") {
step("Select '$market' fee type and capture its amount") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
selectFeeType(FeeType.Market, selectedFeeAmount = marketFeeAmount)
marketFee = parseFeeAmount(selectFeeTypeAndReadFee(FeeType.Market))
}
}
step("Select '$fast' fee type") {
step("Select '$fast' fee type and assert it exceeds the '$market' fee") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
selectFeeType(FeeType.Fast, selectedFeeAmount = fastFeeAmount)
val fastFee = parseFeeAmount(selectFeeTypeAndReadFee(FeeType.Fast))
assertTrue(
"Expected '$fast' fee ($fastFee) to be greater than '$market' fee ($marketFee)",
fastFee > marketFee,
)
}
}
}
}
private fun parseFeeAmount(raw: String): Double = raw
.replace(Regex("[^0-9.]"), "")
.toDoubleOrNull()
?: error("Could not parse a numeric fee amount from '$raw'")
@AllureId("8536")
@DisplayName("Swap: check switch fee type (unable to cover 'Market' and 'Fast' fee)")
@Test

View file

@ -3,6 +3,7 @@ package com.tangem.tests.tangempay
import androidx.test.platform.app.InstrumentationRegistry
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.TANGEM_PAY_ELIGIBILITY_SCENARIO
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.extensions.assertTextContainsSafe
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.extractText
@ -53,11 +54,10 @@ class TangemPayTest : BaseTestCase() {
step("Enter PIN '$newPin'") {
onTangemPayChangePinScreen { inputField.performTextInput(newPin) }
}
step("Click on 'Submit' button") {
onTangemPayChangePinScreen { submitButton.performClick() }
}
step("Assert success screen is displayed") {
onTangemPayChangePinScreen { successTitle.assertIsDisplayed() }
step("Assert success screen title is displayed") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onTangemPayChangePinScreen { successTitle.assertIsDisplayed() }
}
}
step("Click on 'Done' button") {
onTangemPayChangePinScreen { doneButton.clickWithAssertion() }
@ -156,9 +156,11 @@ class TangemPayTest : BaseTestCase() {
).run {
openTangemPay()
step("Click on card button") {
waitForIdle()
onTangemPayMainScreen { cardButton.clickWithAssertion() }
}
step("Click on 'Show details' button") {
waitForIdle()
onTangemPayCardPageScreen { showDetailsButton.clickWithAssertion() }
}
step("Assert number, expiration and CVC values are visible") {

View file

@ -64,6 +64,7 @@ class TangemPayTopUpTest : BaseTestCase() {
onTangemPayMainScreen { balance.assertTextContainsSafe("10", substring = true) }
}
step("Click on 'Top Up' action chip") {
waitForIdle()
onTangemPayMainScreen { topUpButton.clickWithAssertion() }
}
step("Assert 'Add Funds' sheet is displayed") {

View file

@ -19,6 +19,7 @@ object TangemPayTestTags {
const val CARD_DETAILS_COPY_CVC = "TANGEM_PAY_CARD_DETAILS_COPY_CVC"
// Card management (card page settings)
const val SHOW_DETAILS_ROW = "TANGEM_PAY_SHOW_DETAILS_ROW"
const val CHANGE_PIN_ROW = "TANGEM_PAY_CHANGE_PIN_ROW"
const val FREEZE_CARD_ROW = "TANGEM_PAY_FREEZE_CARD_ROW"
const val CARD_FROZEN_BADGE = "TANGEM_PAY_CARD_FROZEN_BADGE"

View file

@ -294,6 +294,7 @@ internal class TangemPayCardPageModel @Inject constructor(
title = TextReference.Res(R.string.tangempay_card_details_title),
onClick = ::onClickViewDetails,
iconRes = CoreUiR.drawable.ic_visa_card_details_24,
testTag = TangemPayTestTags.SHOW_DETAILS_ROW,
),
TangemPayCardPageSettingV2(
id = TangemPayCardPageSettingV2.Id.Freeze,

View file

@ -19,6 +19,7 @@ internal fun TangemPayChangePinCodeSuccessScreenV2(onClose: () -> Unit, modifier
onButtonClick = onClose,
titleTestTag = TangemPayTestTags.PIN_SUCCESS_TITLE,
subtitleTestTag = TangemPayTestTags.PIN_SUCCESS_DESCRIPTION,
buttonTestTag = TangemPayTestTags.PIN_DONE_BUTTON,
)
}

View file

@ -52,6 +52,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.BaseActionButtonsBlockTestTags
import com.tangem.core.ui.test.TangemPayTestTags
import com.tangem.features.tangempay.components.express.PreviewEmptyExpressTransactionsComponent
import com.tangem.features.tangempay.components.txHistory.PreviewTangemPayTxHistoryComponent
@ -428,6 +429,7 @@ private fun LazyItemScope.ActionBlock(
) {
actionButtons.fastForEach { actionConfig ->
TangemPayActionButton(
modifier = Modifier.testTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON),
iconRes = actionConfig.iconResId,
onClick = actionConfig.onClick,
isEnabled = actionConfig.isEnabled,

View file

@ -40,6 +40,7 @@ internal fun TangemPaySuccessScreenWrapper(
fadeColor: Color = Color(DEFAULT_FADE_COLOR),
titleTestTag: String? = null,
subtitleTestTag: String? = null,
buttonTestTag: String? = null,
) {
Box(modifier = modifier.fillMaxSize()) {
Box(
@ -90,7 +91,8 @@ internal fun TangemPaySuccessScreenWrapper(
TangemButton(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = TangemTheme.dimens2.x3),
.padding(vertical = TangemTheme.dimens2.x3)
.then(buttonTestTag?.let { Modifier.testTag(it) } ?: Modifier),
onClick = onButtonClick,
size = TangemButton.Size.X12,
text = buttonText,