diff --git a/app/src/androidTest/kotlin/com/tangem/common/extensions/KNode.kt b/app/src/androidTest/kotlin/com/tangem/common/extensions/KNode.kt index b91b4767eb..3ce482fb53 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/extensions/KNode.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/extensions/KNode.kt @@ -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") +} diff --git a/app/src/androidTest/kotlin/com/tangem/common/extensions/UiDeviceExt.kt b/app/src/androidTest/kotlin/com/tangem/common/extensions/UiDeviceExt.kt index 03dcf422f5..2ec823d1e8 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/extensions/UiDeviceExt.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/extensions/UiDeviceExt.kt @@ -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") } diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SendAddressPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SendAddressPageObject.kt index 0458682ee0..b963fa0d14 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/SendAddressPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/SendAddressPageObject.kt @@ -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 } diff --git a/app/src/androidTest/kotlin/com/tangem/screens/TopBarPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/TopBarPageObject.kt index e18c510849..cb347bb51b 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/TopBarPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/TopBarPageObject.kt @@ -14,6 +14,7 @@ class TopBarPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : ) { val moreButton: KNode = child { hasTestTag(MainScreenTestTags.MORE_BUTTON) + useUnmergedTree = true } } diff --git a/app/src/androidTest/kotlin/com/tangem/tests/FeedbackTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/FeedbackTest.kt index 624ea971b6..f6d9837a42 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/FeedbackTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/FeedbackTest.kt @@ -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") { diff --git a/app/src/androidTest/kotlin/com/tangem/tests/balance/TotalBalanceUpdateTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/balance/TotalBalanceUpdateTest.kt index 2003821062..45000fa4ad 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/balance/TotalBalanceUpdateTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/balance/TotalBalanceUpdateTest.kt @@ -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() diff --git a/app/src/androidTest/kotlin/com/tangem/tests/send/addressScreen/SendAddressScreenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/send/addressScreen/SendAddressScreenTest.kt index 572fbce81c..32177f7b76 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/send/addressScreen/SendAddressScreenTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/send/addressScreen/SendAddressScreenTest.kt @@ -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) } diff --git a/app/src/androidTest/kotlin/com/tangem/tests/send/confirmScreen/SendConfirmScreenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/send/confirmScreen/SendConfirmScreenTest.kt index 6ecfda055b..69307ea8b4 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/send/confirmScreen/SendConfirmScreenTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/send/confirmScreen/SendConfirmScreenTest.kt @@ -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 { diff --git a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapStoriesTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapStoriesTest.kt index 21e65feb9b..9820b6fe9b 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapStoriesTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapStoriesTest.kt @@ -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) diff --git a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenTest.kt index 095b1ccacb..515ca716f9 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenTest.kt @@ -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 diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt index 4a4334393e..b216e3991a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt @@ -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( diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/SendAddressScreenTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/SendAddressScreenTestTags.kt index 662611b4d4..92f6450322 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/SendAddressScreenTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/SendAddressScreenTestTags.kt @@ -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" diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt index a748635db5..9a999b3ece 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt @@ -234,6 +234,7 @@ private fun LazyListScope.listHeaderItem( TextShimmer( style = TangemTheme.typography.subtitle2, text = stringResourceSafe(titleRes), + modifier = Modifier.testTag(SendAddressScreenTestTags.ADDRESSES_SHIMMER), ) } } else { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletTopBar.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletTopBar.kt index 520a13c331..7d820d015c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletTopBar.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletTopBar.kt @@ -115,6 +115,7 @@ internal fun WalletTopBar(config: WalletTopBarConfig) { Icon( painter = painterResource(id = action.iconRes), contentDescription = null, + modifier = Modifier.testTag(MainScreenTestTags.MORE_BUTTON), ) } }