tangem-app-android-audited/app/src/androidTest/kotlin/com/tangem/scenarios/AccountsScenarios.kt
2026-06-03 14:39:06 +03:00

211 lines
No EOL
7.9 KiB
Kotlin

package com.tangem.scenarios
import com.tangem.common.BaseTestCase
import com.tangem.common.R
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_VERY_LONG
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.domain.models.account.Account
import com.tangem.screens.accounts.onAccountDetailsScreen
import com.tangem.screens.accounts.onAccountInfoEditorScreen
import com.tangem.screens.accounts.onArchivedAccountsScreen
import com.tangem.screens.onDetailsScreen
import com.tangem.screens.onDialog
import com.tangem.screens.onMainScreenTopBar
import com.tangem.screens.onWalletSettingsScreen
import com.tangem.utils.logging.TangemLogger
import io.github.kakaocup.kakao.common.utilities.getResourceString
import io.qameta.allure.kotlin.Allure.step
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
private const val ACCOUNT_POLL_INTERVAL_MS = 500L
fun BaseTestCase.openWalletSettingsScreen() {
step("Open 'Wallet details' screen") {
onMainScreenTopBar { moreButton.clickWithAssertion() }
}
step("Open 'Wallet settings' screen") {
onDetailsScreen { walletNameButton.clickWithAssertion() }
}
}
fun BaseTestCase.startAccountCreation() {
step("Click on 'Add account' button") {
onWalletSettingsScreen { addAccountButton.clickWithAssertion() }
}
step("Assert 'Account info editor' screen is displayed") {
onAccountInfoEditorScreen { screenContainer.assertIsDisplayed() }
}
}
fun BaseTestCase.openAccountDetails(accountName: String) {
step("Click on account: '$accountName'") {
onWalletSettingsScreen { accountItem(accountName).clickWithAssertion() }
}
step("Assert 'Account details' screen is displayed") {
onAccountDetailsScreen { screenContainer.assertIsDisplayed() }
}
}
fun BaseTestCase.checkUnsavedChangesCreationModal() {
step("Assert 'Unsaved changes' alert is displayed") {
onDialog { dialogContainer.assertIsDisplayed() }
}
step("Assert 'Unsaved changes' alert has proper title") {
onDialog { title.assertTextContains(getResourceString(R.string.account_unsaved_dialog_title)) }
}
step("Assert 'Unsaved changes' alert has proper description for account creation") {
onDialog {
text.assertTextContains(getResourceString(R.string.account_unsaved_dialog_message_create))
}
}
step("Assert 'Keep editing' button is displayed in alert with proper text") {
onDialog { keepEditButton.assertIsDisplayed() }
}
step("Assert 'Discard' button is displayed in alert") {
onDialog { discardButton.assertIsDisplayed() }
}
}
fun BaseTestCase.assertUnsavedChangesEditionModal() {
step("Assert 'Unsaved changes' alert is displayed") {
onDialog { dialogContainer.assertIsDisplayed() }
}
step("Assert 'Unsaved changes' alert has proper title") {
onDialog { title.assertTextContains(getResourceString(R.string.account_unsaved_dialog_title)) }
}
step("Assert 'Unsaved changes' alert has proper description for account creation") {
onDialog {
text.assertTextContains(getResourceString(R.string.account_unsaved_dialog_message_edit))
}
}
step("Assert 'Keep editing' button is displayed in alert with proper text") {
onDialog { keepEditButton.assertIsDisplayed() }
}
step("Assert 'Discard' button is displayed in alert") {
onDialog { discardButton.assertIsDisplayed() }
}
}
fun BaseTestCase.archiveAccount() {
step("Assert 'Archive' button is displayed") {
onAccountDetailsScreen { archiveAccountButton.assertIsDisplayed() }
}
step("Click on 'Archive' button") {
onAccountDetailsScreen { archiveAccountButton.clickWithAssertion() }
}
step("Confirm archivation in dialog") {
onDialog { archiveButton.clickWithAssertion() }
}
}
fun BaseTestCase.assertArchiveConfirmationDialog() {
step("Assert confirmation dialog is displayed") {
onDialog { dialogContainer.assertIsDisplayed() }
}
step("Assert confirmation dialog has 'Cancel' button") {
onDialog { cancelButton.assertIsDisplayed() }
}
step("Assert confirmation dialog has 'Archive' button") {
onDialog { archiveButton.assertIsDisplayed() }
}
}
fun BaseTestCase.assertErrorDialog(expectedTitle: String, expectedMessage: String) {
step("Assert error dialog is displayed") {
onDialog { dialogContainer.assertIsDisplayed() }
}
step("Assert error dialog has proper title") {
onDialog {
title.assertTextContains(expectedTitle)
}
}
step("Assert error dialog has explanatory text") {
onDialog {
text.assertTextContains(expectedMessage)
}
}
step("Assert error dialog has 'OK' button") {
onDialog { okButton.assertIsDisplayed() }
}
}
fun BaseTestCase.dismissErrorDialog() {
step("Dismiss error dialog by clicking 'Ok' button") {
onDialog { okButton.clickWithAssertion() }
}
}
fun BaseTestCase.openArchivedAccountsScreen() {
step("Click on 'Archived accounts' button") {
onWalletSettingsScreen { openArchivedAccountsButton.clickWithAssertion() }
}
}
fun BaseTestCase.assertArchivedAccountIsDisplayed(accountName: String) {
step("Assert archived account with name '$accountName' is displayed") {
onArchivedAccountsScreen {
findArchivedAccountItemByName(accountName)
.container.assertIsDisplayed()
}
}
}
fun BaseTestCase.restoreArchivedAccount(accountName: String) {
step("Restore account with name '$accountName'") {
onArchivedAccountsScreen {
findArchivedAccountItemByName(accountName)
.restoreButton.clickWithAssertion()
}
}
}
/**
* Polls [singleAccountListSupplier] for the selected wallet until a [Account.CryptoPortfolio] with the given
* [derivationIndex] appears with a non-empty token list, then returns it.
*
* Per-account token derivation paths live in the domain account model
* ([Account.CryptoPortfolio.cryptoCurrencies] → [com.tangem.domain.models.network.Network.derivationPath]),
* not in the tester-menu "Addresses info" (which reads from the account-agnostic wallet managers store and
* only ever shows main/base derivations). Reading the model directly is the reliable source for asserting
* per-account derivations.
*/
fun BaseTestCase.awaitCryptoPortfolioAccount(derivationIndex: Int): Account.CryptoPortfolio {
val walletId = getSelectedWalletSyncUseCase().getOrNull()?.walletId
?: error("No selected wallet found")
var account: Account.CryptoPortfolio? = null
runBlocking {
withTimeout(WAIT_UNTIL_TIMEOUT_VERY_LONG) {
while (true) {
val candidate = singleAccountListSupplier.getSyncOrNull(walletId)
?.accounts
?.filterIsInstance<Account.CryptoPortfolio>()
?.firstOrNull { it.derivationIndex.value == derivationIndex }
if (candidate != null && candidate.cryptoCurrencies.isNotEmpty()) {
TangemLogger.i(
"Account with derivation index $derivationIndex resolved: " +
"${candidate.cryptoCurrencies.size} token(s)",
)
account = candidate
return@withTimeout
}
delay(ACCOUNT_POLL_INTERVAL_MS)
}
}
}
return requireNotNull(account) {
"Account with derivation index $derivationIndex was not found for wallet $walletId"
}
}
/**
* Returns all derivation paths of tokens whose name equals [tokenName] (case-insensitive) within this account.
*/
fun Account.CryptoPortfolio.derivationPathsForToken(tokenName: String): List<String> = cryptoCurrencies
.filter { it.name.equals(tokenName, ignoreCase = true) }
.mapNotNull { it.network.derivationPath.value }