Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-06 11:45:18 +03:00
parent 173ee13120
commit 4b2eb80765
13 changed files with 123 additions and 74 deletions

View file

@ -19,6 +19,7 @@ class ApplicationInjectionExecutionRule : TestRule {
OnComponentReadyRunner.addListener(
tangemApplication, ApplicationEntryPoint::class.java
) { _: ApplicationEntryPoint ->
tangemApplication.preInit()
tangemApplication.init()
}
base.evaluate()

View file

@ -136,6 +136,7 @@ abstract class BaseTestCase : TestCase(
composeTestRule.onRoot(useUnmergedTree = useUnmergedTree).printToLog(tag, maxDepth)
}
fun waitForIdle() = composeTestRule.waitForIdle()
private fun setFeatureToggles() {
runBlocking {

View file

@ -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"

View file

@ -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
}

View file

@ -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") {

View file

@ -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() }
}
}

View file

@ -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<LazyListItemNode> {
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 {

View file

@ -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") {

View file

@ -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() }
}

View file

@ -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()
}
}

View file

@ -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 {

View file

@ -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(

View file

@ -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)
}