Updated on 2026-08-14
This commit is contained in:
commit
92849d43c2
1407 changed files with 64092 additions and 11990 deletions
|
|
@ -184,9 +184,7 @@ abstract class BaseTestCase : TestCase(
|
|||
toggleStates = mapOf(
|
||||
"SWAP_REDESIGN_ENABLED" to false,
|
||||
"ACCOUNTS_FEATURE_ENABLED" to true,
|
||||
"GASLESS_APPROVAL_ENABLED" to true,
|
||||
"MAIN_SCREEN_QR_SCANNING_ENABLED" to true,
|
||||
"ADD_AND_MANAGE_TOKENS_ENABLED" to true,
|
||||
"ASSETS_DISCOVERY_ENABLED" to true,
|
||||
"VISA_ONBOARDING_ENABLED" to true,
|
||||
// Version-gated toggles released in versions <= 6.0 — forced on so tests run against the actual
|
||||
|
|
@ -195,20 +193,13 @@ abstract class BaseTestCase : TestCase(
|
|||
// 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,
|
||||
"SOLANA_SCALED_UI_AMOUNT_ENABLED" to true,
|
||||
"SWAP_AB_ENABLED" to true,
|
||||
"AND_15310_ADD_FUNDS_STAGE1" to true,
|
||||
"AND_15009_SWAP_PROVIDER_FILTER_ENABLED" to true,
|
||||
"AND_15101_TANGEM_PAY_HOT_WALLET_ONBOARDING" to true,
|
||||
"AND_15402_ADI_MAIN_SCREEN_DEFAULT_ENABLED" to true,
|
||||
"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,
|
||||
// 5.39.2
|
||||
"AND_15154_YIELD_PROMO_ENABLED" to true,
|
||||
// 5.40
|
||||
"TWI_1377_MANAGE_FUNDS" to true,
|
||||
// 6.0
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ object TestConstants {
|
|||
const val ALLURE_LABEL_VALUE = "Kaspresso"
|
||||
|
||||
const val USER_TOKENS_API_SCENARIO = "user_tokens_api"
|
||||
const val COINS_API_SCENARIO = "coins_api"
|
||||
const val REFERRAL_API_SCENARIO = "referral_api"
|
||||
const val QUOTES_API_SCENARIO = "quotes_api"
|
||||
const val CREATE_USER_WALLET_API_SCENARIO = "create_user_wallet_api"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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<AddressEntry> {
|
||||
val array = JSONArray(json)
|
||||
val entries = mutableListOf<AddressEntry>()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import android.content.ClipData
|
|||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
|
||||
fun getClipboardText(context: Context): String? {
|
||||
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
|
|
@ -27,12 +29,15 @@ fun clearClipboard(
|
|||
clipboard.clearPrimaryClip()
|
||||
}
|
||||
|
||||
// Kotlin's assert() is a no-op on device (JVM assertions disabled) — use JUnit asserts here.
|
||||
fun assertClipboardTextEquals(
|
||||
expected: String,
|
||||
context: Context = ApplicationProvider.getApplicationContext()
|
||||
) {
|
||||
assertEquals("Clipboard text mismatch", expected, getClipboardText(context))
|
||||
}
|
||||
|
||||
fun assertClipboardIsEmpty(context: Context = ApplicationProvider.getApplicationContext()) {
|
||||
val actual = getClipboardText(context)
|
||||
assert(actual == expected) {
|
||||
"Clipboard text mismatch.\nExpected: '$expected'\nActual: '$actual'"
|
||||
}
|
||||
assertTrue("Expected empty clipboard but was: '$actual'", actual.isNullOrEmpty())
|
||||
}
|
||||
|
|
@ -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].
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,17 @@ fun BaseTestCase.selectStablecoinAsFeeToken(coinName: String, tokenName: String)
|
|||
step("Select '$tokenName' as the fee-paying token") {
|
||||
onSendFeeSelectorBottomSheet { feeTokenItem(tokenName).performClick() }
|
||||
}
|
||||
step("Wait until the '$tokenName' fee is loaded and 'Apply' is enabled") {
|
||||
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
runCatching {
|
||||
onSendFeeSelectorBottomSheet {
|
||||
networkFeeTitle.assertIsDisplayed()
|
||||
feeTokenItem(tokenName).assertIsDisplayed()
|
||||
applyButton.assertIsEnabled()
|
||||
}
|
||||
}.isSuccess
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,154 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.extensions.performTextInputInChunks
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.screens.accounts.onAccountDetailsScreen
|
||||
import com.tangem.screens.onAddCustomTokenScreen
|
||||
import com.tangem.screens.onDetailsScreen
|
||||
import com.tangem.screens.onDialog
|
||||
import com.tangem.screens.onManageTokensScreen
|
||||
import com.tangem.screens.onWalletSettingsScreen
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
|
||||
private fun mainAccountName(): String = getResourceString(CoreResR.string.account_main_account_title)
|
||||
|
||||
// flakySafely is unavailable in BaseTestCase extensions — wait until the assertion/action stops throwing.
|
||||
private fun BaseTestCase.awaitSuccess(block: () -> Unit) {
|
||||
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT) { runCatching(block).isSuccess }
|
||||
}
|
||||
|
||||
fun BaseTestCase.openManageTokens(accountName: String = mainAccountName()) {
|
||||
openWalletSettingsScreen()
|
||||
openAccountDetails(accountName)
|
||||
step("Click on 'Manage tokens' button") {
|
||||
onAccountDetailsScreen { manageTokensButton.clickWithAssertion() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.openAddCustomToken(accountName: String = mainAccountName()) {
|
||||
openManageTokens(accountName)
|
||||
step("Click on 'Add custom token' button") {
|
||||
onManageTokensScreen { addCustomTokenButton.clickWithAssertion() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.addCustomTokenWithCustomDerivation(network: String, contract: String, derivationPath: String) {
|
||||
step("Click on network: '$network'") {
|
||||
awaitSuccess { onAddCustomTokenScreen { scrollToNetwork(network) } }
|
||||
onAddCustomTokenScreen { networkRow(network).performClick() }
|
||||
}
|
||||
step("Enter contract address: '$contract'") {
|
||||
awaitSuccess { onAddCustomTokenScreen { contractAddressField.assertExists() } }
|
||||
onAddCustomTokenScreen { contractAddressField.performTextInputInChunks(contract) }
|
||||
}
|
||||
step("Click on 'Derivation path' field") {
|
||||
awaitSuccess { onAddCustomTokenScreen { derivationSelectorField.assertExists() } }
|
||||
onAddCustomTokenScreen { derivationSelectorField.performClick() }
|
||||
}
|
||||
step("Click on 'Custom derivation' button") {
|
||||
awaitSuccess { onAddCustomTokenScreen { customDerivationButton.assertIsDisplayed() } }
|
||||
onAddCustomTokenScreen { customDerivationButton.performClick() }
|
||||
}
|
||||
step("Enter custom derivation path: '$derivationPath'") {
|
||||
awaitSuccess { onDialog { inputField.assertIsDisplayed() } }
|
||||
onDialog { inputField.performTextInputInChunks(derivationPath) }
|
||||
awaitSuccess { onDialog { okButton.assertIsEnabled() } }
|
||||
onDialog { okButton.performClick() }
|
||||
}
|
||||
step("Click on 'Add token' button") {
|
||||
awaitSuccess { onAddCustomTokenScreen { addTokenButton.assertIsEnabled() } }
|
||||
onAddCustomTokenScreen { addTokenButton.performClick() }
|
||||
}
|
||||
navigateBackToMainFromManageTokens()
|
||||
}
|
||||
|
||||
private const val MAX_BACKS_TO_MANAGE_TOKENS = 5
|
||||
|
||||
// The device-backs to close the sheet vary — press back until Manage Tokens is shown, then fail loudly.
|
||||
private fun BaseTestCase.closeAddCustomTokenSheet() {
|
||||
repeat(MAX_BACKS_TO_MANAGE_TOKENS) {
|
||||
if (runCatching { onManageTokensScreen { searchField.assertIsDisplayed() } }.isSuccess) return
|
||||
device.uiDevice.pressBack()
|
||||
waitForIdle()
|
||||
}
|
||||
onManageTokensScreen { searchField.assertIsDisplayed() }
|
||||
}
|
||||
|
||||
private fun BaseTestCase.navigateBackToWalletSettings() {
|
||||
step("Click on 'Manage tokens' screen 'Back' button") {
|
||||
waitForIdle()
|
||||
onManageTokensScreen { topAppBarBackButton.performClick() }
|
||||
}
|
||||
step("Click on 'Account details' screen 'Back' button") {
|
||||
waitForIdle()
|
||||
onAccountDetailsScreen { topAppBarBackButton.performClick() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.forgetCurrentWalletAndReArmForNextScan() {
|
||||
step("Close 'Add custom token' bottom sheet") { closeAddCustomTokenSheet() }
|
||||
navigateBackToWalletSettings()
|
||||
step("Click on 'Forget wallet' button") {
|
||||
waitForIdle()
|
||||
onWalletSettingsScreen {
|
||||
scrollToForgetWallet()
|
||||
forgetWalletButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Confirm forgetting the wallet") {
|
||||
awaitSuccess { onDialog { forgetButton.assertIsDisplayed() } }
|
||||
onDialog { forgetButton.clickWithAssertion() }
|
||||
}
|
||||
// Markets tooltip is a one-time app-level flag; re-arm it so the next scan's openMainScreen can dismiss it.
|
||||
step("Re-arm the markets tooltip for the next scan") {
|
||||
runBlocking {
|
||||
appPreferencesStore.editData { it.set(PreferencesKeys.SHOULD_SHOW_MARKETS_TOOLTIP_KEY, value = true) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.assertDerivationPathsInSelector(networkToOpen: String, vararg expectedPaths: Pair<String, String>) {
|
||||
step("Click on network: '$networkToOpen'") {
|
||||
awaitSuccess { onAddCustomTokenScreen { scrollToNetwork(networkToOpen) } }
|
||||
onAddCustomTokenScreen { networkRow(networkToOpen).performClick() }
|
||||
}
|
||||
step("Click on 'Derivation path' field") {
|
||||
awaitSuccess { onAddCustomTokenScreen { derivationSelectorField.assertExists() } }
|
||||
onAddCustomTokenScreen { derivationSelectorField.performClick() }
|
||||
}
|
||||
expectedPaths.forEach { (networkId, path) ->
|
||||
step("Assert '$networkId' derivation path is '$path'") {
|
||||
awaitSuccess { onAddCustomTokenScreen { scrollToDerivationRow(networkId) } }
|
||||
onAddCustomTokenScreen { derivationPath(networkId, path).assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.toggleTokenNetworkInManageTokens(tokenTitle: String, networkTitle: String) {
|
||||
step("Click on token: '$tokenTitle'") {
|
||||
awaitSuccess { onManageTokensScreen { tokenItem(tokenTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen { tokenItem(tokenTitle).performClick() }
|
||||
}
|
||||
step("Click on '$networkTitle' switch") {
|
||||
awaitSuccess { onManageTokensScreen { networkSwitch(networkTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen { networkSwitch(networkTitle).performClick() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.navigateBackToMainFromManageTokens() {
|
||||
navigateBackToWalletSettings()
|
||||
step("Click on 'Wallet settings' screen 'Back' button") {
|
||||
waitForIdle()
|
||||
onWalletSettingsScreen { topAppBarBackButton.performClick() }
|
||||
}
|
||||
step("Click on 'Details' screen 'Back' button") {
|
||||
waitForIdle()
|
||||
onDetailsScreen { topAppBarBackButton.performClick() }
|
||||
}
|
||||
}
|
||||
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import androidx.compose.ui.test.performScrollToNode
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.AddCustomTokenScreenTestTags
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||
import androidx.compose.ui.test.hasText as withText
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
|
||||
class AddCustomTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<AddCustomTokenPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val selectorList: KNode = child {
|
||||
hasTestTag(AddCustomTokenScreenTestTags.SELECTOR_LIST)
|
||||
}
|
||||
|
||||
fun networkRow(networkName: String): KNode = child {
|
||||
hasTestTag(AddCustomTokenScreenTestTags.networkRow(networkName))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun scrollToNetwork(networkName: String) = selectorList {
|
||||
performScrollToNode(withTestTag(AddCustomTokenScreenTestTags.networkRow(networkName)))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun scrollToDerivationRow(networkId: String) = selectorList {
|
||||
performScrollToNode(withTestTag(AddCustomTokenScreenTestTags.derivationRow(networkId)))
|
||||
}
|
||||
|
||||
fun derivationPath(networkId: String, path: String): KNode = child {
|
||||
useUnmergedTree = true
|
||||
hasTestTag(AddCustomTokenScreenTestTags.derivationRow(networkId))
|
||||
hasAnyDescendant(withText(path))
|
||||
}
|
||||
|
||||
val contractAddressField: KNode = child {
|
||||
hasTestTag(AddCustomTokenScreenTestTags.CONTRACT_ADDRESS_FIELD)
|
||||
}
|
||||
|
||||
val derivationSelectorField: KNode = child {
|
||||
hasTestTag(AddCustomTokenScreenTestTags.DERIVATION_SELECTOR_FIELD)
|
||||
}
|
||||
|
||||
val customDerivationButton: KNode = child {
|
||||
hasTestTag(AddCustomTokenScreenTestTags.CUSTOM_DERIVATION_BUTTON)
|
||||
}
|
||||
|
||||
val warningNotification: KNode = child {
|
||||
hasTestTag(AddCustomTokenScreenTestTags.WARNING_NOTIFICATION)
|
||||
}
|
||||
|
||||
val addTokenButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(CoreResR.string.common_add_token))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onAddCustomTokenScreen(function: AddCustomTokenPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -14,6 +14,10 @@ class AppSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
|
|||
hasTestTag(AppSettingsScreenTestTags.CURRENCY_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val backButton: KNode = child {
|
||||
hasTestTag(AppSettingsScreenTestTags.BACK_BUTTON)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onAppSettingsScreen(function: AppSettingsPageObject.() -> Unit) =
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ import androidx.compose.ui.test.hasText as withText
|
|||
class DetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<DetailsPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val screenContainer: KNode = child {
|
||||
hasTestTag(DetailsScreenTestTags.SCREEN_CONTAINER)
|
||||
}
|
||||
|
||||
val topAppBarBackButton: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.screens
|
|||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.BaseDialogTestTags
|
||||
|
|
@ -10,6 +11,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.hasTestTag as withTestTag
|
||||
import androidx.compose.ui.test.hasText as withText
|
||||
|
||||
class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<DialogPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
|
@ -18,6 +20,12 @@ class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
hasTestTag(BaseDialogTestTags.CONTAINER)
|
||||
}
|
||||
|
||||
fun containerWithText(text: String): KNode = child {
|
||||
hasTestTag(BaseDialogTestTags.CONTAINER)
|
||||
hasAnyDescendant(withText(text = text, substring = true))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(BaseDialogTestTags.TITLE)
|
||||
}
|
||||
|
|
@ -87,6 +95,11 @@ class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
hasText(getResourceString(R.string.common_ok))
|
||||
}
|
||||
|
||||
val forgetButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(CoreResR.string.common_forget))
|
||||
}
|
||||
|
||||
val changeButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_change))
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.common.R
|
|||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.BaseSearchBarTestTags
|
||||
import com.tangem.core.ui.test.ManageTokensScreenTestTags
|
||||
import com.tangem.core.ui.test.SearchBarTestTags
|
||||
import com.tangem.core.ui.test.SwitchTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
|
|
@ -17,6 +18,7 @@ import androidx.compose.ui.test.hasText as withText
|
|||
import androidx.compose.ui.test.hasAnySibling as withAnySibling
|
||||
import androidx.compose.ui.test.hasAnyDescendant as withAnyDescendant
|
||||
import androidx.compose.ui.test.hasAnyAncestor as withAnyAncestor
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
|
||||
class ManageTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<ManageTokensPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
|
@ -35,11 +37,43 @@ class ManageTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
|
|||
hasTestTag(BaseSearchBarTestTags.SEARCH_BAR)
|
||||
}
|
||||
|
||||
val searchClearButton: KNode = child {
|
||||
hasTestTag(SearchBarTestTags.CLEAR_BUTTON)
|
||||
}
|
||||
|
||||
val addCustomTokenButton: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.MORE_BUTTON)
|
||||
}
|
||||
|
||||
fun tokenItem(tokenName: String): KNode = child {
|
||||
hasTestTag(ManageTokensScreenTestTags.TOKEN_ITEM)
|
||||
hasText(tokenName)
|
||||
}
|
||||
|
||||
fun networkStandard(networkName: String, standard: String): KNode = child {
|
||||
useUnmergedTree = true
|
||||
addSemanticsMatcher(
|
||||
withText(standard).and(
|
||||
withAnyAncestor(
|
||||
withTestTag(ManageTokensScreenTestTags.NETWORK_NAME)
|
||||
.and(withAnyDescendant(withText(networkName, ignoreCase = true))),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun networkName(networkName: String): KNode = child {
|
||||
useUnmergedTree = true
|
||||
addSemanticsMatcher(
|
||||
withTestTag(ManageTokensScreenTestTags.NETWORK_NAME)
|
||||
.and(withAnyDescendant(withText(networkName))),
|
||||
)
|
||||
}
|
||||
|
||||
val contractAddressCopiedMessage: KNode = child {
|
||||
hasText(getResourceString(CoreResR.string.contract_address_copied_message))
|
||||
}
|
||||
|
||||
fun networkSwitch(networkName: String): KNode = child {
|
||||
useUnmergedTree = true
|
||||
addSemanticsMatcher(
|
||||
|
|
|
|||
|
|
@ -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) =
|
||||
|
|
|
|||
|
|
@ -114,6 +114,12 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
|
|||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun tokenTitle(name: String): KNode = child {
|
||||
hasTestTag(TokenDetailsScreenTestTags.TOKEN_TITLE)
|
||||
hasAnyDescendant(withText(text = name, substring = true))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun networkFeeNotificationMessage(
|
||||
currencyName: String,
|
||||
networkName: String,
|
||||
|
|
|
|||
|
|
@ -59,15 +59,15 @@ class AppCurrencyTest : BaseTestCase() {
|
|||
onAppSettingsScreen { currencyButton.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
step("Return to 'Details' screen") {
|
||||
waitForIdle()
|
||||
device.uiDevice.pressBack()
|
||||
step("Return to 'Details' screen via 'Back' button") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
onAppSettingsScreen { backButton.clickWithAssertion() }
|
||||
onDetailsScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
step("Return to 'Main' screen via 'Back' button") {
|
||||
onDetailsScreen { topAppBarBackButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Main' screen is opened") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
onDetailsScreen { topAppBarBackButton.clickWithAssertion() }
|
||||
onMainScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tests.addFunds
|
|||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.extensions.assertTextContainsSafe
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.synchronizeAddresses
|
||||
import com.tangem.screens.onAddTokenBottomSheet
|
||||
|
|
@ -11,6 +12,7 @@ import com.tangem.screens.onBuyTokenDetailsScreen
|
|||
import com.tangem.screens.onMainScreen
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.Issue
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -83,4 +85,24 @@ class BuyTest : BaseTestCase() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3613")
|
||||
@DisplayName("On-ramp Buy: S2C card doesn't have Buy and Sell options")
|
||||
@Test
|
||||
@Issue("[REDACTED_TASK_KEY]")
|
||||
fun buyAndSellIsNotAvailableForS2CCardTest() {
|
||||
setupHooks().run {
|
||||
step("Open 'Main' screen") {
|
||||
openMainScreen(productType = ProductType.Start2Coin)
|
||||
}
|
||||
step("Verify 'Add funds' button is displayed") {
|
||||
onMainScreen { addFundsButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Verify Buy/Sell action buttons are hidden") {
|
||||
onMainScreen {
|
||||
buyButton.assertDoesNotExist()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
package com.tangem.tests.main
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.COINS_API_SCENARIO
|
||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
|
||||
import com.tangem.common.extensions.performTextInputInChunks
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.scenarios.addCustomTokenWithCustomDerivation
|
||||
import com.tangem.scenarios.assertDerivationPathsInSelector
|
||||
import com.tangem.scenarios.forgetCurrentWalletAndReArmForNextScan
|
||||
import com.tangem.scenarios.navigateBackToMainFromManageTokens
|
||||
import com.tangem.scenarios.openAddCustomToken
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.synchronizeAddresses
|
||||
import com.tangem.screens.onAddCustomTokenScreen
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.tap.domain.sdk.mocks.content.Wallet1LegacyDerivationMockContent
|
||||
import com.tangem.tap.domain.sdk.mocks.content.Wallet2NoEd25519Slip0010MockContent
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class AddCustomTokenTest : BaseTestCase() {
|
||||
|
||||
private val richState = "ManageTokensRich"
|
||||
private val tokenTitle = "Tether"
|
||||
private val ethereumNetwork = "Ethereum"
|
||||
private val solanaNetwork = "Solana"
|
||||
private val bitcoinNetworkId = "bitcoin"
|
||||
private val ethereumClassicNetworkId = "ethereum-classic"
|
||||
private val ethContract = "0xdac17f958d2ee523a2206206994597c13d831ec7"
|
||||
private val solanaContract = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
||||
private val customDerivationPath = "m/44'/60'/0'/0/1"
|
||||
|
||||
@AllureId("772")
|
||||
@DisplayName("Add custom token: added token appears on Main")
|
||||
@Test
|
||||
fun addCustomTokenAppearsOnMainTest() {
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Synchronize addresses") {
|
||||
flakySafely { onMainScreen { synchronizeAddressesButton.assertIsDisplayed() } }
|
||||
synchronizeAddresses(assertBalance = false)
|
||||
}
|
||||
step("Open 'Add custom token' screen") { openAddCustomToken() }
|
||||
step("Click on network: '$ethereumNetwork'") {
|
||||
flakySafely { onAddCustomTokenScreen { scrollToNetwork(ethereumNetwork) } }
|
||||
onAddCustomTokenScreen { networkRow(ethereumNetwork).performClick() }
|
||||
}
|
||||
step("Enter contract address: '$ethContract'") {
|
||||
flakySafely { onAddCustomTokenScreen { contractAddressField.assertExists() } }
|
||||
// Chunked input spans several validation ticks — the model drops the first sampled form change.
|
||||
onAddCustomTokenScreen { contractAddressField.performTextInputInChunks(ethContract) }
|
||||
}
|
||||
step("Click on 'Add token' button") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) { onAddCustomTokenScreen { addTokenButton.assertIsEnabled() } }
|
||||
onAddCustomTokenScreen { addTokenButton.performClick() }
|
||||
}
|
||||
step("Navigate back to 'Main Screen'") { navigateBackToMainFromManageTokens() }
|
||||
step("Assert token: '$tokenTitle' is displayed on Main") {
|
||||
flakySafely { onMainScreen { tokenWithTitleAndAddress(tokenTitle).assertIsDisplayed() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("775")
|
||||
@DisplayName("Add custom token: custom derivation path is accepted")
|
||||
@Test
|
||||
fun addCustomTokenWithCustomDerivationTest() {
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Synchronize addresses") {
|
||||
flakySafely { onMainScreen { synchronizeAddressesButton.assertIsDisplayed() } }
|
||||
synchronizeAddresses(assertBalance = false)
|
||||
}
|
||||
step("Open 'Add custom token' screen") { openAddCustomToken() }
|
||||
step("Add custom token with custom derivation on '$ethereumNetwork'") {
|
||||
addCustomTokenWithCustomDerivation(
|
||||
network = ethereumNetwork,
|
||||
contract = ethContract,
|
||||
derivationPath = customDerivationPath,
|
||||
)
|
||||
}
|
||||
step("Assert token: '$tokenTitle' is displayed on Main") {
|
||||
flakySafely { onMainScreen { tokenWithTitleAndAddress(tokenTitle).assertIsDisplayed() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("771")
|
||||
@DisplayName("Add custom token: custom derivation indicator is shown on Main")
|
||||
@Test
|
||||
fun customDerivationIndicatorOnMainTest() {
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Synchronize addresses") {
|
||||
flakySafely { onMainScreen { synchronizeAddressesButton.assertIsDisplayed() } }
|
||||
synchronizeAddresses(assertBalance = false)
|
||||
}
|
||||
step("Open 'Add custom token' screen") { openAddCustomToken() }
|
||||
step("Add custom token with custom derivation on '$ethereumNetwork'") {
|
||||
addCustomTokenWithCustomDerivation(
|
||||
network = ethereumNetwork,
|
||||
contract = ethContract,
|
||||
derivationPath = customDerivationPath,
|
||||
)
|
||||
}
|
||||
step("Assert custom derivation indicator for '$tokenTitle' is displayed on Main") {
|
||||
flakySafely { onMainScreen { tokenWithCustomDerivationIcon(tokenTitle).assertIsDisplayed() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("770")
|
||||
@DisplayName("Add custom token: derivation field is available for a non-EVM network")
|
||||
@Test
|
||||
fun derivationAvailableForNonEvmNetworkTest() {
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Open 'Add custom token' screen") { openAddCustomToken() }
|
||||
step("Click on network: '$solanaNetwork'") {
|
||||
flakySafely { onAddCustomTokenScreen { scrollToNetwork(solanaNetwork) } }
|
||||
onAddCustomTokenScreen { networkRow(solanaNetwork).performClick() }
|
||||
}
|
||||
step("Assert 'Derivation path' field is available") {
|
||||
flakySafely { onAddCustomTokenScreen { derivationSelectorField.assertExists() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("769")
|
||||
@DisplayName("Add custom token: Solana token on a modern card shows no unsupported warning")
|
||||
@Test
|
||||
fun solanaTokenModernCardNoWarningTest() {
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Open 'Add custom token' screen") { openAddCustomToken() }
|
||||
step("Click on network: '$solanaNetwork'") {
|
||||
flakySafely { onAddCustomTokenScreen { scrollToNetwork(solanaNetwork) } }
|
||||
onAddCustomTokenScreen { networkRow(solanaNetwork).performClick() }
|
||||
}
|
||||
step("Enter contract address: '$solanaContract'") {
|
||||
flakySafely { onAddCustomTokenScreen { contractAddressField.assertExists() } }
|
||||
onAddCustomTokenScreen { contractAddressField.performTextInputInChunks(solanaContract) }
|
||||
}
|
||||
step("Assert 'Add token' button is enabled") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) { onAddCustomTokenScreen { addTokenButton.assertIsEnabled() } }
|
||||
}
|
||||
step("Assert unsupported-token warning is not displayed") {
|
||||
onAddCustomTokenScreen { warningNotification.assertDoesNotExist() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("777")
|
||||
@DisplayName("Add custom token: network with a missing curve is not offered")
|
||||
@Test
|
||||
fun missingCurveNetworkNotOfferedTest() {
|
||||
val bitcoinNetwork = "Bitcoin"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen(mockContent = Wallet2NoEd25519Slip0010MockContent) }
|
||||
step("Open 'Add custom token' screen") { openAddCustomToken() }
|
||||
step("Assert supported network '$bitcoinNetwork' is displayed") {
|
||||
flakySafely { onAddCustomTokenScreen { scrollToNetwork(bitcoinNetwork) } }
|
||||
}
|
||||
step("Assert '$solanaNetwork' network is not displayed for the missing-curve card") {
|
||||
onAddCustomTokenScreen { selectorList.assertIsDisplayed() }
|
||||
val solanaOffered = runCatching { onAddCustomTokenScreen { scrollToNetwork(solanaNetwork) } }.isSuccess
|
||||
assertFalse("'$solanaNetwork' should not be offered for a missing-curve card", solanaOffered)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("776")
|
||||
@DisplayName("Add custom token: derivation paths match the card version (V1/V2/V3)")
|
||||
@Test
|
||||
fun derivationPathsMatchCardVersionTest() {
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Assert derivation paths for a legacy-batch V1 Wallet card") {
|
||||
openMainScreen(mockContent = Wallet1LegacyDerivationMockContent)
|
||||
openAddCustomToken()
|
||||
assertDerivationPathsInSelector(
|
||||
ethereumNetwork,
|
||||
bitcoinNetworkId to "m/44'/0'/0'/0/0",
|
||||
ethereumClassicNetworkId to "m/44'/61'/0'/0/0",
|
||||
)
|
||||
forgetCurrentWalletAndReArmForNextScan()
|
||||
}
|
||||
step("Assert derivation paths for a V2 Wallet card") {
|
||||
openMainScreen()
|
||||
openAddCustomToken()
|
||||
assertDerivationPathsInSelector(
|
||||
ethereumNetwork,
|
||||
bitcoinNetworkId to "m/44'/0'/0'/0/0",
|
||||
ethereumClassicNetworkId to "m/44'/60'/0'/0/0",
|
||||
)
|
||||
forgetCurrentWalletAndReArmForNextScan()
|
||||
}
|
||||
step("Assert derivation paths for a V3 Wallet 2 card") {
|
||||
openMainScreen(productType = ProductType.Wallet2)
|
||||
openAddCustomToken()
|
||||
assertDerivationPathsInSelector(
|
||||
ethereumNetwork,
|
||||
bitcoinNetworkId to "m/84'/0'/0'/0/0",
|
||||
ethereumClassicNetworkId to "m/44'/61'/0'/0/0",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
package com.tangem.tests.main
|
||||
|
||||
import androidx.compose.ui.test.longClick
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.COINS_API_SCENARIO
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.assertClipboardIsEmpty
|
||||
import com.tangem.common.utils.assertClipboardTextEquals
|
||||
import com.tangem.common.utils.clearClipboard
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.openManageTokens
|
||||
import com.tangem.scenarios.toggleTokenNetworkInManageTokens
|
||||
import com.tangem.screens.onDialog
|
||||
import com.tangem.screens.onManageTokensScreen
|
||||
import com.tangem.tap.domain.sdk.mocks.content.Firmware451MockContent
|
||||
import com.tangem.tap.domain.sdk.mocks.content.Wallet2NoEd25519Slip0010MockContent
|
||||
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
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
|
||||
@HiltAndroidTest
|
||||
class ManageTokensTest : BaseTestCase() {
|
||||
|
||||
private val richState = "ManageTokensRich"
|
||||
private val solanaTokenTitle = "USD Coin"
|
||||
private val solanaNetworkTitle = "SOLANA"
|
||||
private val solanaName = "Solana"
|
||||
|
||||
@AllureId("765")
|
||||
@DisplayName("Manage tokens: network standard labels are shown for token networks")
|
||||
@Test
|
||||
fun networkStandardLabelsTest() {
|
||||
val tokenTitle = "Tether"
|
||||
val ethereum = "Ethereum"
|
||||
val bnbSmartChain = "BNB Smart Chain"
|
||||
val tron = "Tron"
|
||||
val erc20 = "ERC20"
|
||||
val bep20 = "BEP20"
|
||||
val trc20 = "TRC20"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Open 'Manage tokens' screen") { openManageTokens() }
|
||||
step("Click on token: '$tokenTitle'") {
|
||||
flakySafely { onManageTokensScreen { tokenItem(tokenTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen { tokenItem(tokenTitle).performClick() }
|
||||
}
|
||||
step("Assert '$ethereum' network standard '$erc20' is displayed") {
|
||||
flakySafely {
|
||||
onManageTokensScreen { networkStandard(networkName = ethereum, standard = erc20).assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
step("Assert '$bnbSmartChain' network standard '$bep20' is displayed") {
|
||||
flakySafely {
|
||||
onManageTokensScreen {
|
||||
networkStandard(networkName = bnbSmartChain, standard = bep20).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Assert '$tron' network standard '$trc20' is displayed") {
|
||||
flakySafely {
|
||||
onManageTokensScreen { networkStandard(networkName = tron, standard = trc20).assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("667")
|
||||
@DisplayName("Manage tokens: search by name and ticker filters the list")
|
||||
@Test
|
||||
fun searchByNameAndTickerTest() {
|
||||
val tokenTitle = "Tether"
|
||||
val nameQuery = "Tether"
|
||||
val tickerQuery = "USDT"
|
||||
val emptyQuery = "Zzqnotoken"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Open 'Manage tokens' screen") { openManageTokens() }
|
||||
step("Search by name: '$nameQuery'") {
|
||||
onManageTokensScreen {
|
||||
searchField.performClick()
|
||||
searchField.performTextInput(nameQuery)
|
||||
}
|
||||
}
|
||||
step("Assert token: '$tokenTitle' is displayed") {
|
||||
flakySafely { onManageTokensScreen { tokenItem(tokenTitle).assertIsDisplayed() } }
|
||||
}
|
||||
step("Clear search field") {
|
||||
onManageTokensScreen { searchClearButton.clickWithAssertion() }
|
||||
}
|
||||
step("Search by ticker: '$tickerQuery'") {
|
||||
onManageTokensScreen { searchField.performTextInput(tickerQuery) }
|
||||
}
|
||||
step("Assert token: '$tokenTitle' is displayed") {
|
||||
flakySafely { onManageTokensScreen { tokenItem(tokenTitle).assertIsDisplayed() } }
|
||||
}
|
||||
step("Clear search field") {
|
||||
onManageTokensScreen { searchClearButton.clickWithAssertion() }
|
||||
}
|
||||
step("Search by unknown query: '$emptyQuery'") {
|
||||
onManageTokensScreen { searchField.performTextInput(emptyQuery) }
|
||||
}
|
||||
step("Assert token: '$tokenTitle' is not displayed") {
|
||||
flakySafely { onManageTokensScreen { tokenItem(tokenTitle).assertDoesNotExist() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("763")
|
||||
@DisplayName("Manage tokens: enabling Solana network on a modern card shows no warning")
|
||||
@Test
|
||||
fun solanaNetworkNoWarningTest() {
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Open 'Manage tokens' screen") { openManageTokens() }
|
||||
step("Enable '$solanaNetworkTitle' network for token: '$solanaTokenTitle'") {
|
||||
toggleTokenNetworkInManageTokens(tokenTitle = solanaTokenTitle, networkTitle = solanaNetworkTitle)
|
||||
}
|
||||
step("Assert hide-token alert is not displayed") {
|
||||
waitForIdle()
|
||||
onDialog { dialogContainer.assertDoesNotExist() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("719")
|
||||
@DisplayName("Manage tokens: long tap on a token network copies the contract address, main network does not")
|
||||
@Test
|
||||
fun copyContractAddressOnNetworkLongTapTest() {
|
||||
val tokenTitle = "Tether"
|
||||
val tokenNetworkTitle = "ETHEREUM"
|
||||
val coinTitle = "Bitcoin"
|
||||
val coinNetworkTitle = "BITCOIN"
|
||||
val contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Open 'Manage tokens' screen") { openManageTokens() }
|
||||
step("Click on token: '$tokenTitle'") {
|
||||
flakySafely { onManageTokensScreen { tokenItem(tokenTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen { tokenItem(tokenTitle).performClick() }
|
||||
}
|
||||
step("Long click on '$tokenNetworkTitle' network row") {
|
||||
flakySafely { onManageTokensScreen { networkName(tokenNetworkTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen {
|
||||
networkName(tokenNetworkTitle).performTouchInput {
|
||||
longClick(position = center, durationMillis = 1000L)
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Assert contract-address-copied message is displayed") {
|
||||
flakySafely { onManageTokensScreen { contractAddressCopiedMessage.assertIsDisplayed() } }
|
||||
}
|
||||
step("Assert clipboard contains '$tokenTitle' contract address") {
|
||||
assertClipboardTextEquals(contractAddress)
|
||||
}
|
||||
step("Clear clipboard") { clearClipboard() }
|
||||
step("Click on token: '$coinTitle'") {
|
||||
flakySafely { onManageTokensScreen { tokenItem(coinTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen { tokenItem(coinTitle).performClick() }
|
||||
}
|
||||
step("Long click on '$coinNetworkTitle' main network row") {
|
||||
flakySafely { onManageTokensScreen { networkName(coinNetworkTitle).assertIsDisplayed() } }
|
||||
onManageTokensScreen {
|
||||
networkName(coinNetworkTitle).performTouchInput {
|
||||
longClick(position = center, durationMillis = 1000L)
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Assert clipboard is empty after long click on main network") {
|
||||
waitForIdle()
|
||||
assertClipboardIsEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("737")
|
||||
@DisplayName("Manage tokens: enabling Solana on an old-firmware card shows firmware-limitation warning")
|
||||
@Test
|
||||
fun solanaFirmwareLimitationWarningTest() {
|
||||
val warningMessage = getResourceString(CoreResR.string.alert_manage_tokens_unsupported_message, solanaName)
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen(mockContent = Firmware451MockContent) }
|
||||
step("Open 'Manage tokens' screen") { openManageTokens() }
|
||||
step("Enable '$solanaNetworkTitle' network for token: '$solanaTokenTitle'") {
|
||||
toggleTokenNetworkInManageTokens(tokenTitle = solanaTokenTitle, networkTitle = solanaNetworkTitle)
|
||||
}
|
||||
step("Assert firmware-limitation warning is displayed") {
|
||||
flakySafely { onDialog { containerWithText(warningMessage).assertIsDisplayed() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("767")
|
||||
@DisplayName("Manage tokens: enabling Solana on a card missing its curve shows unsupported-curve warning")
|
||||
@Test
|
||||
fun solanaUnsupportedCurveWarningTest() {
|
||||
val warningMessage =
|
||||
getResourceString(CoreResR.string.alert_manage_tokens_unsupported_curve_message, solanaName)
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(COINS_API_SCENARIO) },
|
||||
).run {
|
||||
step("Set WireMock scenario: '$COINS_API_SCENARIO' to state: '$richState'") {
|
||||
setWireMockScenarioState(COINS_API_SCENARIO, richState)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen(mockContent = Wallet2NoEd25519Slip0010MockContent) }
|
||||
step("Open 'Manage tokens' screen") { openManageTokens() }
|
||||
step("Enable '$solanaNetworkTitle' network for token: '$solanaTokenTitle'") {
|
||||
toggleTokenNetworkInManageTokens(tokenTitle = solanaTokenTitle, networkTitle = solanaNetworkTitle)
|
||||
}
|
||||
step("Assert unsupported-curve warning is displayed") {
|
||||
flakySafely { onDialog { containerWithText(warningMessage).assertIsDisplayed() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -132,9 +132,9 @@ class GaslessSendTest : BaseTestCase() {
|
|||
@DisplayName("Gasless: completed gasless transaction is shown in token transaction history")
|
||||
@Test
|
||||
fun checkGaslessTransactionInHistoryTest() {
|
||||
val sentAmount = "1.00"
|
||||
val operationAmount = "1.00"
|
||||
val gaslessFeeAmount = "0.10"
|
||||
val sentTitle = getResourceString(R.string.common_sent)
|
||||
val operationTitle = getResourceString(R.string.transaction_history_operation)
|
||||
val gaslessFeeTitle = getResourceString(R.string.gasless_transaction_fee)
|
||||
|
||||
setupHooks(
|
||||
|
|
@ -166,13 +166,13 @@ class GaslessSendTest : BaseTestCase() {
|
|||
onTxHistoryScreen { transactionItem(gaslessFeeTitle).assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
step("Assert '$sentTitle' transaction is displayed") {
|
||||
onTxHistoryScreen { transactionItem(sentTitle).assertIsDisplayed() }
|
||||
step("Assert '$operationTitle' transaction is displayed") {
|
||||
onTxHistoryScreen { transactionItem(operationTitle).assertIsDisplayed() }
|
||||
}
|
||||
step("Assert '$sentTitle' amount '$sentAmount' is displayed in '$currencySymbol'") {
|
||||
step("Assert '$operationTitle' amount '$operationAmount' is displayed in '$currencySymbol'") {
|
||||
onTxHistoryScreen {
|
||||
transactionAmount(sentTitle).assertTextContains(sentAmount, substring = true)
|
||||
transactionCurrency(sentTitle).assertTextEquals(currencySymbol)
|
||||
transactionAmount(operationTitle).assertTextContains(operationAmount, substring = true)
|
||||
transactionCurrency(operationTitle).assertTextEquals(currencySymbol)
|
||||
}
|
||||
}
|
||||
step("Assert gasless '$gaslessFeeTitle' amount '$gaslessFeeAmount' is displayed in '$currencySymbol'") {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
package com.tangem.tests.send.reasonBlock
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.synchronizeAddresses
|
||||
import com.tangem.screens.onDialog
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.screens.onTokenDetailsScreen
|
||||
import com.tangem.screens.onTransferBottomSheet
|
||||
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 ReasonBlockTest : BaseTestCase() {
|
||||
|
||||
@AllureId("3616")
|
||||
@DisplayName("Reason block: Send is unavailable if user has pending transaction")
|
||||
@Test
|
||||
fun reasonBlockSendUnavailableWithPendingTransactionTest() {
|
||||
val txHistoryScenarioName = "dogecoin_tx_history"
|
||||
val txHistoryState = "EmptyWithPendingTransaction"
|
||||
val walletsScenarioName = "user_tokens_api"
|
||||
val walletsState = "Dogecoin"
|
||||
val token = "Dogecoin"
|
||||
val reasonText = getResourceString(R.string.token_button_unavailability_reason_pending_transaction_send)
|
||||
.substringBefore("%s")
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(txHistoryScenarioName)
|
||||
resetWireMockScenarioState(walletsScenarioName)
|
||||
}
|
||||
).run {
|
||||
step("Set Wiremock scenario: $txHistoryScenarioName to state $txHistoryState") {
|
||||
setWireMockScenarioState(scenarioName = txHistoryScenarioName, state = txHistoryState)
|
||||
}
|
||||
step("Set Wiremock scenario: $walletsScenarioName to state $walletsState") {
|
||||
setWireMockScenarioState(scenarioName = walletsScenarioName, state = walletsState)
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Click on token with name $token") {
|
||||
onMainScreen { tokenWithTitleAndAddress(token).clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Transfer' button") {
|
||||
onTokenDetailsScreen { transferButton.clickWithAssertion() }
|
||||
}
|
||||
step("Verify 'Send' button is disabled") {
|
||||
onTransferBottomSheet { sendButton.assertIsNotEnabled() }
|
||||
}
|
||||
step("Click on 'Send' button") {
|
||||
onTransferBottomSheet { sendButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert pending-transaction reason dialog is displayed") {
|
||||
onDialog { containerWithText(reasonText).assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3615")
|
||||
@DisplayName("Reason block: Token withdrawal is unavailable if there are no fee coverage")
|
||||
@Test
|
||||
fun reasonBlockTokenWithdrawalUnavailableWithoutFeeCoverage() {
|
||||
val userWalletsScenarioName = "user_tokens_api"
|
||||
val userWalletsState = "SolanaUSDC"
|
||||
val solBalanceScenarioName = "solana_get_account_info_recipient"
|
||||
val solBalanceState = "ZeroBalance"
|
||||
val token = "USDC"
|
||||
val feeCurrencyName = "Solana"
|
||||
val feeCurrencySymbol = "SOL"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(userWalletsScenarioName)
|
||||
resetWireMockScenarioState(solBalanceScenarioName)
|
||||
}
|
||||
).run {
|
||||
step("Set Wiremock scenario: $userWalletsScenarioName to state $userWalletsState") {
|
||||
setWireMockScenarioState(scenarioName = userWalletsScenarioName, state = userWalletsState)
|
||||
}
|
||||
step("Set Wiremock scenario: $solBalanceScenarioName to state $solBalanceState") {
|
||||
setWireMockScenarioState(scenarioName = solBalanceScenarioName, state = solBalanceState)
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Click on token with name $token") {
|
||||
onMainScreen { tokenWithTitleAndAddress(token).clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Insufficient $feeCurrencySymbol for fee' notification is displayed") {
|
||||
onTokenDetailsScreen {
|
||||
networkFeeNotificationTitle(feeCurrencyName).assertIsDisplayed()
|
||||
networkFeeNotificationMessage(
|
||||
currencyName = token,
|
||||
networkName = feeCurrencyName,
|
||||
feeCurrencyName = feeCurrencyName,
|
||||
feeCurrencySymbol = feeCurrencySymbol,
|
||||
).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Click on 'Go to $feeCurrencySymbol' button") {
|
||||
onTokenDetailsScreen { goToBuyCurrencyButton(feeCurrencySymbol).clickWithAssertion() }
|
||||
}
|
||||
step("Assert $feeCurrencyName token screen is opened") {
|
||||
onTokenDetailsScreen { tokenTitle(feeCurrencyName).assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue