Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-06 18:10:28 +03:00
parent 1feec554e6
commit eff4bd0336
24 changed files with 656 additions and 14 deletions

View file

@ -80,6 +80,17 @@ fun List<SemanticsNode>.assertTrustScoresValid() {
}
}
/**
* Extracts the text content from a [KNode]'s semantics.
* Useful when the displayed value is dynamic and you need to capture it for later assertions.
*/
fun KNode.extractText(): String {
val node = delegate.interaction.semanticsNodeInteraction.fetchSemanticsNode(
"Failed to extract text from KNode",
)
return extractText(node) ?: error("Node does not contain SemanticsProperties.Text")
}
/**
* Extracts the text value from a semantic node's config.
*/

View file

@ -167,4 +167,18 @@ fun BaseTestCase.checkChangesInInputTextField(title: String, newValue: String, a
step("Assert '$title' value: '$newValue + $addition'") {
onSendSelectNetworkFeeBottomSheet { inputTextFieldValue(title).assertTextContains(newValue + addition) }
}
}
fun BaseTestCase.openSendConfirmScreenViaNextButton() {
step("Click on 'Next' button") {
onSendAddressScreen {
addressesShimmer.assertIsNotDisplayed()
nextButton.assertIsDisplayed()
nextButton.assertIsEnabled()
nextButton.performClick()
}
}
step("Assert 'Send' button on 'Send confirm' screen is displayed") {
onSendConfirmScreen { sendButton.assertIsDisplayed() }
}
}

View file

@ -0,0 +1,32 @@
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 com.tangem.core.ui.test.ChooseNetworkBottomSheetTestTags
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.hasText as withText
class ChooseNetworkBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<ChooseNetworkBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
val title: KNode = child {
hasTestTag(BaseBottomSheetTestTags.TITLE)
hasText(getResourceString(R.string.common_choose_network))
useUnmergedTree = true
}
fun networkItem(title: String, subtitle: String): KNode = child {
hasTestTag(ChooseNetworkBottomSheetTestTags.NETWORK_ITEM)
hasAnyDescendant(withText(title))
hasAnyDescendant(withText(subtitle))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onChooseNetworkBottomSheet(function: ChooseNetworkBottomSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -59,6 +59,11 @@ class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
hasTestTag(BaseButtonTestTags.BUTTON)
hasText(getResourceString(R.string.common_ok))
}
val changeButton: KNode = child {
hasTestTag(BaseButtonTestTags.BUTTON)
hasText(getResourceString(R.string.common_change))
}
}
internal fun BaseTestCase.onDialog(function: DialogPageObject.() -> Unit) =

View file

@ -67,7 +67,7 @@ class SelectProviderPageObject(semanticsProvider: SemanticsNodeInteractionsProvi
val instantProcessingSpeedText: KNode = child {
hasText(getResourceString(com.tangem.core.ui.R.string.onramp_instant_status))
hasText(getResourceString(R.string.onramp_instant_status))
useUnmergedTree = true
}

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.test.SelectSwapProviderBottomSheetTestTags
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 androidx.compose.ui.test.hasTestTag as withTestTag
import androidx.compose.ui.test.hasText as withText
class SelectSwapProviderBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<SelectSwapProviderBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
fun providerItem(name: String, type: String, isBestRate: Boolean): KNode = child {
hasTestTag(SelectSwapProviderBottomSheetTestTags.PROVIDER_ITEM)
hasAnyDescendant(withText(name))
hasAnyDescendant(withText(type))
if (isBestRate) {
hasAnyDescendant(withTestTag(SelectSwapProviderBottomSheetTestTags.BEST_RATE_LABEL))
}
useUnmergedTree = true
}
}
internal fun BaseTestCase.onSelectSwapProviderBottomSheet(function: SelectSwapProviderBottomSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -23,6 +23,11 @@ class SendAddressPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
useUnmergedTree = true
}
val closeButton: KNode = child {
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
useUnmergedTree = true
}
val addressesShimmer: KNode = child {
hasTestTag(SendAddressScreenTestTags.ADDRESSES_SHIMMER)
useUnmergedTree = true

View file

@ -96,6 +96,21 @@ class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
useUnmergedTree = true
}
val provider: KNode = child {
hasText(getResourceString(R.string.express_provider))
useUnmergedTree = true
}
val providerName: KNode = child {
hasTestTag(SendConfirmScreenTestTags.PROVIDER_NAME)
useUnmergedTree = true
}
val bestRateBadge: KNode = child {
hasTestTag(SendConfirmScreenTestTags.BEST_RATE_BADGE)
useUnmergedTree = true
}
val blockchainAddress: KNode = child {
hasTestTag(SendConfirmScreenTestTags.BLOCKCHAIN_ADDRESS)
useUnmergedTree = true
@ -134,6 +149,7 @@ class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
hasTestTag(BaseButtonTestTags.BUTTON)
hasText(getResourceString(CoreUiR.string.warning_button_refresh))
}
}
internal fun BaseTestCase.onSendConfirmScreen(function: SendConfirmPageObject.() -> Unit) =

View file

@ -74,6 +74,22 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
useUnmergedTree = true
}
val swapToAnotherTokenButton: KNode = child {
hasTestTag(SendScreenTestTags.SWAP_TO_ANOTHER_TOKEN_BUTTON)
useUnmergedTree = true
}
fun swapTokenName(tokenName: String): KNode = child {
hasTestTag(SendScreenTestTags.SWAP_TOKEN_NAME)
hasText(tokenName)
useUnmergedTree = true
}
val closeConvertIcon: KNode = child {
hasTestTag(SendScreenTestTags.CLOSE_CONVERT_ICON)
useUnmergedTree = true
}
val nextButton: KNode = child {
hasTestTag(BaseButtonTestTags.BUTTON)
hasAnyDescendant(withText(getResourceString(SendR.string.common_next)))

View file

@ -0,0 +1,34 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.BaseSearchBarTestTags
import com.tangem.core.ui.test.ManageTokensScreenTestTags
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
import androidx.compose.ui.test.hasText as withText
class SendViaSwapPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<SendViaSwapPageObject>(semanticsProvider = semanticsProvider) {
val closeButton: KNode = child {
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
useUnmergedTree = true
}
val searchField: KNode = child {
hasTestTag(BaseSearchBarTestTags.SEARCH_BAR)
}
fun tokenItem(tokenName: String): KNode = child {
hasTestTag(ManageTokensScreenTestTags.NETWORK_NAME)
hasAnyChild(withText(tokenName))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onSendViaSwapScreen(function: SendViaSwapPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -0,0 +1,41 @@
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.WarningBottomSheetTestTags
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 WarningBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<WarningBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
val icon: KNode = child {
hasTestTag(WarningBottomSheetTestTags.ICON)
useUnmergedTree = true
}
fun title(title: String): KNode = child {
hasTestTag(WarningBottomSheetTestTags.TITLE)
hasText(title)
useUnmergedTree = true
}
fun message(message: String): KNode = child {
hasTestTag(WarningBottomSheetTestTags.MESSAGE)
hasText(message)
useUnmergedTree = true
}
val gotItButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.warning_button_ok))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onWarningBottomSheet(function: WarningBottomSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -0,0 +1,372 @@
package com.tangem.tests.send.sendViaSwap
import com.tangem.common.BaseTestCase
import com.tangem.common.R
import com.tangem.common.extensions.extractText
import com.tangem.common.constants.TestConstants.ETHEREUM_RECIPIENT_ADDRESS
import com.tangem.common.constants.TestConstants.SOLANA_RECIPIENT_ADDRESS
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.openSendConfirmScreenViaNextButton
import com.tangem.scenarios.openSendScreen
import com.tangem.screens.*
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.Test
@HiltAndroidTest
class SendViaSwapTest : BaseTestCase() {
@AllureId("3970")
@DisplayName("Send via Swap: check unsupported token error")
@Test
fun unsupportedTokenErrorTest() {
val tokenName = "Bitcoin"
val stellar = "Stellar"
val bitcoinBalanceScenarioName = "bitcoin_utxo"
val bitcoinBalanceScenarioState = "Balance"
val assetsScenarioName = "express_api_assets"
val assetsScenarioState = "BitcoinExchangeEnabled"
val warningTitle = getResourceString(R.string.express_swap_not_supported_title, stellar)
val warningMessage = getResourceString(R.string.express_swap_not_supported_text)
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(bitcoinBalanceScenarioName)
resetWireMockScenarioState(assetsScenarioName)
}
).run {
step("Set WireMock scenario: '$bitcoinBalanceScenarioName' to state: '$bitcoinBalanceScenarioState'") {
setWireMockScenarioState(scenarioName = bitcoinBalanceScenarioName, state = bitcoinBalanceScenarioState)
}
step("Set WireMock scenario: '$assetsScenarioName' to state: '$assetsScenarioState'") {
setWireMockScenarioState(scenarioName = assetsScenarioName, state = assetsScenarioState)
}
step("Open 'Send' screen") {
openSendScreen(tokenName)
}
step("Click on 'Swap to another token button'") {
onSendScreen { swapToAnotherTokenButton.performClick() }
}
step("Click on token: '$stellar'") {
onSendViaSwapScreen { tokenItem(stellar).performClick() }
}
step("Assert warning bottom sheet icon is displayed") {
onWarningBottomSheet { icon.assertIsDisplayed() }
}
step("Assert warning bottom sheet title is displayed") {
onWarningBottomSheet { title(warningTitle).assertIsDisplayed() }
}
step("Assert warning bottom sheet message is displayed") {
onWarningBottomSheet { message(warningMessage).assertIsDisplayed() }
}
step("Click on 'Ok, Got it!' button") {
onWarningBottomSheet { gotItButton.performClick() }
}
}
}
@AllureId("3968")
@DisplayName("Send via Swap: flow with token search and data changes")
@Test
fun sendViaSwapFlowWithTokenSearchAndDataChangesTest() {
val tokenName = "Bitcoin"
val swapTokenName = "Ethereum"
val main = "MAIN"
val firstInputAmount = "0.001"
val secondInputAmount = "0.002"
val bestRateProviderName = "SimpleSwap"
val regularProviderName = "ChangeHero"
val cexType = "CEX"
val fastSelectorItem = getResourceString(R.string.common_fee_selector_option_fast)
var capturedFastFee: String? = null
val bitcoinBalanceScenarioName = "bitcoin_utxo"
val bitcoinBalanceScenarioState = "Balance"
val assetsScenarioName = "express_api_assets"
val assetsScenarioState = "BitcoinExchangeEnabled"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(bitcoinBalanceScenarioName)
resetWireMockScenarioState(assetsScenarioName)
}
).run {
step("Set WireMock scenario: '$bitcoinBalanceScenarioName' to state: '$bitcoinBalanceScenarioState'") {
setWireMockScenarioState(scenarioName = bitcoinBalanceScenarioName, state = bitcoinBalanceScenarioState)
}
step("Set WireMock scenario: '$assetsScenarioName' to state: '$assetsScenarioState'") {
setWireMockScenarioState(scenarioName = assetsScenarioName, state = assetsScenarioState)
}
step("Open 'Send' screen") {
openSendScreen(tokenName)
}
step("Type '$firstInputAmount' in text field") {
onSendScreen { amountInputTextField.performTextInput(firstInputAmount) }
}
step("Click on 'Swap to another token' button") {
onSendScreen { swapToAnotherTokenButton.performClick() }
}
step("Type '$swapTokenName' in search text field") {
onSendViaSwapScreen {
searchField.performClick()
searchField.performTextInput(swapTokenName)
}
}
step("Click on token: '$swapTokenName'") {
onSendViaSwapScreen { tokenItem(swapTokenName).performClick() }
}
step("Click on '$swapTokenName $main' network") {
onChooseNetworkBottomSheet { networkItem(swapTokenName, main).performClick() }
}
step("Click on 'Next' button") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSendScreen {
nextButton.assertIsEnabled()
nextButton.performClick()
}
}
}
step("Type recipient address") {
onSendAddressScreen { addressTextField.performTextReplacement(ETHEREUM_RECIPIENT_ADDRESS) }
}
step("Open 'Send confirm' screen via 'Next' button") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
openSendConfirmScreenViaNextButton()
}
}
step("Assert 'Best rate' badge is displayed") {
onSendConfirmScreen { bestRateBadge.assertIsDisplayed() }
}
step("Click on 'Provider'") {
onSendConfirmScreen { provider.performClick() }
}
step("Assert '$bestRateProviderName' is displayed") {
onSelectSwapProviderBottomSheet {
providerItem(
name = bestRateProviderName,
type = cexType,
isBestRate = true
).assertIsDisplayed()
}
}
step("Click on '$regularProviderName'") {
onSelectSwapProviderBottomSheet {
providerItem(
name = regularProviderName,
type = cexType,
isBestRate = false
).performClick()
}
}
step("Assert provider name is '$regularProviderName'") {
onSendConfirmScreen { providerName.assertTextContains(regularProviderName) }
}
step("Assert 'Best rate' badge is not displayed") {
onSendConfirmScreen { bestRateBadge.assertIsNotDisplayed() }
}
step("Assert provider name is '$regularProviderName'") {
onSendConfirmScreen { providerName.assertTextContains(regularProviderName) }
}
step("Click on fee selector icon") {
onSendConfirmScreen { feeSelectorIcon.performClick() }
}
step("Click on '$fastSelectorItem' selector item is displayed") {
onSendSelectNetworkFeeBottomSheet { regularFeeSelectorItem(fastSelectorItem).performClick() }
}
step("Capture fast fee value") {
onSendConfirmScreen { capturedFastFee = feeAmount.extractText() }
}
step("Click on send amount") {
onSendConfirmScreen { primaryAmount.performClick() }
}
step("Click on swap token '$tokenName'") {
onSendScreen { swapTokenName(tokenName).performClick() }
}
step("Type '$secondInputAmount' in text field") {
onSendScreen { amountInputTextField.performTextReplacement(secondInputAmount) }
}
step("Click on 'Continue' button") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSendScreen {
continueButton.assertIsDisplayed()
continueButton.assertIsEnabled()
continueButton.performClick()
}
onSendConfirmScreen { sendButton.assertIsDisplayed() }
}
}
step("Assert 'Best rate' badge is displayed") {
onSendConfirmScreen { bestRateBadge.assertIsDisplayed() }
}
step("Assert provider name is '$bestRateProviderName'") {
onSendConfirmScreen { providerName.assertTextContains(bestRateProviderName) }
}
step("Assert fee is equal to previously captured fast fee") {
val expectedFee = requireNotNull(capturedFastFee) { "Fast fee was not captured" }
onSendConfirmScreen { feeAmount.assertTextContains(expectedFee) }
}
}
}
@AllureId("3969")
@DisplayName("Send via Swap: flow cancel and return")
@Test
fun sendViaSwapCancelAndReturnFlowTest() {
val tokenName = "Bitcoin"
val firstSwapTokenName = "Ethereum"
val secondSwapTokenName = "Solana"
val main = "MAIN"
val inputAmount = "0.001"
val bitcoinBalanceScenarioName = "bitcoin_utxo"
val bitcoinBalanceScenarioState = "Balance"
val assetsScenarioName = "express_api_assets"
val assetsScenarioState = "BitcoinExchangeEnabled"
val dialogTitle = getResourceString(R.string.send_with_swap_change_token_alert_title)
val dialogMessage = getResourceString(R.string.send_with_swap_change_token_alert_message)
val addressHint = getResourceString(R.string.send_enter_address_field_ens)
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(bitcoinBalanceScenarioName)
resetWireMockScenarioState(assetsScenarioName)
}
).run {
step("Set WireMock scenario: '$bitcoinBalanceScenarioName' to state: '$bitcoinBalanceScenarioState'") {
setWireMockScenarioState(scenarioName = bitcoinBalanceScenarioName, state = bitcoinBalanceScenarioState)
}
step("Set WireMock scenario: '$assetsScenarioName' to state: '$assetsScenarioState'") {
setWireMockScenarioState(scenarioName = assetsScenarioName, state = assetsScenarioState)
}
step("Open 'Send' screen") {
openSendScreen(tokenName)
}
step("Type '$inputAmount' in text field") {
onSendScreen { amountInputTextField.performTextInput(inputAmount) }
}
step("Click on 'Swap to another token' button") {
onSendScreen { swapToAnotherTokenButton.performClick() }
}
step("Click on token: '$firstSwapTokenName'") {
onSendViaSwapScreen { tokenItem(firstSwapTokenName).performClick() }
}
step("Click on '$firstSwapTokenName $main' network") {
onChooseNetworkBottomSheet { networkItem(firstSwapTokenName, main).performClick() }
}
step("Click on 'Close convert' button") {
onSendScreen { closeConvertIcon.performClick() }
}
step("Assert 'Swap to another token' button is displayed") {
onSendScreen { swapToAnotherTokenButton.assertIsDisplayed() }
}
step("Assert amount: '$inputAmount'") {
onSendScreen { amountInputTextField.assertTextContains(inputAmount) }
}
step("Click on 'Swap to another token' button") {
onSendScreen { swapToAnotherTokenButton.performClick() }
}
step("Click on token: '$secondSwapTokenName'") {
onSendViaSwapScreen { tokenItem(secondSwapTokenName).performClick() }
}
step("Click on '$secondSwapTokenName $main' network") {
onChooseNetworkBottomSheet { networkItem(secondSwapTokenName, main).performClick() }
}
step("Click on token '$tokenName'") {
onSendScreen { swapTokenName(tokenName).performClick() }
}
step("Type '$inputAmount' in text field") {
onSendScreen { amountInputTextField.performTextInput(inputAmount) }
}
step("Click on 'Next' button") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSendScreen {
nextButton.assertIsEnabled()
nextButton.performClick()
}
}
}
step("Type recipient address") {
onSendAddressScreen { addressTextField.performTextReplacement(SOLANA_RECIPIENT_ADDRESS) }
}
step("Open 'Send confirm' screen via 'Next' button") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
openSendConfirmScreenViaNextButton()
}
}
step("Click on 'Amount to receive'") {
onSendConfirmScreen { secondaryAmount.performClick() }
}
step("Click on token '$secondSwapTokenName' to open 'Send via Swap' screen") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSendScreen { swapTokenName(secondSwapTokenName).performClick() }
onSendViaSwapScreen { tokenItem(firstSwapTokenName).assertIsDisplayed() }
}
}
step("Click on token: '$firstSwapTokenName'") {
onSendViaSwapScreen { tokenItem(firstSwapTokenName).performClick() }
}
step("Click on '$firstSwapTokenName $main' network") {
onChooseNetworkBottomSheet { networkItem(firstSwapTokenName, main).performClick() }
}
step("Assert '$dialogTitle' dialog title is displayed") {
onDialog { title.assertTextEquals(dialogTitle) }
}
step("Assert '$dialogMessage' dialog message is displayed") {
onDialog { text.assertTextEquals(dialogMessage) }
}
step("Click on 'Cancel' button") {
onDialog { cancelButton.performClick() }
}
step("Click on 'Close' button") {
onSendViaSwapScreen { closeButton.performClick() }
}
step("Click on 'Continue' button") {
onSendScreen { continueButton.performClick() }
}
step("Assert recipient address is displayed") {
onSendConfirmScreen { recipientAddress(SOLANA_RECIPIENT_ADDRESS).assertIsDisplayed() }
}
step("Click on 'Amount to receive'") {
onSendConfirmScreen { secondaryAmount.performClick() }
}
step("Click on token '$secondSwapTokenName' to open 'Send via Swap' screen") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSendScreen { swapTokenName(secondSwapTokenName).performClick() }
onSendViaSwapScreen { tokenItem(firstSwapTokenName).assertIsDisplayed() }
}
}
step("Click on token: $firstSwapTokenName") {
onSendViaSwapScreen { tokenItem(firstSwapTokenName).performClick() }
}
step("Click on '$firstSwapTokenName $main' network") {
onChooseNetworkBottomSheet { networkItem(firstSwapTokenName, main).performClick() }
}
step("Click on 'Change' button") {
onDialog { changeButton.performClick() }
}
step("Assert '$firstSwapTokenName' is displayed") {
onSendScreen { swapTokenName(firstSwapTokenName).assertIsDisplayed() }
}
step("Click on 'Next' button to open 'Send address' screen") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSendScreen {
nextButton.assertIsEnabled()
nextButton.performClick()
}
onSendAddressScreen { container.assertIsDisplayed() }
}
}
step("Assert address text field is empty and hint is displayed") {
onSendAddressScreen { addressTextFieldHint.assertTextContains(addressHint) }
}
}
}
}

View file

@ -13,6 +13,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
@ -32,6 +33,7 @@ import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.WarningBottomSheetTestTags
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
@ -99,7 +101,8 @@ private fun ContentContainer(state: MessageBottomSheetUM.InfoBlock, modifier: Mo
Text(
modifier = Modifier
.fillMaxWidth()
.padding(top = TangemTheme.dimens.spacing24),
.padding(top = TangemTheme.dimens.spacing24)
.testTag(WarningBottomSheetTestTags.TITLE),
text = title.resolveReference(),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
@ -110,7 +113,8 @@ private fun ContentContainer(state: MessageBottomSheetUM.InfoBlock, modifier: Mo
Text(
modifier = Modifier
.fillMaxWidth()
.padding(top = TangemTheme.dimens.spacing8),
.padding(top = TangemTheme.dimens.spacing8)
.testTag(WarningBottomSheetTestTags.MESSAGE),
text = body.resolveAnnotatedReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,

View file

@ -11,8 +11,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.WarningBottomSheetTestTags
@Composable
fun HighlightedIcon(
@ -25,7 +27,8 @@ fun HighlightedIcon(
modifier = modifier
.size(TangemTheme.dimens.size56)
.clip(CircleShape)
.background(backgroundColor.copy(alpha = 0.1F)),
.background(backgroundColor.copy(alpha = 0.1F))
.testTag(WarningBottomSheetTestTags.ICON),
contentAlignment = Alignment.Center,
content = {
Icon(

View file

@ -0,0 +1,8 @@
package com.tangem.core.ui.test
object ChooseNetworkBottomSheetTestTags {
const val NETWORK_ITEM = "CHOOSE_NETWORK_BOTTOM_SHEET_NETWORK_ITEM"
const val NETWORK_ICON = "CHOOSE_NETWORK_BOTTOM_SHEET_NETWORK_ICON"
const val NETWORK_TITLE = "CHOOSE_NETWORK_BOTTOM_SHEET_NETWORK_TITLE"
const val NETWORK_SUBTITLE = "CHOOSE_NETWORK_BOTTOM_SHEET_NETWORK_SUBTITLE"
}

View file

@ -0,0 +1,10 @@
package com.tangem.core.ui.test
object SelectSwapProviderBottomSheetTestTags {
const val PROVIDER_NAME = "SELECT_SWAP_PROVIDER_BOTTOM_SHEET_PROVIDER_NAME"
const val PROVIDER_TYPE = "SELECT_SWAP_PROVIDER_BOTTOM_SHEET_PROVIDER_TYPE"
const val BEST_RATE_LABEL = "SELECT_SWAP_PROVIDER_BOTTOM_SHEET_BEST_RATE_LABEL"
const val AMOUNT = "SELECT_SWAP_PROVIDER_BOTTOM_SHEET_AMOUNT"
const val PROVIDER_ITEM = "SELECT_SWAP_PROVIDER_BOTTOM_SHEET_PROVIDER_ITEM"
}

View file

@ -5,4 +5,6 @@ object SendConfirmScreenTestTags {
const val RECIPIENT_ADDRESS = "SEND_CONFIRM_SCREEN_RECIPIENT_ADDRESS"
const val BLOCKCHAIN_ADDRESS = "SEND_CONFIRM_SCREEN_BLOCKCHAIN_ADDRESS"
const val RECIPIENT_ADDRESS_ICON = "SEND_CONFIRM_SCREEN_RECIPIENT_ADDRESS_ICON"
const val PROVIDER_NAME = "SEND_CONFIRM_SCREEN_PROVIDER_NAME"
const val BEST_RATE_BADGE = "SEND_CONFIRM_SCREEN_BEST_RATE_BADGE"
}

View file

@ -14,4 +14,8 @@ object SendScreenTestTags {
const val MAX_BUTTON = "SEND_SCREEN_MAX_BUTTON"
const val PREVIOUS_BUTTON = "SEND_SCREEN_PREVIOUS_BUTTON"
const val SWAP_TO_ANOTHER_TOKEN_BUTTON = "SEND_SCREEN_SWAP_TO_ANOTHER_TOKEN_BUTTON"
const val SWAP_TOKEN_NAME = "SEND_SCREEN_SWAP_TOKEN_NAME"
const val CLOSE_CONVERT_ICON = "SEND_SCREEN_CLOSE_CONVERT_ICON"
}

View file

@ -0,0 +1,7 @@
package com.tangem.core.ui.test
object WarningBottomSheetTestTags {
const val ICON = "BASE_WARNING_BOTTOM_SHEET_ICON"
const val TITLE = "BASE_WARNING_BOTTOM_SHEET_TITLE"
const val MESSAGE = "BASE_WARNING_BOTTOM_SHEET_MESSAGE"
}

View file

@ -11,6 +11,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -23,6 +24,7 @@ import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.SendScreenTestTags
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.subcomponents.amount.model.SendAmountClickIntents
import com.tangem.features.send.v2.subcomponents.amount.ui.preview.SendAmountClickIntentsStub
@ -65,7 +67,8 @@ private fun SendConvertTokenButton(onConvertToAnother: () -> Unit) {
Row(
modifier = Modifier
.padding(12.dp)
.align(Alignment.Center),
.align(Alignment.Center)
.testTag(SendScreenTestTags.SWAP_TO_ANOTHER_TOKEN_BUTTON),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {

View file

@ -19,6 +19,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -39,6 +40,7 @@ import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.SendScreenTestTags
import com.tangem.domain.express.models.ExpressRateType
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
@ -131,7 +133,9 @@ private fun SwapAmountBlockSeparator(onClick: () -> Unit, modifier: Modifier = M
),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
modifier = Modifier.size(16.dp),
modifier = Modifier
.size(16.dp)
.testTag(SendScreenTestTags.CLOSE_CONVERT_ICON),
)
}
}
@ -333,6 +337,7 @@ private fun SwapAmountInfoMain(
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
maxLines = 1,
modifier = Modifier.testTag(SendScreenTestTags.SWAP_TOKEN_NAME),
)
} else {
TextShimmer(

View file

@ -22,6 +22,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
@ -40,6 +41,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.SendConfirmScreenTestTags
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.express.models.ExpressProviderType
import com.tangem.domain.express.models.ExpressRateType
@ -171,7 +173,8 @@ private fun ProviderInfo(
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
end.linkTo(iconRef.start, goneMargin = 12.dp)
},
}
.testTag(SendConfirmScreenTestTags.PROVIDER_NAME),
)
Icon(
painter = rememberVectorPainter(
@ -287,7 +290,8 @@ private fun ConstraintLayoutScope.BestRateBadge(
tint = TangemTheme.colors.icon.constant,
modifier = Modifier
.padding(iconHorizontalPaddings, iconVerticalPaddings)
.size(iconSize),
.size(iconSize)
.testTag(SendConfirmScreenTestTags.BEST_RATE_BADGE),
)
AnimatedVisibility(
visibleState = animateState,

View file

@ -10,6 +10,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@ -25,6 +26,7 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.SelectSwapProviderBottomSheetTestTags
import com.tangem.features.swap.v2.impl.chooseprovider.entity.SwapProviderState
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
@ -45,7 +47,9 @@ internal fun SwapProviderItem(state: SwapProviderState, modifier: Modifier = Mod
@Composable
private fun ProviderContentState(state: SwapProviderState.Content, modifier: Modifier = Modifier) {
Row(
modifier = modifier.fillMaxWidth(),
modifier = modifier
.fillMaxWidth()
.testTag(SelectSwapProviderBottomSheetTestTags.PROVIDER_ITEM),
verticalAlignment = Alignment.CenterVertically,
) {
SubcomposeAsyncImage(
@ -67,12 +71,15 @@ private fun ProviderContentState(state: SwapProviderState.Content, modifier: Mod
text = state.name,
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.primary1,
modifier = Modifier.testTag(SelectSwapProviderBottomSheetTestTags.PROVIDER_NAME),
)
Text(
text = state.type,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.padding(start = 4.dp),
modifier = Modifier
.padding(start = 4.dp)
.testTag(SelectSwapProviderBottomSheetTestTags.PROVIDER_TYPE),
)
val badgeModifier = Modifier.padding(start = 4.dp)
when (state.additionalBadge) {
@ -91,6 +98,7 @@ private fun ProviderContentState(state: SwapProviderState.Content, modifier: Mod
color = TangemTheme.colors.text.tertiary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
modifier = Modifier.testTag(SelectSwapProviderBottomSheetTestTags.AMOUNT),
)
if (state.diffPercent is SwapQuoteUM.Content.DifferencePercent.Diff) {
@ -143,7 +151,9 @@ private fun BestTradeItem(modifier: Modifier = Modifier) {
text = stringResourceSafe(R.string.express_provider_best_rate),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.icon.accent,
modifier = Modifier.padding(horizontal = 6.dp),
modifier = Modifier
.padding(horizontal = 6.dp)
.testTag(SelectSwapProviderBottomSheetTestTags.BEST_RATE_LABEL),
maxLines = 1,
)
}

View file

@ -17,6 +17,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -31,6 +32,7 @@ import com.tangem.core.ui.decorations.roundedShapeItemDecoration
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.ChooseNetworkBottomSheetTestTags
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseNetworkUM
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkContentUM
@ -116,15 +118,20 @@ private fun SwapChooseTokenNetworkContentList(swapNetworks: ImmutableList<SwapCh
indication = ripple(),
onClick = { network.onNetworkClick() },
)
.padding(vertical = 12.dp, horizontal = 14.dp),
.padding(vertical = 12.dp, horizontal = 14.dp)
.testTag(ChooseNetworkBottomSheetTestTags.NETWORK_ITEM),
) {
Image(
modifier = Modifier.size(36.dp),
modifier = Modifier
.size(36.dp)
.testTag(ChooseNetworkBottomSheetTestTags.NETWORK_ICON),
painter = painterResource(id = network.iconResId),
contentDescription = null,
)
Text(
modifier = Modifier.padding(start = 6.dp),
modifier = Modifier
.padding(start = 6.dp)
.testTag(ChooseNetworkBottomSheetTestTags.NETWORK_TITLE),
text = network.title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
@ -137,6 +144,7 @@ private fun SwapChooseTokenNetworkContentList(swapNetworks: ImmutableList<SwapCh
} else {
TangemTheme.colors.text.tertiary
},
modifier = Modifier.testTag(ChooseNetworkBottomSheetTestTags.NETWORK_SUBTITLE),
)
}
}