Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-12 11:28:18 +03:00
parent 1ea13a7484
commit 6588ff4fbc
14 changed files with 144 additions and 28 deletions

View file

@ -1,6 +1,8 @@
package com.tangem.common.extensions
import androidx.compose.ui.test.ComposeTimeoutException
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.ComposeTestRule
import io.github.kakaocup.compose.node.element.KNode
fun KNode.clickWithAssertion() {
@ -34,3 +36,25 @@ fun KNode.assertVisibility(shouldBeDisplayed: Boolean) {
assertIsNotDisplayed()
}
}
fun KNode.clickAndWaitFor(
rule: ComposeTestRule,
timeoutMs: Long = 5_000,
maxRetries: Int = 3,
expectedCondition: () -> Unit,
) {
for (attempt in 1..maxRetries) {
performClick()
rule.waitForIdle()
try {
rule.waitUntil(timeoutMs) {
runCatching { expectedCondition() }.isSuccess
}
return
} catch (_: ComposeTimeoutException) {
}
}
throw AssertionError("Condition not met after $maxRetries click attempts")
}

View file

@ -1,6 +1,7 @@
package com.tangem.common.extensions
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.wallet.R
@ -46,7 +47,23 @@ fun BaseTestCase.swipeMarketsBlock(direction: SwipeDirection) {
}
fun BaseTestCase.openTheAppFromRecents() {
device.uiDevice.waitForIdle()
device.uiDevice.pressRecentApps()
device.uiDevice.waitForIdle()
val recentsOpened = device.uiDevice.wait(
Until.hasObject(By.res("com.android.launcher3:id/snapshot")),
3_000
)
if (!recentsOpened) {
device.uiDevice.pressRecentApps()
device.uiDevice.wait(
Until.hasObject(By.res("com.android.launcher3:id/snapshot")),
3_000
)
}
val centerX = device.uiDevice.displayWidth / 2
val centerY = device.uiDevice.displayHeight / 3
@ -54,6 +71,14 @@ fun BaseTestCase.openTheAppFromRecents() {
device.uiDevice.click(centerX, centerY)
}
fun BaseTestCase.collapseAppByHomeButton() {
device.uiDevice.pressHome()
device.uiDevice.wait(
Until.hasObject(By.pkg(device.uiDevice.launcherPackageName)),
3_000
)
}
fun BaseTestCase.disableWiFi() {
device.uiDevice.executeShellCommand("svc wifi disable")
}

View file

@ -23,6 +23,11 @@ class SendAddressPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
useUnmergedTree = true
}
val addressesShimmer: KNode = child {
hasTestTag(SendAddressScreenTestTags.ADDRESSES_SHIMMER)
useUnmergedTree = true
}
val topAppBarTitle: KNode = child {
hasTestTag(TopAppBarTestTags.TITLE)
hasText(getResourceString(CoreUiR.string.common_address))
@ -62,7 +67,7 @@ class SendAddressPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
}
val clearTextFieldButton: KNode = child {
hasContentDescription(getResourceString(CoreUiR.string.common_close))
hasTestTag(SendAddressScreenTestTags.CROSS_ICON)
useUnmergedTree = true
}

View file

@ -14,6 +14,7 @@ class TopBarPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
) {
val moreButton: KNode = child {
hasTestTag(MainScreenTestTags.MORE_BUTTON)
useUnmergedTree = true
}
}

View file

@ -5,6 +5,7 @@ import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.RECIPIENT_ADDRESS
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.clickAndWaitFor
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.domain.redux.StateDialog
import com.tangem.scenarios.checkFailedTransactionDialog
@ -46,6 +47,7 @@ class FeedbackTest : BaseTestCase() {
onTopBar { moreButton.clickWithAssertion() }
}
step("Click 'Contact support' button") {
waitForIdle()
onDetailsScreen { contactSupportButton.clickWithAssertion() }
}
step("Assert 'Gmail' app is open") {
@ -96,17 +98,29 @@ class FeedbackTest : BaseTestCase() {
step("Enter address") {
onSendAddressScreen { addressTextField.performTextInput(recipientAddress) }
}
step("Click 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
step("Assert text field contains text: $recipientAddress") {
onSendAddressScreen { addressTextField.assertTextEquals(recipientAddress) }
}
step("Assert sеnding text is displayed") {
onSendConfirmScreen { sendingText.assertIsDisplayed() }
step("Click 'Next' button") {
onSendAddressScreen {
nextButton.clickAndWaitFor(
rule = composeTestRule,
expectedCondition = {
onSendConfirmScreen { sendingText.assertIsDisplayed() }
}
)
}
}
step("Click 'Send' button") {
waitForIdle()
onSendConfirmScreen {
sendButton.assertIsEnabled()
sendButton.performClick()
sendButton.clickAndWaitFor(
rule = composeTestRule,
expectedCondition = {
onFailedTransactionDialog { dialogContainer.assertIsDisplayed() }
}
)
}
}
step("Check 'Failed transaction' dialog") {

View file

@ -126,7 +126,7 @@ class TotalBalanceUpdateTest : BaseTestCase() {
onMainScreen { totalBalanceText.assertTextContains(TOTAL_BALANCE) }
}
step("Press 'Home' to collapse the app") {
device.uiDevice.pressHome()
collapseAppByHomeButton()
}
step("Open the app from recent apps") {
openTheAppFromRecents()

View file

@ -8,10 +8,12 @@ import com.tangem.common.constants.TestConstants.ENS_ETHEREUM_RECIPIENT_SHORTENE
import com.tangem.common.constants.TestConstants.ENS_NAME
import com.tangem.common.constants.TestConstants.ETHEREUM_ADDRESS
import com.tangem.common.constants.TestConstants.ETHEREUM_RECIPIENT_ADDRESS
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.constants.TestConstants.XRP_RECIPIENT_ADDRESS
import com.tangem.common.constants.TestConstants.XRP_X_ADDRESS
import com.tangem.common.constants.TestConstants.XRP_X_RECIPIENT_ADDRESS
import com.tangem.common.constants.TestConstants.XRP_X_RECIPIENT_ADDRESS_WITH_TAG
import com.tangem.common.extensions.clickAndWaitFor
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.clearClipboard
import com.tangem.common.utils.resetWireMockScenarioState
@ -115,6 +117,9 @@ class SendAddressScreenTest : BaseTestCase() {
clearClipboard()
}
).run {
step("Set clipboard text") {
setClipboardText(context, recipientAddress)
}
step("Open 'Main Screen'") {
openMainScreen()
}
@ -124,10 +129,11 @@ class SendAddressScreenTest : BaseTestCase() {
step("Open 'Send Address' screen") {
openSendAddressScreen(tokenName, sendAmount)
}
step("Set clipboard text") {
setClipboardText(context, recipientAddress)
step("Assert 'Addresses shimmer' is not displayed") {
onSendAddressScreen { addressesShimmer.assertIsNotDisplayed() }
}
step("Click on 'Paste' button") {
waitForIdle()
onSendAddressScreen { addressPasteButton.clickWithAssertion() }
}
step("Assert address text field contains correct recipient address") {
@ -142,8 +148,22 @@ class SendAddressScreenTest : BaseTestCase() {
step("Set clipboard text") {
setClipboardText(context, invalidAddress)
}
step("Assert 'Addresses shimmer' is not displayed") {
onSendAddressScreen { addressesShimmer.assertIsNotDisplayed() }
}
step("Click on 'Cross' button") {
onSendAddressScreen { clearTextFieldButton.clickWithAssertion() }
waitForIdle()
onSendAddressScreen {
clearTextFieldButton.clickAndWaitFor(
rule = composeTestRule,
expectedCondition = {
onSendAddressScreen { addressPasteButton.assertIsDisplayed() }
}
)
}
}
step("Assert 'Addresses shimmer' is not displayed") {
onSendAddressScreen { addressesShimmer.assertIsNotDisplayed() }
}
step("Click on 'Paste' button") {
onSendAddressScreen { addressPasteButton.clickWithAssertion() }
@ -154,20 +174,38 @@ class SendAddressScreenTest : BaseTestCase() {
step("Assert invalid address text field title is displayed") {
onSendAddressScreen { addressTextFieldTitle.assertTextContains(notAValidAddress) }
}
step("Assert 'Next' button is disabled") {
onSendAddressScreen { nextButton.assertIsNotEnabled() }
}
step("Set clipboard text") {
setClipboardText(context, walletAddress)
}
step("Assert 'Next' button is disabled") {
onSendAddressScreen { nextButton.assertIsNotEnabled() }
}
step("Assert 'Addresses shimmer' is not displayed") {
onSendAddressScreen { addressesShimmer.assertIsNotDisplayed() }
}
step("Click on 'Cross' button") {
onSendAddressScreen { clearTextFieldButton.clickWithAssertion() }
onSendAddressScreen {
clearTextFieldButton.clickAndWaitFor(
rule = composeTestRule,
expectedCondition = {
onSendAddressScreen { addressPasteButton.assertIsDisplayed() }
}
)
}
}
step("Assert 'Addresses shimmer' is not displayed") {
onSendAddressScreen { addressesShimmer.assertDoesNotExist() }
}
step("Click on 'Paste' button") {
waitForIdle()
onSendAddressScreen { addressPasteButton.clickWithAssertion() }
}
step("Assert address text field contains invalid address") {
onSendAddressScreen { addressTextField.assertTextContains(walletAddress) }
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSendAddressScreen {
addressTextField.assertTextContains(walletAddress)
}
}
}
step("Assert 'Address is the same as wallet address' error title is displayed") {
onSendAddressScreen { addressTextFieldTitle.assertTextContains(sameAsWalletAddress) }

View file

@ -267,22 +267,21 @@ class SendConfirmScreenTest : BaseTestCase() {
step("Click on 'Next' button") {
onSendScreen { nextButton.clickWithAssertion() }
}
step("Type address in input text field") {
onSendAddressScreen { addressTextField.performTextReplacement(POLKADOT_RECIPIENT_ADDRESS) }
}
step("Turn off internet") {
disableWiFi()
disableMobileData()
}
step("Type address in input text field") {
onSendAddressScreen { addressTextField.performTextReplacement(POLKADOT_RECIPIENT_ADDRESS) }
}
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
}
step("Turn on internet") {
enableWiFi()
enableMobileData()
}
step("Assert 'Network fee info unreachable' warning title is displayed") {
onSendConfirmScreen { warningTitle(warningTitle).assertIsDisplayed() }
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
waitForIdle()
onSendConfirmScreen { warningTitle(warningTitle).assertIsDisplayed() }
}
}
step("Assert 'Check your internet connection' warning message is displayed") {
onSendConfirmScreen { warningMessage(warningMessageResId).assertIsDisplayed() }
@ -290,6 +289,10 @@ class SendConfirmScreenTest : BaseTestCase() {
step("Assert warning icon is displayed") {
onSendConfirmScreen { warningIcon(warningTitle).assertIsDisplayed() }
}
step("Turn on internet") {
enableWiFi()
enableMobileData()
}
step("Click on 'Refresh' button") {
waitForIdle()
onSendConfirmScreen {

View file

@ -3,6 +3,7 @@ package com.tangem.tests.swap
import androidx.compose.ui.test.longClick
import androidx.test.InstrumentationRegistry.getTargetContext
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.extensions.assertHasBadge
import com.tangem.common.extensions.restartApp
import com.tangem.common.utils.resetWireMockScenarioState
@ -208,7 +209,10 @@ class SwapStoriesTest : BaseTestCase() {
}
step("Assert 'Swap' button has badge") {
waitForIdle()
onMainScreen { swapButton.assertHasBadge() }
flakySafely(WAIT_UNTIL_TIMEOUT) {
composeTestRule.mainClock.advanceTimeBy(500)
onMainScreen { swapButton.assertHasBadge() }
}
}
step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.TokenDetails, storiesExist = true)

View file

@ -557,9 +557,6 @@ class SwapTokenScreenTest : BaseTestCase() {
}
}
@ApiEnv(
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
)
@AllureId("8536")
@DisplayName("Swap: check switch fee type (unable to cover 'Market' and 'Fast' fee)")
@Test

View file

@ -124,7 +124,8 @@ fun InputRowRecipient(
onClick = onPasteClick,
modifier = Modifier
.align(CenterVertically)
.padding(start = TangemTheme.dimens.spacing8),
.padding(start = TangemTheme.dimens.spacing8)
.testTag(SendAddressScreenTestTags.CROSS_ICON),
)
}
Row(

View file

@ -7,7 +7,9 @@ object SendAddressScreenTestTags {
const val ADDRESS_TEXT_FIELD = "SEND_ADDRESS_SCREEN_TEXT_FIELD"
const val QR_BUTTON = "SEND_ADDRESS_SCREEN_QR_BUTTON"
const val ADDRESS_PASTE_BUTTON = "SEND_ADDRESS_SCREEN_ADDRESS_PASTE_BUTTON"
const val CROSS_ICON = "SEND_ADDRESS_SCREEN_ADDRESS_CROSS_ICON"
const val RESOLVED_ADDRESS = "SEND_ADDRESS_SCREEN_RESOLVED_ADDRESS"
const val ADDRESSES_SHIMMER = "SEND_ADDRESS_SCREEN_ADDRESSES_SHIMMER"
const val RECENT_ADDRESS_ITEM = "SEND_ADDRESS_SCREEN_RECENT_ADDRESS_ITEM"
const val RECENT_ADDRESS_TITLE = "SEND_ADDRESS_SCREEN_RECENT_ADDRESS_TITLE"

View file

@ -234,6 +234,7 @@ private fun LazyListScope.listHeaderItem(
TextShimmer(
style = TangemTheme.typography.subtitle2,
text = stringResourceSafe(titleRes),
modifier = Modifier.testTag(SendAddressScreenTestTags.ADDRESSES_SHIMMER),
)
}
} else {

View file

@ -115,6 +115,7 @@ internal fun WalletTopBar(config: WalletTopBarConfig) {
Icon(
painter = painterResource(id = action.iconRes),
contentDescription = null,
modifier = Modifier.testTag(MainScreenTestTags.MORE_BUTTON),
)
}
}