Updated on 2026-08-14
This commit is contained in:
parent
df4186cca0
commit
1c1509640a
16 changed files with 889 additions and 20 deletions
|
|
@ -24,6 +24,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
|
|||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.walletmanager.WalletManagersStore
|
||||
import com.tangem.datasource.utils.WireMockRedirectInterceptor
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import com.tangem.tap.MainActivity
|
||||
|
|
@ -63,6 +64,9 @@ abstract class BaseTestCase : TestCase(
|
|||
@Inject
|
||||
lateinit var getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase
|
||||
|
||||
@Inject
|
||||
lateinit var singleAccountListSupplier: SingleAccountListSupplier
|
||||
|
||||
private val hiltRule = HiltAndroidRule(this)
|
||||
private val apiEnvironmentRule = ApiEnvironmentRule()
|
||||
private val permissionRule = GrantPermissionRule.grant(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import androidx.test.uiautomator.By
|
|||
import androidx.test.uiautomator.Until
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
|
||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_SHORT
|
||||
|
||||
|
||||
fun BaseTestCase.swipeVertical(
|
||||
direction: SwipeDirection,
|
||||
|
|
@ -96,6 +98,12 @@ fun BaseTestCase.restartApp(packageName: String) {
|
|||
waitForIdle()
|
||||
}
|
||||
|
||||
fun BaseTestCase.clickOnSystemButton(buttonName: String) {
|
||||
device.uiDevice.wait(Until.hasObject(By.text(buttonName)), WAIT_UNTIL_TIMEOUT_SHORT)
|
||||
device.uiDevice.findObject(By.text(buttonName))?.click()
|
||||
?: throw AssertionError("System '$buttonName' button not found")
|
||||
}
|
||||
|
||||
enum class SwipeDirection {
|
||||
UP, DOWN
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.common.utils
|
||||
|
||||
/**
|
||||
* Helper for inspecting individual nodes of a BIP-44-style derivation path string
|
||||
* (e.g. one read from `Network.derivationPath` of a token in the domain account model).
|
||||
*/
|
||||
object DerivationPathHelper {
|
||||
|
||||
/**
|
||||
* Returns the [index1Based]-th node of a derivation path, ignoring the leading `m`.
|
||||
* For "m/44'/0'/1'/0/0": node 1 = "44'", node 3 = "1'", node 5 = "0".
|
||||
*/
|
||||
fun nodeAt(derivationPath: String, index1Based: Int): String {
|
||||
val nodes = derivationPath.removePrefix("m/").split("/")
|
||||
require(index1Based in 1..nodes.size) {
|
||||
"Node #$index1Based is out of range for path '$derivationPath' (${nodes.size} nodes)"
|
||||
}
|
||||
return nodes[index1Based - 1]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,25 @@
|
|||
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") {
|
||||
|
|
@ -19,6 +30,15 @@ fun BaseTestCase.openWalletSettingsScreen() {
|
|||
}
|
||||
}
|
||||
|
||||
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() }
|
||||
|
|
@ -28,6 +48,46 @@ fun BaseTestCase.openAccountDetails(accountName: String) {
|
|||
}
|
||||
}
|
||||
|
||||
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() }
|
||||
|
|
@ -99,4 +159,53 @@ fun BaseTestCase.restoreArchivedAccount(accountName: String) {
|
|||
.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 }
|
||||
|
|
@ -32,6 +32,11 @@ class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val gotItButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_got_it))
|
||||
}
|
||||
|
||||
val cancelButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_cancel))
|
||||
|
|
@ -52,6 +57,16 @@ class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
hasText(getResourceString(R.string.account_details_archive_action))
|
||||
}
|
||||
|
||||
val discardButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.account_unsaved_dialog_action_second))
|
||||
}
|
||||
|
||||
val keepEditButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.account_unsaved_dialog_action_first))
|
||||
}
|
||||
|
||||
val continueButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_continue))
|
||||
|
|
|
|||
|
|
@ -256,6 +256,22 @@ class MainScreenPageObject(private val semanticsProvider: SemanticsNodeInteracti
|
|||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty-tokens placeholder shown under an expanded account that has no tokens.
|
||||
*/
|
||||
val emptyAccountTokensPlaceholder: KNode = child {
|
||||
hasTestTag(MainScreenTestTags.EMPTY_TOKENS_PLACEHOLDER)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 'Add tokens' button inside the empty-account placeholder. Click opens manage tokens for that account.
|
||||
*/
|
||||
val emptyAccountAddTokensButton: KNode = child {
|
||||
hasTestTag(MainScreenTestTags.EMPTY_TOKENS_ADD_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Main account header on the main screen. Click to expand/collapse its tokens list.
|
||||
*/
|
||||
|
|
@ -358,6 +374,46 @@ class MainScreenPageObject(private val semanticsProvider: SemanticsNodeInteracti
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Account row on the main screen. Tappable — click to expand/collapse its tokens.
|
||||
*/
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun findAccountSectionByName(accountName: String): KNode {
|
||||
return lazyList.child {
|
||||
hasTestTag(MainScreenTestTags.ACCOUNT_LIST_ITEM)
|
||||
hasAnyDescendant(withText(accountName))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the account row into view and collapses the top bar so the account's tokens (or the
|
||||
* empty placeholder) land within screen bounds after expansion. Click via [findAccountSectionByName].
|
||||
*/
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun scrollToAccountSection(accountName: String) {
|
||||
collapseHeader()
|
||||
lazyList.childWith<LazyListItemNode> {
|
||||
hasTestTag(MainScreenTestTags.ACCOUNT_LIST_ITEM)
|
||||
hasAnyDescendant(withText(accountName))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a token row on the main screen by token name. Tokens belonging to collapsed accounts
|
||||
* are hidden from the semantics tree, so expanding a single account before calling this
|
||||
* effectively scopes the lookup to that account's tokens.
|
||||
*/
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun findTokenInAnyAccountByName(tokenName: String): KNode {
|
||||
return lazyList.child {
|
||||
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
||||
hasAnyDescendant(withText(tokenName))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
fun KNode.assertIsUnreachable() {
|
||||
this {
|
||||
hasAnyAncestor(withText(getResourceString(R.string.common_unreachable)))
|
||||
|
|
@ -370,16 +426,11 @@ class MainScreenPageObject(private val semanticsProvider: SemanticsNodeInteracti
|
|||
* Tests will fail if assertIsNotDisplayed() or assertDoesNotExist() are used instead.
|
||||
*/
|
||||
fun assertTokenDoesNotExist(tokenTitle: String) {
|
||||
try {
|
||||
tokenWithTitleAndAddress(tokenTitle).assertExists()
|
||||
throw AssertionError("Token with title '$tokenTitle' should not exist but was found")
|
||||
} catch (e: AssertionError) {
|
||||
if (e.message?.contains("No node found") == true) {
|
||||
return
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
lazyList.child<KNode> {
|
||||
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
||||
hasAnyDescendant(withText(tokenTitle))
|
||||
useUnmergedTree = true
|
||||
}.assertDoesNotExist()
|
||||
}
|
||||
|
||||
fun assertTokensCount(expectedCount: Int) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ 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.SwitchTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
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
|
||||
|
|
@ -20,6 +21,16 @@ import androidx.compose.ui.test.hasAnyAncestor as withAnyAncestor
|
|||
class ManageTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<ManageTokensPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val topAppBarBackButton: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
|
||||
}
|
||||
|
||||
val topAppBarTitle: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
hasText(getResourceString(com.tangem.core.ui.R.string.add_tokens_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val searchField: KNode = child {
|
||||
hasTestTag(BaseSearchBarTestTags.SEARCH_BAR)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ import androidx.compose.ui.test.hasText as withText
|
|||
class WalletSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<WalletSettingsPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val screenContainer: KNode = child {
|
||||
hasTestTag(WalletSettingsScreenTestTags.SCREEN_CONTAINER)
|
||||
}
|
||||
|
||||
val topAppBarBackButton: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.screens.accounts
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import com.tangem.core.ui.test.accounts.AccountInfoEditScreenTestTags
|
||||
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
|
||||
|
||||
class AccountInfoEditPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<AccountInfoEditPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val screenContainer: KNode = child {
|
||||
hasTestTag(AccountInfoEditScreenTestTags.ACCOUNT_DETAILS_CONTAINER)
|
||||
}
|
||||
|
||||
val accountNameField: KNode = child {
|
||||
hasTestTag(AccountInfoEditScreenTestTags.NAME_FIELD)
|
||||
}
|
||||
|
||||
val accountCurrentIcon: KNode = child {
|
||||
hasTestTag(AccountInfoEditScreenTestTags.SELECTED_ICON)
|
||||
}
|
||||
|
||||
val accountColorOption: KNode = child {
|
||||
hasTestTag(AccountInfoEditScreenTestTags.COLOR_OPTION)
|
||||
}
|
||||
|
||||
val accountTypeOption: KNode = child {
|
||||
hasTestTag(AccountInfoEditScreenTestTags.TYPE_OPTION)
|
||||
}
|
||||
|
||||
val saveAccountButton: KNode = child {
|
||||
hasTestTag(AccountInfoEditScreenTestTags.SAVE_ACCOUNT_BUTTON)
|
||||
}
|
||||
|
||||
val crossButton: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onAccountInfoEditorScreen(function: AccountInfoEditPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.tests.accounts
|
|||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.REFERRAL_API_SCENARIO
|
||||
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||
import com.tangem.common.extensions.clickAndWaitFor
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
|
|
@ -10,7 +11,9 @@ import com.tangem.core.ui.R
|
|||
import com.tangem.scenarios.*
|
||||
import com.tangem.screens.accounts.onAccountDetailsScreen
|
||||
import com.tangem.screens.accounts.onArchivedAccountsScreen
|
||||
import com.tangem.screens.onDetailsScreen
|
||||
import com.tangem.screens.onDialog
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.screens.onWalletSettingsScreen
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
|
|
@ -164,8 +167,8 @@ class AccountArchivationsTest : BaseTestCase() {
|
|||
|
||||
@Test
|
||||
@AllureId("5976")
|
||||
@DisplayName("Accounts: restore an archived account")
|
||||
fun restoreArchivedAccountTest() {
|
||||
@DisplayName("Accounts: restore a simple archived account")
|
||||
fun restoreSimpleArchivedAccountTest() {
|
||||
val archivedAccountName = "Account 3"
|
||||
val userAccountsInitialState = "TwoAccountsWithArchivedAccounts"
|
||||
val userAccountsAfterArchivationState = "ReadyToRestore"
|
||||
|
|
@ -202,6 +205,108 @@ class AccountArchivationsTest : BaseTestCase() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@AllureId("5980")
|
||||
@DisplayName("Accounts: restore archived account with custom token transfer")
|
||||
fun restoreArchivedAccountWithCustomTokensTest() {
|
||||
val mainAccountName = "Main account"
|
||||
val archivedAccountName = "Account 2"
|
||||
val customTokenName = "Ethereum"
|
||||
val expectedArchivedTokensInfo = "1 token"
|
||||
val userAccountsInitialState = "OneAccountWithArchivedCustomToken"
|
||||
val userAccountsReadyToRestoreState = "ReadyToRestoreCustomToken"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeSection = {
|
||||
setWireMockScenarioState(userTokensScenario, userAccountsInitialState)
|
||||
},
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(userTokensScenario)
|
||||
},
|
||||
).run {
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Synchronize addresses") { synchronizeAddresses() }
|
||||
step("Open wallet settings") { openWalletSettingsScreen() }
|
||||
step("Open 'Archived accounts' screen") { openArchivedAccountsScreen() }
|
||||
|
||||
step("Verify archived account '$archivedAccountName' shows '$expectedArchivedTokensInfo'") {
|
||||
onArchivedAccountsScreen {
|
||||
val row = findArchivedAccountItemByName(archivedAccountName)
|
||||
row.container.assertIsDisplayed()
|
||||
row.subtitle.assertTextContains(expectedArchivedTokensInfo, substring = true)
|
||||
}
|
||||
}
|
||||
step("Switch WireMock to '$userAccountsReadyToRestoreState'") {
|
||||
setWireMockScenarioState(userTokensScenario, userAccountsReadyToRestoreState)
|
||||
}
|
||||
step("Click restore button for '$archivedAccountName'") {
|
||||
onArchivedAccountsScreen {
|
||||
findArchivedAccountItemByName(archivedAccountName)
|
||||
.restoreButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
|
||||
step("Assert custom token migration dialog is displayed") {
|
||||
onDialog { dialogContainer.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert dialog text mentions main account '$mainAccountName'") {
|
||||
onDialog { text.assertTextContains(mainAccountName, substring = true) }
|
||||
}
|
||||
step("Assert dialog text mentions restoring account '$archivedAccountName'") {
|
||||
onDialog { text.assertTextContains(archivedAccountName, substring = true) }
|
||||
}
|
||||
step("Confirm migration in dialog") {
|
||||
onDialog { gotItButton.clickWithAssertion() }
|
||||
}
|
||||
|
||||
step("Assert 'Wallet settings' screen is displayed") {
|
||||
onWalletSettingsScreen { addAccountButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert restored account '$archivedAccountName' is in active accounts list") {
|
||||
onWalletSettingsScreen { accountItem(archivedAccountName).assertIsDisplayed() }
|
||||
}
|
||||
step("Navigate back to wallet details") {
|
||||
onWalletSettingsScreen { topAppBarBackButton.clickWithAssertion() }
|
||||
}
|
||||
step("Navigate back to main screen") {
|
||||
onDetailsScreen { topAppBarBackButton.clickWithAssertion() }
|
||||
}
|
||||
|
||||
step("Assert main account '$mainAccountName' is visible on main screen") {
|
||||
onMainScreen { findAccountSectionByName(mainAccountName).assertIsDisplayed() }
|
||||
}
|
||||
step("Assert restored account '$archivedAccountName' is visible on main screen") {
|
||||
onMainScreen { findAccountSectionByName(archivedAccountName).assertIsDisplayed() }
|
||||
}
|
||||
|
||||
step("Expand main account '$mainAccountName'") {
|
||||
onMainScreen { findAccountSectionByName(mainAccountName).clickWithAssertion() }
|
||||
}
|
||||
step("Assert '$customTokenName' is NOT displayed under main account") {
|
||||
onMainScreen { assertTokenDoesNotExist(customTokenName) }
|
||||
}
|
||||
step("Expand main account '$mainAccountName'") {
|
||||
onMainScreen { findAccountSectionByName(mainAccountName).clickWithAssertion() }
|
||||
}
|
||||
step("Assert '$customTokenName' is NOT displayed under main account") {
|
||||
onMainScreen {
|
||||
assertTokenDoesNotExist(customTokenName)
|
||||
}
|
||||
}
|
||||
|
||||
step("Expand restored account '$archivedAccountName' and assert '$customTokenName' is displayed") {
|
||||
onMainScreen {
|
||||
findAccountSectionByName(archivedAccountName).clickAndWaitFor(
|
||||
rule = composeTestRule,
|
||||
expectedCondition = {
|
||||
onMainScreen { findTokenInAnyAccountByName(customTokenName).assertIsDisplayed() }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@AllureId("7962")
|
||||
@DisplayName("Accounts: restore archived account error")
|
||||
|
|
@ -250,4 +355,5 @@ class AccountArchivationsTest : BaseTestCase() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,492 @@
|
|||
package com.tangem.tests.accounts
|
||||
|
||||
import androidx.compose.ui.test.longClick
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.R
|
||||
import com.tangem.common.extensions.clickAndWaitFor
|
||||
import com.tangem.common.extensions.clickOnSystemButton
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.DerivationPathHelper
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setClipboardText
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.scenarios.*
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.screens.accounts.onAccountInfoEditorScreen
|
||||
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.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class AccountCreationTest : BaseTestCase() {
|
||||
|
||||
private val userTokensScenario = "user_tokens_api"
|
||||
|
||||
@Test
|
||||
@AllureId("5504")
|
||||
@DisplayName("Accounts: account creation network error handling")
|
||||
fun accountCreationErrorTest() {
|
||||
val accountName = "Account 2"
|
||||
val userAccountsGetErrorState = "AccountsGetError"
|
||||
val userAccountsPutErrorState = "AccountsPutError"
|
||||
val userAccountsBeforeCreationState = "AccountReadyToCreate"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeSection = {
|
||||
setWireMockScenarioState(userTokensScenario, userAccountsGetErrorState)
|
||||
},
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(userTokensScenario)
|
||||
},
|
||||
).run {
|
||||
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Synchronize addresses") { synchronizeAddresses() }
|
||||
step("Open wallet settings") { openWalletSettingsScreen() }
|
||||
step("Start account creation") { startAccountCreation() }
|
||||
|
||||
step("Enter account name: '$accountName'") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.performClick()
|
||||
accountNameField.performTextInput(accountName)
|
||||
}
|
||||
}
|
||||
step("Click 'Add account' button (GET accounts is blocked)") {
|
||||
onAccountInfoEditorScreen {
|
||||
saveAccountButton.clickAndWaitFor(
|
||||
rule = composeTestRule,
|
||||
expectedCondition = {
|
||||
onDialog { dialogContainer.assertIsDisplayed() }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
step("Assert error dialog details") {
|
||||
assertErrorDialog(
|
||||
expectedTitle = getResourceString(R.string.common_something_went_wrong),
|
||||
expectedMessage = getResourceString(com.tangem.core.ui.R.string.account_generic_error_dialog_message),
|
||||
)
|
||||
}
|
||||
step("Dismiss error dialog") { dismissErrorDialog() }
|
||||
step("Assert still on account creation screen") {
|
||||
onAccountInfoEditorScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
|
||||
step("Unblock GET accounts, block PUT accounts") {
|
||||
setWireMockScenarioState(userTokensScenario, userAccountsPutErrorState)
|
||||
}
|
||||
step("Click 'Add account' button again (PUT accounts is blocked)") {
|
||||
onAccountInfoEditorScreen {
|
||||
saveAccountButton.clickAndWaitFor(
|
||||
rule = composeTestRule,
|
||||
expectedCondition = {
|
||||
onDialog { dialogContainer.assertIsDisplayed() }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
step("Assert still on account creation screen") {
|
||||
assertErrorDialog(
|
||||
expectedTitle = getResourceString(R.string.common_something_went_wrong),
|
||||
expectedMessage = getResourceString(R.string.account_generic_error_dialog_message),
|
||||
)
|
||||
}
|
||||
|
||||
step("Unblock both 'accounts' requests") {
|
||||
setWireMockScenarioState(userTokensScenario, userAccountsBeforeCreationState)
|
||||
}
|
||||
|
||||
step("Dismiss error dialog") { dismissErrorDialog() }
|
||||
step("Assert still on account creation screen") {
|
||||
onAccountInfoEditorScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
|
||||
step("Click 'Add account' button again (both requests unblocked)") {
|
||||
onAccountInfoEditorScreen {
|
||||
saveAccountButton.clickAndWaitFor(
|
||||
rule = composeTestRule,
|
||||
expectedCondition = {
|
||||
onManageTokensScreen { topAppBarTitle.assertIsDisplayed() }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
step("Assert 'Manage Tokens' title is displayed") {
|
||||
onManageTokensScreen { topAppBarTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Close 'Manage Tokens' screen") {
|
||||
onManageTokensScreen { topAppBarBackButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Wallet settings' screen is displayed") {
|
||||
onWalletSettingsScreen { addAccountButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert new account '$accountName' appears in accounts list") {
|
||||
onWalletSettingsScreen { accountItem(accountName).assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@AllureId("5507")
|
||||
@DisplayName("Accounts: name field verifications")
|
||||
fun accountsCreationNameFieldValidationTest() {
|
||||
val accountName = "TestAccount12"
|
||||
val longName = "A".repeat(21)
|
||||
val emptyPlaceholderValue = "New account"
|
||||
val editedName = "Edited"
|
||||
val context = device.context
|
||||
val pasteButtonName = "Paste"
|
||||
|
||||
setupHooks().run {
|
||||
step("Set clipboard text '$longName'") {
|
||||
setClipboardText(context,longName)
|
||||
}
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Synchronize addresses") { synchronizeAddresses() }
|
||||
step("Open 'Wallet settings' screen") { openWalletSettingsScreen() }
|
||||
step("Click on 'Add account' button") {
|
||||
onWalletSettingsScreen { addAccountButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Edit account details' dialog screen appears") {
|
||||
onAccountInfoEditorScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
|
||||
step("Enter account name manually: '$accountName'") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.performClick()
|
||||
accountNameField.performTextInput(accountName)
|
||||
}
|
||||
}
|
||||
step("Assert name input is stable (keyboard doesn't flicker)") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.assertTextContains(accountName)
|
||||
}
|
||||
}
|
||||
step("Assert 'Add account' button is enabled") {
|
||||
onAccountInfoEditorScreen {
|
||||
saveAccountButton.assertIsEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
step("Clear the 'Edit name' field") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.performTextClearance()
|
||||
}
|
||||
}
|
||||
step("Assert 'Add account' button becomes inactive when field is empty") {
|
||||
onAccountInfoEditorScreen {
|
||||
saveAccountButton.assertIsNotEnabled()
|
||||
}
|
||||
}
|
||||
step("Paste name from clipboard: '$accountName'") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.performTextReplacement(accountName)
|
||||
}
|
||||
}
|
||||
step("Assert pasted text is displayed in 'Account name' field") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.assertTextContains(accountName)
|
||||
}
|
||||
}
|
||||
step("Assert 'Add account' button is enabled") {
|
||||
onAccountInfoEditorScreen {
|
||||
saveAccountButton.assertIsEnabled()
|
||||
}
|
||||
}
|
||||
step("Edit the entered name (clear and retype)") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.performTextReplacement(editedName)
|
||||
}
|
||||
}
|
||||
step("Assert edited name in 'Account name' field is displayed") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.assertTextContains(editedName)
|
||||
}
|
||||
}
|
||||
step("Delete all text and leave 'Account name' field empty") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.performTextClearance()
|
||||
}
|
||||
}
|
||||
step("Assert 'Add account' button is inactive") {
|
||||
onAccountInfoEditorScreen {
|
||||
saveAccountButton.assertIsNotEnabled()
|
||||
}
|
||||
}
|
||||
step("Type name with more than 20 symbols") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.performTextReplacement(longName)
|
||||
}
|
||||
}
|
||||
step("Assert text over 20 symbols was not pasted and placeholder remains empty") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.assertTextContains(emptyPlaceholderValue, substring = true)
|
||||
}
|
||||
}
|
||||
step("Assert 'Add account' button is inactive") {
|
||||
onAccountInfoEditorScreen {
|
||||
saveAccountButton.assertIsNotEnabled()
|
||||
}
|
||||
}
|
||||
step("Clear text field") {
|
||||
onAccountInfoEditorScreen { accountNameField.performTextClearance() }
|
||||
}
|
||||
step("Paste text longer than 20 characters to 'Account name' field") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.performTouchInput { longClick(durationMillis = 2_000L) }
|
||||
}
|
||||
}
|
||||
step("Click on system 'Paste' button to paste clipboard text") {
|
||||
clickOnSystemButton(pasteButtonName)
|
||||
}
|
||||
step("Assert text over 20 symbols was not pasted and placeholder remains empty") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.assertTextContains(emptyPlaceholderValue, substring = true)
|
||||
}
|
||||
}
|
||||
step("Assert 'Add account' button is inactive") {
|
||||
onAccountInfoEditorScreen {
|
||||
saveAccountButton.assertIsNotEnabled()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@AllureId("5505")
|
||||
@DisplayName(
|
||||
"Accounts: check unsaved changes notification " +
|
||||
"after attempt to close edited account creation form"
|
||||
)
|
||||
fun accountsCreationUnsavedChangesForNameFieldNotificationTest() {
|
||||
val accountName = "Hikarik Test"
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Synchronize addresses") { synchronizeAddresses() }
|
||||
step("Open 'Wallet settings' screen") { openWalletSettingsScreen() }
|
||||
step("Click on 'Add account' button") {
|
||||
onWalletSettingsScreen { addAccountButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert edit account details dialog screen appears") {
|
||||
onAccountInfoEditorScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
|
||||
step("Enter account name manually: '$accountName'") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.performClick()
|
||||
accountNameField.performTextInput(accountName)
|
||||
}
|
||||
}
|
||||
step("Tap 'Cross' button to attempt closing the screen") {
|
||||
onAccountInfoEditorScreen {
|
||||
crossButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Verify 'Unsaved changes' screen parts") {
|
||||
checkUnsavedChangesCreationModal()
|
||||
}
|
||||
|
||||
step("Tap 'Keep Editing' button to stay on screen") {
|
||||
onDialog { keepEditButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert app still on 'Create account' screen") {
|
||||
onAccountInfoEditorScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert previously entered data is preserved") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.assertTextContains(accountName)
|
||||
}
|
||||
}
|
||||
|
||||
step("Tap 'Cross' button to attempt closing the screen") {
|
||||
onAccountInfoEditorScreen { crossButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Unsaved changes' alert is displayed again") {
|
||||
onDialog { dialogContainer.assertIsDisplayed() }
|
||||
}
|
||||
step("Tap 'Discard' button to discard and close") {
|
||||
onDialog { discardButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Create account' screen is closed and 'Wallet settings' displayed again") {
|
||||
onWalletSettingsScreen {
|
||||
screenContainer.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Verify no new account has appeared in the list") {
|
||||
onWalletSettingsScreen {
|
||||
accountItem(accountName).assertDoesNotExist()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@AllureId("5502")
|
||||
@DisplayName("Accounts: account creation, accounts mode and per-account token derivation")
|
||||
fun accountCreationAndDerivationTest() {
|
||||
val createdAccountName = "Account 2"
|
||||
val accountReadyState = "AccountReadyToCreateDerivation"
|
||||
val accountIndex = "1"
|
||||
val btcTokenName = "Bitcoin"
|
||||
val ethTokenName = "Ethereum"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeSection = {
|
||||
setWireMockScenarioState(userTokensScenario, accountReadyState)
|
||||
},
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(userTokensScenario)
|
||||
},
|
||||
).run {
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Synchronize addresses") { synchronizeAddresses() }
|
||||
step("Open wallet settings") { openWalletSettingsScreen() }
|
||||
step("Start account creation") { startAccountCreation() }
|
||||
|
||||
step("Enter account name: '$createdAccountName'") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.performClick()
|
||||
accountNameField.performTextInput(createdAccountName)
|
||||
}
|
||||
}
|
||||
step("Assert account creation screen with derivation hint is displayed") {
|
||||
onAccountInfoEditorScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
|
||||
step("Click 'Add account' and wait for 'Manage Tokens'") {
|
||||
onAccountInfoEditorScreen {
|
||||
saveAccountButton.clickAndWaitFor(
|
||||
rule = composeTestRule,
|
||||
expectedCondition = {
|
||||
onManageTokensScreen { topAppBarTitle.assertIsDisplayed() }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
step("Close 'Manage Tokens' screen") {
|
||||
onManageTokensScreen { topAppBarBackButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Wallet settings' screen is displayed") {
|
||||
onWalletSettingsScreen { addAccountButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert new account '$createdAccountName' appears (last) in accounts list") {
|
||||
onWalletSettingsScreen { accountItem(createdAccountName).assertIsDisplayed() }
|
||||
}
|
||||
step("Navigate back to wallet details") {
|
||||
onWalletSettingsScreen { topAppBarBackButton.clickWithAssertion() }
|
||||
}
|
||||
step("Navigate back to main screen") {
|
||||
onDetailsScreen { topAppBarBackButton.clickWithAssertion() }
|
||||
}
|
||||
|
||||
step("Assert accounts mode is on main: account '$createdAccountName' section is visible") {
|
||||
onMainScreen { findAccountSectionByName(createdAccountName).assertIsDisplayed() }
|
||||
}
|
||||
|
||||
step("Assert per-account token derivation paths from the domain account model") {
|
||||
val account = awaitCryptoPortfolioAccount(derivationIndex = accountIndex.toInt())
|
||||
|
||||
val btcPaths = account.derivationPathsForToken(btcTokenName)
|
||||
assertTrue(
|
||||
"Expected a $btcTokenName derivation with 3rd node = $accountIndex' (account index). Paths: $btcPaths",
|
||||
btcPaths.any { DerivationPathHelper.nodeAt(it, index1Based = 3) == "$accountIndex'" },
|
||||
)
|
||||
|
||||
val ethPaths = account.derivationPathsForToken(ethTokenName)
|
||||
assertTrue(
|
||||
"Expected an $ethTokenName derivation with 5th node = $accountIndex (account index). Paths: $ethPaths",
|
||||
ethPaths.any { DerivationPathHelper.nodeAt(it, index1Based = 5) == accountIndex },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@AllureId("8746")
|
||||
@DisplayName("Accounts: empty account placeholder and 'Add tokens' entry to manage tokens")
|
||||
fun emptyAccountPlaceholderTest() {
|
||||
val createdAccountName = "Account 2"
|
||||
val accountReadyState = "AccountReadyToCreateEmpty"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeSection = {
|
||||
setWireMockScenarioState(userTokensScenario, accountReadyState)
|
||||
},
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(userTokensScenario)
|
||||
},
|
||||
).run {
|
||||
step("Open 'Main Screen'") { openMainScreen() }
|
||||
step("Synchronize addresses") { synchronizeAddresses() }
|
||||
step("Open wallet settings") { openWalletSettingsScreen() }
|
||||
step("Start account creation") { startAccountCreation() }
|
||||
|
||||
step("Enter account name: '$createdAccountName'") {
|
||||
onAccountInfoEditorScreen {
|
||||
accountNameField.performClick()
|
||||
accountNameField.performTextInput(createdAccountName)
|
||||
}
|
||||
}
|
||||
step("Click on 'Add account' and wait for 'Manage Tokens'") {
|
||||
onAccountInfoEditorScreen {
|
||||
saveAccountButton.clickAndWaitFor(
|
||||
rule = composeTestRule,
|
||||
expectedCondition = {
|
||||
onManageTokensScreen { topAppBarTitle.assertIsDisplayed() }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
step("Close 'Manage Tokens' without adding any token") {
|
||||
onManageTokensScreen { topAppBarBackButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Wallet settings' screen is displayed") {
|
||||
onWalletSettingsScreen { addAccountButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert new empty account '$createdAccountName' appears in accounts list") {
|
||||
onWalletSettingsScreen { accountItem(createdAccountName).assertIsDisplayed() }
|
||||
}
|
||||
step("Navigate back to wallet details") {
|
||||
onWalletSettingsScreen { topAppBarBackButton.clickWithAssertion() }
|
||||
}
|
||||
step("Navigate back to main screen") {
|
||||
onDetailsScreen { topAppBarBackButton.clickWithAssertion() }
|
||||
}
|
||||
step("Expand empty account '$createdAccountName' section") {
|
||||
onMainScreen {
|
||||
scrollToAccountSection(createdAccountName)
|
||||
findAccountSectionByName(createdAccountName).clickAndWaitFor(
|
||||
rule = composeTestRule,
|
||||
expectedCondition = {
|
||||
onMainScreen { emptyAccountTokensPlaceholder.assertIsDisplayed() }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
step("Assert empty tokens placeholder is displayed") {
|
||||
onMainScreen { emptyAccountTokensPlaceholder.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Add tokens' button is displayed under the placeholder") {
|
||||
onMainScreen { emptyAccountAddTokensButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Add tokens' button") {
|
||||
onMainScreen {
|
||||
emptyAccountAddTokensButton.clickAndWaitFor(
|
||||
rule = composeTestRule,
|
||||
expectedCondition = {
|
||||
onManageTokensScreen { topAppBarTitle.assertIsDisplayed() }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
step("Assert 'Manage Tokens' screen is opened for the account") {
|
||||
onManageTokensScreen { topAppBarTitle.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue