Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-10 11:46:50 +03:00
parent 23ddf1a665
commit 8632680f20
13 changed files with 140 additions and 124 deletions

View file

@ -7,23 +7,91 @@ import dagger.hilt.android.testing.OnComponentReadyRunner
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import timber.log.Timber
class ApplicationInjectionExecutionRule : TestRule {
class ApplicationInjectionExecutionRule(
private val toggleStates: Map<String, Boolean>
) : TestRule {
private val tangemApplication: TangemApplication
get() = ApplicationProvider.getApplicationContext()
private var originalFeatureTogglesValues: Map<String, String>? = null
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
saveOriginalFeatureToggles()
overrideFeatureToggles()
OnComponentReadyRunner.addListener(
tangemApplication, ApplicationEntryPoint::class.java
) { _: ApplicationEntryPoint ->
tangemApplication.preInit()
tangemApplication.init()
}
base.evaluate()
try {
base.evaluate()
} finally {
restoreOriginalFeatureToggles()
}
}
}
}
@Suppress("UNCHECKED_CAST")
private fun saveOriginalFeatureToggles() {
try {
val featureTogglesClass = Class.forName("com.tangem.core.configtoggle.FeatureToggles")
val valuesField = featureTogglesClass.getDeclaredField("values")
valuesField.isAccessible = true
originalFeatureTogglesValues = valuesField.get(null) as Map<String, String>
} catch (e: Exception) {
Timber.e("Failed to save original toggles values: ${e.message}")
}
}
@Suppress("UNCHECKED_CAST")
private fun overrideFeatureToggles() {
try {
val featureTogglesClass = Class.forName("com.tangem.core.configtoggle.FeatureToggles")
val valuesField = featureTogglesClass.getDeclaredField("values")
valuesField.isAccessible = true
val originalValues = originalFeatureTogglesValues ?:
(valuesField.get(null) as Map<String, String>)
val newValues = originalValues.toMutableMap()
toggleStates.forEach { (toggle, enabled) ->
if (enabled) {
newValues[toggle] = "1.0.0"
} else {
newValues.remove(toggle)
}
}
valuesField.set(null, newValues)
Timber.i("FeatureToggles.values updated: $toggleStates")
} catch (e: Exception) {
Timber.e("FeatureToggles.values didn't change with error: ${e.message}")
}
}
private fun restoreOriginalFeatureToggles() {
try {
if (originalFeatureTogglesValues != null) {
val featureTogglesClass = Class.forName("com.tangem.core.configtoggle.FeatureToggles")
val valuesField = featureTogglesClass.getDeclaredField("values")
valuesField.isAccessible = true
valuesField.set(null, originalFeatureTogglesValues)
Timber.i("FeatureToggles.values restored")
}
} catch (e: Exception) {
Timber.e("FeatureToggles.values didn't restored with error: ${e.message}")
}
}
}

View file

