Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-16 09:08:09 +03:00
commit df1ddfaa36
7 changed files with 378 additions and 9 deletions

View file

@ -0,0 +1,34 @@
package com.tangem.common.extensions
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.test.SemanticsNodeInteractionCollection
import androidx.compose.ui.test.assertIsDisplayed
/** Returns the first non-blank text found in this node or its subtree (unmerged tree). */
fun SemanticsNode.firstTextOrNull(): String? {
config.getOrNull(SemanticsProperties.Text)
?.firstOrNull()?.text?.takeIf { it.isNotBlank() }
?.let { return it }
children.forEach { child -> child.firstTextOrNull()?.let { return it } }
return null
}
/**
* Reads the first text of every currently displayed node in this collection, in visual order
* (top-to-bottom, then left-to-right). Non-displayed nodes and nodes without text are skipped.
*/
fun SemanticsNodeInteractionCollection.displayedTextsInVisualOrder(): List<String> {
val count = fetchSemanticsNodes().size
return (0 until count)
.mapNotNull { index ->
val interaction = get(index)
if (runCatching { interaction.assertIsDisplayed() }.isFailure) return@mapNotNull null
val node = interaction.fetchSemanticsNode()
val text = node.firstTextOrNull() ?: return@mapNotNull null
node.boundsInRoot to text
}
.sortedWith(compareBy({ it.first.top }, { it.first.left }))
.map { it.second }
}

View file

@ -1,12 +1,15 @@
package com.tangem.scenarios package com.tangem.scenarios
import com.tangem.common.BaseTestCase import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.extensions.SwipeDirection import com.tangem.common.extensions.SwipeDirection
import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.swipeVertical import com.tangem.common.extensions.swipeVertical
import com.tangem.screens.onAddAndManageBottomSheet import com.tangem.screens.onAddAndManageBottomSheet
import com.tangem.screens.onMainScreen import com.tangem.screens.onMainScreen
import com.tangem.screens.onOrganizeTokensScreen
import io.qameta.allure.kotlin.Allure.step import io.qameta.allure.kotlin.Allure.step
import org.junit.Assert.assertEquals
fun BaseTestCase.openOrganizeTokensScreen() { fun BaseTestCase.openOrganizeTokensScreen() {
step("Swipe to 'Add & Manage' button") { step("Swipe to 'Add & Manage' button") {
@ -19,4 +22,30 @@ fun BaseTestCase.openOrganizeTokensScreen() {
step("Click on 'Organize tokens' button in bottom sheet") { step("Click on 'Organize tokens' button in bottom sheet") {
onAddAndManageBottomSheet { organizeTokensButton.clickWithAssertion() } onAddAndManageBottomSheet { organizeTokensButton.clickWithAssertion() }
} }
}
fun BaseTestCase.getMainScreenTokensOrder(): List<String> {
var tokens: List<String> = emptyList()
step("Read displayed token titles from 'Main' screen") {
awaitSuccess(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) {
onMainScreen { tokens = getDisplayedTokenTitles() }
require(tokens.isNotEmpty()) { "No token titles found on the main screen" }
}
}
return tokens
}
fun BaseTestCase.assertOrganizeTokensMatch(expectedTokens: List<String>) {
step("Open 'Organize tokens' bottom-sheet") {
onMainScreen { clickDisplayedAddAndManageButton() }
onAddAndManageBottomSheet { organizeTokensButton.clickWithAssertion() }
}
step("Assert 'Organize tokens' list matches the main screen order") {
onOrganizeTokensScreen {
assertEquals(expectedTokens, getDisplayedTokenTitles())
}
}
step("Return to 'Main' screen") {
onOrganizeTokensScreen { cancelButton.clickWithAssertion() }
}
} }

View file

@ -45,6 +45,21 @@ fun BaseTestCase.addNewCardWallet(mockContent: MockContent) {
} }
} }
fun BaseTestCase.addNewCardWalletWithoutSync(mockContent: MockContent) {
step("Click 'More' button on TopBar") {
onMainScreenTopBar { moreButton.clickWithAssertion() }
}
MockProvider.setMocks(mockContent)
step("Click on 'Add Wallet' button (scans a new hardware wallet)") {
onDetailsScreen { addWalletButton.clickWithAssertion() }
}
step("Assert 'Main' screen is displayed with the new wallet") {
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_VERY_LONG) {
runCatching { onMainScreenTopBar { moreButton.assertIsDisplayed() } }.isSuccess
}
}
}
fun BaseTestCase.clickDisplayedTokenOnMain(tokenName: String) { fun BaseTestCase.clickDisplayedTokenOnMain(tokenName: String) {
step("Click on token '$tokenName' on the visible wallet") { step("Click on token '$tokenName' on the visible wallet") {
onMainScreen { clickDisplayedToken(tokenName) } onMainScreen { clickDisplayedToken(tokenName) }

View file

@ -1,8 +1,11 @@
package com.tangem.screens package com.tangem.screens
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.semantics.SemanticsProperties import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.* import androidx.compose.ui.test.*
import com.tangem.common.BaseTestCase import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.displayedTextsInVisualOrder
import com.tangem.common.extensions.firstTextOrNull
import com.tangem.common.extensions.getQuantityString import com.tangem.common.extensions.getQuantityString
import com.tangem.common.extensions.hasLazyListItemPosition import com.tangem.common.extensions.hasLazyListItemPosition
import com.tangem.common.utils.LazyListItemNode import com.tangem.common.utils.LazyListItemNode
@ -14,6 +17,7 @@ import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onCompose
import io.github.kakaocup.compose.node.element.KNode import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode
import io.github.kakaocup.kakao.common.utilities.getResourceString import io.github.kakaocup.kakao.common.utilities.getResourceString
import kotlin.math.abs
import androidx.compose.ui.test.hasTestTag as withTestTag import androidx.compose.ui.test.hasTestTag as withTestTag
import androidx.compose.ui.test.hasText as withText import androidx.compose.ui.test.hasText as withText
import com.tangem.core.res.R as CoreResR import com.tangem.core.res.R as CoreResR
@ -121,16 +125,75 @@ class MainScreenPageObject(private val semanticsProvider: SemanticsNodeInteracti
error("Token '$tokenName' is not displayed on the current wallet page") error("Token '$tokenName' is not displayed on the current wallet page")
} }
// Adjacent pager pages stay mounted; swipe the wallet card that's actually on-screen. /**
* Switches to the previous/next wallet in the pager. A horizontal swipe is a silent no-op unless
* the collapsing balance header is fully expanded and pinned to the top, so we retry: swipe, and
* whenever the wallet identity doesn't change, expand the header and try again.
*/
fun swipeToAdjacentWallet(toPrevious: Boolean) { fun swipeToAdjacentWallet(toPrevious: Boolean) {
val nodes = semanticsProvider.onAllNodes(withTestTag(MainScreenTestTags.WALLET_LIST_ITEM)) val before = displayedWalletIdentity()
for (i in 0 until nodes.fetchSemanticsNodes().size) { repeat(times = WALLET_SWITCH_ATTEMPTS) {
val swiped = runCatching { swipeCurrentPage(toPrevious)
nodes[i].assertIsDisplayed() val now = displayedWalletIdentity()
nodes[i].performTouchInput { if (toPrevious) swipeRight() else swipeLeft() } if (now != null && now != before) return
}.isSuccess expandCollapsingHeader()
if (swiped) return
} }
error("Wallet did not switch from '$before' after $WALLET_SWITCH_ATTEMPTS attempts")
}
// Expand only *after* a swipe that didn't page: if the header is already pinned, a swipe-down here
// would trigger pull-to-refresh and un-pin it, breaking the horizontal swipe.
private fun expandCollapsingHeader() {
onScreenPage()?.performTouchInput {
swipeDown(startY = visibleSize.height * 0.3f, endY = visibleSize.height * 0.8f)
}
}
private fun swipeCurrentPage(toPrevious: Boolean) {
onScreenPage()?.performTouchInput { if (toPrevious) swipeRight() else swipeLeft() }
}
// Identity = title + balance: a still-restoring wallet has no CARD_TITLE but always a WALLET_BALANCE.
private fun displayedWalletIdentity(): String? {
val title = onScreenPageChild(withTestTag(MainScreenTestTags.CARD_TITLE))?.firstText()
val balance = onScreenPageChild(withTestTag(MainScreenTestTags.WALLET_BALANCE))?.firstText()
return listOfNotNull(title, balance).joinToString(separator = "|").ifBlank { null }
}
/**
* The full-width pager-page container currently on-screen. The pager keeps adjacent pages composed
* off-screen at ±pageWidth (so [assertIsDisplayed] can't tell them apart), hence selection by
* geometry: the on-screen page is the only one whose left edge is within half a page of x=0.
*/
private fun onScreenPage(): SemanticsNodeInteraction? =
firstNodeMatching(withTestTag(MainScreenTestTags.SCREEN_CONTAINER), useUnmergedTree = false) {
abs(it.left) < it.width / 2f
}
/** A node matching [matcher] whose centre lies within the on-screen page (skips zero-size off-screen copies). */
private fun onScreenPageChild(matcher: SemanticsMatcher): SemanticsNodeInteraction? {
val page = onScreenPage()?.fetchSemanticsNode()?.boundsInRoot ?: return null
return firstNodeMatching(matcher) {
it.width > 0f && it.height > 0f && it.center.x >= page.left && it.center.x < page.right
}
}
private fun SemanticsNodeInteraction.firstText(): String? = fetchSemanticsNode().firstTextOrNull()
// Single geometry primitive behind the pager helpers: the first node matching [matcher] whose
// bounds satisfy [predicate]. Replaces the per-caller onAllNodes(...)[i] loops.
private fun firstNodeMatching(
matcher: SemanticsMatcher,
useUnmergedTree: Boolean = true,
predicate: (Rect) -> Boolean,
): SemanticsNodeInteraction? {
val nodes = semanticsProvider.onAllNodes(matcher, useUnmergedTree = useUnmergedTree)
repeat(times = nodes.fetchSemanticsNodes().size) { index ->
val node = nodes[index]
val matches = runCatching { predicate(node.fetchSemanticsNode().boundsInRoot) }.getOrDefault(false)
if (matches) return node
}
return null
} }
val restoringProgressText: KNode = child { val restoringProgressText: KNode = child {
@ -380,6 +443,23 @@ class MainScreenPageObject(private val semanticsProvider: SemanticsNodeInteracti
useUnmergedTree = true useUnmergedTree = true
} }
/** Collapses the header, scrolls to and clicks the 'Add & manage' button on the on-screen wallet page. */
@OptIn(ExperimentalTestApi::class)
fun clickDisplayedAddAndManageButton() {
val container = onScreenPage() ?: error("No on-screen wallet page found")
// Best-effort: the button is a footer outside the scrollable list, so performScrollToNode can
// throw when it's already visible — that must not abort the click below.
runCatching {
container.performTouchInput {
swipeUp(startY = visibleSize.height * 0.6f, endY = visibleSize.height * 0.1f)
}
container.performScrollToNode(withTestTag(MainScreenTestTags.ADD_AND_MANAGE_BUTTON))
}
val button = onScreenPageChild(withTestTag(MainScreenTestTags.ADD_AND_MANAGE_BUTTON))
?: error("'Add & manage' button is not displayed on the current wallet page")
button.performClick()
}
val searchThroughMarketPlaceholder: KNode = child { val searchThroughMarketPlaceholder: KNode = child {
hasText(getResourceString(R.string.markets_search_title_placeholder)) hasText(getResourceString(R.string.markets_search_title_placeholder))
useUnmergedTree = true useUnmergedTree = true
@ -485,6 +565,21 @@ class MainScreenPageObject(private val semanticsProvider: SemanticsNodeInteracti
useUnmergedTree = true useUnmergedTree = true
}.assertIsDisplayed() }.assertIsDisplayed()
} }
/**
* Token titles displayed on the current wallet page, in visual order. Reads the
* [TokenElementsTestTags.TOKEN_TITLE] rows so network group headers (which share the
* TOKEN_LIST_ITEM tag) are excluded, keeping the result symmetric with the 'Organize tokens' reader.
*/
fun getDisplayedTokenTitles(): List<String> =
semanticsProvider.onAllNodes(
withTestTag(TokenElementsTestTags.TOKEN_TITLE),
useUnmergedTree = true,
).displayedTextsInVisualOrder()
private companion object {
const val WALLET_SWITCH_ATTEMPTS = 4
}
} }
internal fun BaseTestCase.onMainScreen(function: MainScreenPageObject.() -> Unit) = internal fun BaseTestCase.onMainScreen(function: MainScreenPageObject.() -> Unit) =

View file

@ -1,8 +1,12 @@
package com.tangem.screens package com.tangem.screens
import androidx.compose.ui.geometry.lerp
import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import androidx.compose.ui.test.hasAnyDescendant
import androidx.compose.ui.test.performTouchInput
import com.tangem.common.BaseTestCase import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.displayedTextsInVisualOrder
import com.tangem.common.extensions.hasLazyListItemPosition import com.tangem.common.extensions.hasLazyListItemPosition
import com.tangem.common.utils.LazyListItemNode import com.tangem.common.utils.LazyListItemNode
import com.tangem.core.ui.test.OrganizeTokensScreenTestTags import com.tangem.core.ui.test.OrganizeTokensScreenTestTags
@ -14,12 +18,13 @@ import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onCompose
import io.github.kakaocup.compose.node.element.KNode import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode
import io.github.kakaocup.kakao.common.utilities.getResourceString import io.github.kakaocup.kakao.common.utilities.getResourceString
import androidx.compose.ui.test.hasAnyAncestor as withAnyAncestor
import androidx.compose.ui.test.hasAnyChild as withAnyChild import androidx.compose.ui.test.hasAnyChild as withAnyChild
import androidx.compose.ui.test.hasAnySibling as withAnySibling import androidx.compose.ui.test.hasAnySibling as withAnySibling
import androidx.compose.ui.test.hasTestTag as withTestTag import androidx.compose.ui.test.hasTestTag as withTestTag
import androidx.compose.ui.test.hasText as withText import androidx.compose.ui.test.hasText as withText
class OrganizeTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : class OrganizeTokensPageObject(private val semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<OrganizeTokensPageObject>(semanticsProvider = semanticsProvider) { ComposeScreen<OrganizeTokensPageObject>(semanticsProvider = semanticsProvider) {
// region TopBar // region TopBar
@ -93,6 +98,15 @@ class OrganizeTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvi
} }
} }
// Read TOKEN_TITLE nodes, not the outer TOKEN_LIST_ITEM: the item container here doesn't merge its
// descendants, so the nested title Text never surfaces on the item node's semantics.
fun getDisplayedTokenTitles(): List<String> =
semanticsProvider.onAllNodes(
withTestTag(TokenElementsTestTags.TOKEN_TITLE) and
withAnyAncestor(withTestTag(OrganizeTokensScreenTestTags.TOKENS_LAZY_LIST)),
useUnmergedTree = true,
).displayedTextsInVisualOrder()
fun tokenDraggableButton(tokenTitle: String): KNode { fun tokenDraggableButton(tokenTitle: String): KNode {
return lazyList.child { return lazyList.child {
hasTestTag(OrganizeTokensScreenTestTags.DRAGGABLE_IMAGE) hasTestTag(OrganizeTokensScreenTestTags.DRAGGABLE_IMAGE)
@ -108,6 +122,47 @@ class OrganizeTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvi
) )
} }
} }
// Reorder by synthesizing a drag on the source row's handle: down, hold, step to the destination
// centre, lift — a single `swipe` won't engage the reorder detector. Only valid within one network
// group (isValidDropTarget), so source and destination must share a group.
fun dragToken(source: String, destination: String) {
val sourceHandle = semanticsProvider.onNode(dragHandleMatcher(source), useUnmergedTree = true)
val sourceNode = sourceHandle.fetchSemanticsNode()
val destinationNode = semanticsProvider
.onNode(tokenItemMatcher(destination), useUnmergedTree = true)
.fetchSemanticsNode()
// performTouchInput coordinates are local to the source node; express both endpoints there.
val origin = sourceNode.positionInRoot
val start = sourceNode.boundsInRoot.center - origin
val end = destinationNode.boundsInRoot.center - origin
sourceHandle.performTouchInput {
down(start)
advanceEventTime(DRAG_HOLD_MS)
repeat(times = DRAG_STEPS) { step ->
moveTo(lerp(start = start, stop = end, fraction = (step + 1).toFloat() / DRAG_STEPS))
advanceEventTime(DRAG_STEP_MS)
}
up()
}
}
// The drag handle sits under an untagged wrapper inside TOKEN_NON_FIAT_BLOCK, so anchor on the
// enclosing TOKEN_LIST_ITEM (one handle + one title per item) instead of the direct parent.
private fun dragHandleMatcher(tokenTitle: String): SemanticsMatcher =
withTestTag(OrganizeTokensScreenTestTags.DRAGGABLE_IMAGE) and
withAnyAncestor(tokenItemMatcher(tokenTitle))
private fun tokenItemMatcher(tokenTitle: String): SemanticsMatcher =
withTestTag(OrganizeTokensScreenTestTags.TOKEN_LIST_ITEM) and hasAnyDescendant(withText(tokenTitle))
private companion object {
const val DRAG_HOLD_MS = 200L
const val DRAG_STEPS = 16
const val DRAG_STEP_MS = 16L
}
} }
internal fun BaseTestCase.onOrganizeTokensScreen(function: OrganizeTokensPageObject.() -> Unit) = internal fun BaseTestCase.onOrganizeTokensScreen(function: OrganizeTokensPageObject.() -> Unit) =

View file

@ -0,0 +1,86 @@
package com.tangem.tests.main
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.scenarios.addNewCardWalletWithoutSync
import com.tangem.scenarios.assertOrganizeTokensMatch
import com.tangem.scenarios.getMainScreenTokensOrder
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.switchToPreviousWallet
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.onAddAndManageBottomSheet
import com.tangem.screens.onMainScreen
import com.tangem.screens.onOrganizeTokensScreen
import com.tangem.tap.domain.sdk.mocks.content.Wallet2MockContent
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 OrganizeTokensTest : BaseTestCase() {
@AllureId("71")
@DisplayName("Organize tokens: Correct tokens list displaying for current wallet")
@Test
fun organizeTokensCorrectTokensListDisplaying() {
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Add a second card wallet") {
addNewCardWalletWithoutSync(Wallet2MockContent)
}
step("Switch to first wallet Main screen") {
switchToPreviousWallet()
}
val firstWalletTokens = getMainScreenTokensOrder()
assertOrganizeTokensMatch(firstWalletTokens)
step("Switch to second wallet Main screen") {
onMainScreen { swipeToAdjacentWallet(toPrevious = false) }
}
val secondWalletTokens = getMainScreenTokensOrder()
assertOrganizeTokensMatch(secondWalletTokens)
}
}
@AllureId("2753")
@DisplayName("Organize tokens: Tokens order changing")
@Test
fun organizeTokensOrderChanging() {
setupHooks().run{
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Open 'Organize tokens' bottom-sheet") {
onMainScreen { clickDisplayedAddAndManageButton() }
onAddAndManageBottomSheet { organizeTokensButton.clickWithAssertion() }
}
step("Drag the 3rd token onto the 2nd position and assert the new order") {
onOrganizeTokensScreen {
val sourceIndex = 2
val destinationIndex = 1
val before = getDisplayedTokenTitles()
require(before.size > sourceIndex && before.size > destinationIndex) {
"Expected at least ${maxOf(sourceIndex, destinationIndex) + 1} tokens to reorder, but got ${before.size}: $before"
}
dragToken(source = before[sourceIndex], destination = before[destinationIndex])
val expected = before.toMutableList().apply {
add(destinationIndex, removeAt(sourceIndex))
}
assertEquals(expected, getDisplayedTokenTitles())
}
}
}
}
}

