Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-09 16:31:02 +03:00
parent 3bde764060
commit 4e1b562e82
20 changed files with 690 additions and 19 deletions

View file

@ -1,8 +1,8 @@
package com.tangem.common
import android.Manifest
import androidx.compose.ui.test.isRoot
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
@ -121,6 +121,8 @@ abstract class BaseTestCase : TestCase(
/**
* Prints the Compose semantics tree to logcat for debugging UI tests.
*
* @param rootIndex Use rootIndex > 0, if you need to print semantics tree for bottom sheet.
* Default: 0.
* @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).
@ -129,11 +131,13 @@ abstract class BaseTestCase : TestCase(
* Default: Int.MAX_VALUE (unlimited depth).
*/
fun printSemanticTree(
rootIndex: Int = 0,
useUnmergedTree: Boolean = false,
tag: String = "SEMANTIC_TREE",
maxDepth: Int = Int.MAX_VALUE)
{
composeTestRule.onRoot(useUnmergedTree = useUnmergedTree).printToLog(tag, maxDepth)
maxDepth: Int = Int.MAX_VALUE
) {
composeTestRule.onAllNodes(isRoot(), useUnmergedTree = useUnmergedTree)[rootIndex]
.printToLog(tag, maxDepth)
}
fun waitForIdle() = composeTestRule.waitForIdle()

View file

@ -4,6 +4,7 @@ object TestConstants {
const val TOTAL_BALANCE = "$3,299.18"
const val RECIPIENT_ADDRESS = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0"
const val BITCOIN_ADDRESS = "bc1qtg9aa6jcpqtvun0pe0uct7sxm8nq2nsxfmfxm3"
const val CARDANO_ADDRESS = "addr1q8f9499e58k4hhfd9vhawprxt3xd94x7rmlyp33ee4xkatakcl2zgkrg0p6ceqkndtkw4cumfe9enhdph8yhuswn785srksm9p"
const val WAIT_UNTIL_TIMEOUT = 20_000L

View file

@ -3,6 +3,7 @@ package com.tangem.common.utils
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import androidx.test.core.app.ApplicationProvider
fun getClipboardText(context: Context): String? {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
@ -17,4 +18,21 @@ fun setClipboardText(context: Context, text: String?) {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("label", text)
clipboard.setPrimaryClip(clip)
}
fun clearClipboard(
context: Context = ApplicationProvider.getApplicationContext()
) {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.clearPrimaryClip()
}
fun assertClipboardTextEquals(
expected: String,
context: Context = ApplicationProvider.getApplicationContext()
) {
val actual = getClipboardText(context)
assert(actual == expected) {
"Clipboard text mismatch.\nExpected: '$expected'\nActual: '$actual'"
}
}

View file

@ -0,0 +1,55 @@
package com.tangem.screens
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
import com.kaspersky.kaspresso.screens.KScreen
object ChromeBrowserPageObject : KScreen<ChromeBrowserPageObject>() {
override val layoutId: Int? = null
override val viewClass: Class<*>? = null
private val device: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private const val TIMEOUT = 10000L
fun assertChromeIsOpened() {
val chromePackage = "com.android.chrome"
device.wait(Until.hasObject(By.pkg(chromePackage).depth(0)), TIMEOUT)
assert(device.hasObject(By.pkg(chromePackage))) {
"Chrome browser is not opened"
}
}
fun assertUrlContains(expectedUrl: String) {
val urlBar = device.findObject(
By.res("com.android.chrome:id/url_bar")
)
urlBar?.let {
assert(it.text.contains(expectedUrl)) {
"URL doesn't contain expected: $expectedUrl, actual: ${it.text}"
}
}
}
private fun findElementByText(text: String): UiObject2? {
device.wait(Until.hasObject(By.text(text)), TIMEOUT)
return device.findObject(By.text(text))
}
fun assertElementWithTextExists(text: String) {
val element = findElementByText(text)
assert(element != null) { "Element with text '$text' not found" }
}
fun isElementWithTextExists(text: String): Boolean {
return device.wait(Until.hasObject(By.text(text)), TIMEOUT)
}
fun clickOnElementWithText(text: String) {
findElementByText(text)?.click()
?: throw AssertionError("Cannot click - element with text '$text' not found")
}
}

View file

@ -17,6 +17,7 @@ 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.ui.R as CoreUiR
class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<MainScreenPageObject>(semanticsProvider = semanticsProvider) {
@ -200,6 +201,10 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
useUnmergedTree = true
}
val snackbarCopiedAddressMessage: KNode = child {
hasText(getResourceString(CoreUiR.string.wallet_notification_address_copied))
}
/**
* Find token list item with title and address
*/

View file

@ -3,20 +3,19 @@ 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.BaseBottomSheetTestTags
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 BaseBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<BaseBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
class ReceiveAssetsBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<ReceiveAssetsBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
val hideButton: KNode = child {
hasText(getResourceString(R.string.token_details_hide_token))
hasTestTag(BaseBottomSheetTestTags.ACTION_TITLE)
val showQrCodeButton: KNode = child {
hasText(getResourceString(R.string.token_receive_show_qr_code_title))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onBottomSheet(function: BaseBottomSheetPageObject.() -> Unit) =
internal fun BaseTestCase.onReceiveAssetsBottomSheet(function: ReceiveAssetsBottomSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -71,6 +71,10 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
hasText(getResourceString(R.string.common_swap))
}
fun tokenSymbol(symbol: String): KNode = child {
hasTestTag(SwapTokenScreenTestTags.TOKEN_SYMBOL)
}
}
internal fun BaseTestCase.onSwapTokenScreen(function: SwapTokenPageObject.() -> Unit) =

View file

@ -0,0 +1,75 @@
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.BaseBottomSheetTestTags
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
import androidx.compose.ui.test.hasTestTag as withTestTag
import androidx.compose.ui.test.hasText as withText
class TokenActionsBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<TokenActionsBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
val analyticsButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_analytics)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val copyAddressButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_copy_address)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val receiveButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_receive)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val sendButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_send)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val swapButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_swap)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val buyButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_buy)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val sellButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_sell)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val hideTokenButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.token_details_hide_token)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onTokenActionsBottomSheet(function: TokenActionsBottomSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -0,0 +1,56 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.BaseBottomSheetTestTags
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.TokenReceiveQrCodeBottomSheetTestTags
import com.tangem.wallet.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.kakao.common.utilities.getResourceString
class TokenReceiveQrCodeBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<TokenReceiveQrCodeBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
val closeButton: KNode = child {
hasTestTag(BaseBottomSheetTestTags.CLOSE_BUTTON)
useUnmergedTree = true
}
val title: KNode = child {
hasTestTag(TokenReceiveQrCodeBottomSheetTestTags.TITLE)
useUnmergedTree = true
}
val qrCode: KNode = child {
hasTestTag(TokenReceiveQrCodeBottomSheetTestTags.QR_CODE)
useUnmergedTree = true
}
val addressTitle: KNode = child {
hasText(getResourceString(R.string.wc_common_address))
useUnmergedTree = true
}
val address: KNode = child {
hasTestTag(TokenReceiveQrCodeBottomSheetTestTags.ADDRESS)
useUnmergedTree = true
}
val copyButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_copy))
useUnmergedTree = true
}
val shareButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_share))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onTokenReceiveQrCodeBottomSheet(function: TokenReceiveQrCodeBottomSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -0,0 +1,28 @@
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.BaseButtonTestTags
import com.tangem.core.ui.test.TokenReceiveWarningBottomSheetTestTags
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 TokenReceiveWarningBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<TokenReceiveWarningBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
val bottomSheet: KNode = child {
hasTestTag(TokenReceiveWarningBottomSheetTestTags.BOTTOM_SHEET)
}
val gotItButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_got_it))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onTokenReceiveWarningBottomSheet(function: TokenReceiveWarningBottomSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -0,0 +1,405 @@
package com.tangem.tests.actionButtons
import androidx.compose.ui.test.longClick
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.BITCOIN_ADDRESS
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.assertClipboardTextEquals
import com.tangem.common.utils.clearClipboard
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.*
import com.tangem.screens.onTokenActionsBottomSheet
import com.tangem.screens.onBuyTokenDetailsScreen
import com.tangem.screens.onDialog
import com.tangem.screens.onMainScreen
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 MainScreenActionButtonsTest : BaseTestCase() {
@AllureId("79")
@DisplayName("Action buttons (long tap): validate UI")
@Test
fun actionButtonsValidateLongTapUiTest() {
val tokenTitle = "Ethereum"
val balance = TOTAL_BALANCE
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Analytics' button is displayed") {
onTokenActionsBottomSheet { analyticsButton.assertIsDisplayed() }
}
step("Assert 'Copy address' button is displayed") {
onTokenActionsBottomSheet { copyAddressButton.assertIsDisplayed() }
}
step("Assert 'Receive' button is displayed") {
onTokenActionsBottomSheet { receiveButton.assertIsDisplayed() }
}
step("Assert 'Send' button is displayed") {
onTokenActionsBottomSheet { sendButton.assertIsDisplayed() }
}
step("Assert 'Swap' button is displayed") {
onTokenActionsBottomSheet { swapButton.assertIsDisplayed() }
}
step("Assert 'Buy' button is displayed") {
onTokenActionsBottomSheet { buyButton.assertIsDisplayed() }
}
step("Assert 'Sell' button is displayed") {
onTokenActionsBottomSheet { sellButton.assertIsDisplayed() }
}
step("Assert 'Hide token' button is displayed") {
onTokenActionsBottomSheet { hideTokenButton.assertIsDisplayed() }
}
}
}
@AllureId("84")
@DisplayName("Action buttons (long tap): check 'Copy address' button")
@Test
fun clickOnCopyAddressButtonTest() {
val tokenTitle = "Bitcoin"
val balance = TOTAL_BALANCE
val bitcoinAddress = BITCOIN_ADDRESS
setupHooks(
additionalBeforeSection = {
clearClipboard()
},
additionalAfterSection = {
clearClipboard()
}
).run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Copy address' button is displayed") {
onTokenActionsBottomSheet { copyAddressButton.assertIsDisplayed() }
}
step("Click on 'Copy address' button") {
onTokenActionsBottomSheet { copyAddressButton.performClick() }
}
step("Assert snack bar message is displayed") {
onMainScreen { snackbarCopiedAddressMessage.assertIsDisplayed() }
}
step("Check clipboard has '$tokenTitle' address '$bitcoinAddress'") {
waitForIdle()
assertClipboardTextEquals(expected = bitcoinAddress)
}
}
}
@AllureId("82")
@DisplayName("Action buttons (long tap): check 'Buy' button")
@Test
fun clickOnBuyButtonTest() {
val tokenTitle = "Bitcoin"
val tokenSymbol = "BTC"
val balance = TOTAL_BALANCE
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Buy' button is displayed") {
onTokenActionsBottomSheet { buyButton.assertIsDisplayed() }
}
step("Click on 'Buy' button") {
onTokenActionsBottomSheet { buyButton.performClick() }
}
step("Click on 'Confirm' button in 'Dialog'") {
waitForIdle()
onDialog { confirmButton.clickWithAssertion() }
}
step("Assert top app bar title contains '$tokenTitle'") {
onBuyTokenDetailsScreen { topBarTitle.assertTextContains("Buy $tokenTitle") }
}
step("Assert fiat currency text field is displayed") {
onBuyTokenDetailsScreen { fiatAmountTextField.assertIsDisplayed() }
}
step("Assert fiat currency icon is displayed") {
onBuyTokenDetailsScreen { fiatCurrencyIcon.assertIsDisplayed() }
}
step("Assert token amount field is displayed") {
onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenSymbol, substring = true) }
}
step("Assert 'Continue' button") {
onBuyTokenDetailsScreen { continueButton.assertIsDisplayed() }
}
}
}
@AllureId("87")
@DisplayName("Action buttons (long tap): check 'Swap' button")
@Test
fun clickOnSwapButtonTest() {
val tokenTitle = "Ethereum"
val tokenSymbol = "ETH"
val balance = TOTAL_BALANCE
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Swap' button is displayed") {
onTokenActionsBottomSheet { swapButton.assertIsDisplayed() }
}
step("Click on 'Swap' button") {
onTokenActionsBottomSheet { swapButton.performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
}
step("Assert 'Swap' screen title is displayed") {
onSwapTokenScreen { title.assertIsDisplayed() }
}
step("Assert token symbol: '$tokenSymbol' is displayed") {
onSwapTokenScreen { tokenSymbol(tokenSymbol).assertIsDisplayed() }
}
}
}
@AllureId("83")
@DisplayName("Action buttons (long tap): check 'Send' button")
@Test
fun clickOnSendButtonTest() {
val tokenTitle = "Ethereum"
val tokenSymbol = "ETH"
val balance = TOTAL_BALANCE
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Send' button is displayed") {
onTokenActionsBottomSheet { sendButton.assertIsDisplayed() }
}
step("Click on 'Send' button") {
onTokenActionsBottomSheet { sendButton.performClick() }
}
step("Assert amount input text field contains token symbol: '$tokenSymbol'") {
onSendScreen {
amountInputTextField.assertTextContains(value = tokenSymbol, substring = true)
}
}
}
}
@AllureId("86")
@DisplayName("Action buttons (long tap): check 'Receive' button")
@Test
fun clickOnReceiveButtonTest() {
val tokenTitle = "Bitcoin"
val balance = TOTAL_BALANCE
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Receive' button is displayed") {
onTokenActionsBottomSheet { receiveButton.assertIsDisplayed() }
}
step("Click on 'Receive' button") {
onTokenActionsBottomSheet { receiveButton.performClick() }
}
step("Assert 'Token receive warning' bottom sheet is displayed") {
onTokenReceiveWarningBottomSheet { bottomSheet.assertIsDisplayed() }
}
step("Click on 'Got it' button") {
onTokenReceiveWarningBottomSheet { gotItButton.performClick() }
}
step("Click on 'Show QR code' button") {
onReceiveAssetsBottomSheet { showQrCodeButton.clickWithAssertion() }
}
step("Assert bottom sheet with QR code title is displayed") {
onTokenReceiveQrCodeBottomSheet { title.assertIsDisplayed() }
}
step("Assert QR code is displayed") {
onTokenReceiveQrCodeBottomSheet { qrCode.assertIsDisplayed() }
}
step("Assert address title is displayed") {
onTokenReceiveQrCodeBottomSheet { addressTitle.assertIsDisplayed() }
}
step("Assert address is displayed") {
onTokenReceiveQrCodeBottomSheet { address.assertIsDisplayed() }
}
step("Assert 'Copy' button is displayed") {
onTokenReceiveQrCodeBottomSheet { copyButton.assertIsDisplayed() }
}
step("Assert 'Share' button is displayed") {
onTokenReceiveQrCodeBottomSheet { shareButton.assertIsDisplayed() }
}
}
}
@AllureId("85")
@DisplayName("Action buttons (long tap): check 'Sell' button")
@Test
fun clickOnSellButtonTest() {
val tokenTitle = "Ethereum"
val tokenSymbol = "ETH"
val balance = TOTAL_BALANCE
val url = "sell.moonpay.com"
val useWithoutAccount = "Use without an account"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Receive' button is displayed") {
onTokenActionsBottomSheet { sellButton.assertIsDisplayed() }
}
step("Click on 'Receive' button") {
onTokenActionsBottomSheet { sellButton.performClick() }
}
step("Assert Chrome Browser is opened") {
ChromeBrowserPageObject { assertChromeIsOpened() }
}
if (ChromeBrowserPageObject.isElementWithTextExists(useWithoutAccount)) {
step("Click on '$useWithoutAccount' button on Chrome browser") {
ChromeBrowserPageObject { clickOnElementWithText(useWithoutAccount) }
}
}
step("Assert url contains: '$url'") {
ChromeBrowserPageObject { assertUrlContains(url) }
}
step("Assert token symbol '$tokenSymbol' is displayed") {
ChromeBrowserPageObject { assertElementWithTextExists(tokenSymbol) }
}
}
}
@AllureId("77")
@DisplayName("Action buttons (long tap): assert 'Sell' button is not displayed if token doesn't support it")
@Test
fun assertSellButtonIsNotDisplayedTest() {
val tokenTitle = "Bitcoin"
val balance = TOTAL_BALANCE
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Sell' button is not displayed") {
onTokenActionsBottomSheet { sellButton.assertIsNotDisplayed() }
}
}
}
}