@ -16,8 +16,6 @@ import com.tangem.common.allure.FailedStepScreenshotInterceptor
import com.tangem.common.constants.TestConstants.ALLURE_LABEL_NAME
import com.tangem.common.constants.TestConstants.ALLURE_LABEL_VALUE
import com.tangem.common.rules.ApiEnvironmentRule
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.core.configtoggle.feature.MutableFeatureTogglesManager
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
@ -53,9 +51,6 @@ abstract class BaseTestCase : TestCase(
@Inject
lateinit var appPreferencesStore: AppPreferencesStore
@Inject
lateinit var featureTogglesManager: FeatureTogglesManager
@Inject
lateinit var promoRepository: PromoRepository
@ -75,7 +70,7 @@ abstract class BaseTestCase : TestCase(
@JvmField
val ruleChain: TestRule = RuleChain
.outerRule(hiltRule)
.around(ApplicationInjectionExecutionRule())
.around(applicationInjectionRule())
.around(permissionRule)
.around(apiEnvironmentRule)
.around(composeTestRule)
@ -110,7 +105,6 @@ abstract class BaseTestCase : TestCase(
apiEnvironmentRule.setup(apiConfigsManager)
ActivityScenario.launch(MainActivity::class.java)
Intents.init()
setFeatureToggles()
additionalBeforeSection()
}.after {
additionalAfterSection()
@ -141,14 +135,15 @@ abstract class BaseTestCase : TestCase(
fun waitForIdle() = composeTestRule.waitForIdle()
private fun setFeatureToggles() {
runBlocking {
with(featureTogglesManager as MutableFeatureTogglesManager) {
changeToggle("NEW_TOKEN_RECEIVE_ENABLED", true)
changeToggle("WALLET_BALANCE_FETCHER_ENABLED", true)
changeToggle("SWAP_REDESIGN_ENABLED", true)
changeToggle("NEW_ONRAMP_MAIN_ENABLED", true)
}
}
private fun applicationInjectionRule(): ApplicationInjectionExecutionRule {
return ApplicationInjectionExecutionRule(
toggleStates = mapOf(
"NEW_TOKEN_RECEIVE_ENABLED" to true,
"WALLET_BALANCE_FETCHER_ENABLED" to true,
"SWAP_REDESIGN_ENABLED" to true,
"NEW_ONRAMP_MAIN_ENABLED" to true,
"HOT_WALLET_ENABLED" to true
)
)
}
}

View file

@ -26,8 +26,11 @@ fun BaseTestCase.scanCard(
step("Click on 'Accept' button") {
onDisclaimerScreen { acceptButton.clickWithAssertion() }
}
step("Click on 'Scan' button") {
onStoriesScreen { scanButton.clickWithAssertion() }
step("Click on 'Get started' button") {
onStoriesScreen { getStartedButton.clickWithAssertion() }
}
step("Click on 'Scan card or ring' button") {
onCreateWalletStartScreen { scanCardOrRingButton.clickWithAssertion() }
}
if (alreadyActivatedDialogIsShown) {
step("Click on 'This is my wallet' button") {

View file

@ -0,0 +1,23 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.BaseButtonTestTags
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 com.tangem.features.onboarding.v2.impl.R as OnboardingImplR
class CreateWalletStartPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<CreateWalletStartPageObject>(semanticsProvider = semanticsProvider) {
val scanCardOrRingButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(OnboardingImplR.string.welcome_unlock_card))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onCreateWalletStartScreen(function: CreateWalletStartPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -42,6 +42,7 @@ class DetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
hasText(getResourceString(R.string.app_settings_title))
}
val contactSupportButton: KNode = child {
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
hasText(getResourceString(R.string.details_row_title_contact_to_support))
@ -50,6 +51,11 @@ class DetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
hasText(getResourceString(R.string.disclaimer_title))
}
val versionName: KNode = child {
hasTestTag(DetailsScreenTestTags.VERSION_NAME)
useUnmergedTree = true
}
}
internal fun BaseTestCase.onDetailsScreen(function: DetailsPageObject.() -> Unit) =

View file

@ -2,20 +2,20 @@ package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.StoriesScreenTestTags
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.features.onboarding.v2.impl.R
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 StoriesPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<StoriesPageObject>(semanticsProvider = semanticsProvider) {
val scanButton: KNode = child {
hasTestTag(StoriesScreenTestTags.SCAN_BUTTON)
}
val orderButton: KNode = child {
hasTestTag(StoriesScreenTestTags.ORDER_BUTTON)
val getStartedButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_get_started))
useUnmergedTree = true
}
}

View file

@ -4,10 +4,7 @@ import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.domain.models.scan.ProductType
import com.tangem.scenarios.openMainScreen
import com.tangem.screens.onDetailsScreen
import com.tangem.screens.onReferralProgramScreen
import com.tangem.screens.onTopBar
import com.tangem.screens.onWalletSettingsScreen
import com.tangem.screens.*
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
@ -31,9 +28,6 @@ class DetailsTest : BaseTestCase() {
step("Assert 'Wallet connect' button is visible") {
walletConnectButton.assertIsDisplayed()
}
step("Assert 'Scan card' button is visible") {
scanCardButton.assertIsDisplayed()
}
step("Assert 'Buy Tangem card' button is visible") {
buyTangemButton.assertIsDisplayed()
}
@ -131,9 +125,6 @@ class DetailsTest : BaseTestCase() {
step("Assert 'Wallet connect' button does not exist") {
walletConnectButton.assertIsNotDisplayed()
}
step("Assert 'Scan card' button is visible") {
scanCardButton.assertIsDisplayed()
}
step("Assert 'Buy Tangem card' button is visible") {
buyTangemButton.assertIsDisplayed()
}

View file

@ -139,8 +139,11 @@ class FeedbackTest : BaseTestCase() {
step("Set scanning error") {
MockProvider.setEmulateError(TangemSdkError.TagLost())
}
step("Click on 'Scan' button") {
onStoriesScreen { scanButton.performClick() }
step("Click on 'Get started' button") {
onStoriesScreen { getStartedButton.clickWithAssertion() }
}
step("Click on 'Scan card or ring' button") {
onCreateWalletStartScreen { scanCardOrRingButton.clickWithAssertion() }
}
step("Force show 'Scan warning' dialog"){
runOnUiThread {
@ -178,8 +181,11 @@ class FeedbackTest : BaseTestCase() {
step("Click on 'Accept' button") {
onDisclaimerScreen { acceptButton.clickWithAssertion() }
}
step("Click on 'Scan' button") {
onStoriesScreen { scanButton.clickWithAssertion() }
step("Click on 'Get started' button") {
onStoriesScreen { getStartedButton.clickWithAssertion() }
}
step("Click on 'Scan card or ring' button") {
onCreateWalletStartScreen { scanCardOrRingButton.clickWithAssertion() }
}
step("Check 'Already used Wallet' dialog") {
checkAlreadyUsedWalletDialog()

View file

@ -1,39 +0,0 @@
package com.tangem.tests
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.screens.onDisclaimerScreen
import com.tangem.screens.onMainScreen
import com.tangem.screens.onStoriesScreen
import com.tangem.tap.domain.sdk.mocks.MockProvider
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Test
@HiltAndroidTest
class ScanErrorTest : BaseTestCase() {
@Test
fun goToMainTest() =
setupHooks().run {
onDisclaimerScreen {
step("Click on 'Accept' button") {
acceptButton.clickWithAssertion()
}
}
onStoriesScreen {
step("Click on 'Scan' button emulating scan error") {
MockProvider.setEmulateError()
scanButton.clickWithAssertion()
}
step("Click on 'Scan' button again without emulating error") {
MockProvider.resetEmulateError()
scanButton.clickWithAssertion()
}
}
onMainScreen {
step("Make sure wallet screen is visible") {
assertIsDisplayed()
}
}
}
}

View file

@ -1,37 +0,0 @@
package com.tangem.tests
import android.content.Intent.ACTION_VIEW
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.screens.onDisclaimerScreen
import com.tangem.screens.onStoriesScreen
import dagger.hilt.android.testing.HiltAndroidTest
import io.github.kakaocup.kakao.intent.KIntent
@HiltAndroidTest
class StoriesTest : BaseTestCase() {
// @Test
fun clickOnOrderButtonTest() =
setupHooks().run {
val buyWalletUrl = "https://buy.tangem.com/"
onDisclaimerScreen {
step("Click on 'Accept' button") {
acceptButton.clickWithAssertion()
}
}
onStoriesScreen {
step("Click on 'Order' button") {
orderButton.clickWithAssertion()
}
step("Assert: browser opened") {
val expectedIntent = KIntent {
hasAction(ACTION_VIEW)
hasData { toString().startsWith(buyWalletUrl) }
}
expectedIntent.intended()
device.uiDevice.pressBack()
}
}
}
}

View file

@ -37,8 +37,7 @@ class TermsOfServiceTest : BaseTestCase() {
}
step("Assert 'Stories' screen is opened") {
onStoriesScreen {
scanButton.assertIsDisplayed()
orderButton.assertIsDisplayed()
getStartedButton.assertIsDisplayed()
}
}
}
@ -91,8 +90,7 @@ class TermsOfServiceTest : BaseTestCase() {
}
step("Assert 'Stories' screen is opened") {
onStoriesScreen {
scanButton.assertIsDisplayed()
orderButton.assertIsDisplayed()
getStartedButton.assertIsDisplayed()
}
}
step("Stop app") {
@ -103,8 +101,7 @@ class TermsOfServiceTest : BaseTestCase() {
}
step("Assert 'Stories' screen is opened") {
onStoriesScreen {
scanButton.assertIsDisplayed()
orderButton.assertIsDisplayed()
getStartedButton.assertIsDisplayed()
}
}
}