Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-13 09:07:42 +02:00
parent cf30e34188
commit b13137406c
10 changed files with 299 additions and 1 deletions

View file

@ -69,5 +69,7 @@ object TestConstants {
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
const val TANGEM_PAY_ELIGIBILITY_SCENARIO = "tangem_pay_eligibility"
const val TANGEM_PAY_ELIGIBILITY_CHANNELS_SCENARIO = "tangem_pay_eligibility_channels"
const val TANGEM_PAY_KYC_STATUS_SCENARIO = "tangem_pay_kyc_status"
const val TANGEM_PAY_ACCESS_CODE = "517384"
}

View file

@ -10,6 +10,7 @@ import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onCompose
import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
import androidx.compose.ui.test.hasText as withText
import com.tangem.core.res.R as CoreResR
class DetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<DetailsPageObject>(semanticsProvider = semanticsProvider) {
@ -65,6 +66,12 @@ class DetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
hasAnyDescendant(withText(name))
useUnmergedTree = true
}
val getTangemPayRow: KNode = child {
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
hasAnyDescendant(withText(getResourceString(CoreResR.string.tangempay_get_tangem_pay)))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onDetailsScreen(function: DetailsPageObject.() -> Unit) =

View file

@ -202,6 +202,12 @@ class MainScreenPageObject(private val semanticsProvider: SemanticsNodeInteracti
useUnmergedTree = true
}
val getTangemPayBanner: KNode = child {
hasTestTag(NotificationTestTags.TITLE)
hasText(getResourceString(CoreResR.string.tangempay_onboarding_banner_title))
useUnmergedTree = true
}
val devCardNotificationIcon: KNode = child {
hasAnySibling(withText(getResourceString(R.string.warning_developer_card_title)))
hasTestTag(NotificationTestTags.ICON)

View file

@ -0,0 +1,33 @@
package com.tangem.screens.tangempay
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.WarningBottomSheetTestTags
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 androidx.compose.ui.test.hasText as withText
class TangemPayKycSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<TangemPayKycSheetPageObject>(semanticsProvider = semanticsProvider) {
fun title(text: String): KNode = child {
hasTestTag(WarningBottomSheetTestTags.TITLE)
hasText(text)
useUnmergedTree = true
}
fun primaryButtonWithText(text: String): KNode = child {
hasTestTag(WarningBottomSheetTestTags.BUTTON_PRIMARY)
hasAnyDescendant(withText(text))
useUnmergedTree = true
}
val closeButton: KNode = child {
hasTestTag(WarningBottomSheetTestTags.CLOSE_BUTTON)
useUnmergedTree = true
}
}
internal fun BaseTestCase.onTangemPayKycSheet(function: TangemPayKycSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -20,6 +20,12 @@ class TangemPayMainPageObject(semanticsProvider: SemanticsNodeInteractionsProvid
useUnmergedTree = true
}
fun tileWithSubtitle(subtitle: String): KNode = child {
hasTestTag(TangemPayTestTags.MAIN_SCREEN_TILE)
hasAnyDescendant(withText(subtitle))
useUnmergedTree = true
}
val balance: KNode = child {
hasTestTag(TangemPayTestTags.PAYMENT_ACCOUNT_BALANCE)
useUnmergedTree = true

View file

@ -0,0 +1,21 @@
package com.tangem.screens.tangempay
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.res.R as CoreResR
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
class TangemPayOnboardingPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<TangemPayOnboardingPageObject>(semanticsProvider = semanticsProvider) {
val title: KNode = child {
hasText(getResourceString(CoreResR.string.tangempay_onboarding_title))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onTangemPayOnboardingScreen(function: TangemPayOnboardingPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -0,0 +1,220 @@
package com.tangem.tests.tangempay
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.SVS_SEED_PHRASE_12
import com.tangem.common.constants.TestConstants.TANGEM_PAY_ACCESS_CODE
import com.tangem.common.constants.TestConstants.TANGEM_PAY_ELIGIBILITY_CHANNELS_SCENARIO
import com.tangem.common.constants.TestConstants.TANGEM_PAY_ELIGIBILITY_SCENARIO
import com.tangem.common.constants.TestConstants.TANGEM_PAY_KYC_STATUS_SCENARIO
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_SHORT
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.openMainScreenWithExistingHotWallet
import com.tangem.screens.onDetailsScreen
import com.tangem.screens.onMainScreen
import com.tangem.screens.onMainScreenTopBar
import com.tangem.screens.tangempay.onTangemPayKycSheet
import com.tangem.screens.tangempay.onTangemPayMainScreen
import com.tangem.screens.tangempay.onTangemPayOnboardingScreen
import com.tangem.core.res.R as CoreResR
import dagger.hilt.android.testing.HiltAndroidTest
import io.github.kakaocup.kakao.common.utilities.getResourceString
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Test
@HiltAndroidTest
class TangemPayOnboardingKycTest : BaseTestCase() {
@AllureId("9630")
@DisplayName("Tangem Pay: existing customer authorization shows the tile on Main")
@Test
fun existingUserAuthorizationShowsTangemPayTileOnMainTest() {
val paeraCustomerState = "PaeraCustomer"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO)
},
).run {
step("Set WireMock scenario '$TANGEM_PAY_ELIGIBILITY_SCENARIO' to '$paeraCustomerState'") {
setWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO, paeraCustomerState)
}
step("Open 'Main' screen with existing hot wallet") {
openMainScreenWithExistingHotWallet(SVS_SEED_PHRASE_12, accessCode = TANGEM_PAY_ACCESS_CODE)
}
step("Assert Tangem Pay tile is displayed on Main") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onTangemPayMainScreen { mainScreenTile.assertIsDisplayed() }
}
}
}
}
@AllureId("9632")
@DisplayName("Tangem Pay: Get Tangem Pay banner is shown on Main for the BANNER channel")
@Test
fun getTangemPayBannerShownOnMainWhenBannerChannelReceivedTest() {
val eligibilityStartedState = "Started"
val channelsBannerState = "Banner"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO)
resetWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_CHANNELS_SCENARIO)
},
).run {
step("Set WireMock scenario '$TANGEM_PAY_ELIGIBILITY_SCENARIO' to '$eligibilityStartedState'") {
setWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO, eligibilityStartedState)
}
step("Set WireMock scenario '$TANGEM_PAY_ELIGIBILITY_CHANNELS_SCENARIO' to '$channelsBannerState'") {
setWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_CHANNELS_SCENARIO, channelsBannerState)
}
step("Open 'Main' screen with existing hot wallet") {
openMainScreenWithExistingHotWallet(SVS_SEED_PHRASE_12, accessCode = TANGEM_PAY_ACCESS_CODE)
}
step("Assert 'Get Tangem Pay' banner is displayed on Main") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onMainScreen { getTangemPayBanner.assertIsDisplayed() }
}
}
}
}
@AllureId("9673")
@DisplayName("Tangem Pay: onboarding opens from app Details for the DETAILS channel")
@Test
fun onboardingOpensFromAppDetailsWhenDetailsChannelReceivedTest() {
val eligibilityStartedState = "Started"
val channelsBannerAndDetailsState = "BannerAndDetails"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO)
resetWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_CHANNELS_SCENARIO)
},
).run {
step("Set WireMock scenario '$TANGEM_PAY_ELIGIBILITY_SCENARIO' to '$eligibilityStartedState'") {
setWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO, eligibilityStartedState)
}
step("Set WireMock scenario '$TANGEM_PAY_ELIGIBILITY_CHANNELS_SCENARIO' to '$channelsBannerAndDetailsState'") {
setWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_CHANNELS_SCENARIO, channelsBannerAndDetailsState)
}
step("Open 'Main' screen with existing hot wallet") {
openMainScreenWithExistingHotWallet(SVS_SEED_PHRASE_12, accessCode = TANGEM_PAY_ACCESS_CODE)
}
step("Click on 'More' button") {
onMainScreenTopBar { moreButton.clickWithAssertion() }
}
step("Assert 'Get Tangem Pay' row is displayed") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onDetailsScreen { getTangemPayRow.assertIsDisplayed() }
}
}
step("Click on 'Get Tangem Pay' row") {
onDetailsScreen { getTangemPayRow.clickWithAssertion() }
}
step("Assert Tangem Pay onboarding screen is displayed") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onTangemPayOnboardingScreen { title.assertIsDisplayed() }
}
}
}
}
@AllureId("9643")
@DisplayName("Tangem Pay: KYC in progress sheet closes and the status remains on Main")
@Test
fun kycInProgressSheetClosesAndStatusRemainsOnMainTest() {
val paeraCustomerState = "PaeraCustomer"
val kycInProgressState = "InProgress"
val kycInProgressText = getResourceString(CoreResR.string.tangempay_kyc_in_progress)
val viewStatusText = getResourceString(CoreResR.string.tangempay_kyc_in_progress_notification_button)
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO)
resetWireMockScenarioState(TANGEM_PAY_KYC_STATUS_SCENARIO)
},
).run {
step("Set WireMock scenario '$TANGEM_PAY_ELIGIBILITY_SCENARIO' to '$paeraCustomerState'") {
setWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO, paeraCustomerState)
}
step("Set WireMock scenario '$TANGEM_PAY_KYC_STATUS_SCENARIO' to '$kycInProgressState'") {
setWireMockScenarioState(TANGEM_PAY_KYC_STATUS_SCENARIO, kycInProgressState)
}
step("Open 'Main' screen with existing hot wallet") {
openMainScreenWithExistingHotWallet(SVS_SEED_PHRASE_12, accessCode = TANGEM_PAY_ACCESS_CODE)
}
step("Assert Tangem Pay tile with '$kycInProgressText' is displayed") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onTangemPayMainScreen { tileWithSubtitle(kycInProgressText).assertIsDisplayed() }
}
}
step("Click on Tangem Pay tile") {
onTangemPayMainScreen { mainScreenTile.clickWithAssertion() }
}
step("Assert KYC in progress sheet is displayed") {
onTangemPayKycSheet {
title(kycInProgressText).assertIsDisplayed()
primaryButtonWithText(viewStatusText).assertIsDisplayed()
}
}
step("Click on 'Close' button") {
onTangemPayKycSheet { closeButton.clickWithAssertion() }
}
step("Assert KYC in progress sheet is not displayed") {
flakySafely(WAIT_UNTIL_TIMEOUT_SHORT) {
onTangemPayKycSheet { primaryButtonWithText(viewStatusText).assertDoesNotExist() }
}
}
step("Assert Tangem Pay tile with '$kycInProgressText' is still displayed") {
onTangemPayMainScreen { tileWithSubtitle(kycInProgressText).assertIsDisplayed() }
}
}
}
@AllureId("9517")
@DisplayName("Tangem Pay: KYC rejected status shown on Main and in the sheet")
@Test
fun kycRejectedStatusShownOnMainAndInSheetTest() {
val paeraCustomerState = "PaeraCustomer"
val kycDeclinedState = "Declined"
val kycRejectedTileText = getResourceString(CoreResR.string.tangempay_kyc_has_failed)
val rejectedSheetTitle = getResourceString(CoreResR.string.tangempay_kyc_rejected)
val goToSupportText = getResourceString(CoreResR.string.tangempay_go_to_support)
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO)
resetWireMockScenarioState(TANGEM_PAY_KYC_STATUS_SCENARIO)
},
).run {
step("Set WireMock scenario '$TANGEM_PAY_ELIGIBILITY_SCENARIO' to '$paeraCustomerState'") {
setWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO, paeraCustomerState)
}
step("Set WireMock scenario '$TANGEM_PAY_KYC_STATUS_SCENARIO' to '$kycDeclinedState'") {
setWireMockScenarioState(TANGEM_PAY_KYC_STATUS_SCENARIO, kycDeclinedState)
}
step("Open 'Main' screen with existing hot wallet") {
openMainScreenWithExistingHotWallet(SVS_SEED_PHRASE_12, accessCode = TANGEM_PAY_ACCESS_CODE)
}
step("Assert Tangem Pay tile with '$kycRejectedTileText' is displayed") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onTangemPayMainScreen { tileWithSubtitle(kycRejectedTileText).assertIsDisplayed() }
}
}
step("Click on Tangem Pay tile") {
onTangemPayMainScreen { mainScreenTile.clickWithAssertion() }
}
step("Assert KYC rejected sheet is displayed") {
onTangemPayKycSheet {
title(rejectedSheetTitle).assertIsDisplayed()
primaryButtonWithText(goToSupportText).assertIsDisplayed()
}
}
}
}
}

View file

@ -63,6 +63,7 @@ fun MessageBottomSheetV2(state: MessageBottomSheetUM, onDismissRequest: () -> Un
type = TangemTopBarType.BottomSheet,
endContent = {
TangemButton(
modifier = Modifier.testTag(WarningBottomSheetTestTags.CLOSE_BUTTON),
iconStart = TangemIconUM.Icon(iconRes = R.drawable.ic_close_24),
onClick = stateWithOnDismiss.onDismissRequest,
size = TangemButton.Size.X11,

View file

@ -6,4 +6,5 @@ object WarningBottomSheetTestTags {
const val MESSAGE = "BASE_WARNING_BOTTOM_SHEET_MESSAGE"
const val BUTTON_PRIMARY = "BASE_WARNING_BOTTOM_SHEET_BUTTON_PRIMARY"
const val BUTTON_SECONDARY = "BASE_WARNING_BOTTOM_SHEET_BUTTON_SECONDARY"
const val CLOSE_BUTTON = "BASE_WARNING_BOTTOM_SHEET_CLOSE_BUTTON"
}

View file

@ -149,7 +149,8 @@ private fun TangemPayStateRow(
modifier = modifier
.clip(RoundedCornerShape(size = 18.dp))
.background(TangemTheme.colors2.surface.level3)
.conditional(onClick != null && isEnabled) { clickableSingle(onClick = requireNotNull(onClick)) },
.conditional(onClick != null && isEnabled) { clickableSingle(onClick = requireNotNull(onClick)) }
.testTag(TangemPayTestTags.MAIN_SCREEN_TILE),
) {
Image(
painter = getVisaIconPainter(),