diff --git a/app/src/androidTest/kotlin/com/tangem/common/extensions/NavigationExt.kt b/app/src/androidTest/kotlin/com/tangem/common/extensions/NavigationExt.kt new file mode 100644 index 0000000000..ab3a5e9722 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/extensions/NavigationExt.kt @@ -0,0 +1,20 @@ +package com.tangem.common.extensions + +import androidx.compose.ui.test.hasTestTag +import androidx.compose.ui.test.performClick +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.test.AppSettingsScreenTestTags +import com.tangem.core.ui.test.TokenDetailsTopBarTestTags +import com.tangem.core.ui.test.TopAppBarTestTags +import com.tangem.core.ui.test.TopNavigationTestTags + +fun BaseTestCase.tapBackButton() { + waitForIdle() + composeTestRule.onNode( + hasTestTag(TopNavigationTestTags.BACK_BUTTON) + or hasTestTag(TopAppBarTestTags.CLOSE_BUTTON) + or hasTestTag(TokenDetailsTopBarTestTags.BACK_BUTTON) + or hasTestTag(AppSettingsScreenTestTags.BACK_BUTTON), + useUnmergedTree = true, + ).performClick() +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/common/utils/AddressComparisonHelper.kt b/app/src/androidTest/kotlin/com/tangem/common/utils/AddressComparisonHelper.kt index 2faf0b96ab..8c8c80e617 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/utils/AddressComparisonHelper.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/utils/AddressComparisonHelper.kt @@ -42,6 +42,17 @@ object AddressComparisonHelper { assertTrue(report, false) } + fun derivationPathForBlockchain(json: String, blockchain: String): String { + val array = JSONArray(json) + for (i in 0 until array.length()) { + val entry = array.getJSONObject(i) + if (entry.getString("blockchain").equals(blockchain, ignoreCase = true)) { + return entry.getString("derivationPath").trim() + } + } + error("No entry with blockchain '$blockchain' found in addresses JSON") + } + private fun parseAndNormalize(json: String): List { val array = JSONArray(json) val entries = mutableListOf() diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/AddressScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/AddressScenarios.kt index e4d65afb99..b373a75985 100644 --- a/app/src/androidTest/kotlin/com/tangem/scenarios/AddressScenarios.kt +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/AddressScenarios.kt @@ -1,6 +1,5 @@ package com.tangem.scenarios -import android.view.KeyEvent import androidx.test.core.app.ApplicationProvider import com.tangem.common.BaseTestCase import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_VERY_LONG @@ -57,31 +56,6 @@ fun BaseTestCase.verifyAddresses(seedPhrase: String, apiAddressesJson: String) { } } -private const val TESTER_MENU_MAX_ATTEMPTS = 3 - -/** - * Presses 'Volume Down' twice to open tester menu. - * Retries up to [TESTER_MENU_MAX_ATTEMPTS] times if the menu doesn't appear. - */ -private fun BaseTestCase.openTesterMenu() { - repeat(TESTER_MENU_MAX_ATTEMPTS) { attempt -> - waitForIdle() - device.uiDevice.pressKeyCode(KeyEvent.KEYCODE_VOLUME_DOWN) - device.uiDevice.pressKeyCode(KeyEvent.KEYCODE_VOLUME_DOWN) - - val opened = runCatching { - onTesterMenuScreen { addressesInfoButton.assertIsDisplayed() } - }.isSuccess - - if (opened) { - TangemLogger.i("Tester menu opened on attempt ${attempt + 1}") - return - } - TangemLogger.w("Tester menu not opened on attempt ${attempt + 1}, retrying...") - } - error("Failed to open tester menu after $TESTER_MENU_MAX_ATTEMPTS attempts") -} - /** * Polls [walletManagersStore] until the wallet manager count stops growing for [WALLET_MANAGERS_SETTLE_MS]. diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/ReferralScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/ReferralScenarios.kt new file mode 100644 index 0000000000..20e33c2b8a --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/ReferralScenarios.kt @@ -0,0 +1,27 @@ +package com.tangem.scenarios + +import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.clickWithAssertion +import com.tangem.screens.onDetailsScreen +import com.tangem.screens.onMainScreenTopBar +import com.tangem.screens.onReferralProgramScreen +import com.tangem.screens.onWalletSettingsScreen +import io.qameta.allure.kotlin.Allure.step + +fun BaseTestCase.referralTakeParticipate() { + step("Open 'Details' screen") { + onMainScreenTopBar { moreButton.clickWithAssertion() } + } + step("Open 'Wallet settings' screen") { + onDetailsScreen { walletNameButton.clickWithAssertion() } + } + step("Open 'Referral program' screen") { + onWalletSettingsScreen { referralProgramButton.clickWithAssertion() } + } + step("Tap 'Participate'") { + onReferralProgramScreen { participateButton.clickWithAssertion() } + } + step("Verify referral code is displayed") { + onReferralProgramScreen { personalCodeCard.assertIsDisplayed() } + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/TesterScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/TesterScenarios.kt new file mode 100644 index 0000000000..91e3bb607c --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/TesterScenarios.kt @@ -0,0 +1,33 @@ +package com.tangem.scenarios + +import android.view.KeyEvent +import com.tangem.common.BaseTestCase +import com.tangem.screens.onTesterMenuScreen +import com.tangem.utils.logging.TangemLogger + +private const val TESTER_MENU_MAX_ATTEMPTS = 3 + +/** + * Opens the tester menu by sending two 'Volume Down' key events in a single shell command. + * VolumeButtonDoublePressObserver needs two ACTION_DOWN within 300ms; two separate pressKeyCode + * calls are too slow, but both events in one `input keyevent` invocation arrive back-to-back within + * the window. Retries up to [TESTER_MENU_MAX_ATTEMPTS] times if it doesn't appear. + */ +fun BaseTestCase.openTesterMenu() { + repeat(TESTER_MENU_MAX_ATTEMPTS) { attempt -> + waitForIdle() + val volumeDown = KeyEvent.KEYCODE_VOLUME_DOWN + device.uiDevice.executeShellCommand("input keyevent $volumeDown $volumeDown") + + val opened = runCatching { + onTesterMenuScreen { addressesInfoButton.assertIsDisplayed() } + }.isSuccess + + if (opened) { + TangemLogger.i("Tester menu opened on attempt ${attempt + 1}") + return + } + TangemLogger.w("Tester menu not opened on attempt ${attempt + 1}, retrying...") + } + error("Failed to open tester menu after $TESTER_MENU_MAX_ATTEMPTS attempts") +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/ReferralProgramPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/ReferralProgramPageObject.kt index b16bba6546..7e6f0b9511 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/ReferralProgramPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/ReferralProgramPageObject.kt @@ -78,6 +78,11 @@ class ReferralProgramPageObject(semanticsProvider: SemanticsNodeInteractionsProv hasTestTag(BaseButtonTestTags.TEXT) useUnmergedTree = true } + + val personalCodeCard: KNode = child { + hasTestTag(ReferralProgramScreenTestTags.PERSONAL_CODE_CARD) + useUnmergedTree = true + } } internal fun BaseTestCase.onReferralProgramScreen(function: ReferralProgramPageObject.() -> Unit) = diff --git a/app/src/androidTest/kotlin/com/tangem/tests/referral/ReferralTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/referral/ReferralTest.kt new file mode 100644 index 0000000000..f0d93d7ea1 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/referral/ReferralTest.kt @@ -0,0 +1,178 @@ +package com.tangem.tests.referral + +import androidx.test.core.app.ApplicationProvider +import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.clickWithAssertion +import com.tangem.common.extensions.tapBackButton +import com.tangem.common.utils.AddressComparisonHelper +import com.tangem.common.utils.getClipboardText +import com.tangem.common.utils.resetWireMockScenarios +import com.tangem.common.utils.setWireMockScenarioState +import com.tangem.domain.models.scan.ProductType +import com.tangem.scenarios.openMainScreen +import com.tangem.scenarios.openTesterMenu +import com.tangem.scenarios.referralTakeParticipate +import com.tangem.scenarios.synchronizeAddresses +import com.tangem.screens.onDetailsScreen +import com.tangem.screens.onMainScreen +import com.tangem.screens.onMainScreenTopBar +import com.tangem.screens.onTesterMenuScreen +import com.tangem.screens.onWalletSettingsScreen +import dagger.hilt.android.testing.HiltAndroidTest +import io.qameta.allure.kotlin.AllureId +import io.qameta.allure.kotlin.junit4.DisplayName +import org.junit.Assert.assertEquals +import org.junit.Test + +@HiltAndroidTest +class ReferralTest : BaseTestCase() { + + @AllureId("3630") + @DisplayName("Referral program: Token and Blockchain added on Main screen after participation") + @Test + fun referralTokenAndBlockchainAddedAfterParticipationTest() { + val tokenNetwork = "Tron" + val token = "Tether" + + setupHooks( + additionalAfterSection = { + resetWireMockScenarios() + } + ).run { + step("Open 'Main' screen") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses() + } + step("Verify network $tokenNetwork and token $token is not displaying") { + onMainScreen { + assertTokenDoesNotExist(tokenNetwork) + assertTokenDoesNotExist(token) + } + } + step("Take participate in Referral program") { + referralTakeParticipate() + } + step("Return to the 'Main' screen") { + tapBackButton() + onWalletSettingsScreen { screenContainer.assertIsDisplayed() } + tapBackButton() + onDetailsScreen { screenContainer.assertIsDisplayed() } + tapBackButton() + onMainScreen { screenContainer.assertIsDisplayed() } + } + step("Verify network $tokenNetwork and token $token is displayed on 'Main' screen") { + onMainScreen { + tokenWithTitleAndAddress(tokenNetwork) + tokenWithTitleAndAddress(token) + } + } + } + } + + @AllureId("10098") + @DisplayName("Referral program: Token added on Main screen after participation") + @Test + fun referralTokenAddedAfterParticipationTest() { + val userWalletScenarioName = "user_tokens_api" + val userWalletState = "Tron" + val tokenNetwork = "Tron" + val token = "Tether" + + setupHooks( + additionalAfterSection = { + resetWireMockScenarios() + } + ).run { + step("Set wiremock scenario: $userWalletScenarioName to state $userWalletState") { + setWireMockScenarioState(scenarioName = userWalletScenarioName, state = userWalletState) + } + step("Open 'Main' screen") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses() + } + step("Verify token $token is not displaying") { + onMainScreen { assertTokenDoesNotExist(token) } + } + step("Verify network $tokenNetwork is displaying") { + onMainScreen { tokenWithTitleAndAddress(tokenNetwork) } + } + step("Take participate in Referral program") { + referralTakeParticipate() + } + step("Return to the 'Main' screen") { + tapBackButton() + onWalletSettingsScreen { screenContainer.assertIsDisplayed() } + tapBackButton() + onDetailsScreen { screenContainer.assertIsDisplayed() } + tapBackButton() + onMainScreen { screenContainer.assertIsDisplayed() } + } + step("Verify $token is displayed on 'Main' screen") { + onMainScreen { tokenWithTitleAndAddress(token) } + } + } + } + + @AllureId("3629") + @DisplayName("Referral program: Participating unavailable for No-Wallet cards") + @Test + fun referralUnavailableForNoWalletCardsTest() { + setupHooks().run { + step("Open 'Main' screen") { + openMainScreen(productType = ProductType.Note) + } + step("Open 'Details' screen") { + onMainScreenTopBar { moreButton.clickWithAssertion() } + } + step("Open 'Wallet settings' screen") { + onDetailsScreen { walletNameButton.clickWithAssertion() } + } + step("Verify 'Referral program' button does not displaying") { + onWalletSettingsScreen { referralProgramButton.assertDoesNotExist() } + } + } + } + + @AllureId("3636") + @DisplayName("Referral program: Verify wallet derivation") + @Test + fun referralVerifyWalletDerivationTest() { + val tokenNetwork = "Tron" + val expectedDerivationPath = "m/44'/195'/0'/0/0" + + setupHooks( + additionalAfterSection = { + resetWireMockScenarios() + } + ).run { + step("Open 'Main' screen") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses() + } + step("Take participate in Referral program") { + referralTakeParticipate() + } + step("Open Debug menu") { + openTesterMenu() + } + step("Open 'Addresses info' screen") { + onTesterMenuScreen { addressesInfoButton.clickWithAssertion() } + } + step("Verify $tokenNetwork derivation path is '$expectedDerivationPath'") { + onTesterMenuScreen { jsonTab.clickWithAssertion() } + onTesterMenuScreen { copyButton.clickWithAssertion() } + val addressesJson = getClipboardText(ApplicationProvider.getApplicationContext()) + ?: error("Clipboard is empty after copying addresses") + val actualDerivationPath = + AddressComparisonHelper.derivationPathForBlockchain(addressesJson, tokenNetwork) + assertEquals(expectedDerivationPath, actualDerivationPath) + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds2/button/TangemButtonExt.kt b/core/ui/src/main/java/com/tangem/core/ui/ds2/button/TangemButtonExt.kt index a023d2cf26..640d24d748 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/ds2/button/TangemButtonExt.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/ds2/button/TangemButtonExt.kt @@ -3,16 +3,18 @@ package com.tangem.core.ui.ds2.button import androidx.compose.runtime.Composable import androidx.compose.runtime.NonRestartableComposable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import com.tangem.core.ui.ds.image.TangemIconUM import com.tangem.core.ui.res.generated.icons.Icons import com.tangem.core.ui.res.generated.icons.ic_arrow_left_20 import com.tangem.core.ui.res.generated.icons.ic_cross_20 +import com.tangem.core.ui.test.TopNavigationTestTags @Composable @NonRestartableComposable fun TangemButton.Back(modifier: Modifier = Modifier, onClick: () -> Unit) { TangemButton( - modifier = modifier, + modifier = modifier.testTag(TopNavigationTestTags.BACK_BUTTON), variant = TangemButton.Variant.Material, iconStart = TangemIconUM.Icon(Icons.ic_arrow_left_20), onClick = onClick, diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/ReferralProgramScreenTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/ReferralProgramScreenTestTags.kt index 55a20fabe0..aec008677e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/ReferralProgramScreenTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/ReferralProgramScreenTestTags.kt @@ -5,4 +5,5 @@ object ReferralProgramScreenTestTags { const val CONDITION_BLOCK = "REFERRAL_PROGRAM_SCREEN_CONDITION_BLOCK" const val INFO_FOR_YOU_TEXT = "REFERRAL_PROGRAM_SCREEN_INFO_FOR_YOU_TEXT" const val INFO_FOR_YOUR_FRIEND_TEXT = "REFERRAL_PROGRAM_INFO_FOR_YOUR_FRIEND_TEXT" + const val PERSONAL_CODE_CARD = "REFERRAL_PROGRAM_SCREEN_PERSONAL_CODE_CARD" } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/TopNavigationTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/TopNavigationTestTags.kt new file mode 100644 index 0000000000..5beed834c5 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/test/TopNavigationTestTags.kt @@ -0,0 +1,5 @@ +package com.tangem.core.ui.test + +object TopNavigationTestTags { + const val BACK_BUTTON = "TOP_NAVIGATION_BACK_BUTTON" +} \ No newline at end of file diff --git a/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index 1795f345ba..34d7240d33 100644 --- a/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextOverflow @@ -41,6 +42,7 @@ import com.tangem.core.ui.res.LocalRedesignEnabled import com.tangem.core.ui.res.LocalTopSnackbarHostState import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.test.ReferralProgramScreenTestTags import com.tangem.feature.referral.domain.models.ExpectedAward import com.tangem.feature.referral.domain.models.ExpectedAwards import com.tangem.feature.referral.models.ReferralStateHolder @@ -261,6 +263,7 @@ private fun ExtraItems(extraItems: List, overallItemsLastIndex: I private fun PersonalCodeCard(code: String, accountAward: ReferralStateHolder.AccountAward?) { Column( modifier = Modifier + .testTag(ReferralProgramScreenTestTags.PERSONAL_CODE_CARD) .clip(RoundedCornerShape(TangemTheme.dimens.radius12)) .background(color = TangemTheme.colors.background.primary) .fillMaxWidth()