393 lines
No EOL
14 KiB
Kotlin
393 lines
No EOL
14 KiB
Kotlin
package com.tangem.screens
|
|
|
|
import androidx.compose.ui.semantics.SemanticsProperties
|
|
import androidx.compose.ui.test.ExperimentalTestApi
|
|
import androidx.compose.ui.test.SemanticsMatcher
|
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
|
import androidx.compose.ui.test.assertCountEquals
|
|
import androidx.compose.ui.test.hasAnyAncestor
|
|
import androidx.compose.ui.test.swipeUp
|
|
import com.tangem.common.BaseTestCase
|
|
import com.tangem.common.extensions.getQuantityString
|
|
import com.tangem.common.extensions.hasLazyListItemPosition
|
|
import com.tangem.common.utils.LazyListItemNode
|
|
import com.tangem.core.ui.test.*
|
|
import com.tangem.core.ui.utils.LazyListItemPositionSemantics
|
|
import com.tangem.feature.wallet.impl.R
|
|
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.compose.node.element.lazylist.KLazyListNode
|
|
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
|
|
import com.tangem.core.ui.R as CoreUiR
|
|
|
|
class MainScreenPageObject(private val semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|
ComposeScreen<MainScreenPageObject>(semanticsProvider = semanticsProvider) {
|
|
|
|
private val lazyList = KLazyListNode(
|
|
semanticsProvider = semanticsProvider,
|
|
viewBuilderAction = { hasTestTag(MainScreenTestTags.SCREEN_CONTAINER) },
|
|
itemTypeBuilder = {
|
|
itemType(::LazyListItemNode)
|
|
},
|
|
positionMatcher = { position ->
|
|
SemanticsMatcher.expectValue(
|
|
LazyListItemPositionSemantics,
|
|
position
|
|
)
|
|
}
|
|
)
|
|
|
|
val screenContainer: KNode = child {
|
|
hasTestTag(MainScreenTestTags.SCREEN_CONTAINER)
|
|
}
|
|
|
|
val synchronizeAddressesButton: KNode = lazyList.child {
|
|
hasText(getResourceString(R.string.common_generate_addresses))
|
|
}
|
|
|
|
val buyButton: KNode = child {
|
|
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
|
hasAnyDescendant(withText(getResourceString(R.string.common_buy)))
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val addFundsButton: KNode = child {
|
|
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
|
hasText(getResourceString(R.string.common_add_funds))
|
|
}
|
|
|
|
val sendButton: KNode = child {
|
|
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
|
hasAnyDescendant(withText(getResourceString(R.string.common_send)))
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val receiveButton: KNode = child {
|
|
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
|
hasAnyDescendant(withText(getResourceString(R.string.common_receive)))
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val sellButton: KNode = child {
|
|
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
|
hasAnyDescendant(withText(getResourceString(R.string.common_sell)))
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val swapButton: KNode = child {
|
|
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
|
hasAnyDescendant(withText(getResourceString(R.string.common_swap)))
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val walletNameText: KNode = child {
|
|
hasTestTag(MainScreenTestTags.CARD_TITLE)
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val walletImage: KNode = child {
|
|
hasTestTag(MainScreenTestTags.CARD_IMAGE)
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
/**
|
|
* Collapses the collapsing header via a touch-based swipe so that items near the bottom
|
|
* of the lazy list fall within screen bounds before programmatic childWith scroll.
|
|
* Required because TangemCollapsingTopBar places the body at y=collapsingHeight, which
|
|
* pushes lower list items off-screen when the header is expanded.
|
|
*/
|
|
private fun collapseHeader() {
|
|
screenContainer {
|
|
performTouchInput { swipeUp(startY = visibleSize.height * 0.6f, endY = visibleSize.height * 0.1f) }
|
|
}
|
|
}
|
|
|
|
val restoringProgressText: KNode = child {
|
|
hasTestTag(MainScreenTestTags.SYNC_PROGRESS_TEXT)
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val walletImportedBanner: KNode = child {
|
|
hasTestTag(WalletNotificationTestTags.ASSETS_DISCOVERY_BANNER)
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val walletImportedBannerCheckHereButton: KNode = child {
|
|
hasAnyAncestor(withTestTag(WalletNotificationTestTags.ASSETS_DISCOVERY_BANNER))
|
|
hasText(getResourceString(CoreResR.string.main_manage_tokens))
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
@OptIn(ExperimentalTestApi::class)
|
|
fun marketPriceBlock(): LazyListItemNode {
|
|
collapseHeader()
|
|
return lazyList.childWith<LazyListItemNode> {
|
|
hasTestTag(MarketPriceBlockTestTags.BLOCK)
|
|
useUnmergedTree = true
|
|
}
|
|
}
|
|
|
|
val marketPriceText: KNode = child {
|
|
hasTestTag(MarketPriceBlockTestTags.TEXT)
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val transactionsExplorerIcon: KNode = child {
|
|
hasTestTag(TransactionHistoryBlockTestTags.EXPLORER_ICON)
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val transactionsTitle: KNode = child {
|
|
hasTestTag(TransactionHistoryBlockTestTags.TITLE_TEXT)
|
|
hasText(getResourceString(R.string.common_transactions))
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val transactionsExplorerText: KNode = child {
|
|
hasTestTag(TransactionHistoryBlockTestTags.EXPLORER_TEXT)
|
|
hasText(getResourceString(R.string.common_explorer))
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val emptyTransactionBlock: KNode = child {
|
|
hasTestTag(EmptyTransactionBlockTestTags.BLOCK)
|
|
}
|
|
|
|
val emptyTransactionBlockIcon: KNode = child {
|
|
hasTestTag(EmptyTransactionBlockTestTags.ICON)
|
|
}
|
|
|
|
val emptyTransactionBlockText: KNode = child {
|
|
hasTestTag(EmptyTransactionBlockTestTags.TEXT)
|
|
}
|
|
|
|
val emptyTransactionBlockExploreButton: KNode = child {
|
|
hasTestTag(EmptyTransactionBlockTestTags.EXPLORE_BUTTON)
|
|
}
|
|
|
|
val notificationContainer: KNode = child {
|
|
hasTestTag(NotificationTestTags.CONTAINER)
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val devCardNotificationIcon: KNode = child {
|
|
hasAnySibling(withText(getResourceString(R.string.warning_developer_card_title)))
|
|
hasTestTag(NotificationTestTags.ICON)
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val devCardNotificationTitle: KNode = child {
|
|
hasTestTag(NotificationTestTags.TITLE)
|
|
hasText(getResourceString(R.string.warning_developer_card_title))
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val devCardNotificationMessage: KNode = child {
|
|
hasTestTag(NotificationTestTags.MESSAGE)
|
|
hasText(getResourceString(R.string.warning_developer_card_message))
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val missingAddressNotificationIcon: KNode = child {
|
|
hasAnySibling(withText(getResourceString(R.string.warning_missing_derivation_title)))
|
|
hasTestTag(NotificationTestTags.ICON)
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val missingAddressNotificationTitle: KNode = child {
|
|
hasTestTag(NotificationTestTags.TITLE)
|
|
hasText(getResourceString(R.string.warning_missing_derivation_title))
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
fun missingAddressNotificationMessage(networkCount: Int): KNode = child {
|
|
hasTestTag(NotificationTestTags.MESSAGE)
|
|
hasText(
|
|
getQuantityString(
|
|
R.plurals.warning_missing_derivation_message,
|
|
networkCount,
|
|
networkCount
|
|
)
|
|
)
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val totalBalanceContainer: KNode = child {
|
|
hasTestTag(MainScreenTestTags.WALLET_LIST_ITEM)
|
|
}
|
|
|
|
val totalBalanceMenuRenameWallet: KNode = child {
|
|
hasTestTag(MainScreenTestTags.TOTAL_BALANCE_MENU_ITEM)
|
|
hasText(getResourceString(R.string.common_rename))
|
|
}
|
|
|
|
val totalBalanceMenuDeleteWallet: KNode = child {
|
|
hasTestTag(MainScreenTestTags.TOTAL_BALANCE_MENU_ITEM)
|
|
hasText(getResourceString(R.string.common_delete))
|
|
}
|
|
|
|
val totalBalanceText: KNode = child {
|
|
hasAnyAncestor(withTestTag(MainScreenTestTags.WALLET_BALANCE))
|
|
addSemanticsMatcher(SemanticsMatcher.keyIsDefined(SemanticsProperties.Text))
|
|
}
|
|
|
|
val notificationYesButton: KNode = child {
|
|
hasTestTag(BaseButtonTestTags.TEXT)
|
|
hasText(getResourceString(R.string.common_yes))
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val notificationNoButton: KNode = child {
|
|
hasTestTag(BaseButtonTestTags.TEXT)
|
|
hasText(getResourceString(R.string.common_no))
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val snackbarCopiedAddressMessage: KNode = child {
|
|
hasText(getResourceString(CoreUiR.string.wallet_notification_address_copied))
|
|
}
|
|
|
|
val addAndManageButtonNode: KNode = child {
|
|
hasTestTag(MainScreenTestTags.ADD_AND_MANAGE_BUTTON)
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
/**
|
|
* Main account header on the main screen. Click to expand/collapse its tokens list.
|
|
*/
|
|
fun mainAccount(): LazyListItemNode = accountWithName(getResourceString(CoreUiR.string.account_main_account_title))
|
|
|
|
/**
|
|
* Account header on the main screen, located by its visible name. Click to expand/collapse its tokens list.
|
|
* The account's title text lives on a descendant of the test-tagged node, so we match by descendant.
|
|
*/
|
|
@OptIn(ExperimentalTestApi::class)
|
|
fun accountWithName(name: String): LazyListItemNode {
|
|
collapseHeader()
|
|
return lazyList.childWith<LazyListItemNode> {
|
|
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
|
hasAnyDescendant(withText(name))
|
|
useUnmergedTree = true
|
|
}
|
|
}
|
|
|
|
@OptIn(ExperimentalTestApi::class)
|
|
fun tokenRowWithTitle(tokenTitle: String): LazyListItemNode {
|
|
return lazyList.childWith<LazyListItemNode> {
|
|
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
|
hasText(tokenTitle)
|
|
useUnmergedTree = true
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find token list item with title and address
|
|
*/
|
|
@OptIn(ExperimentalTestApi::class)
|
|
fun tokenWithTitleAndAddress(tokenTitle: String): KNode {
|
|
collapseHeader()
|
|
return lazyList.childWith<LazyListItemNode> {
|
|
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
|
hasText(tokenTitle)
|
|
useUnmergedTree = true
|
|
}.child<KNode> {
|
|
hasTestTag(TokenElementsTestTags.TOKEN_FIAT_AMOUNT)
|
|
useUnmergedTree = true
|
|
}
|
|
}
|
|
|
|
@OptIn(ExperimentalTestApi::class)
|
|
fun tokenWithCustomDerivationIcon(tokenTitle: String): KNode {
|
|
collapseHeader()
|
|
return lazyList.childWith<LazyListItemNode> {
|
|
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
|
hasText(tokenTitle)
|
|
useUnmergedTree = true
|
|
}.child<KNode> {
|
|
hasTestTag(TokenElementsTestTags.TOKEN_CUSTOM_DERIVATION_ICON)
|
|
useUnmergedTree = true
|
|
}
|
|
}
|
|
|
|
@OptIn(ExperimentalTestApi::class)
|
|
fun addAndManageButton(): KNode {
|
|
collapseHeader()
|
|
return lazyList.childWith<LazyListItemNode> {
|
|
hasTestTag(MainScreenTestTags.ADD_AND_MANAGE_BUTTON)
|
|
}.child<KNode> {
|
|
hasText(getResourceString(CoreResR.string.main_add_and_manage_tokens))
|
|
useUnmergedTree = true
|
|
}
|
|
}
|
|
|
|
val addAndManageButtonWithoutLazySearch: KNode = child {
|
|
hasTestTag(MainScreenTestTags.ADD_AND_MANAGE_BUTTON)
|
|
hasText(getResourceString(CoreResR.string.main_add_and_manage_tokens))
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
val searchThroughMarketPlaceholder: KNode = child {
|
|
hasText(getResourceString(R.string.markets_search_title_placeholder))
|
|
useUnmergedTree = true
|
|
}
|
|
|
|
fun tokenNetworkGroupTitle(tokenNetwork: String): KNode {
|
|
collapseHeader()
|
|
return lazyList.child {
|
|
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
|
hasAnyChild(withText(tokenNetwork))
|
|
useUnmergedTree = true
|
|
}
|
|
}
|
|
|
|
@OptIn(ExperimentalTestApi::class)
|
|
fun tokenWithTitleAndPosition(tokenTitle: String, index: Int): KNode {
|
|
collapseHeader()
|
|
return lazyList.childWith<LazyListItemNode> {
|
|
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
|
hasText(tokenTitle)
|
|
hasLazyListItemPosition(index)
|
|
useUnmergedTree = true
|
|
}.child<KNode> {
|
|
hasTestTag(TokenElementsTestTags.TOKEN_TITLE)
|
|
useUnmergedTree = true
|
|
}
|
|
}
|
|
|
|
fun KNode.assertIsUnreachable() {
|
|
this {
|
|
hasAnyAncestor(withText(getResourceString(R.string.common_unreachable)))
|
|
assertIsDisplayed()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This assertion is required to properly verify the token's absence in the semantic tree.
|
|
* 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
|
|
}
|
|
}
|
|
}
|
|
|
|
fun assertTokensCount(expectedCount: Int) {
|
|
semanticsProvider
|
|
.onAllNodes(withTestTag(TokenElementsTestTags.TOKEN_PRICE))
|
|
.assertCountEquals(expectedCount)
|
|
}
|
|
}
|
|
|
|
internal fun BaseTestCase.onMainScreen(function: MainScreenPageObject.() -> Unit) =
|
|
onComposeScreen(composeTestRule, function) |