View file

@ -162,7 +162,7 @@ class TotalBalanceUpdateTest : BaseTestCase() {
}
}
step("Click 'Hide token' button") {
onBottomSheet { hideButton.clickWithAssertion() }
onTokenActionsBottomSheet { hideTokenButton.clickWithAssertion() }
}
step("Click 'Hide' button in dialog") {
onDialog {

View file

@ -40,7 +40,7 @@ fun SimpleSettingsRow(
onItemsClick()
}
},
).testTag(BaseBottomSheetTestTags.ACTION_TITLE),
).testTag(BaseBottomSheetTestTags.ACTION_BUTTON),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
) {
@ -48,7 +48,8 @@ fun SimpleSettingsRow(
painter = painterResource(id = icon),
contentDescription = null,
modifier = Modifier
.padding(horizontal = if (redesign) TangemTheme.dimens.spacing12 else TangemTheme.dimens.spacing20),
.padding(horizontal = if (redesign) TangemTheme.dimens.spacing12 else TangemTheme.dimens.spacing20)
.testTag(BaseBottomSheetTestTags.ACTION_ICON),
tint = rowColors.iconColor(enabled = enabled).value,
)
Column(

View file

@ -1,7 +1,8 @@
package com.tangem.core.ui.test
object BaseBottomSheetTestTags {
const val ACTION_TITLE = "BASE_BOTTOM_SHEET_ACTION_TITLE"
const val ACTION_BUTTON = "BASE_BOTTOM_SHEET_ACTION_BUTTON"
const val ACTION_ICON = "BASE_BOTTOM_SHEET_ACTION_ICON"
const val TITLE = "BASE_BOTTOM_SHEET_TITLE"
const val SUBTITLE = "BASE_BOTTOM_SHEET_SUBTITLE"
const val CLOSE_BUTTON = "BASE_BOTTOM_SHEET_CLOSE_BUTTON"

View file

@ -9,6 +9,6 @@ object SwapTokenScreenTestTags {
const val PROVIDERS_BLOCK = "SWAP_TOKEN_SCREEN_PROVIDERS_BLOCK"
const val SWAP_BUTTON = "SWAP_TOKEN_SCREEN_SWAP_BUTTON"
const val TOKEN = "SWAP_TOKEN_SCREEN_TOKEN"
const val TOKEN_NAME = "SWAP_TOKEN_SCREEN_TOKEN_NAME"
const val TOKEN_SYMBOL = "SWAP_TOKEN_SCREEN_TOKEN_SYMBOL"
const val TOKEN_ICON = "SWAP_TOKEN_SCREEN_TOKEN_ICON"
}

View file

@ -0,0 +1,7 @@
package com.tangem.core.ui.test
object TokenReceiveQrCodeBottomSheetTestTags {
const val TITLE = "TOKEN_RECEIVE_QR_CODE_BOTTOM_SHEET_TITLE"
const val QR_CODE = "TOKEN_RECEIVE_QR_CODE_BOTTOM_SHEET_QR_CODE"
const val ADDRESS = "TOKEN_RECEIVE_QR_CODE_BOTTOM_SHEET_ADDRESS"
}

View file

@ -0,0 +1,5 @@
package com.tangem.core.ui.test
object TokenReceiveWarningBottomSheetTestTags {
const val BOTTOM_SHEET = "TOKEN_RECEIVE_WARNING_BOTTOM_SHEET"
}

View file

@ -390,7 +390,7 @@ fun Token(
textAlign = TextAlign.Center,
modifier = Modifier
.defaultMinSize(minWidth = TangemTheme.dimens.size80)
.testTag(SwapTokenScreenTestTags.TOKEN_NAME),
.testTag(SwapTokenScreenTestTags.TOKEN_SYMBOL),
)
}
}

View file

@ -21,6 +21,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -32,6 +33,7 @@ import com.tangem.core.ui.components.*
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TokenReceiveQrCodeBottomSheetTestTags
import com.tangem.features.tokenreceive.impl.R
import com.tangem.features.tokenreceive.ui.state.QrCodeUM
import kotlinx.coroutines.launch
@ -88,6 +90,7 @@ private fun QrCodePage(addressFullName: TextReference, addressValue: String, net
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
style = TangemTheme.typography.h3,
modifier = Modifier.testTag(TokenReceiveQrCodeBottomSheetTestTags.TITLE),
)
SpacerH(20.dp)
@ -99,7 +102,8 @@ private fun QrCodePage(addressFullName: TextReference, addressValue: String, net
color = TangemTheme.colors.icon.constant,
shape = RoundedCornerShape(8.dp),
)
.padding(8.dp),
.padding(8.dp)
.testTag(TokenReceiveQrCodeBottomSheetTestTags.QR_CODE),
) {
Image(
@ -126,6 +130,7 @@ private fun QrCodePage(addressFullName: TextReference, addressValue: String, net
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
style = TangemTheme.typography.subtitle1,
modifier = Modifier.testTag(TokenReceiveQrCodeBottomSheetTestTags.ADDRESS),
)
}
}

View file

@ -9,6 +9,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -25,6 +26,7 @@ import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TokenReceiveWarningBottomSheetTestTags
import com.tangem.features.tokenreceive.impl.R
import com.tangem.features.tokenreceive.ui.state.WarningUM
@ -40,7 +42,7 @@ internal fun TokenReceiveWarningContent(warningUM: WarningUM) {
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
).testTag(TokenReceiveWarningBottomSheetTestTags.BOTTOM_SHEET),
horizontalAlignment = Alignment.CenterHorizontally,
) {
CurrencyIcon(