View file

@ -4,12 +4,17 @@ import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
import com.tangem.common.utils.resetWireMockScenarioState import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.addNewCardWalletWithoutSync
import com.tangem.scenarios.getMainScreenTokensOrder
import com.tangem.scenarios.openMainScreen import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.onMainScreen import com.tangem.screens.onMainScreen
import com.tangem.tap.domain.sdk.mocks.content.Wallet2MockContent
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
@HiltAndroidTest @HiltAndroidTest
@ -48,4 +53,54 @@ class TokenListTest : BaseTestCase() {
} }
} }
@AllureId("177")
@DisplayName("Main: token list differs after switching to a second wallet")
@Test
fun tokenListChangedAfterSwitchingWalletTest() {
val userTokensState = "ReducedTokens"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
}
).run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
val firstWalletTokens = getMainScreenTokensOrder()
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
setWireMockScenarioState(USER_TOKENS_API_SCENARIO, userTokensState)
}
step("Add a second card wallet") {
addNewCardWalletWithoutSync(Wallet2MockContent)
}
val secondWalletTokens = getMainScreenTokensOrder()
step("Assert both wallets exposed a non-empty token list") {
assertTrue(
"First wallet token list should not be empty",
firstWalletTokens.isNotEmpty(),
)
assertTrue(
"Second wallet token list should not be empty",
secondWalletTokens.isNotEmpty(),
)
}
step("Assert the two wallets show different token lists") {
assertNotEquals(
"Expected the second wallet's token list to differ from the first " +
"(first=$firstWalletTokens, second=$secondWalletTokens)",
firstWalletTokens,
secondWalletTokens,
)
}
}
}
} }