From 4b2eb807653458339a12133b01612a157a246f91 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 6 Oct 2025 11:45:18 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../ApplicationInjectionExecutionRule.kt | 1 + .../kotlin/com/tangem/common/BaseTestCase.kt | 1 + .../tangem/common/constants/TestConstants.kt | 1 + .../{BaseTestCaseExt.kt => UiDeviceExt.kt} | 25 +++++++++ .../com/tangem/scenarios/BaseScenarios.kt | 3 +- .../scenarios/CheckMainScreenScenarios.kt | 55 +++++++++---------- .../tangem/screens/MainScreenPageObject.kt | 29 +++++----- .../kotlin/com/tangem/tests/FeedbackTest.kt | 4 +- .../kotlin/com/tangem/tests/SendTest.kt | 5 ++ .../kotlin/com/tangem/tests/SwapTokenTest.kt | 31 ++++++++--- .../com/tangem/tests/TermsOfServiceTest.kt | 34 +++++++----- .../tests/balance/TotalBalanceUpdateTest.kt | 6 +- .../java/com/tangem/tap/TangemApplication.kt | 2 +- 13 files changed, 123 insertions(+), 74 deletions(-) rename app/src/androidTest/kotlin/com/tangem/common/extensions/{BaseTestCaseExt.kt => UiDeviceExt.kt} (73%) diff --git a/app/src/androidTest/kotlin/com/tangem/common/ApplicationInjectionExecutionRule.kt b/app/src/androidTest/kotlin/com/tangem/common/ApplicationInjectionExecutionRule.kt index e1c23e8e9b..75662e25d4 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/ApplicationInjectionExecutionRule.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/ApplicationInjectionExecutionRule.kt @@ -19,6 +19,7 @@ class ApplicationInjectionExecutionRule : TestRule { OnComponentReadyRunner.addListener( tangemApplication, ApplicationEntryPoint::class.java ) { _: ApplicationEntryPoint -> + tangemApplication.preInit() tangemApplication.init() } base.evaluate() diff --git a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt index 3dcc0122f9..798d610177 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt @@ -136,6 +136,7 @@ abstract class BaseTestCase : TestCase( composeTestRule.onRoot(useUnmergedTree = useUnmergedTree).printToLog(tag, maxDepth) } + fun waitForIdle() = composeTestRule.waitForIdle() private fun setFeatureToggles() { runBlocking { diff --git a/app/src/androidTest/kotlin/com/tangem/common/constants/TestConstants.kt b/app/src/androidTest/kotlin/com/tangem/common/constants/TestConstants.kt index 025c523ab1..82b612164d 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/constants/TestConstants.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/constants/TestConstants.kt @@ -7,6 +7,7 @@ object TestConstants { const val CARDANO_ADDRESS = "addr1q8f9499e58k4hhfd9vhawprxt3xd94x7rmlyp33ee4xkatakcl2zgkrg0p6ceqkndtkw4cumfe9enhdph8yhuswn785srksm9p" const val WAIT_UNTIL_TIMEOUT = 20_000L + const val WAIT_UNTIL_TIMEOUT_LONG = 30_000L const val MARKETS_MAIN_NETWORK_SUFFIX = "MAIN" diff --git a/app/src/androidTest/kotlin/com/tangem/common/extensions/BaseTestCaseExt.kt b/app/src/androidTest/kotlin/com/tangem/common/extensions/UiDeviceExt.kt similarity index 73% rename from app/src/androidTest/kotlin/com/tangem/common/extensions/BaseTestCaseExt.kt rename to app/src/androidTest/kotlin/com/tangem/common/extensions/UiDeviceExt.kt index 2c2fd2e178..b8751cb679 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/extensions/BaseTestCaseExt.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/extensions/UiDeviceExt.kt @@ -53,6 +53,31 @@ fun BaseTestCase.openTheAppFromRecents() { device.uiDevice.click(centerX, centerY) } +fun BaseTestCase.disableWiFi() { + device.uiDevice.executeShellCommand("svc wifi disable") +} + +fun BaseTestCase.disableMobileData() { + device.uiDevice.executeShellCommand("svc data disable") +} + +fun BaseTestCase.enableWiFi() { + device.uiDevice.executeShellCommand("svc wifi enable") +} + +fun BaseTestCase.enableMobileData() { + device.uiDevice.executeShellCommand("svc data enable") +} + +fun BaseTestCase.stopApp(packageName: String) { + device.apps.kill(packageName) +} + +fun BaseTestCase.launchApp(packageName: String) { + device.apps.waitForAppLaunchAndReady(packageName = packageName) + device.apps.launch(packageName) +} + enum class SwipeDirection { UP, DOWN } \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/BaseScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/BaseScenarios.kt index b2af3fbfaa..3212a90432 100644 --- a/app/src/androidTest/kotlin/com/tangem/scenarios/BaseScenarios.kt +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/BaseScenarios.kt @@ -29,7 +29,7 @@ fun BaseTestCase.scanCard( } if (alreadyActivatedDialogIsShown) { step("Click on 'This is my wallet' button") { - composeTestRule.waitForIdle() + waitForIdle() AlreadyUsedWalletDialogPageObject { thisIsMyWalletButton.click() } } } @@ -76,6 +76,7 @@ fun BaseTestCase.synchronizeAddresses(balance: String) { fun BaseTestCase.openDeviceSettingsScreen() { step("Open wallet details") { + waitForIdle() onTopBar { moreButton.clickWithAssertion() } } step("Open 'Wallet settings' screen") { diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/CheckMainScreenScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/CheckMainScreenScenarios.kt index 971702b746..b0ba1c71d8 100644 --- a/app/src/androidTest/kotlin/com/tangem/scenarios/CheckMainScreenScenarios.kt +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/CheckMainScreenScenarios.kt @@ -1,7 +1,6 @@ package com.tangem.scenarios import com.tangem.common.BaseTestCase -import com.tangem.common.extensions.assertElementDoesNotExist import com.tangem.screens.onMainScreen import io.qameta.allure.kotlin.Allure.step @@ -14,30 +13,6 @@ fun BaseTestCase.checkSingleCurrencyMainScreen( step("Assert card title equal '$cardTitle'") { onMainScreen { walletNameText.assertTextEquals(cardTitle) } } - if (withTransactions) { - step("Assert 'Transactions' block is displayed") { - onMainScreen { transactionsExplorerText().assertExists() } - } - step("Assert 'Transactions' title is displayed") { - onMainScreen { transactionsTitle.assertIsDisplayed() } - } - step("Assert 'Explorer' icon is displayed") { - onMainScreen { transactionsExplorerIcon.assertIsDisplayed() } - } - } else { - step("Assert empty 'Transactions' block is displayed") { - onMainScreen { emptyTransactionBlock.assertIsDisplayed() } - } - step("Assert empty 'Transactions' block icon is displayed") { - onMainScreen { emptyTransactionBlockIcon.assertIsDisplayed() } - } - step("Assert empty 'Transactions' block text is displayed") { - onMainScreen { emptyTransactionBlockText.assertIsDisplayed() } - } - step("Assert empty 'Transactions' block 'Explore' button is displayed") { - onMainScreen { emptyTransactionBlockExploreButton.assertIsDisplayed() } - } - } if (withWalletImage) { step("Assert card image is displayed") { //TODO: create assertion method for checking images onMainScreen { walletImage.assertIsDisplayed() } @@ -63,15 +38,37 @@ fun BaseTestCase.checkSingleCurrencyMainScreen( onMainScreen { swapButton.assertIsNotDisplayed() } } step("Assert 'Market Price' on single card main screen is displayed") { - onMainScreen { marketPriceBlock.assertIsDisplayed() } + onMainScreen { marketPriceBlock().assertIsDisplayed() } } step("Assert 'Market Price' title equals $cardBlockchain Market Price") { onMainScreen { marketPriceText.assertTextContains("$cardBlockchain Market Price") } } - step("Assert 'Organize tokens' button is not displayed") { - onMainScreen { - assertElementDoesNotExist({ organizeTokensButtonWithoutLazySearch() }, "Organize tokens button") + if (withTransactions) { + step("Assert 'Transactions' block is displayed") { + onMainScreen { transactionsExplorerText.assertIsDisplayed() } } + step("Assert 'Transactions' title is displayed") { + onMainScreen { transactionsTitle.assertIsDisplayed() } + } + step("Assert 'Explorer' icon is displayed") { + onMainScreen { transactionsExplorerIcon.assertIsDisplayed() } + } + } else { + step("Assert empty 'Transactions' block is displayed") { + onMainScreen { emptyTransactionBlock.assertIsDisplayed() } + } + step("Assert empty 'Transactions' block icon is displayed") { + onMainScreen { emptyTransactionBlockIcon.assertIsDisplayed() } + } + step("Assert empty 'Transactions' block text is displayed") { + onMainScreen { emptyTransactionBlockText.assertIsDisplayed() } + } + step("Assert empty 'Transactions' block 'Explore' button is displayed") { + onMainScreen { emptyTransactionBlockExploreButton.assertIsDisplayed() } + } + } + step("Assert 'Organize tokens' button is not displayed") { + onMainScreen { organizeTokensButtonWithoutLazySearch.assertIsNotDisplayed() } } } diff --git a/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt index 23e264e6f5..da077e296f 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt @@ -83,9 +83,12 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) useUnmergedTree = true } - val marketPriceBlock: KNode = child { - hasTestTag(MarketPriceBlockTestTags.BLOCK) - useUnmergedTree = true + @OptIn(ExperimentalTestApi::class) + fun marketPriceBlock(): LazyListItemNode { + return lazyList.childWith { + hasTestTag(MarketPriceBlockTestTags.BLOCK) + useUnmergedTree = true + } } val marketPriceText: KNode = child { @@ -104,12 +107,10 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) useUnmergedTree = true } - fun transactionsExplorerText(): KNode { - return child { - hasTestTag(TransactionHistoryBlockTestTags.EXPLORER_TEXT) - hasText(getResourceString(R.string.common_explorer)) - useUnmergedTree = true - } + val transactionsExplorerText: KNode = child { + hasTestTag(TransactionHistoryBlockTestTags.EXPLORER_TEXT) + hasText(getResourceString(R.string.common_explorer)) + useUnmergedTree = true } val emptyTransactionBlock: KNode = child { @@ -223,12 +224,10 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) } } - fun organizeTokensButtonWithoutLazySearch(): KNode { - return child { - hasTestTag(MainScreenTestTags.ORGANIZE_TOKENS_BUTTON) - hasText(getResourceString(R.string.organize_tokens_title)) - useUnmergedTree = true - } + val organizeTokensButtonWithoutLazySearch: KNode = child { + hasTestTag(MainScreenTestTags.ORGANIZE_TOKENS_BUTTON) + hasText(getResourceString(R.string.organize_tokens_title)) + useUnmergedTree = true } fun tokenNetworkGroupTitle(tokenNetwork: String): KNode { diff --git a/app/src/androidTest/kotlin/com/tangem/tests/FeedbackTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/FeedbackTest.kt index 9742ee2362..2264356999 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/FeedbackTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/FeedbackTest.kt @@ -87,12 +87,13 @@ class FeedbackTest : BaseTestCase() { onSendScreen { nextButton.clickWithAssertion() } } step("Enter address") { - onSendAddressScreen { addressTextField.performTextReplacement(recipientAddress) } + onSendAddressScreen { addressTextField.performTextInput(recipientAddress) } } step("Click 'Next' button") { onSendAddressScreen { nextButton.clickWithAssertion() } } step("Click 'Send' button") { + waitForIdle() onSendConfirmScreen { sendButton.assertIsEnabled() sendButton.performClick() @@ -138,6 +139,7 @@ class FeedbackTest : BaseTestCase() { } } step("Check 'Scan warning' dialog") { + waitForIdle() checkScanWarningDialog() } step("Click on 'Request support' button") { diff --git a/app/src/androidTest/kotlin/com/tangem/tests/SendTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/SendTest.kt index 7cf0a321e3..b4a7286a5f 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/SendTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/SendTest.kt @@ -1,7 +1,9 @@ package com.tangem.tests import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.SwipeDirection import com.tangem.common.extensions.clickWithAssertion +import com.tangem.common.extensions.swipeVertical import com.tangem.common.utils.resetWireMockScenarioState import com.tangem.common.utils.setWireMockScenarioState import com.tangem.scenarios.openMainScreen @@ -42,6 +44,9 @@ class SendTest : BaseTestCase() { step("Synchronize addresses") { synchronizeAddresses(balance) } + step("Swipe up") { + swipeVertical(SwipeDirection.UP) + } step("Click on token with name: $currencyName") { onMainScreen { tokenWithTitleAndAddress(currencyName).clickWithAssertion() } } diff --git a/app/src/androidTest/kotlin/com/tangem/tests/SwapTokenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/SwapTokenTest.kt index 887bffc37a..e30447031f 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/SwapTokenTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/SwapTokenTest.kt @@ -6,7 +6,9 @@ import com.tangem.common.annotations.ApiEnv import com.tangem.common.annotations.ApiEnvConfig import com.tangem.common.constants.TestConstants.TOTAL_BALANCE import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT -import com.tangem.common.extensions.clickWithAssertion +import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG +import com.tangem.common.extensions.* +import com.tangem.common.utils.* import com.tangem.datasource.api.common.config.ApiConfig import com.tangem.datasource.api.common.config.ApiEnvironment import com.tangem.screens.* @@ -28,10 +30,12 @@ class SwapTokenTest : BaseTestCase() { @Test fun networkFeeTest() { val inputAmount = "100" - setupHooks().run { - val tokenTitle = "Polygon" - val balance = TOTAL_BALANCE + val tokenTitle = "Polygon" + val balance = TOTAL_BALANCE + setupHooks().run { + + resetWireMockScenarios() step("Open 'Main Screen'") { openMainScreen() } @@ -71,7 +75,7 @@ class SwapTokenTest : BaseTestCase() { } } step("Input swap amount = '$inputAmount'") { - composeTestRule.waitForIdle() + waitForIdle() onSwapTokenScreen { textInput.clickWithAssertion() textInput.performTextReplacement(inputAmount) @@ -89,7 +93,7 @@ class SwapTokenTest : BaseTestCase() { } step("Assert 'Network fee' block is displayed") { onSwapTokenScreen { - flakySafely(WAIT_UNTIL_TIMEOUT) { + flakySafely(WAIT_UNTIL_TIMEOUT_LONG) { networkFeeBlock.assertIsDisplayed() } } @@ -104,7 +108,12 @@ class SwapTokenTest : BaseTestCase() { @DisplayName("Swap: network error test") @Test fun networkErrorSwapTest() { - setupHooks().run { + setupHooks( + additionalAfterSection = { + enableWiFi() + enableMobileData() + } + ).run { val tokenTitle = "Polygon" val balance = TOTAL_BALANCE @@ -120,6 +129,10 @@ class SwapTokenTest : BaseTestCase() { step("Click on token with name: '$tokenTitle'") { onTokenDetailsScreen { title.assertIsDisplayed() } } + step("Turn off Wi-Fi and Mobile Data") { + disableWiFi() + disableMobileData() + } step("Click on 'Swap' button") { onTokenDetailsScreen { swapButton.performClick() } } @@ -189,7 +202,7 @@ class SwapTokenTest : BaseTestCase() { } } step("Input swap amount = '$inputAmount'") { - composeTestRule.waitForIdle() + waitForIdle() onSwapTokenScreen { textInput.clickWithAssertion() textInput.performTextReplacement(inputAmount) @@ -200,7 +213,7 @@ class SwapTokenTest : BaseTestCase() { } step("Click on 'Network fee' block") { onSwapTokenScreen { - flakySafely(WAIT_UNTIL_TIMEOUT) { + flakySafely(WAIT_UNTIL_TIMEOUT_LONG) { networkFeeBlock.clickWithAssertion() } } diff --git a/app/src/androidTest/kotlin/com/tangem/tests/TermsOfServiceTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/TermsOfServiceTest.kt index 379b5b898e..3b956d2bea 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/TermsOfServiceTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/TermsOfServiceTest.kt @@ -2,9 +2,9 @@ package com.tangem.tests import androidx.test.InstrumentationRegistry.getTargetContext import com.tangem.common.BaseTestCase -import com.tangem.common.extensions.SwipeDirection import com.tangem.common.extensions.clickWithAssertion -import com.tangem.common.extensions.swipeVertical +import com.tangem.common.extensions.launchApp +import com.tangem.common.extensions.stopApp import com.tangem.screens.onDisclaimerScreen import com.tangem.screens.onStoriesScreen import dagger.hilt.android.testing.HiltAndroidTest @@ -49,8 +49,9 @@ class TermsOfServiceTest : BaseTestCase() { @Test fun acceptTermsOfServiceAfterAppRestart() { val packageName = getTargetContext().packageName + val tosUrl = "https://tangem.com/tangem_tos.html" + setupHooks().run { - val tosUrl = "https://tangem.com/tangem_tos.html" step("Assert title of 'Disclaimer screen' is displayed") { onDisclaimerScreen { title.assertIsDisplayed() } } @@ -65,14 +66,11 @@ class TermsOfServiceTest : BaseTestCase() { step("'Accept' button is displayed") { onDisclaimerScreen { acceptButton.assertIsDisplayed() } } - step("Open recent apps") { - device.uiDevice.pressRecentApps() - } - step("Stop app by swipe") { - swipeVertical(SwipeDirection.UP, startHeightRatio = 0.5f) + step("Stop app") { + stopApp(packageName) } step("Launch app") { - device.apps.launch(packageName) + launchApp(packageName) } step("Assert title of 'Disclaimer screen' is displayed") { onDisclaimerScreen { title.assertIsDisplayed() } @@ -86,16 +84,22 @@ class TermsOfServiceTest : BaseTestCase() { } } step("Click on 'Accept' button") { - onDisclaimerScreen { acceptButton.clickWithAssertion() } + onDisclaimerScreen { + waitForIdle() + acceptButton.clickWithAssertion() + } } - step("Open recent apps") { - device.uiDevice.pressRecentApps() + step("Assert 'Stories' screen is opened") { + onStoriesScreen { + scanButton.assertIsDisplayed() + orderButton.assertIsDisplayed() + } } - step("Stop app by swipe") { - swipeVertical(SwipeDirection.UP, startHeightRatio = 0.5f) + step("Stop app") { + stopApp(packageName) } step("Launch app") { - device.apps.launch(packageName) + launchApp(packageName) } step("Assert 'Stories' screen is opened") { onStoriesScreen { 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 1c7fd38068..6fbc4b4812 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/balance/TotalBalanceUpdateTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/balance/TotalBalanceUpdateTest.kt @@ -74,7 +74,7 @@ class TotalBalanceUpdateTest : BaseTestCase() { } step("Open 'Markets screen'") { swipeMarketsBlock(SwipeDirection.UP) - composeTestRule.waitForIdle() + waitForIdle() } step("Click on $tokenTitle token") { onMarketsScreen { tokenWithTitle(tokenTitle).clickWithAssertion() } @@ -95,7 +95,7 @@ class TotalBalanceUpdateTest : BaseTestCase() { onDialog { continueButton.assertIsNotDisplayed() } } step("Go back to 'Markets: tokens list'") { - composeTestRule.waitForIdle() + waitForIdle() onMarketsScreen { topBarBackButton.clickWithAssertion() } } step("Close 'Markets screen'") { @@ -151,7 +151,7 @@ class TotalBalanceUpdateTest : BaseTestCase() { onMainScreen { totalBalanceText.assertTextContains(TOTAL_BALANCE) } } step("Long click on token with name: '$tokenTitle'") { - composeTestRule.waitForIdle() + waitForIdle() onMainScreen { tokenWithTitleAndAddress(tokenTitle).performTouchInput { longClick( diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index ed454e9111..0920d365f9 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -290,7 +290,7 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration. /** * Initialize components that need to be initialized before [super.onCreate] is called */ - private fun preInit() { + fun preInit() { tangemAppLoggerInitializer.initialize() registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks) }