Updated on 2026-08-14
This commit is contained in:
parent
96a3074bdb
commit
d616571df4
29 changed files with 421 additions and 19 deletions
|
|
@ -2,6 +2,8 @@ package com.tangem.common
|
|||
|
||||
import android.Manifest
|
||||
import androidx.compose.ui.test.junit4.createEmptyComposeRule
|
||||
import androidx.compose.ui.test.onRoot
|
||||
import androidx.compose.ui.test.printToLog
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
|
|
@ -79,4 +81,22 @@ abstract class BaseTestCase : TestCase(
|
|||
additionalAfterSection()
|
||||
Intents.release()
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the Compose semantics tree to logcat for debugging UI tests.
|
||||
*
|
||||
* @param useUnmergedTree When true, shows unmerged tree with all individual nodes.
|
||||
* Use for accessing inner elements of compound components.
|
||||
* Default: false (merged tree - accessibility view).
|
||||
* @param tag Log tag for filtering in logcat. Default: "SEMANTIC_TREE".
|
||||
* @param maxDepth Maximum nesting level to print. Use to avoid log overflow.
|
||||
* Default: Int.MAX_VALUE (unlimited depth).
|
||||
*/
|
||||
fun printSemanticTree(
|
||||
useUnmergedTree: Boolean = false,
|
||||
tag: String = "SEMANTIC_TREE",
|
||||
maxDepth: Int = Int.MAX_VALUE)
|
||||
{
|
||||
composeTestRule.onRoot(useUnmergedTree = useUnmergedTree).printToLog(tag, maxDepth)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.common.utils
|
||||
|
||||
import androidx.compose.ui.semantics.SemanticsNode
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import io.github.kakaocup.compose.node.element.lazylist.KLazyListItemNode
|
||||
|
||||
/**
|
||||
* Represents an item node in a LazyList for Compose UI testing.
|
||||
* Allows test actions (clicks, scrolls) and assertions on individual list items.
|
||||
*/
|
||||
class LazyListItemNode(
|
||||
semanticsNode: SemanticsNode,
|
||||
semanticsProvider: SemanticsNodeInteractionsProvider,
|
||||
) : KLazyListItemNode<LazyListItemNode>(semanticsNode, semanticsProvider)
|
||||
|
|
@ -32,9 +32,9 @@ class OpenMainScreenScenario(
|
|||
assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<TopBarPageObject>(testRule) {
|
||||
ComposeScreen.onComposeScreen<MarketsTooltipPageObject>(testRule) {
|
||||
step("Close Markets tooltip"){
|
||||
performClick()
|
||||
contentContainer.performClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.DialogTestTags
|
||||
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
|
||||
|
||||
class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<DialogPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val dialogContainer: KNode = child {
|
||||
hasTestTag(DialogTestTags.DIALOG_CONTAINER)
|
||||
}
|
||||
|
||||
val cancelButton: KNode = child {
|
||||
hasTestTag(DialogTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_cancel))
|
||||
}
|
||||
|
||||
val hideButton: KNode = child {
|
||||
hasTestTag(DialogTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.token_details_hide_alert_hide))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onDialog(function: DialogPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -1,16 +1,85 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.SemanticsMatcher
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.utils.LazyListItemNode
|
||||
import com.tangem.core.ui.test.MainScreenTestTags
|
||||
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
|
||||
|
||||
class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<MainScreenPageObject>(
|
||||
ComposeScreen<MainScreenPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
private val lazyList = KLazyListNode(
|
||||
semanticsProvider = semanticsProvider,
|
||||
viewBuilderAction = { hasTestTag(MainScreenTestTags.SCREEN_CONTAINER) }
|
||||
viewBuilderAction = { hasTestTag(MainScreenTestTags.SCREEN_CONTAINER) },
|
||||
itemTypeBuilder = {
|
||||
itemType(::LazyListItemNode)
|
||||
},
|
||||
positionMatcher = { position ->
|
||||
SemanticsMatcher.expectValue(
|
||||
LazyListItemPositionSemantics,
|
||||
position
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
val synchronizeAddressesButton: KNode = child {
|
||||
hasText(getResourceString(R.string.common_generate_addresses))
|
||||
}
|
||||
|
||||
/**
|
||||
* Find token list item with title and address
|
||||
*/
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun tokenWithTitleAndAddress(tokenTitle: String): KNode {
|
||||
return lazyList.childWith<LazyListItemNode> {
|
||||
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
||||
hasText(tokenTitle)
|
||||
}.child<KNode> {
|
||||
hasTestTag(MainScreenTestTags.TOKEN_FIAT_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find node with wallet balance using lazyList. This construction doesn't affect next step with lazyList.
|
||||
*/
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun walletBalance(): KNode {
|
||||
return lazyList.childWith<LazyListItemNode> {
|
||||
hasAnyDescendant(withTestTag(MainScreenTestTags.WALLET_LIST_ITEM))
|
||||
}.child<KNode> {
|
||||
hasTestTag(MainScreenTestTags.WALLET_BALANCE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onMainScreen(function: MainScreenPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.MarketTooltipTestTags
|
||||
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 MarketsTooltipPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<MarketsTooltipPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val contentContainer: KNode = child {
|
||||
hasTestTag(MarketTooltipTestTags.CONTAINER)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onMarketsTooltipScreen(function: MarketsTooltipPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.PopUpMenuTestTags
|
||||
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
|
||||
|
||||
class PopUpMenuPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<PopUpMenuPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val popUpContainer: KNode = child {
|
||||
hasTestTag(PopUpMenuTestTags.CONTAINER)
|
||||
}
|
||||
|
||||
val hideTokenButton: KNode = child {
|
||||
hasTestTag(PopUpMenuTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.token_details_hide_token))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onPopUpMenu(function: PopUpMenuPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
|
||||
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 TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<TokenDetailsPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val screenContainer: KNode = child {
|
||||
hasTestTag(TokenDetailsScreenTestTags.SCREEN_CONTAINER)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onTokenDetailsScreen(function: TokenDetailsPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.TokenDetailsTopBarTestTags
|
||||
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 TokenDetailsTopBarPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<TokenDetailsTopBarPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val moreButton: KNode = child {
|
||||
hasTestTag(TokenDetailsTopBarTestTags.MORE_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val backButton: KNode = child {
|
||||
hasTestTag(TokenDetailsTopBarTestTags.BACK_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onTokenDetailsTopBar(function: TokenDetailsTopBarPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
58
app/src/androidTest/kotlin/com/tangem/tests/HideTokenTest.kt
Normal file
58
app/src/androidTest/kotlin/com/tangem/tests/HideTokenTest.kt
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.screens.*
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class HideTokenTest : BaseTestCase() {
|
||||
|
||||
@AllureId("880")
|
||||
@DisplayName("Hide token in 'Token details' screen by 'Hide' button in topBar menu")
|
||||
@Test
|
||||
fun hideWalletTokenByHideButtonTest() {
|
||||
val tokenTitle = "Polygon"
|
||||
val balance = "<$0.01"
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button" ) {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Token details screen' open") {
|
||||
onTokenDetailsScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
step("Click 'More button'") {
|
||||
onTokenDetailsTopBar { moreButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click 'Hide token' button") {
|
||||
onPopUpMenu {
|
||||
popUpContainer.assertIsDisplayed()
|
||||
hideTokenButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Click 'Hide' button in dialog") {
|
||||
onDialog {
|
||||
dialogContainer.assertIsDisplayed()
|
||||
hideButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Assert token: '$tokenTitle' is not displayed") {
|
||||
onMainScreen { assertTokenDoesNotExist(tokenTitle) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue