Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-03 21:52:57 +03:00
commit a0c9ace219
1607 changed files with 40580 additions and 16776 deletions

1
.gitignore vendored
View file

@ -12,6 +12,7 @@
**/build/outputs/**
**/build/reports/**
**/build/tmp/**
**/test-results/**
# Local configuration file (sdk path, etc)
local.properties

View file

@ -5,6 +5,39 @@
<JavaCodeStyleSettings>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="5" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="3" />
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="" withSubpackages="true" static="false" module="true" />
<package name="android" withSubpackages="true" static="true" />
<package name="androidx" withSubpackages="true" static="true" />
<package name="com" withSubpackages="true" static="true" />
<package name="junit" withSubpackages="true" static="true" />
<package name="net" withSubpackages="true" static="true" />
<package name="org" withSubpackages="true" static="true" />
<package name="java" withSubpackages="true" static="true" />
<package name="javax" withSubpackages="true" static="true" />
<package name="" withSubpackages="true" static="true" />
<emptyLine />
<package name="android" withSubpackages="true" static="false" />
<emptyLine />
<package name="androidx" withSubpackages="true" static="false" />
<emptyLine />
<package name="com" withSubpackages="true" static="false" />
<emptyLine />
<package name="junit" withSubpackages="true" static="false" />
<emptyLine />
<package name="net" withSubpackages="true" static="false" />
<emptyLine />
<package name="org" withSubpackages="true" static="false" />
<emptyLine />
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
</value>
</option>
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">

40
.idea/file.template.settings.xml generated Normal file
View file

@ -0,0 +1,40 @@
<application>
<component name="ExportableFileTemplateSettings">
<default_templates>
<template name="Decompose component.kt" file-name="${NAME}Component" reformat="true" live-template-enabled="false">
<template name="Decompose component.kt.child.5.kt" file-name="di/${NAME}ModelModule" reformat="true" live-template-enabled="false" />
<template name="Decompose component.kt.child.4.kt" file-name="di/${NAME}ComponentModule" reformat="true" live-template-enabled="false" />
<template name="Decompose component.kt.child.0.kt" file-name="entity/${NAME}UM" reformat="true" live-template-enabled="false" />
<template name="Decompose component.kt.child.1.kt" file-name="ui/${NAME}Content" reformat="true" live-template-enabled="false" />
<template name="Decompose component.kt.child.2.kt" file-name="model/${NAME}Model" reformat="true" live-template-enabled="false" />
<template name="Decompose component.kt.child.3.kt" file-name="Default${NAME}Component" reformat="true" live-template-enabled="false" />
</template>
<template name="Decompose component.kt.child.0.kt" file-name="entity/${NAME}UM" reformat="true" live-template-enabled="false" />
<template name="Decompose component.kt.child.1.kt" file-name="ui/${NAME}Content" reformat="true" live-template-enabled="false" />
<template name="Decompose component.kt.child.2.kt" file-name="model/${NAME}Model" reformat="true" live-template-enabled="false" />
<template name="Decompose component.kt.child.3.kt" file-name="Default${NAME}Component" reformat="true" live-template-enabled="false" />
<template name="Decompose component.kt.child.4.kt" file-name="di/${NAME}ComponentModule" reformat="true" live-template-enabled="false" />
<template name="Decompose component.kt.child.5.kt" file-name="di/${NAME}ModelModule" reformat="true" live-template-enabled="false" />
<template name="FeatureToggles.kt" file-name="${NAME}FeatureToggles" reformat="true" live-template-enabled="false">
<template name="FeatureToggles.kt.child.0.kt" file-name="Default${NAME}FeatureToggles" reformat="true" live-template-enabled="false" />
</template>
<template name="FeatureToggles.kt.child.0.kt" file-name="Default${NAME}FeatureToggles" reformat="true" live-template-enabled="false" />
<template name="FlowPackaging.kt" file-name="producer/${NAME}Producer" reformat="true" live-template-enabled="false">
<template name="FlowPackaging.kt.child.0.kt" file-name="di/${NAME}FetcherModule" reformat="true" live-template-enabled="false" />
<template name="FlowPackaging.kt.child.1.kt" file-name="di/${NAME}ProducerFactoryModule" reformat="true" live-template-enabled="false" />
<template name="FlowPackaging.kt.child.2.kt" file-name="di/${NAME}SupplierModule" reformat="true" live-template-enabled="false" />
<template name="FlowPackaging.kt.child.3.kt" file-name="fetcher/Default${NAME}Fetcher" reformat="true" live-template-enabled="false" />
<template name="FlowPackaging.kt.child.4.kt" file-name="producer/Default${NAME}Producer" reformat="true" live-template-enabled="false" />
<template name="FlowPackaging.kt.child.5.kt" file-name="fetcher/${NAME}Fetcher" reformat="true" live-template-enabled="false" />
<template name="FlowPackaging.kt.child.6.kt" file-name="supplier/${NAME}Supplier" reformat="true" live-template-enabled="false" />
</template>
<template name="FlowPackaging.kt.child.0.kt" file-name="di/${NAME}FetcherModule" reformat="true" live-template-enabled="false" />
<template name="FlowPackaging.kt.child.1.kt" file-name="di/${NAME}ProducerFactoryModule" reformat="true" live-template-enabled="false" />
<template name="FlowPackaging.kt.child.2.kt" file-name="di/${NAME}SupplierModule" reformat="true" live-template-enabled="false" />
<template name="FlowPackaging.kt.child.3.kt" file-name="fetcher/Default${NAME}Fetcher" reformat="true" live-template-enabled="false" />
<template name="FlowPackaging.kt.child.4.kt" file-name="producer/Default${NAME}Producer" reformat="true" live-template-enabled="false" />
<template name="FlowPackaging.kt.child.5.kt" file-name="fetcher/${NAME}Fetcher" reformat="true" live-template-enabled="false" />
<template name="FlowPackaging.kt.child.6.kt" file-name="supplier/${NAME}Supplier" reformat="true" live-template-enabled="false" />
</default_templates>
</component>
</application>

View file

@ -0,0 +1,13 @@
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}#end
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
interface ${NAME}Component : ComposableContentComponent {
data class Params(
val data: Any, // TODO("Not yet implemented")
)
interface Factory : ComponentFactory<Params, ${NAME}Component>
}

View file

@ -0,0 +1,5 @@
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}.entity#end
internal data class ${NAME}UM(
val onBackClick: () -> Unit,
)

View file

@ -0,0 +1,56 @@
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}.ui#end
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import ${PACKAGE_NAME}.entity.${NAME}UM
@Composable
internal fun ${NAME}Content(state: ${NAME}UM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.background(color = TangemTheme.colors.background.secondary)
.fillMaxSize()
.imePadding()
.systemBarsPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AppBarWithBackButton(
onBackClick = state.onBackClick,
modifier = Modifier.height(TangemTheme.dimens.size56),
)
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = TangemTheme.dimens.spacing16)
.weight(1f),
) {
}
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ${NAME}ContentPreview(@PreviewParameter(PreviewStateProvider::class) state: ${NAME}UM) {
TangemThemePreview {
${NAME}Content(state = state)
}
}
private class PreviewStateProvider : CollectionPreviewParameterProvider<${NAME}UM>(
buildList {
TODO("Not yet implemented")
},
)

View file

@ -0,0 +1,31 @@
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}.model#end
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import ${PACKAGE_NAME}.${NAME}Component
import ${PACKAGE_NAME}.entity.${NAME}UM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@ModelScoped
internal class ${NAME}Model @Inject constructor(
paramsContainer: ParamsContainer,
private val messageSender: UiMessageSender,
private val router: Router,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
private val params = paramsContainer.require<${NAME}Component.Params>()
val uiState: StateFlow<${NAME}UM>
field: MutableStateFlow<${NAME}UM> = MutableStateFlow(getInitialState())
private fun getInitialState(): ${NAME}UM {
TODO("Not yet implemented")
}
}

View file

@ -0,0 +1,39 @@
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}#end
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import ${PACKAGE_NAME}.model.${NAME}Model
import ${PACKAGE_NAME}.ui.${NAME}Content
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class Default${NAME}Component @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: ${NAME}Component.Params,
) : AppComponentContext by appComponentContext, ${NAME}Component {
private val model: ${NAME}Model = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
${NAME}Content(modifier = modifier, state = state)
BackHandler(onBack = state.onBackClick)
}
@AssistedFactory
interface Factory : ${NAME}Component.Factory {
override fun create(
context: AppComponentContext,
params: ${NAME}Component.Params,
): Default${NAME}Component
}
}

View file

@ -0,0 +1,20 @@
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}.di#end
import ${PACKAGE_NAME}.Default${NAME}Component
import ${PACKAGE_NAME}.${NAME}Component
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface ${NAME}ComponentModule {
@Binds
@Singleton
fun bind${NAME}ComponentFactory(
factory: Default${NAME}Component.Factory,
): ${NAME}Component.Factory
}

View file

@ -0,0 +1,20 @@
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}.di#end
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import ${PACKAGE_NAME}.model.${NAME}Model
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(ModelComponent::class)
internal interface ${NAME}ModelModule {
@Binds
@IntoMap
@ClassKey(${NAME}Model::class)
fun bind${NAME}Model(model: ${NAME}Model): Model
}

11
.idea/fileTemplates/FeatureToggles.kt generated Normal file
View file

@ -0,0 +1,11 @@
#if ($TOGGLE_NAME && $TOGGLE_NAME != "")
#set($TOGGLE = $TOGGLE_NAME)
#else
#set($TOGGLE = "TODO")
#end
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
interface ${NAME}FeatureToggles {
val isFeatureEnabled: Boolean
}

View file

@ -0,0 +1,16 @@
#if ($TOGGLE_NAME && $TOGGLE_NAME != "")
#set($TOGGLE = $TOGGLE_NAME)
#else
#set($TOGGLE = "TODO")
#end
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
internal class Default${NAME}FeatureToggles(
private val featureTogglesManager: FeatureTogglesManager,
) : ${NAME}FeatureToggles {
override val isFeatureEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "$TOGGLE")
}

17
.idea/fileTemplates/FlowPackaging.kt generated Normal file
View file

@ -0,0 +1,17 @@
#if ($FlowType && $FlowType != "")
#set($Type = $FlowType)
#else
#set($Type = "TODO")
#end
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}.producer#end
import com.tangem.domain.core.flow.FlowProducer
interface ${NAME}Producer : FlowProducer<${FlowType}> {
data class Params(
val id: String, // TODO("Not yet implemented")
)
interface Factory : FlowProducer.Factory<Params, ${NAME}Producer>
}

View file

@ -0,0 +1,18 @@
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}.di#end
import ${PACKAGE_NAME}.fetcher.Default${NAME}Fetcher
import ${PACKAGE_NAME}.fetcher.${NAME}Fetcher
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface ${NAME}FetcherModule {
@Binds
@Singleton
fun bind${NAME}Fetcher(impl: Default${NAME}Fetcher): ${NAME}Fetcher
}

View file

@ -0,0 +1,20 @@
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}.di#end
import ${PACKAGE_NAME}.producer.Default${NAME}Producer
import ${PACKAGE_NAME}.producer.${NAME}Producer
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface ${NAME}ProducerFactoryModule {
@Binds
@Singleton
fun bind${NAME}ProducerFactory(
impl: Default${NAME}Producer.Factory,
): ${NAME}Producer.Factory
}

View file

@ -0,0 +1,23 @@
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}.di#end
import ${PACKAGE_NAME}.producer.${NAME}Producer
import ${PACKAGE_NAME}.supplier.${NAME}Supplier
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object ${NAME}SupplierModule {
@Provides
@Singleton
fun provide${NAME}Supplier(factory: ${NAME}Producer.Factory): ${NAME}Supplier {
return object : ${NAME}Supplier(
factory = factory,
keyCreator = { TODO("Not yet implemented") },
) {}
}
}

View file

@ -0,0 +1,14 @@
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}.fetcher#end
import arrow.core.Either
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import javax.inject.Inject
internal class Default${NAME}Fetcher @Inject constructor(
private val dispatchers: CoroutineDispatcherProvider,
) : ${NAME}Fetcher {
override suspend fun invoke(params: ${NAME}Fetcher.Params): Either<Throwable, Unit> {
TODO("Not yet implemented")
}
}

View file

@ -0,0 +1,31 @@
#if ($FlowType && $FlowType != "")
#set($Type = $FlowType)
#else
#set($Type = "TODO")
#end
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}.producer#end
import arrow.core.Option
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
internal class Default${NAME}Producer @AssistedInject constructor(
@Assisted val params: ${NAME}Producer.Params,
private val dispatchers: CoroutineDispatcherProvider,
) : TestProducer {
override val fallback: Option<$Type>
get() = TODO("Not yet implemented")
override fun produce(): Flow<$Type> {
TODO("Not yet implemented")
}
@AssistedFactory
interface Factory : ${NAME}Producer.Factory {
override fun create(params: ${NAME}Producer.Params): Default${NAME}Producer
}
}

View file

@ -0,0 +1,10 @@
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}.fetcher#end
import com.tangem.domain.core.flow.FlowFetcher
interface ${NAME}Fetcher : FlowFetcher<${NAME}Fetcher.Params> {
data class Params(
val id: String, // TODO("Not yet implemented")
)
}

View file

@ -0,0 +1,15 @@
#if ($FlowType && $FlowType != "")
#set($Type = $FlowType)
#else
#set($Type = "TODO")
#end
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}.supplier#end
import ${PACKAGE_NAME}.producer.${NAME}Producer
import com.tangem.domain.core.flow.FlowCachingSupplier
import kotlinx.coroutines.flow.Flow
abstract class ${NAME}Supplier(
override val factory: ${NAME}Producer.Factory,
override val keyCreator: (${NAME}Producer.Params) -> String,
) : FlowCachingSupplier<${NAME}Producer, ${NAME}Producer.Params, $Type>()

View file

@ -28,6 +28,7 @@ android {
packaging {
jniLibs {
useLegacyPackaging = true
excludes.add("lib/x86_64/libargon2.so")
}
resources.excludes.add("META-INF/LICENSE.md")
resources.excludes.add("META-INF/NOTICE.md")
@ -96,6 +97,9 @@ configurations.all {
force(
"org.bouncycastle:bcpkix-jdk15on:1.70",
deps.kotlin.coroutines,
deps.kotlin.coroutines.jvm,
deps.kotlin.coroutines.android,
)
}
}
@ -111,6 +115,7 @@ dependencies {
implementation(projects.domain.account)
implementation(projects.domain.models)
implementation(projects.domain.core)
api(projects.domain.common)
implementation(projects.domain.card)
implementation(projects.domain.demo)
implementation(projects.domain.demo.models)
@ -154,6 +159,7 @@ dependencies {
implementation(projects.domain.walletManager)
implementation(projects.domain.walletManager.models)
implementation(projects.domain.yieldSupply)
implementation(projects.domain.blockaid)
implementation(projects.common)
implementation(projects.common.routing)
@ -267,6 +273,8 @@ dependencies {
implementation(projects.features.welcome.impl)
implementation(projects.features.createWalletSelection.api)
implementation(projects.features.createWalletSelection.impl)
implementation(projects.features.createWalletStart.api)
implementation(projects.features.createWalletStart.impl)
implementation(projects.features.home.api)
implementation(projects.features.home.impl)
implementation(projects.features.account.api)

View file

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

View file

@ -1,8 +1,8 @@
package com.tangem.common
import android.Manifest
import androidx.compose.ui.test.isRoot
import androidx.compose.ui.test.junit4.createEmptyComposeRule
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.printToLog
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.intent.Intents
@ -100,6 +100,12 @@ abstract class BaseTestCase : TestCase(
value = false
)
}
appPreferencesStore.editData { mutablePreferences ->
mutablePreferences.set(
key = PreferencesKeys.getShouldShowNotificationKey("EnablePushesReminderNotification"),
value = false
)
}
promoRepository.setNeverToShowWalletPromo(PromoId.Sepa)
}
apiEnvironmentRule.setup(apiConfigsManager)
@ -115,6 +121,8 @@ abstract class BaseTestCase : TestCase(
/**
* Prints the Compose semantics tree to logcat for debugging UI tests.
*
* @param rootIndex Use rootIndex > 0, if you need to print semantics tree for bottom sheet.
* Default: 0.
* @param useUnmergedTree When true, shows unmerged tree with all individual nodes.
* Use for accessing inner elements of compound components.
* Default: false (merged tree - accessibility view).
@ -123,22 +131,23 @@ abstract class BaseTestCase : TestCase(
* Default: Int.MAX_VALUE (unlimited depth).
*/
fun printSemanticTree(
rootIndex: Int = 0,
useUnmergedTree: Boolean = false,
tag: String = "SEMANTIC_TREE",
maxDepth: Int = Int.MAX_VALUE)
{
composeTestRule.onRoot(useUnmergedTree = useUnmergedTree).printToLog(tag, maxDepth)
maxDepth: Int = Int.MAX_VALUE
) {
composeTestRule.onAllNodes(isRoot(), useUnmergedTree = useUnmergedTree)[rootIndex]
.printToLog(tag, maxDepth)
}
fun waitForIdle() = composeTestRule.waitForIdle()
private fun setFeatureToggles() {
runBlocking {
with(featureTogglesManager as MutableFeatureTogglesManager) {
changeToggle("WALLET_CONNECT_REDESIGN_ENABLED", true)
changeToggle("NEW_TOKEN_RECEIVE_ENABLED", true)
changeToggle("WALLET_BALANCE_FETCHER_ENABLED", true)
changeToggle("SEND_VIA_SWAP_ENABLED", true)
changeToggle("SWAP_REDESIGN_ENABLED", true)
changeToggle("SEND_REDESIGN_ENABLED", true)
changeToggle("NEW_ONRAMP_MAIN_ENABLED", true)
}
}

View file

@ -4,8 +4,11 @@ object TestConstants {
const val TOTAL_BALANCE = "$3,299.18"
const val RECIPIENT_ADDRESS = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0"
const val BITCOIN_ADDRESS = "bc1qtg9aa6jcpqtvun0pe0uct7sxm8nq2nsxfmfxm3"
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

@ -25,7 +25,7 @@ fun BaseTestCase.pullToRefresh() {
direction = SwipeDirection.DOWN,
startHeightRatio = 0.2f,
endHeightRatio = 0.8f,
steps = 2000
steps = 1000
)
}
@ -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

@ -0,0 +1,38 @@
package com.tangem.common.utils
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import androidx.test.core.app.ApplicationProvider
fun getClipboardText(context: Context): String? {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
return if (clipboard.hasPrimaryClip()) {
clipboard.primaryClip?.getItemAt(0)?.text?.toString()
} else {
null
}
}
fun setClipboardText(context: Context, text: String?) {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("label", text)
clipboard.setPrimaryClip(clip)
}
fun clearClipboard(
context: Context = ApplicationProvider.getApplicationContext()
) {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.clearPrimaryClip()
}
fun assertClipboardTextEquals(
expected: String,
context: Context = ApplicationProvider.getApplicationContext()
) {
val actual = getClipboardText(context)
assert(actual == expected) {
"Clipboard text mismatch.\nExpected: '$expected'\nActual: '$actual'"
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.scenarios
import androidx.compose.ui.test.hasText
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.domain.models.scan.ProductType
@ -7,12 +8,14 @@ import com.tangem.screens.*
import com.tangem.screens.AlreadyUsedWalletDialogPageObject.thisIsMyWalletButton
import com.tangem.tap.domain.sdk.mocks.MockContent
import com.tangem.tap.domain.sdk.mocks.MockProvider
import com.tangem.utils.StringsSigns.DASH_SIGN
import io.qameta.allure.kotlin.Allure.step
fun BaseTestCase.openMainScreen(
fun BaseTestCase.scanCard(
productType: ProductType? = null,
mockContent: MockContent? = null,
alreadyActivatedDialogIsShown : Boolean = false
alreadyActivatedDialogIsShown: Boolean = false,
isTwinsCard: Boolean = false,
) {
if (productType != null) {
MockProvider.setMocks(productType)
@ -28,10 +31,34 @@ fun BaseTestCase.openMainScreen(
}
if (alreadyActivatedDialogIsShown) {
step("Click on 'This is my wallet' button") {
composeTestRule.waitForIdle()
waitForIdle()
AlreadyUsedWalletDialogPageObject { thisIsMyWalletButton.click() }
}
}
if (isTwinsCard) {
step("Click on 'Continue' button") {
onOnboardingScreen { continueButton.clickWithAssertion() }
}
step("Click on 'Continue to my wallet' button") {
onOnboardingScreen { continueToMyWalletButton.clickWithAssertion() }
}
}
}
fun BaseTestCase.openMainScreen(
productType: ProductType? = null,
mockContent: MockContent? = null,
alreadyActivatedDialogIsShown: Boolean = false,
isTwinsCard: Boolean = false,
) {
step("Scan card") {
scanCard(
productType = productType,
mockContent = mockContent,
alreadyActivatedDialogIsShown = alreadyActivatedDialogIsShown,
isTwinsCard = isTwinsCard,
)
}
step("Assert 'Main' screen is displayed") {
onMainScreen { screenContainer.assertIsDisplayed() }
}
@ -40,17 +67,35 @@ fun BaseTestCase.openMainScreen(
}
}
fun BaseTestCase.synchronizeAddresses(balance: String) {
fun BaseTestCase.synchronizeAddresses(
balance: String? = null,
isBalanceAvailable: Boolean = true
) {
step("Click on 'Synchronize addresses' button") {
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
}
step("Assert wallet balance = '$balance'") {
onMainScreen { totalBalanceText.assertTextContains(balance) }
when {
!isBalanceAvailable -> step("Assert wallet balance = '$DASH_SIGN'") {
onMainScreen { totalBalanceText.assertTextContains(DASH_SIGN) }
}
balance != null -> {
step("Assert wallet balance != '$DASH_SIGN'") {
onMainScreen { totalBalanceText.assert(!hasText(DASH_SIGN)) }
}
step("Assert wallet balance = '$balance'") {
onMainScreen { totalBalanceText.assertTextContains(balance) }
}
}
else -> step("Assert wallet balance != '$DASH_SIGN'") {
onMainScreen { totalBalanceText.assert(!hasText(DASH_SIGN)) }
}
}
}
fun BaseTestCase.openDeviceSettingsScreen() {
step("Open wallet details") {
waitForIdle()
onTopBar { moreButton.clickWithAssertion() }
}
step("Open 'Wallet settings' screen") {
@ -59,4 +104,13 @@ fun BaseTestCase.openDeviceSettingsScreen() {
step("Click on 'Device settings' button") {
onWalletSettingsScreen { deviceSettingsButton.clickWithAssertion() }
}
}
fun BaseTestCase.openWalletConnectScreen() {
step("Click 'More' button on TopBar") {
onTopBar { moreButton.clickWithAssertion() }
}
step("Click on 'Wallet Connect' button") {
onDetailsScreen { walletConnectButton.clickWithAssertion() }
}
}

View file

@ -0,0 +1,118 @@
package com.tangem.scenarios
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.SwipeDirection
import com.tangem.common.extensions.swipeVertical
import com.tangem.screens.onMainScreen
import io.qameta.allure.kotlin.Allure.step
fun BaseTestCase.checkSingleCurrencyMainScreen(
cardBlockchain: String,
cardTitle: String,
withTransactions: Boolean = false,
withWalletImage: Boolean = true
) {
step("Assert card title equal '$cardTitle'") {
onMainScreen { walletNameText.assertTextEquals(cardTitle) }
}
if (withWalletImage) {
step("Assert card image is displayed") { //TODO: create assertion method for checking images
onMainScreen { walletImage.assertIsDisplayed() }
}
} else {
step("Assert card image is not displayed") {
onMainScreen { walletImage.assertIsNotDisplayed() }
}
}
step("Assert 'Receive' button is displayed") {
onMainScreen { receiveButton.assertIsDisplayed() }
}
step("Assert 'Buy' button is displayed") {
onMainScreen { buyButton.assertIsDisplayed() }
}
step("Assert 'Send' button is displayed") {
onMainScreen { sendButton.assertIsDisplayed() }
}
step("Assert 'Sell' button is displayed") {
onMainScreen { sellButton.assertIsDisplayed() }
}
step("Assert 'Swap' button is not displayed") {
onMainScreen { swapButton.assertIsNotDisplayed() }
}
step("Assert 'Market Price' on single card main screen is displayed") {
onMainScreen { marketPriceBlock().assertIsDisplayed() }
}
step("Assert 'Market Price' title equals $cardBlockchain Market Price") {
onMainScreen { marketPriceText.assertTextContains("$cardBlockchain Market Price") }
}
step("Swipe up") {
swipeVertical(SwipeDirection.UP)
}
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() }
}
}
fun BaseTestCase.checkMultiCurrencyMainScreen(
devicesCount: String,
cardTitle: String,
withWalletImage: Boolean = true
) {
step("Assert card title equal '$cardTitle'") {
onMainScreen { walletNameText.assertTextEquals(cardTitle) }
}
if (withWalletImage) {
step("Assert card image is displayed") {
onMainScreen { walletImage.assertIsDisplayed() }
}
} else {
step("Assert card image is not displayed") {
onMainScreen { walletImage.assertIsNotDisplayed() }
}
}
step("Assert devices count equal to '$devicesCount'") {
onMainScreen { walletDevicesCount.assertTextContains(devicesCount) }
}
step("Assert 'Buy' button is displayed") {
onMainScreen { buyButton.assertIsDisplayed() }
}
step("Assert 'Swap' button is displayed") {
onMainScreen { swapButton.assertIsDisplayed() }
}
step("Assert 'Sell' button is displayed") {
onMainScreen { sellButton.assertIsDisplayed() }
}
step("Assert 'Send' button is not displayed") {
onMainScreen { sendButton.assertIsNotDisplayed() }
}
step("Assert 'Receive' button is not displayed") {
onMainScreen { receiveButton.assertIsNotDisplayed() }
}
step("Assert 'Organize tokens' button is displayed") {
onMainScreen { organizeTokensButton().assertIsDisplayed() }
}
}

View file

@ -0,0 +1,112 @@
package com.tangem.scenarios
import com.tangem.common.BaseTestCase
import com.tangem.screens.onOnboardingScreen
import io.qameta.allure.kotlin.Allure.step
fun BaseTestCase.checkBackupScreen() {
step("Assert 'Creating a backup' top bar title is displayed") {
onOnboardingScreen { creatingBackupTobBarTitle.assertIsDisplayed() }
}
step("Assert 'Prepare your scan or ring' title is displayed") {
onOnboardingScreen { prepareYouCardOrRingTitle.assertIsDisplayed() }
}
step("Assert 'Start backup' text is displayed") {
onOnboardingScreen { startBackupText.assertIsDisplayed() }
}
step("Assert 'Scan card' button is displayed") {
onOnboardingScreen { scanCardButton.assertIsDisplayed() }
}
step("Assert 'Skip for later' button is not displayed") {
onOnboardingScreen { skipForLaterButton.assertIsNotDisplayed() }
}
}
fun BaseTestCase.checkCreateWalletScreenForWalletNoWallets() {
step("Assert 'Create wallet' tob bar title is displayed") {
onOnboardingScreen { createWalletTopBarTitle.assertIsDisplayed() }
}
step("Assert top bar 'Back' button is displayed") {
onOnboardingScreen { topBarBackButton.assertIsDisplayed() }
}
step("Assert top bar 'More' button is displayed") {
onOnboardingScreen { topBarMoreButton.assertIsDisplayed() }
}
step("Assert 'Create wallet' title is displayed") {
onOnboardingScreen { createWalletTitle.assertIsDisplayed() }
}
step("Assert 'Create wallet' text is displayed") {
onOnboardingScreen { createWalletText.assertIsDisplayed() }
}
step("Assert 'Create wallet' button is displayed") {
onOnboardingScreen { createWalletButton.assertIsDisplayed() }
}
}
fun BaseTestCase.openAndCheckBackupScreenForWalletNoWallets() {
step("Click on 'Create wallet' button") {
onOnboardingScreen { createWalletButton.performClick() }
}
step("Assert 'Getting started' top bar title is displayed") {
onOnboardingScreen { gettingStartedTobBarTitle.assertIsDisplayed() }
}
step("Assert 'Backup wallet' title is displayed") {
onOnboardingScreen { backupWalletTitle.assertIsDisplayed() }
}
step("Assert 'Backup wallet' text is displayed") {
onOnboardingScreen { backupWalletText.assertIsDisplayed() }
}
step("Assert 'Backup now' button is displayed") {
onOnboardingScreen { backupWalletButton.assertIsDisplayed() }
}
step("Assert 'Skip for later' button is not displayed") {
onOnboardingScreen { skipForLaterButton.assertIsNotDisplayed() }
}
}
fun BaseTestCase.checkCreateWalletScreenForWallet2NoWallets() {
step("Assert 'Create a wallet' top bar title is displayed") {
onOnboardingScreen { createWalletTopBarTitle.assertIsDisplayed() }
}
step("Assert top bar 'Back' button is displayed") {
onOnboardingScreen { topBarBackButton.assertIsDisplayed() }
}
step("Assert top bar 'More' button is displayed") {
onOnboardingScreen { topBarMoreButton.assertIsDisplayed() }
}
step("Assert 'Generate keys privately' title is displayed") {
onOnboardingScreen { generateKeysPrivatelyTitle.assertIsDisplayed() }
}
step("Assert 'Generate keys privately' text is displayed") {
onOnboardingScreen { generateKeysPrivatelyText.assertIsDisplayed() }
}
step("Assert 'Create wallet' button is displayed") {
onOnboardingScreen { createWalletButton.assertIsDisplayed() }
}
step("Assert 'Other options' button is displayed") {
onOnboardingScreen { otherOptionsButton.assertIsDisplayed() }
}
}
fun BaseTestCase.openAndCheckBackupScreenForWallet2NoWallets() {
step("Click on 'Create wallet' button") {
onOnboardingScreen { createWalletButton.performClick() }
}
step("Assert 'Creating a backup' top bar title is displayed") {
onOnboardingScreen { creatingBackupTobBarTitle.assertIsDisplayed() }
}
step("Assert 'No backup devices' title is displayed") {
onOnboardingScreen { noBackupDevicesTitle.assertIsDisplayed() }
}
step("Assert 'No backup devices' text is displayed") {
onOnboardingScreen { noBackupDevicesText.assertIsDisplayed() }
}
step("Assert 'Add card or ring' button is displayed") {
onOnboardingScreen { addCardOrRingButton.assertIsDisplayed() }
}
step("Assert 'Finalize backup' button is displayed") {
onOnboardingScreen { finalizeBackupButton.assertIsDisplayed() }
}
step("Assert 'Skip for later' button is not displayed") {
onOnboardingScreen { skipForLaterButton.assertIsNotDisplayed() }
}
}

View file

@ -0,0 +1,124 @@
package com.tangem.scenarios
import com.tangem.common.BaseTestCase
import com.tangem.screens.onSendScreen
import com.tangem.screens.onStakingConfirmScreen
import com.tangem.screens.onStakingDetailsScreen
import io.qameta.allure.kotlin.Allure.step
fun BaseTestCase.checkStakingDetailsScreen(withStaking: Boolean) {
step("Assert 'Title' is displayed") {
onStakingDetailsScreen { stakingTitle.assertIsDisplayed() }
}
step("Assert 'Annual percentage rate' is displayed") {
onStakingDetailsScreen { annualPercentageRate.assertIsDisplayed() }
}
step("Assert 'Available' block is displayed") {
onStakingDetailsScreen { availableBlock.assertIsDisplayed() }
}
step("Assert 'Unbonding Period' block is displayed") {
onStakingDetailsScreen { unbondingPeriodBlock.assertIsDisplayed() }
}
step("Assert 'Reward claiming' block is displayed") {
onStakingDetailsScreen { rewardClaimingBlock.assertIsDisplayed() }
}
step("Assert 'Reward schedule' block is displayed") {
onStakingDetailsScreen { rewardScheduleBlock.assertIsDisplayed() }
}
step("Assert 'ToS' text is displayed") {
onStakingDetailsScreen { toSText.assertIsDisplayed() }
}
if (withStaking) {
step("Assert 'Rewards block' is displayed") {
onStakingDetailsScreen { rewardsBlock.assertIsDisplayed() }
}
step("Assert 'Rewards block' title is displayed") {
onStakingDetailsScreen { rewardsBlockTitle.assertIsDisplayed() }
}
step("Assert 'Rewards block' text is displayed") {
onStakingDetailsScreen { rewardsBlockText.assertIsDisplayed() }
}
step("Assert 'Active staking block' is displayed") {
onStakingDetailsScreen { activeStakingBlock.assertIsDisplayed() }
}
step("Assert 'Your stakes' title is displayed") {
onStakingDetailsScreen { yourStakesTitle.assertIsDisplayed() }
}
step("Assert 'Stake more' button is displayed") {
onStakingDetailsScreen { stakeMoreButton.assertIsDisplayed() }
}
} else {
step("Assert banner image is displayed") {
onStakingDetailsScreen { bannerImage.assertIsDisplayed() }
}
step("Assert banner text is displayed") {
onStakingDetailsScreen { bannerText.assertIsDisplayed() }
}
step("Assert 'Stake' button is displayed") {
onStakingDetailsScreen { stakeButton.assertIsDisplayed() }
}
}
}
fun BaseTestCase.checkStakingScreen(stakingAmount: String) {
step("Assert 'Staking' screen is displayed") {
onSendScreen { screenContainer.assertIsDisplayed() }
}
step("Assert top app bar 'Close' button is displayed") {
onSendScreen { closeButton.assertIsDisplayed() }
}
step("Assert 'Send' screen title is displayed") {
onSendScreen { title.assertIsDisplayed() }
}
step("Assert amount container title is displayed") {
onSendScreen { amountContainerTitle.assertIsDisplayed() }
}
step("Assert input text field is displayed") {
onSendScreen { amountInputTextField.assertIsDisplayed() }
}
step("Assert token name is displayed") {
onSendScreen { tokenName.assertIsDisplayed() }
}
step("Assert primary amount is displayed") {
onSendScreen { primaryAmount.assertIsDisplayed() }
}
step("Assert secondary amount is displayed") {
onSendScreen { secondaryAmount.assertIsDisplayed() }
}
step("Type '$stakingAmount' in input text field") {
onSendScreen {
amountInputTextField.performClick()
amountInputTextField.performTextReplacement(stakingAmount)
}
}
step("Assert input text field has value: '$stakingAmount'") {
onSendScreen { amountInputTextField.assertTextContains(value = stakingAmount, substring = true) }
}
step("Assert 'Max' button is displayed") {
onSendScreen { maxButton.assertIsDisplayed() }
}
step("Assert 'Next' button is displayed") {
onSendScreen { nextButton.assertIsDisplayed() }
}
}
fun BaseTestCase.checkStakingConfirmScreen() {
step("Assert 'Staking confirm' screen title is displayed") {
onStakingConfirmScreen { title.assertIsDisplayed() }
}
step("Assert primary amount is displayed") {
onStakingConfirmScreen { primaryAmount.assertIsDisplayed() }
}
step("Assert secondary amount is displayed") {
onStakingConfirmScreen { secondaryAmount.assertIsDisplayed() }
}
step("Assert 'Validator' block is displayed") {
onStakingConfirmScreen { validatorBlock.assertIsDisplayed() }
}
step("Assert 'Network Fee' block is displayed") {
onStakingConfirmScreen { networkFeeBlock.assertIsDisplayed() }
}
step("Assert 'Stake' button is displayed") {
onStakingConfirmScreen { stakeButton.assertIsDisplayed() }
}
}

View file

@ -7,6 +7,7 @@ import com.tangem.screens.onWalletConnectScreen
import io.qameta.allure.kotlin.Allure.step
fun BaseTestCase.checkWalletConnectBottomSheet() {
waitForIdle()
step("Assert 'Wallet Connect' bottom sheet title is displayed") {
onWalletConnectBottomSheet { title.assertIsDisplayed() }
}
@ -60,34 +61,64 @@ fun BaseTestCase.checkWalletConnectBottomSheet() {
}
}
fun BaseTestCase.checkWalletConnectScreen() {
fun BaseTestCase.checkWalletConnectScreen(withConnections: Boolean) {
waitForIdle()
step("Assert 'Wallet Connect' title is displayed") {
onWalletConnectScreen { title.assertIsDisplayed() }
}
step("Assert 'More' button is displayed") {
onWalletConnectScreen { moreButton.assertIsDisplayed() }
}
step("Assert wallet name is displayed") {
onWalletConnectScreen { walletName.assertIsDisplayed() }
}
step("Assert app icon is displayed") {
onWalletConnectScreen { appIcon.assertIsDisplayed() }
}
step("Assert app name is displayed") {
onWalletConnectScreen { appName.assertIsDisplayed() }
}
step("Assert approve icon is displayed") {
onWalletConnectScreen { approveIcon.assertIsDisplayed() }
}
step("Assert app URL is displayed") {
onWalletConnectScreen { appUrl.assertIsDisplayed() }
}
step("Assert 'New Connection' button is displayed") {
onWalletConnectScreen { newConnectionButton.assertIsDisplayed() }
}
if (withConnections) {
step("Assert 'More' button is displayed") {
onWalletConnectScreen { moreButton.assertIsDisplayed() }
}
step("Assert wallet name is displayed") {
onWalletConnectScreen { walletName.assertIsDisplayed() }
}
step("Assert app icon is displayed") {
onWalletConnectScreen { appIcon.assertIsDisplayed() }
}
step("Assert app name is displayed") {
onWalletConnectScreen { appName.assertIsDisplayed() }
}
step("Assert approve icon is displayed") {
onWalletConnectScreen { approveIcon.assertIsDisplayed() }
}
step("Assert app URL is displayed") {
onWalletConnectScreen { appUrl.assertIsDisplayed() }
}
} else {
step("Assert wallet name is not displayed") {
onWalletConnectScreen { walletName.assertIsNotDisplayed() }
}
step("Assert app icon is not displayed") {
onWalletConnectScreen { appIcon.assertIsNotDisplayed() }
}
step("Assert app name is not displayed") {
onWalletConnectScreen { appName.assertIsNotDisplayed() }
}
step("Assert approve icon is not displayed") {
onWalletConnectScreen { approveIcon.assertIsNotDisplayed() }
}
step("Assert app URL is not displayed") {
onWalletConnectScreen { appUrl.assertIsNotDisplayed() }
}
step("Assert 'Wallet Connect' image is displayed") {
onWalletConnectScreen { walletConnectImage.assertIsDisplayed() }
}
step("Assert 'No session' title is displayed") {
onWalletConnectScreen { noSessionTitle.assertIsDisplayed() }
}
step("Assert 'No session' text is displayed") {
onWalletConnectScreen { noSessionText.assertIsDisplayed() }
}
}
}
fun BaseTestCase.checkWalletConnectDetailsBottomSheet(dAppName: String) {
waitForIdle()
step("Assert connection details title is displayed") {
onWalletConnectDetailsBottomSheet { title.assertIsDisplayed() }
}
@ -128,7 +159,7 @@ fun BaseTestCase.checkWalletConnectDetailsBottomSheet(dAppName: String) {
onWalletConnectDetailsBottomSheet { connectedNetworkIcon.assertIsDisplayed() }
}
step("Assert connected dApp name: '$dAppName'") {
onWalletConnectDetailsBottomSheet { connectedNetworkName.assertTextContains(dAppName) }
onWalletConnectDetailsBottomSheet { appName.assertTextContains(dAppName) }
}
step("Assert connected network symbol is displayed") {
onWalletConnectDetailsBottomSheet { connectedNetworkSymbol.assertIsDisplayed() }

View file

@ -3,15 +3,13 @@ package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.R
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.BuyTokenDetailsScreenTestTags
import com.tangem.core.ui.test.NotificationTestTags
import com.tangem.core.ui.test.TopAppBarTestTags
import com.tangem.core.ui.test.*
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 androidx.compose.ui.test.hasTestTag as withTestTag
import com.tangem.features.onramp.impl.R as OnrampImplR
class BuyTokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<BuyTokenDetailsPageObject>(semanticsProvider = semanticsProvider) {
@ -68,31 +66,65 @@ class BuyTokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProv
useUnmergedTree = true
}
val providerLoadingTitle: KNode = child {
hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_LOADING_TITLE)
val recommendedTitle: KNode = child {
hasText(getResourceString(OnrampImplR.string.onramp_recommended_title))
}
val providerLoadingText: KNode = child {
hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_LOADING_TEXT)
}
val providerTitle: KNode = child {
hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_TITLE)
val bestRateIcon: KNode = child {
hasTestTag(OnrampOffersBlockTestTags.BEST_RATE_ICON)
useUnmergedTree = true
}
val providerText: KNode = child {
hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_TEXT)
val bestRateTitle: KNode = child {
hasTestTag(OnrampOffersBlockTestTags.BEST_RATE_TITLE)
useUnmergedTree = true
}
val offerTokenAmount: KNode = child {
hasTestTag(OnrampOffersBlockTestTags.OFFER_TOKEN_AMOUNT)
useUnmergedTree = true
}
val timingIcon: KNode = child {
hasTestTag(OnrampOffersBlockTestTags.TIMING_ICON)
useUnmergedTree = true
}
val payWith: KNode = child {
hasTestTag(OnrampOffersBlockTestTags.PAY_WITH)
useUnmergedTree = true
}
val paymentMethodIcon: KNode = child {
hasTestTag(OnrampOffersBlockTestTags.PAYMENT_METHOD_ICON)
useUnmergedTree = true
}
val instantProcessingSpeedText: KNode = child {
hasText(getResourceString(R.string.onramp_instant_status))
useUnmergedTree = true
}
val providerName: KNode = child {
hasTestTag(OnrampOffersBlockTestTags.PROVIDER_NAME)
useUnmergedTree = true
}
val buyButton: KNode = child {
hasTestTag(BaseButtonTestTags.BUTTON)
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_buy))
useUnmergedTree = true
}
val toSBlock: KNode = child {
hasTestTag(BuyTokenDetailsScreenTestTags.TOS_BLOCK)
val continueButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_continue))
useUnmergedTree = true
}
val allOffersButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.onramp_all_offers_button_title))
useUnmergedTree = true
}
}

View file

@ -17,6 +17,7 @@ import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
import androidx.compose.ui.test.hasTestTag as withTestTag
import androidx.compose.ui.test.hasText as withText
import com.tangem.core.ui.R as CoreUiR
class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<MainScreenPageObject>(semanticsProvider = semanticsProvider) {
@ -44,10 +45,30 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
}
val buyButton: KNode = child {
hasTestTag(MainScreenTestTags.MULTI_CURRENCY_ACTION_BUTTON)
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_buy))
}
val sendButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_send))
}
val receiveButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_receive))
}
val sellButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_sell))
}
val swapButton: KNode = child {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_swap))
}
val walletNameText: KNode = child {
hasTestTag(MainScreenTestTags.CARD_TITLE)
useUnmergedTree = true
@ -58,11 +79,19 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
useUnmergedTree = true
}
val marketPriceBlock: KNode = child {
hasTestTag(MarketPriceBlockTestTags.BLOCK)
val walletDevicesCount: KNode = child {
hasTestTag(MainScreenTestTags.DEVICES_COUNT)
useUnmergedTree = true
}
@OptIn(ExperimentalTestApi::class)
fun marketPriceBlock(): LazyListItemNode {
return lazyList.childWith<LazyListItemNode> {
hasTestTag(MarketPriceBlockTestTags.BLOCK)
useUnmergedTree = true
}
}
val marketPriceText: KNode = child {
hasTestTag(MarketPriceBlockTestTags.TEXT)
useUnmergedTree = true
@ -79,12 +108,26 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
useUnmergedTree = true
}
fun transactionsExplorer(): 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 {
hasTestTag(EmptyTransactionBlockTestTags.BLOCK)
}
val emptyTransactionBlockIcon: KNode = child {
hasTestTag(EmptyTransactionBlockTestTags.ICON)
}
val emptyTransactionBlockText: KNode = child {
hasTestTag(EmptyTransactionBlockTestTags.TEXT)
}
val emptyTransactionBlockExploreButton: KNode = child {
hasTestTag(EmptyTransactionBlockTestTags.EXPLORE_BUTTON)
}
val notificationContainer: KNode = child {
@ -158,6 +201,10 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
useUnmergedTree = true
}
val snackbarCopiedAddressMessage: KNode = child {
hasText(getResourceString(CoreUiR.string.wallet_notification_address_copied))
}
/**
* Find token list item with title and address
*/
@ -182,12 +229,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

@ -0,0 +1,163 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.StoriesScreenTestTags
import com.tangem.core.ui.test.TopAppBarTestTags
import com.tangem.wallet.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
import io.github.kakaocup.kakao.common.views.KView
import io.github.kakaocup.kakao.text.KButton
import com.tangem.features.onboarding.v2.impl.R as OnboardingImplR
class OnboardingPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<OnboardingPageObject>(semanticsProvider = semanticsProvider) {
val topBarBackButton: KNode = child {
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
useUnmergedTree = true
}
val continueButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_continue))
useUnmergedTree = true
}
val continueToMyWalletButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.onboarding_button_continue_wallet))
useUnmergedTree = true
}
val createWalletTopBarTitle: KNode = child {
hasTestTag(TopAppBarTestTags.TITLE)
hasText(getResourceString(OnboardingImplR.string.onboarding_create_wallet_header))
useUnmergedTree = true
}
val createWalletTitle: KNode = child {
hasTestTag(StoriesScreenTestTags.TITLE)
hasText(getResourceString(OnboardingImplR.string.onboarding_create_wallet_header))
useUnmergedTree = true
}
val createWalletText: KNode = child {
hasTestTag(StoriesScreenTestTags.TEXT)
hasText(getResourceString(OnboardingImplR.string.onboarding_create_wallet_body))
useUnmergedTree = true
}
val gettingStartedTobBarTitle: KNode = child {
hasTestTag(TopAppBarTestTags.TITLE)
hasText(getResourceString(OnboardingImplR.string.onboarding_getting_started))
useUnmergedTree = true
}
val creatingBackupTobBarTitle: KNode = child {
hasTestTag(TopAppBarTestTags.TITLE)
hasText(getResourceString(OnboardingImplR.string.onboarding_navbar_title_creating_backup))
useUnmergedTree = true
}
val prepareYouCardOrRingTitle: KNode = child {
hasText(getResourceString(OnboardingImplR.string.onboarding_title_scan_origin_card))
useUnmergedTree = true
}
val startBackupText: KNode = child {
hasText(getResourceString(OnboardingImplR.string.onboarding_subtitle_scan_primary))
useUnmergedTree = true
}
val topBarMoreButton: KNode = child {
hasTestTag(TopAppBarTestTags.MORE_BUTTON)
}
val backupWalletTitle: KNode = child {
hasText(getResourceString(OnboardingImplR.string.onboarding_wallet_info_title_first))
useUnmergedTree = true
}
val backupWalletText: KNode = child {
hasText(getResourceString(OnboardingImplR.string.onboarding_wallet_info_subtitle_first))
useUnmergedTree = true
}
val generateKeysPrivatelyTitle: KNode = child {
hasText(getResourceString(OnboardingImplR.string.onboarding_create_wallet_options_title))
useUnmergedTree = true
}
val generateKeysPrivatelyText: KNode = child {
hasText(getResourceString(OnboardingImplR.string.onboarding_create_wallet_options_message))
useUnmergedTree = true
}
val noBackupDevicesTitle: KNode = child {
hasText(getResourceString(OnboardingImplR.string.onboarding_subtitle_no_backup_cards))
useUnmergedTree = true
}
val noBackupDevicesText: KNode = child {
hasText(getResourceString(OnboardingImplR.string.onboarding_title_no_backup_cards))
useUnmergedTree = true
}
val createWalletButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(OnboardingImplR.string.onboarding_create_wallet_button_create_wallet))
useUnmergedTree = true
}
val backupWalletButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(OnboardingImplR.string.onboarding_button_backup_now))
useUnmergedTree = true
}
val scanCardButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(OnboardingImplR.string.onboarding_button_backup_card))
useUnmergedTree = true
}
val otherOptionsButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(OnboardingImplR.string.onboarding_create_wallet_options_button_options))
useUnmergedTree = true
}
val addCardOrRingButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(OnboardingImplR.string.onboarding_button_add_backup_card))
useUnmergedTree = true
}
val finalizeBackupButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(OnboardingImplR.string.onboarding_button_finalize_backup))
useUnmergedTree = true
}
val skipForLaterButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(OnboardingImplR.string.onboarding_button_skip_backup))
useUnmergedTree = true
}
val enableNFCAlert: KView = KView {
withId(R.id.alertTitle)
}
val cancelButton: KButton = KButton {
withId(android.R.id.button2)
}
}
internal fun BaseTestCase.onOnboardingScreen(function: OnboardingPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -3,20 +3,19 @@ package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.R
import com.tangem.core.ui.test.BaseBottomSheetTestTags
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 BaseBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<BaseBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
class ReceiveAssetsBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<ReceiveAssetsBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
val hideButton: KNode = child {
hasText(getResourceString(R.string.token_details_hide_token))
hasTestTag(BaseBottomSheetTestTags.ACTION_TITLE)
val showQrCodeButton: KNode = child {
hasText(getResourceString(R.string.token_receive_show_qr_code_title))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onBottomSheet(function: BaseBottomSheetPageObject.() -> Unit) =
internal fun BaseTestCase.onReceiveAssetsBottomSheet(function: ReceiveAssetsBottomSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -1,17 +1,14 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.common.utils.LazyListItemNode
import com.tangem.core.ui.test.BaseBottomSheetTestTags
import com.tangem.core.ui.test.OnrampOffersBlockTestTags
import com.tangem.core.ui.test.SelectPaymentMethodBottomSheetTestTags
import com.tangem.core.ui.test.TopAppBarTestTags
import com.tangem.core.ui.utils.LazyListItemPositionSemantics
import com.tangem.features.onramp.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.compose.node.element.lazylist.KLazyListNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
import androidx.compose.ui.test.hasTestTag as withTestTag
import androidx.compose.ui.test.hasText as withText
@ -19,30 +16,31 @@ import androidx.compose.ui.test.hasText as withText
class SelectPaymentMethodPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<SelectPaymentMethodPageObject>(semanticsProvider = semanticsProvider) {
private val lazyList = KLazyListNode(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(SelectPaymentMethodBottomSheetTestTags.LAZY_LIST) },
itemTypeBuilder = { itemType(::LazyListItemNode) },
positionMatcher = { position ->
SemanticsMatcher.expectValue(
LazyListItemPositionSemantics,
position
)
}
)
val title: KNode = child {
hasTestTag(TopAppBarTestTags.TITLE)
hasText(getResourceString(R.string.onramp_pay_with))
hasTestTag(BaseBottomSheetTestTags.TITLE)
hasText(getResourceString(R.string.onramp_all_offers_button_title))
}
fun paymentMethodWithNameAndIcon(name: String): KNode {
return lazyList.child<KNode> {
hasAnyDescendant(withText(name))
hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.PAYMENT_METHOD_ICON))
useUnmergedTree = true
}
val subtitle: KNode = child {
hasTestTag(BaseBottomSheetTestTags.SUBTITLE)
hasText(getResourceString(R.string.onramp_payment_method_subtitle))
}
val closeButton: KNode = child {
hasTestTag(BaseBottomSheetTestTags.CLOSE_BUTTON)
}
fun paymentMethodWithName(name: String): KNode = child {
hasTestTag(SelectPaymentMethodBottomSheetTestTags.PAYMENT_METHOD)
hasAnyDescendant(withText(name))
hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.PAYMENT_METHOD_ICON))
hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.TIMING_ICON))
hasAnyDescendant(withTestTag(OnrampOffersBlockTestTags.TIMING_TEXT))
hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.TOKEN_AMOUNT))
hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.PROVIDER_COUNT_ICON))
hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.PROVIDER_COUNT_TEXT))
hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.UP_TO_TEXT))
useUnmergedTree = true
}
}

View file

@ -2,87 +2,78 @@ package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.SelectProviderBottomSheetTestTags
import com.tangem.core.ui.test.BaseBottomSheetTestTags
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.OnrampOffersBlockTestTags
import com.tangem.features.onramp.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
import androidx.compose.ui.test.hasText as withText
import com.tangem.core.ui.R as CoreUiR
class SelectProviderPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<SelectProviderPageObject>(semanticsProvider = semanticsProvider) {
val title: KNode = child {
hasText(getResourceString(R.string.onramp_choose_provider_title_hint))
hasTestTag(BaseBottomSheetTestTags.TITLE)
hasText(getResourceString(R.string.onramp_all_offers_button_title))
}
val subtitle: KNode = child {
hasTestTag(BaseBottomSheetTestTags.SUBTITLE)
hasText(getResourceString(R.string.express_provider))
useUnmergedTree = true
}
val bestRateIcon: KNode = child {
hasTestTag(OnrampOffersBlockTestTags.BEST_RATE_ICON)
useUnmergedTree = true
}
val bestRateTitle: KNode = child {
hasTestTag(OnrampOffersBlockTestTags.BEST_RATE_TITLE)
useUnmergedTree = true
}
val offerTokenAmount: KNode = child {
hasTestTag(OnrampOffersBlockTestTags.OFFER_TOKEN_AMOUNT)
useUnmergedTree = true
}
val timingIcon: KNode = child {
hasTestTag(OnrampOffersBlockTestTags.TIMING_ICON)
useUnmergedTree = true
}
val payWith: KNode = child {
hasTestTag(OnrampOffersBlockTestTags.PAY_WITH)
useUnmergedTree = true
}
val youGetText: KNode = child {
hasText(getResourceString(R.string.onramp_title_you_get))
}
val paymentMethodIcon: KNode = child {
hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_ICON)
hasTestTag(OnrampOffersBlockTestTags.PAYMENT_METHOD_ICON)
useUnmergedTree = true
}
val paymentMethodTitle: KNode = child {
hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_NAME)
val providerName: KNode = child {
hasTestTag(OnrampOffersBlockTestTags.PROVIDER_NAME)
useUnmergedTree = true
}
val paymentMethodName: KNode = child {
hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_NAME)
val instantProcessingSpeedText: KNode = child {
hasText(getResourceString(com.tangem.core.ui.R.string.onramp_instant_status))
useUnmergedTree = true
}
val paymentMethodExpandButton: KNode = child {
hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_EXPAND_BUTTON)
useUnmergedTree = true
}
val availableProviderItem: KNode = child {
hasTestTag(SelectProviderBottomSheetTestTags.AVAILABLE_PROVIDER_ITEM)
useUnmergedTree = true
}
val availableProviderName: KNode = child {
hasTestTag(SelectProviderBottomSheetTestTags.AVAILABLE_PROVIDER_NAME)
useUnmergedTree = true
}
val tokenAmount: KNode = child {
hasTestTag(SelectProviderBottomSheetTestTags.TOKEN_AMOUNT)
useUnmergedTree = true
}
val unavailableProviderItem: KNode = child {
hasTestTag(SelectProviderBottomSheetTestTags.UNAVAILABLE_PROVIDER_ITEM)
useUnmergedTree = true
}
val unavailableProviderName: KNode = child {
hasTestTag(SelectProviderBottomSheetTestTags.UNAVAILABLE_PROVIDER_NAME)
useUnmergedTree = true
}
val moreProvidersIcon: KNode = child {
hasTestTag(SelectProviderBottomSheetTestTags.MORE_PROVIDERS_ICON)
useUnmergedTree = true
}
val moreProvidersText: KNode = child {
hasTestTag(SelectProviderBottomSheetTestTags.MORE_PROVIDERS_TEXT)
useUnmergedTree = true
}
val bestRateLabel: KNode = child {
hasTestTag(SelectProviderBottomSheetTestTags.BEST_RATE_LABEL)
useUnmergedTree = true
}
fun availableProviderWithName(name: String, tokenAmount: String, rate: String): KNode = child {
hasTestTag(SelectProviderBottomSheetTestTags.AVAILABLE_PROVIDER_ITEM)
hasAnyChild(withText(name))
hasAnyChild(withText(tokenAmount))
hasAnyChild(withText(rate))
val buyButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(CoreUiR.string.common_buy))
useUnmergedTree = true
}
}

View file

@ -4,11 +4,15 @@ import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.R
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.NotificationTestTags
import com.tangem.core.ui.test.SendConfirmScreenTestTags
import com.tangem.core.ui.test.TopAppBarTestTags
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 androidx.compose.ui.test.hasText as withText
import com.tangem.common.ui.R as CommonUiR
class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<SendConfirmPageObject>(semanticsProvider = semanticsProvider) {
@ -24,6 +28,46 @@ class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
useUnmergedTree = true
}
val minimumSendAmountErrorTitle: KNode = child {
hasTestTag(NotificationTestTags.TITLE)
hasText(getResourceString(CommonUiR.string.send_notification_invalid_amount_title))
useUnmergedTree = true
}
val sendingText: KNode = child {
hasTestTag(SendConfirmScreenTestTags.SENDING_TEXT)
useUnmergedTree = true
}
fun minimumSendAmountErrorIcon(amount: String): KNode = child {
hasAnySibling(
withText(
getResourceString(
CommonUiR.string.send_notification_invalid_minimum_amount_text,
amount,
amount,
)
)
)
hasTestTag(NotificationTestTags.ICON)
useUnmergedTree = true
}
fun minimumSendAmountErrorMessage(
amount: String,
): KNode = child {
hasTestTag(NotificationTestTags.MESSAGE)
hasText(
getResourceString(
CommonUiR.string.send_notification_invalid_minimum_amount_text,
amount,
amount,
)
)
useUnmergedTree = true
}
}
internal fun BaseTestCase.onSendConfirmScreen(function: SendConfirmPageObject.() -> Unit) =

View file

@ -10,7 +10,6 @@ import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onCompose
import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
import com.tangem.features.send.v2.impl.R as SendR
import androidx.compose.ui.test.hasTestTag as withTestTag
class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<SendPageObject>(semanticsProvider = semanticsProvider) {
@ -19,6 +18,11 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
hasTestTag(SendScreenTestTags.SCREEN_CONTAINER)
}
val closeButton: KNode = child {
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
useUnmergedTree = true
}
val title: KNode = child {
hasTestTag(TopAppBarTestTags.TITLE)
useUnmergedTree = true
@ -29,13 +33,18 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
useUnmergedTree = true
}
val amountContainerText: KNode = child {
hasTestTag(SendScreenTestTags.AMOUNT_CONTAINER_TEXT)
val amountInputTextField: KNode = child {
hasTestTag(SendScreenTestTags.INPUT_TEXT_FIELD)
useUnmergedTree = true
}
val amountInputTextField: KNode = child {
hasTestTag(SendScreenTestTags.INPUT_TEXT_FIELD)
val tokenName: KNode = child {
hasTestTag(SendScreenTestTags.TOKEN_NAME)
useUnmergedTree = true
}
val primaryAmount: KNode = child {
hasTestTag(SendScreenTestTags.PRIMARY_AMOUNT)
useUnmergedTree = true
}
@ -44,28 +53,11 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
useUnmergedTree = true
}
val currencyButton: KNode = child {
hasTestTag(SendScreenTestTags.CURRENCY_BUTTON)
hasAnyChild(withTestTag(SendScreenTestTags.CURRENCY_ICON))
useUnmergedTree = true
}
val fiatButton: KNode = child {
hasTestTag(SendScreenTestTags.CURRENCY_BUTTON)
hasAnyChild(withTestTag(SendScreenTestTags.FIAT_ICON))
useUnmergedTree = true
}
val maxButton: KNode = child {
hasTestTag(SendScreenTestTags.MAX_BUTTON)
useUnmergedTree = true
}
val previousButton: KNode = child {
hasTestTag(SendScreenTestTags.PREVIOUS_BUTTON)
useUnmergedTree = true
}
val nextButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(SendR.string.common_next))

View file

@ -3,18 +3,12 @@ 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.wallet.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.views.KView
import io.github.kakaocup.kakao.text.KButton
class StoriesPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<StoriesPageObject>(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(StoriesScreenTestTags.SCREEN_CONTAINER) }
) {
ComposeScreen<StoriesPageObject>(semanticsProvider = semanticsProvider) {
val scanButton: KNode = child {
hasTestTag(StoriesScreenTestTags.SCAN_BUTTON)
@ -23,14 +17,6 @@ class StoriesPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
val orderButton: KNode = child {
hasTestTag(StoriesScreenTestTags.ORDER_BUTTON)
}
val enableNFCAlert: KView = KView {
withId(R.id.alertTitle)
}
val cancelButton: KButton = KButton {
withId(android.R.id.button2)
}
}
internal fun BaseTestCase.onStoriesScreen(function: StoriesPageObject.() -> Unit) =

View file

@ -71,6 +71,10 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
hasText(getResourceString(R.string.common_swap))
}
fun tokenSymbol(symbol: String): KNode = child {
hasTestTag(SwapTokenScreenTestTags.TOKEN_SYMBOL)
}
}
internal fun BaseTestCase.onSwapTokenScreen(function: SwapTokenPageObject.() -> Unit) =

View file

@ -0,0 +1,55 @@
package com.tangem.screens
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
import com.kaspersky.kaspresso.screens.KScreen
object ThirdPartyAppPageObject : KScreen<ThirdPartyAppPageObject>() {
override val layoutId: Int? = null
override val viewClass: Class<*>? = null
private val device: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private const val TIMEOUT = 10000L
fun assertChromeIsOpened() {
val chromePackage = "com.android.chrome"
device.wait(Until.hasObject(By.pkg(chromePackage).depth(0)), TIMEOUT)
assert(device.hasObject(By.pkg(chromePackage))) {
"Chrome browser is not opened"
}
}
fun assertUrlContains(expectedUrl: String) {
val urlBar = device.findObject(
By.res("com.android.chrome:id/url_bar")
)
urlBar?.let {
assert(it.text.contains(expectedUrl)) {
"URL doesn't contain expected: $expectedUrl, actual: ${it.text}"
}
}
}
private fun findElementByText(text: String): UiObject2? {
device.wait(Until.hasObject(By.text(text)), TIMEOUT)
return device.findObject(By.text(text))
}
fun assertElementWithTextExists(text: String) {
val element = findElementByText(text)
assert(element != null) { "Element with text '$text' not found" }
}
fun isElementWithTextExists(text: String): Boolean {
return device.wait(Until.hasObject(By.text(text)), TIMEOUT)
}
fun clickOnElementWithText(text: String) {
findElementByText(text)?.click()
?: throw AssertionError("Cannot click - element with text '$text' not found")
}
}

View file

@ -0,0 +1,75 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.R
import com.tangem.core.ui.test.BaseBottomSheetTestTags
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 androidx.compose.ui.test.hasTestTag as withTestTag
import androidx.compose.ui.test.hasText as withText
class TokenActionsBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<TokenActionsBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
val analyticsButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_analytics)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val copyAddressButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_copy_address)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val receiveButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_receive)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val sendButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_send)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val swapButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_swap)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val buyButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_buy)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val sellButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_sell)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val hideTokenButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.token_details_hide_token)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onTokenActionsBottomSheet(function: TokenActionsBottomSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -5,6 +5,7 @@ import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.common.utils.LazyListItemNode
import com.tangem.core.ui.test.BaseActionButtonsBlockTestTags
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.NotificationTestTags
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
@ -85,7 +86,7 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
private val horizontalActionChips = KLazyListNode(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(TokenDetailsScreenTestTags.HORIZONTAL_ACTION_CHIPS) },
viewBuilderAction = { hasTestTag(BaseActionButtonsBlockTestTags.HORIZONTAL_ACTION_CHIPS) },
itemTypeBuilder = { itemType(::LazyListItemNode) },
positionMatcher = { position ->
SemanticsMatcher.expectValue(
@ -97,25 +98,25 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
@OptIn(ExperimentalTestApi::class)
val swapButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
hasTestTag(TokenDetailsScreenTestTags.ACTION_BUTTON)
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_swap))
}
@OptIn(ExperimentalTestApi::class)
val sellButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
hasTestTag(TokenDetailsScreenTestTags.ACTION_BUTTON)
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_sell))
}
@OptIn(ExperimentalTestApi::class)
val buyButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
hasTestTag(TokenDetailsScreenTestTags.ACTION_BUTTON)
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_buy))
}
@OptIn(ExperimentalTestApi::class)
val sendButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
hasTestTag(TokenDetailsScreenTestTags.ACTION_BUTTON)
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_send))
}
@ -131,6 +132,18 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
useUnmergedTree = true
}
val topUpYourWalletNotificationTitle: KNode = child {
hasTestTag(NotificationTestTags.TITLE)
hasText(getResourceString(R.string.warning_no_account_title))
useUnmergedTree = true
}
val topUpYourWalletNotificationIcon: KNode = child {
hasAnySibling(withText(getResourceString(R.string.warning_no_account_title)))
hasTestTag(NotificationTestTags.ICON)
useUnmergedTree = true
}
fun networkFeeNotificationMessage(
currencyName: String,
networkName: String,
@ -151,6 +164,23 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
useUnmergedTree = true
}
fun topUpYourWalletNotificationMessage(
networkName: String,
amount: String,
currencySymbol: String,
): KNode = child {
hasTestTag(NotificationTestTags.MESSAGE)
hasText(
getResourceString(
R.string.no_account_generic,
networkName,
amount,
currencySymbol,
)
)
useUnmergedTree = true
}
fun goToBuyCurrencyButton(feeCurrencySymbol: String): KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_buy_currency, feeCurrencySymbol))

View file

@ -0,0 +1,56 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.BaseBottomSheetTestTags
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.TokenReceiveQrCodeBottomSheetTestTags
import com.tangem.wallet.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 TokenReceiveQrCodeBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<TokenReceiveQrCodeBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
val closeButton: KNode = child {
hasTestTag(BaseBottomSheetTestTags.CLOSE_BUTTON)
useUnmergedTree = true
}
val title: KNode = child {
hasTestTag(TokenReceiveQrCodeBottomSheetTestTags.TITLE)
useUnmergedTree = true
}
val qrCode: KNode = child {
hasTestTag(TokenReceiveQrCodeBottomSheetTestTags.QR_CODE)
useUnmergedTree = true
}
val addressTitle: KNode = child {
hasText(getResourceString(R.string.wc_common_address))
useUnmergedTree = true
}
val address: KNode = child {
hasTestTag(TokenReceiveQrCodeBottomSheetTestTags.ADDRESS)
useUnmergedTree = true
}
val copyButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_copy))
useUnmergedTree = true
}
val shareButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_share))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onTokenReceiveQrCodeBottomSheet(function: TokenReceiveQrCodeBottomSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -0,0 +1,28 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.R
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.TokenReceiveWarningBottomSheetTestTags
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 TokenReceiveWarningBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<TokenReceiveWarningBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
val bottomSheet: KNode = child {
hasTestTag(TokenReceiveWarningBottomSheetTestTags.BOTTOM_SHEET)
}
val gotItButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_got_it))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onTokenReceiveWarningBottomSheet(function: TokenReceiveWarningBottomSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -9,13 +9,11 @@ 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.walletconnect.impl.R as WalletConnectImplR
class WalletConnectBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<WalletConnectBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
val title: KNode = child {
hasText(getResourceString(WalletConnectImplR.string.wc_wallet_connect))
hasTestTag(WalletConnectBottomSheetTestTags.TITLE)
useUnmergedTree = true
}

View file

@ -2,6 +2,7 @@ package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.BaseBottomSheetTestTags
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.WalletConnectBottomSheetTestTags
import com.tangem.core.ui.test.WalletConnectDetailsBottomSheetTestTags
@ -15,18 +16,18 @@ class WalletConnectDetailsBottomSheetPageObject(semanticsProvider: SemanticsNode
ComposeScreen<WalletConnectDetailsBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
val title: KNode = child {
hasTestTag(WalletConnectDetailsBottomSheetTestTags.TITLE)
hasTestTag(BaseBottomSheetTestTags.TITLE)
hasText(getResourceString(WalletConnectImplR.string.wc_connected_app_title))
useUnmergedTree = true
}
val date: KNode = child {
hasTestTag(WalletConnectDetailsBottomSheetTestTags.DATE)
hasTestTag(BaseBottomSheetTestTags.SUBTITLE)
useUnmergedTree = true
}
val closeButton: KNode = child {
hasTestTag(WalletConnectDetailsBottomSheetTestTags.CLOSE_BUTTON)
hasTestTag(BaseBottomSheetTestTags.CLOSE_BUTTON)
useUnmergedTree = true
}

View file

@ -53,6 +53,21 @@ class WalletConnectPageObject(semanticsProvider: SemanticsNodeInteractionsProvid
hasText(getResourceString(R.string.wc_new_connection))
useUnmergedTree = true
}
val walletConnectImage: KNode = child {
hasTestTag(WalletConnectScreenTestTags.WALLET_CONNECT_IMAGE)
useUnmergedTree = true
}
val noSessionTitle: KNode = child {
hasText(getResourceString(R.string.wc_no_sessions_title))
useUnmergedTree = true
}
val noSessionText: KNode = child {
hasText(getResourceString(R.string.wc_no_sessions_desc))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onWalletConnectScreen(function: WalletConnectPageObject.() -> Unit) =

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.R
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
class WalletConnectScanQrPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<WalletConnectScanQrPageObject>(semanticsProvider = semanticsProvider) {
val pasteFromClipboardButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.wallet_connect_paste_from_clipboard))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onWalletConnectScanQrScreen(function: WalletConnectScanQrPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -1,35 +0,0 @@
package com.tangem.steps
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.assertElementDoesNotExist
import com.tangem.screens.onMainScreen
import io.qameta.allure.kotlin.Allure.step
fun BaseTestCase.checkSingleCurrencyMainScreen(cardBlockchain: String, cardTitle: String) {
step("Assert card title equal '$cardTitle'") {
onMainScreen { walletNameText.assertTextEquals(cardTitle) }
}
step("Assert 'Transactions' block is displayed") {
onMainScreen { transactionsExplorer().assertExists() }
}
step("Assert 'Transactions' title is displayer") {
onMainScreen { transactionsTitle.assertIsDisplayed() }
}
step("Assert 'Explorer Icon' is displayed") {
onMainScreen { transactionsExplorerIcon.assertIsDisplayed() }
}
step("Assert card image is displayed") { //TODO: Придумать нормальную проверку / реализовать скриншот-тестинг
onMainScreen { walletImage.assertIsDisplayed() }
}
step("Assert 'Market Price' on single card main screen is displayed") {
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")
}
}
}

View file

@ -0,0 +1,197 @@
package com.tangem.tests
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.CARDANO_ADDRESS
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.pullToRefresh
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.*
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Test
@HiltAndroidTest
class BlockchainTest : BaseTestCase() {
@AllureId("3644")
@DisplayName("ADA: Checking the min amount/change = 1ADA")
@Test
fun adaCheckMinAmountTest() {
val tokenName = "Cardano"
val errorSendAmount = "0.1"
val validSendAmount = "10"
val minAmount = "ADA 1.00"
val address = CARDANO_ADDRESS
val scenarioName = "user_tokens_api"
val scenarioState = "Cardano"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(scenarioName)
}
).run {
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
step("Click on 'Send' button") {
onTokenDetailsScreen { sendButton.performClick() }
}
step("Type '$errorSendAmount' in input text field") {
onSendScreen {
amountInputTextField.performClick()
amountInputTextField.performTextReplacement(errorSendAmount)
}
}
step("Click on 'Next' button") {
onSendScreen { nextButton.clickWithAssertion() }
}
step("Type address in input text field") {
onSendAddressScreen { addressTextField.performTextReplacement(address) }
}
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
}
step("Assert 'Invalid amount' error title is displayed") {
onSendConfirmScreen { minimumSendAmountErrorTitle.assertIsDisplayed() }
}
step("Assert 'Invalid amount' error icon is displayed") {
onSendConfirmScreen { minimumSendAmountErrorIcon(minAmount).assertIsDisplayed() }
}
step("Assert 'Invalid amount' error message is displayed") {
onSendConfirmScreen { minimumSendAmountErrorMessage(minAmount).assertIsDisplayed() }
}
step("Press system 'Back' button") {
device.uiDevice.pressBack()
}
step("Assert address text field is displayed") {
onSendAddressScreen { addressTextField.assertIsDisplayed() }
}
step("Press system 'Back' button") {
device.uiDevice.pressBack()
}
step("Type '$validSendAmount' in input text field") {
onSendScreen {
amountInputTextField.performClick()
amountInputTextField.performTextReplacement(errorSendAmount)
}
}
step("Click on 'Next' button") {
onSendScreen { nextButton.clickWithAssertion() }
}
step("Assert address text field is displayed") {
onSendAddressScreen { addressTextField.assertIsDisplayed() }
}
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
}
step("Assert 'Invalid amount' error title is not displayed") {
onSendConfirmScreen { minimumSendAmountErrorTitle.assertIsNotDisplayed() }
}
step("Assert 'Invalid amount' error icon is not displayed") {
onSendConfirmScreen { minimumSendAmountErrorIcon(minAmount).assertIsNotDisplayed() }
}
step("Assert 'Invalid amount' error message is not displayed") {
onSendConfirmScreen { minimumSendAmountErrorMessage(minAmount).assertIsNotDisplayed() }
}
}
}
@AllureId("990")
@DisplayName("XRP: Check warning")
@Test
fun xrpCheckWarningTest() {
val tokenName = "XRP Ledger"
val amount = "1.00"
val currencySymbol = "XRP"
val userTokensScenarioName = "user_tokens_api"
val userTokensScenarioState = "XRP"
val rippleAccountInfoScenarioName = "ripple_account_info"
val rippleAccountInfoStartedState = "Started"
val rippleAccountInfoErrorState = "AccountNotFound"
val rippleAccountLinesScenarioName = "ripple_account_lines"
val rippleAccountLinesStartedState = "Started"
val rippleAccountLinesErrorState = "AccountNotFound"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(userTokensScenarioName)
resetWireMockScenarioState(rippleAccountInfoScenarioName)
resetWireMockScenarioState(rippleAccountLinesScenarioName)
}
).run {
step("Set WireMock scenario: '$userTokensScenarioName' to state: '$userTokensScenarioState'") {
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
}
step("Set WireMock scenario: '$rippleAccountInfoScenarioName' to state: '$rippleAccountInfoErrorState'") {
setWireMockScenarioState(scenarioName = rippleAccountInfoScenarioName, state = rippleAccountInfoErrorState)
}
step("Set WireMock scenario: '$rippleAccountLinesScenarioName' to state: '$rippleAccountLinesErrorState'") {
setWireMockScenarioState(scenarioName = rippleAccountLinesScenarioName, state = rippleAccountLinesErrorState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() }
}
step("Assert 'Top up wallet' notification title is displayed") {
onTokenDetailsScreen { topUpYourWalletNotificationTitle.assertIsDisplayed() }
}
step("Assert 'Top up wallet' notification message is displayed") {
onTokenDetailsScreen {
topUpYourWalletNotificationMessage(
networkName = tokenName,
amount = amount,
currencySymbol = currencySymbol,
).assertIsDisplayed()
}
}
step("Assert 'Top up wallet' notification icon is displayed") {
onTokenDetailsScreen { topUpYourWalletNotificationIcon.assertIsDisplayed() }
}
step("Set WireMock scenario: '$userTokensScenarioName' to state: '$userTokensScenarioState'") {
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
}
step("Set WireMock scenario: '$rippleAccountInfoScenarioName' to state: '$rippleAccountInfoStartedState'") {
setWireMockScenarioState(scenarioName = rippleAccountInfoScenarioName, state = rippleAccountInfoStartedState)
}
step("Set WireMock scenario: '$rippleAccountLinesScenarioName' to state: '$rippleAccountLinesStartedState'") {
setWireMockScenarioState(scenarioName = rippleAccountLinesScenarioName, state = rippleAccountLinesStartedState)
}
step("Pull to refresh") {
pullToRefresh()
}
step("Assert 'Top up wallet' notification title is not displayed") {
onTokenDetailsScreen { topUpYourWalletNotificationTitle.assertIsNotDisplayed() }
}
step("Assert 'Top up wallet' notification message not is displayed") {
onTokenDetailsScreen {
topUpYourWalletNotificationMessage(
networkName = tokenName,
amount = amount,
currencySymbol = currencySymbol,
).assertIsNotDisplayed()
}
}
step("Assert 'Top up wallet' notification icon not is displayed") {
onTokenDetailsScreen { topUpYourWalletNotificationIcon.assertIsNotDisplayed() }
}
}
}
}

View file

@ -1,14 +1,12 @@
package com.tangem.tests
import com.tangem.common.BaseTestCase
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.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.screens.*
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.*
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
@ -23,7 +21,6 @@ class BuyTokenTest : BaseTestCase() {
fun errorInProvidersLoadingTest() {
val scenarioName = "payment_methods"
val tokenTitle = "Bitcoin"
val balance = TOTAL_BALANCE
setupHooks(
additionalAfterSection = {
@ -40,7 +37,7 @@ class BuyTokenTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Click on 'Buy' button") {
onMainScreen { buyButton.clickWithAssertion() }
@ -69,12 +66,11 @@ class BuyTokenTest : BaseTestCase() {
fun validateCurrencySelectorTest() {
setupHooks().run {
val tokenTitle = "Polygon"
val balance = TOTAL_BALANCE
val popularFiatsTitle = "Popular Fiats"
val otherCurrenciesTitle = "Other currencies"
val australianDollar = "AUD"
val fiatAmount = "1"
val tokenAmount = "POL 488.24938338"
val tokenAmount = "~488.24938338 POL"
val scenarioName = "payment_methods"
step("Reset WireMock scenario '$scenarioName'") {
@ -85,7 +81,7 @@ class BuyTokenTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Click on 'Buy' button") {
onMainScreen { buyButton.clickWithAssertion() }
@ -102,18 +98,6 @@ class BuyTokenTest : BaseTestCase() {
step("Write fiat amount = '$fiatAmount'") {
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
}
step("Assert 'Provider loading block' is displayed") {
onBuyTokenDetailsScreen {
providerLoadingTitle.assertIsDisplayed()
providerLoadingText.assertIsDisplayed()
}
}
step("Assert 'Provider block' is displayed") {
onBuyTokenDetailsScreen {
providerTitle.assertIsDisplayed()
providerText.assertIsDisplayed()
}
}
step("Assert token amount = '$tokenAmount'") {
onBuyTokenDetailsScreen {
tokenAmountField.assertTextContains(tokenAmount)
@ -159,10 +143,9 @@ class BuyTokenTest : BaseTestCase() {
fun validateBuyTokenScreenTest() {
setupHooks().run {
val tokenTitle = "Polygon"
val balance = TOTAL_BALANCE
val euro = "EUR"
val fiatAmount = "1"
val tokenAmount = "POL 488.24938338"
val tokenAmount = "~488.24938338 POL"
val scenarioName = "payment_methods"
step("Reset WireMock scenario '$scenarioName'") {
@ -173,7 +156,7 @@ class BuyTokenTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Click on 'Buy' button") {
onMainScreen { buyButton.clickWithAssertion() }
@ -199,27 +182,45 @@ class BuyTokenTest : BaseTestCase() {
step("Assert fiat amount = '$fiatAmount'") {
onBuyTokenDetailsScreen { fiatAmountTextField.assertTextContains(euro + fiatAmount) }
}
step("Assert 'Provider loading block' is displayed") {
onBuyTokenDetailsScreen {
providerLoadingTitle.assertIsDisplayed()
providerLoadingText.assertIsDisplayed()
}
}
step("Assert 'Provider block' is displayed") {
onBuyTokenDetailsScreen {
providerTitle.assertIsDisplayed()
providerText.assertIsDisplayed()
}
}
step("Assert token amount = '$tokenAmount'") {
onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenAmount) }
}
step("Assert 'ToS' block is displayed") {
onBuyTokenDetailsScreen { toSBlock.assertIsDisplayed()}
step("Click on 'Continue' button") {
onBuyTokenDetailsScreen { continueButton.clickWithAssertion() }
}
step("Assert 'Recommended' title is displayed") {
onBuyTokenDetailsScreen { recommendedTitle.clickWithAssertion() }
}
step("Assert 'Best rate' icon is displayed") {
onBuyTokenDetailsScreen { bestRateIcon.assertIsDisplayed() }
}
step("Assert 'Best rate' title is displayed") {
onBuyTokenDetailsScreen { bestRateTitle.assertIsDisplayed() }
}
step("Assert offer amount is displayed") {
onBuyTokenDetailsScreen { offerTokenAmount.assertIsDisplayed() }
}
step("Assert 'Buy' button is displayed") {
onBuyTokenDetailsScreen { buyButton.assertIsDisplayed()}
}
step("Assert timing icon is displayed") {
onBuyTokenDetailsScreen { timingIcon.assertIsDisplayed() }
}
step("Assert 'Instant' processing time text is displayed") {
onBuyTokenDetailsScreen { instantProcessingSpeedText.assertIsDisplayed() }
}
step("Assert provider name is displayed") {
onBuyTokenDetailsScreen { providerName.assertIsDisplayed() }
}
step("Assert 'Pay with' is displayed") {
onBuyTokenDetailsScreen { payWith.assertIsDisplayed() }
}
step("Assert payment method icon is displayed") {
onBuyTokenDetailsScreen { paymentMethodIcon.assertIsDisplayed() }
}
step("Assert 'All offers' button is displayed") {
onBuyTokenDetailsScreen { allOffersButton.assertIsDisplayed() }
}
step("Assert 'Close' button in top bar is displayed") {
onBuyTokenDetailsScreen { topBarCloseButton.assertIsDisplayed() }
}
@ -232,7 +233,6 @@ class BuyTokenTest : BaseTestCase() {
fun validateResidenceSettingsScreenTest() {
setupHooks().run {
val tokenTitle = "Polygon"
val balance = TOTAL_BALANCE
val country = "Albania"
val unavailableCountry = "Lebanon"
val scenarioName = "payment_methods"
@ -245,7 +245,7 @@ class BuyTokenTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Click on 'Buy' button") {
onMainScreen { buyButton.clickWithAssertion() }
@ -313,14 +313,10 @@ class BuyTokenTest : BaseTestCase() {
fun validateProvidersScreenTest() {
setupHooks().run {
val tokenTitle = "Polygon"
val balance = TOTAL_BALANCE
val paymentMethod = "Card"
val paymentMethod = "Invoice Revolut Pay"
val fiatAmount = "1"
val providerNameMercuryo = "Mercuryo"
val providerNameSimplex = "Simplex"
val tokenAmount = "POL 488.24938338"
val bestRate = "Best rate"
val rate = "-0.00%"
val tokenAmount = "~488.24938338 POL"
val scenarioName = "payment_methods"
step("Reset WireMock scenario '$scenarioName'") {
@ -331,7 +327,7 @@ class BuyTokenTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Click on 'Buy' button") {
onMainScreen { buyButton.clickWithAssertion() }
@ -348,58 +344,53 @@ class BuyTokenTest : BaseTestCase() {
step("Write fiat amount = '$fiatAmount'") {
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
}
step("Assert token amount = '$tokenAmount'") {
onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenAmount) }
}
step("Click on 'Continue' button") {
onBuyTokenDetailsScreen { continueButton.clickWithAssertion() }
}
step("Assert 'Provider block' is displayed") {
onBuyTokenDetailsScreen {
providerTitle.assertIsDisplayed()
providerText.assertIsDisplayed()
}
onBuyTokenDetailsScreen { providerName.assertIsDisplayed() }
}
step("Open 'Select Provider' bottom sheet") {
onBuyTokenDetailsScreen { providerTitle.performClick() }
step("Click on 'All offers' button") {
onBuyTokenDetailsScreen { allOffersButton.clickWithAssertion() }
}
step("Assert available provider name is displayed") {
onSelectProviderBottomSheet {
flakySafely(WAIT_UNTIL_TIMEOUT) {
availableProviderItem.assertIsDisplayed()
}
}
step("Click on '$paymentMethod' payment method") {
onSelectPaymentMethodBottomSheet { paymentMethodWithName(paymentMethod).clickWithAssertion() }
}
step("Click on 'Expand payment methods' button") {
onSelectProviderBottomSheet { paymentMethodExpandButton.clickWithAssertion() }
}
step("Click on payment method: '$paymentMethod'") {
onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(paymentMethod).clickWithAssertion() }
}
step("Assert 'Select Provider' bottom sheet title is displayed") {
step("Assert 'Provider' bottom sheet title is displayed") {
onSelectProviderBottomSheet { title.assertIsDisplayed() }
}
step("Assert 'Provider' bottom sheet subtitle is displayed") {
onSelectProviderBottomSheet { subtitle.assertIsDisplayed() }
}
step("Assert 'Best rate' icon is displayed") {
onSelectProviderBottomSheet { bestRateIcon.assertIsDisplayed() }
}
step("Assert 'Best rate' title is displayed") {
onSelectProviderBottomSheet { bestRateTitle.assertIsDisplayed() }
}
step("Assert offer token amount is displayed") {
onSelectProviderBottomSheet { offerTokenAmount.assertIsDisplayed() }
}
step("Assert 'Buy' button is displayed") {
onSelectProviderBottomSheet { buyButton.assertIsDisplayed() }
}
step("Assert timing icon is displayed") {
onSelectProviderBottomSheet { timingIcon.assertIsDisplayed() }
}
step("Assert 'Instant' processing time text is displayed") {
onSelectProviderBottomSheet { instantProcessingSpeedText.assertIsDisplayed() }
}
step("Assert 'Pay with' text is displayed") {
onSelectProviderBottomSheet { payWith.assertIsDisplayed() }
}
step("Assert payment method icon is displayed") {
onSelectProviderBottomSheet { paymentMethodIcon.assertIsDisplayed() }
}
step("Assert payment method title is displayed") {
onSelectProviderBottomSheet { paymentMethodTitle.assertIsDisplayed() }
}
step("Assert payment method name is displayed") {
onSelectProviderBottomSheet { paymentMethodName.assertIsDisplayed() }
}
step("Assert provider with name: '$providerNameMercuryo' and rate: '$bestRate' is displayed") {
onSelectProviderBottomSheet {
availableProviderWithName(providerNameMercuryo, tokenAmount, bestRate).assertIsDisplayed()
}
}
step("Assert provider with name: '$providerNameSimplex' and rate: '$rate' is displayed") {
onSelectProviderBottomSheet {
availableProviderWithName(providerNameSimplex, tokenAmount, rate).assertIsDisplayed()
}
}
step("Assert 'More providers' icon is displayed") {
onSelectProviderBottomSheet { moreProvidersIcon.assertIsDisplayed() }
}
step("Assert 'More providers' text is displayed") {
onSelectProviderBottomSheet { moreProvidersText.assertIsDisplayed() }
}
step("Assert 'Best rate' label is displayed") {
onSelectProviderBottomSheet { bestRateLabel.assertIsDisplayed() }
step("Assert provider name: '$providerNameMercuryo'") {
onSelectProviderBottomSheet { providerName.assertTextContains(providerNameMercuryo) }
}
}
}
@ -410,12 +401,12 @@ class BuyTokenTest : BaseTestCase() {
fun validatePaymentMethodScreenTest() {
setupHooks().run {
val tokenTitle = "Polygon"
val balance = TOTAL_BALANCE
val card = "Card"
val googlePay = "Google Pay"
val invoiceRevolutPay = "Invoice Revolut Pay"
val sepa = "Sepa"
val fiatAmount = "1"
val tokenAmount = "~488.24938338 POL"
val scenarioName = "payment_methods"
step("Reset WireMock scenario '$scenarioName'") {
@ -426,7 +417,7 @@ class BuyTokenTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Click on 'Buy' button") {
onMainScreen { buyButton.clickWithAssertion() }
@ -443,40 +434,39 @@ class BuyTokenTest : BaseTestCase() {
step("Write fiat amount = '$fiatAmount'") {
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
}
step("Assert token amount = '$tokenAmount'") {
onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenAmount) }
}
step("Click on 'Continue' button") {
onBuyTokenDetailsScreen { continueButton.clickWithAssertion() }
}
step("Assert 'Provider block' is displayed") {
onBuyTokenDetailsScreen {
providerTitle.assertIsDisplayed()
providerText.assertIsDisplayed()
}
onBuyTokenDetailsScreen { providerName.assertIsDisplayed() }
}
step("Open 'Select Provider' bottom sheet") {
onBuyTokenDetailsScreen { providerTitle.performClick() }
}
step("Click on 'Expand payment methods' button") {
onSelectProviderBottomSheet { paymentMethodExpandButton.clickWithAssertion() }
step("Click on 'All offers' button") {
onBuyTokenDetailsScreen { allOffersButton.clickWithAssertion() }
}
step("Assert 'Select Payment Method' bottom sheet title is displayed") {
onSelectPaymentMethodBottomSheet { title.assertIsDisplayed() }
}
step("Assert 'Select Payment Method' bottom sheet subtitle is displayed") {
onSelectPaymentMethodBottomSheet { subtitle.assertIsDisplayed() }
}
step("Assert 'Select Payment Method' bottom sheet close button is displayed") {
onSelectPaymentMethodBottomSheet { closeButton.assertIsDisplayed() }
}
step("Assert payment method: '$card' is displayed") {
onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(card).assertIsDisplayed() }
onSelectPaymentMethodBottomSheet { paymentMethodWithName(card).assertIsDisplayed() }
}
step("Assert payment method: '$googlePay' is displayed") {
onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(googlePay).assertIsDisplayed() }
onSelectPaymentMethodBottomSheet { paymentMethodWithName(googlePay).assertIsDisplayed() }
}
step("Assert payment method: '$invoiceRevolutPay' is displayed") {
onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(invoiceRevolutPay).assertIsDisplayed() }
onSelectPaymentMethodBottomSheet { paymentMethodWithName(invoiceRevolutPay).assertIsDisplayed() }
}
step("Assert payment method: '$sepa' is displayed") {
onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(sepa).assertIsDisplayed() }
}
step("Press 'Back' button") {
onSelectPaymentMethodBottomSheet { device.uiDevice.pressBack() }
}
step("Assert 'Select Provider' bottom sheet title is displayed") {
onSelectProviderBottomSheet { title.assertIsDisplayed() }
onSelectPaymentMethodBottomSheet { paymentMethodWithName(sepa).assertIsDisplayed() }
}
}
}
}

View file

@ -3,7 +3,6 @@ package com.tangem.tests
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.RECIPIENT_ADDRESS
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.clickWithAssertion
@ -28,6 +27,8 @@ class FeedbackTest : BaseTestCase() {
@DisplayName("Send feedback: from details")
@Test
fun sendFeedbackFromDetailsTest() {
val gmailText = "Welcome to Gmail"
setupHooks(
additionalAfterSection = {
device.uiDevice.pressBack()
@ -42,8 +43,8 @@ class FeedbackTest : BaseTestCase() {
step("Click 'Contact support' button") {
onDetailsScreen { contactSupportButton.clickWithAssertion() }
}
step("Check 'Contact support' intent is called") {
checkSendEMailIntentCalled()
step("Assert 'Gmail' app is open") {
ThirdPartyAppPageObject { assertElementWithTextExists(gmailText) }
}
}
}
@ -52,10 +53,10 @@ class FeedbackTest : BaseTestCase() {
@DisplayName("Send feedback: failed transaction")
@Test
fun sendFeedbackFromFailedTransactionTest() {
val balance = TOTAL_BALANCE
val tokenName = "Polygon"
val recipientAddress = RECIPIENT_ADDRESS
val sendAmount = "1"
val gmailText = "Welcome to Gmail"
setupHooks(
additionalAfterSection = {
@ -66,7 +67,7 @@ class FeedbackTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
@ -87,18 +88,23 @@ 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("Assert sanding text is displayed") {
onSendConfirmScreen { sendingText.assertIsDisplayed() }
}
step("Click 'Send' button") {
waitForIdle()
onSendConfirmScreen {
sendButton.assertIsEnabled()
sendButton.performClick()
}
}
step("Check 'Failed transaction' dialog") {
waitForIdle()
flakySafely(WAIT_UNTIL_TIMEOUT) {
checkFailedTransactionDialog()
}
@ -106,8 +112,8 @@ class FeedbackTest : BaseTestCase() {
step("Click on 'Support' button") {
onFailedTransactionDialog { supportButton.performClick() }
}
step("Check 'Contact support' intent is called") {
checkSendEMailIntentCalled()
step("Assert 'Gmail' app is open") {
ThirdPartyAppPageObject { assertElementWithTextExists(gmailText) }
}
}
}
@ -116,6 +122,8 @@ class FeedbackTest : BaseTestCase() {
@DisplayName("Send feedback: from 'Warning' dialog after card scan")
@Test
fun sendFeedbackFromScanScreenTest() {
val gmailText = "Welcome to Gmail"
setupHooks(
additionalAfterSection = {
device.uiDevice.pressBack()
@ -138,13 +146,14 @@ class FeedbackTest : BaseTestCase() {
}
}
step("Check 'Scan warning' dialog") {
waitForIdle()
checkScanWarningDialog()
}
step("Click on 'Request support' button") {
ScanWarningDialogPageObject { requestSupportButton.click() }
}
step("Check 'Contact support' intent is called") {
checkSendEMailIntentCalled()
step("Assert 'Gmail' app is open") {
ThirdPartyAppPageObject { assertElementWithTextExists(gmailText) }
}
}
}
@ -153,6 +162,8 @@ class FeedbackTest : BaseTestCase() {
@DisplayName("Send feedback: from scan already used wallet alert dialog")
@Test
fun sendFeedbackAfterScanAlreadyUsedWalletTest() {
val gmailText = "Welcome to Gmail"
setupHooks(
additionalAfterSection = {
device.uiDevice.pressBack()
@ -173,8 +184,8 @@ class FeedbackTest : BaseTestCase() {
step("Click on 'Request support' button") {
AlreadyUsedWalletDialogPageObject { requestSupportButton.click() }
}
step("Check 'Contact support' intent is called") {
checkSendEMailIntentCalled()
step("Assert 'Gmail' app is open") {
ThirdPartyAppPageObject { assertElementWithTextExists(gmailText) }
}
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.tests
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
@ -19,13 +18,12 @@ class HideTokenTest : BaseTestCase() {
@Test
fun hideWalletTokenByHideButtonTest() {
val tokenTitle = "Polygon"
val balance = TOTAL_BALANCE
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Click on token with name: '$tokenTitle'") {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }

View file

@ -0,0 +1,64 @@
package com.tangem.tests
import com.tangem.common.BaseTestCase
import com.tangem.scenarios.*
import com.tangem.tap.domain.sdk.mocks.content.ShibaNoBackupMockContent
import com.tangem.tap.domain.sdk.mocks.content.ShibaNoBackupNoWalletsMockContent
import com.tangem.tap.domain.sdk.mocks.content.Wallet2NoBackupMockContent
import com.tangem.tap.domain.sdk.mocks.content.Wallet2NoBackupNoWalletsMockContent
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Test
@HiltAndroidTest
class OnboardingTest : BaseTestCase() {
@AllureId("248")
@DisplayName("Onboarding: 'Shiba' no wallets backup screen test")
@Test
fun shibaNoWalletsBackupScreenTest() {
setupHooks().run {
scanCard(mockContent = ShibaNoBackupNoWalletsMockContent)
checkCreateWalletScreenForWalletNoWallets()
openAndCheckBackupScreenForWalletNoWallets()
}
}
@AllureId("3989")
@DisplayName("Onboarding: 'Shiba' with wallets backup screen test")
@Test
fun shibaBackupScreenTest() {
setupHooks().run {
scanCard(
mockContent = ShibaNoBackupMockContent,
alreadyActivatedDialogIsShown = true
)
checkBackupScreen()
}
}
@AllureId("246")
@DisplayName("Onboarding: 'Wallet 2' no wallets backup screen test")
@Test
fun wallet2NoWalletsBackupScreenTest() {
setupHooks().run {
scanCard(mockContent = Wallet2NoBackupNoWalletsMockContent)
checkCreateWalletScreenForWallet2NoWallets()
openAndCheckBackupScreenForWallet2NoWallets()
}
}
@AllureId("3990")
@DisplayName("Onboarding: 'Wallet 2' with wallets backup screen test")
@Test
fun wallet2BackupScreenTest() {
setupHooks().run {
scanCard(
mockContent = Wallet2NoBackupMockContent,
alreadyActivatedDialogIsShown = true
)
checkBackupScreen()
}
}
}

View file

@ -2,7 +2,6 @@ package com.tangem.tests
import androidx.compose.ui.test.onAllNodesWithText
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
import com.tangem.common.extensions.SwipeDirection
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.swipeVertical
@ -92,13 +91,12 @@ class OrganizeTokensTest : BaseTestCase() {
setupHooks().run {
val ethereumTitle = "Ethereum"
val bitcoinTitle = "Bitcoin"
val balance = TOTAL_BALANCE
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Check positions of tokens on 'Main Screen'") {
onMainScreen {
@ -179,13 +177,12 @@ class OrganizeTokensTest : BaseTestCase() {
val bitcoinTitle = "Bitcoin"
val polygonTitle = "Polygon"
val polExMaticTitle = "POL (ex-MATIC)"
val balance = TOTAL_BALANCE
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Check positions of tokens on 'Main Screen'") {
onMainScreen {

View file

@ -0,0 +1,204 @@
package com.tangem.tests
import com.tangem.common.BaseTestCase
import com.tangem.common.annotations.ApiEnv
import com.tangem.common.annotations.ApiEnvConfig
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.domain.models.scan.ProductType
import com.tangem.scenarios.checkMultiCurrencyMainScreen
import com.tangem.scenarios.checkSingleCurrencyMainScreen
import com.tangem.scenarios.openMainScreen
import com.tangem.tap.domain.sdk.mocks.MockContent
import com.tangem.tap.domain.sdk.mocks.content.*
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Test
@HiltAndroidTest
class ScanCardTest : BaseTestCase() {
@ApiEnv(
ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD),
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
)
@AllureId("868")
@DisplayName("Scan: Scanning single-currency cards")
@Test
fun singleTokenNoteCardScanTest() {
val cardBlockchain = "DOGE"
val cardType: ProductType = ProductType.Note
setupHooks().run {
step("Open 'Main Screen' on '${cardType.name}' card") {
openMainScreen(cardType)
}
step("Check 'Main' screen for '${cardType.name}' $cardBlockchain card") {
checkSingleCurrencyMainScreen(
cardBlockchain = cardBlockchain,
cardTitle = cardType.name,
withTransactions = true
)
}
}
}
@AllureId("869")
@DisplayName("Scan: 'Twin' card")
@Test
fun twinsCardScanTest() {
val cardBlockchain = "BTC"
val cardType: MockContent = TwinsMockContent
val cardName = "Twin"
setupHooks().run {
step("Open 'Main Screen' on '$cardName' card") {
openMainScreen(mockContent = cardType, isTwinsCard = true)
}
step("Check 'Main' screen for '$cardName' $cardBlockchain card") {
checkSingleCurrencyMainScreen(cardBlockchain = cardBlockchain, cardTitle = cardName)
}
}
}
@AllureId("872")
@DisplayName("Scan: Card with Secp256k1 curve")
@Test
fun secpk1CurveCardScanTest() {
val devicesCount = "1 device"
val cardType: MockContent = Secpk1CurveMockContent
val cardName = "Wallet"
val card = "card with Secp256k1 curve"
setupHooks().run {
step("Open 'Main Screen' on $card") {
openMainScreen(mockContent = cardType, alreadyActivatedDialogIsShown = true)
}
step("Check 'Main' screen for $card curve with devices count = '$devicesCount'") {
checkMultiCurrencyMainScreen(
devicesCount = devicesCount,
cardTitle = cardName,
withWalletImage = false
)
}
}
}
@AllureId("870")
@DisplayName("Scan: Card with Ed25519 curve")
@Test
fun edCurveCardScanTest() {
val cardBlockchain = "XLM"
val cardType: MockContent = EdCurveMockContent
val cardName = "Tangem card"
val card = "card with Ed25519 curve"
setupHooks().run {
step("Open 'Main Screen' on $card") {
openMainScreen(mockContent = cardType)
}
step("Check 'Main' screen for $card with blockchain: '$cardBlockchain'") {
checkSingleCurrencyMainScreen(
cardBlockchain = cardBlockchain,
cardTitle = cardName,
withWalletImage = false
)
}
}
}
@AllureId("866")
@DisplayName("Scan: 'Shiba' card")
@Test
fun shibaCardScanTest() {
val devicesCount = "2 devices"
val cardType: MockContent = ShibaMockContent
val cardName = "Wallet"
val card = "Shiba"
setupHooks().run {
step("Open 'Main Screen' on '$card' card") {
openMainScreen(mockContent = cardType, alreadyActivatedDialogIsShown = true)
}
step("Check 'Main' screen for '$card' card with devices count = '$devicesCount'") {
checkMultiCurrencyMainScreen(devicesCount, cardName)
}
}
}
@AllureId("864")
@DisplayName("Scan: 'Ring'")
@Test
fun ringScanTest() {
val devicesCount = "3 devices"
val cardType: ProductType = ProductType.Ring
val cardName = "Wallet"
val ring = "Ring"
setupHooks().run {
step("Open 'Main Screen' on '$ring'") {
openMainScreen(productType = cardType, alreadyActivatedDialogIsShown = true)
}
step("Check 'Main' screen for '$ring' with devices count = '$devicesCount'") {
checkMultiCurrencyMainScreen(devicesCount, cardName)
}
}
}
@AllureId("867")
@DisplayName("Scan: 'Wallet' card")
@Test
fun walletCardScanTest() {
val devicesCount = "1 device"
val cardType: ProductType = ProductType.Wallet
val cardName = "Wallet"
setupHooks().run {
step("Open 'Main Screen' on '$cardName' card") {
openMainScreen(productType = cardType)
}
step("Check 'Main' screen for '$cardName' card with devices count = '$devicesCount'") {
checkMultiCurrencyMainScreen(devicesCount, cardName)
}
}
}
@AllureId("865")
@DisplayName("Scan: 'Wallet 2' card")
@Test
fun wallet2ScanTest() {
val devicesCount = "2 devices"
val cardType: MockContent = Wallet2MockContent
val cardName = "Wallet"
val card = "Wallet 2"
setupHooks().run {
step("Open 'Main Screen' on '$card' card") {
openMainScreen(mockContent = cardType, alreadyActivatedDialogIsShown = true)
}
step("Check 'Main' screen for '$card' card with devices count = '$devicesCount'") {
checkMultiCurrencyMainScreen(devicesCount, cardName)
}
}
}
@AllureId("871")
@DisplayName("Scan: Card with 4.12 firmware")
@Test
fun firmware412CardScanTest() {
val devicesCount = "1 device"
val cardType: MockContent = Firmware412MockContent
val cardName = "Tangem card"
val card = "card with 4.12 firmware"
setupHooks().run {
step("Open 'Main Screen' on '$card'") {
openMainScreen(mockContent = cardType)
}
step("Check 'Main' screen for '$card' with devices count = '$devicesCount'") {
checkMultiCurrencyMainScreen(devicesCount, cardName)
}
}
}
}

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
@ -23,7 +25,6 @@ class SendTest : BaseTestCase() {
val currencyName = "POL (ex-MATIC)"
val feeCurrencyName = "Ethereum"
val feeCurrencySymbol = "ETH"
val balance = "$763.55"
val scenarioName = "eth_network_balance"
val scenarioState = "Empty"
@ -40,7 +41,10 @@ class SendTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Swipe up") {
swipeVertical(SwipeDirection.UP)
}
step("Click on token with name: $currencyName") {
onMainScreen { tokenWithTitleAndAddress(currencyName).clickWithAssertion() }

View file

@ -1,31 +0,0 @@
package com.tangem.tests
import com.tangem.common.BaseTestCase
import com.tangem.domain.models.scan.ProductType
import com.tangem.scenarios.openMainScreen
import com.tangem.steps.checkSingleCurrencyMainScreen
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Test
@HiltAndroidTest
class SingleCurrencyCardsScan : BaseTestCase() {
@AllureId("868")
@DisplayName("Scan: Scanning single-currency cards")
@Test
fun singleTokenNoteScanTest() {
val cardBlockchain = "DOGE"
val cardType: ProductType = ProductType.Note
setupHooks().run {
step("Open 'Main Screen' on ${cardType.name} card") {
openMainScreen(cardType)
}
step("Check 'Main' screen for ${cardType.name} $cardBlockchain card") {
checkSingleCurrencyMainScreen(cardBlockchain = cardBlockchain, cardTitle = cardType.name)
}
}
}
}

View file

@ -1,13 +1,13 @@
package com.tangem.tests
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
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.*
import com.tangem.screens.*
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
@ -21,7 +21,6 @@ class StakingTest : BaseTestCase() {
@Test
fun validateStakingBlockTest() {
val tokenTitle = "POL (ex-MATIC)"
val balance = "$3,299.37"
val scenarioName = "staking_eth_pol_balances_android"
val scenarioState = "Staked"
@ -39,11 +38,14 @@ class StakingTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Assert 'Organize tokens' button is displayed") {
onMainScreen { organizeTokensButton().assertIsDisplayed() }
}
step("Swipe up") {
swipeVertical(SwipeDirection.UP)
}
step("Click on token with name: '$tokenTitle'") {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
}
@ -76,7 +78,6 @@ class StakingTest : BaseTestCase() {
@Test
fun validateStakingMoreScreensTest() {
val tokenTitle = "POL (ex-MATIC)"
val balance = "$3,299.37"
val scenarioName = "staking_eth_pol_balances_android"
val scenarioState = "Staked"
val stakingAmount = "1"
@ -95,11 +96,14 @@ class StakingTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Assert 'Organize tokens' button is displayed") {
onMainScreen { organizeTokensButton().assertIsDisplayed() }
}
step("Swipe up") {
swipeVertical(SwipeDirection.UP)
}
step("Click on token with name: '$tokenTitle'") {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
}
@ -109,116 +113,20 @@ class StakingTest : BaseTestCase() {
step("Click on 'Staking block'") {
onTokenDetailsScreen { stakingBlock.clickWithAssertion() }
}
step("Assert 'Title' is displayed") {
onStakingDetailsScreen { stakingTitle.assertIsDisplayed() }
}
step("Assert 'Annual percentage rate' is displayed") {
onStakingDetailsScreen { annualPercentageRate.assertIsDisplayed() }
}
step("Assert 'Available' block is displayed") {
onStakingDetailsScreen { availableBlock.assertIsDisplayed() }
}
step("Assert 'Unbonding Period' block is displayed") {
onStakingDetailsScreen { unbondingPeriodBlock.assertIsDisplayed() }
}
step("Assert 'Reward claiming' block is displayed") {
onStakingDetailsScreen { rewardClaimingBlock.assertIsDisplayed() }
}
step("Assert 'Reward schedule' block is displayed") {
onStakingDetailsScreen { rewardScheduleBlock.assertIsDisplayed() }
}
step("Assert 'Rewards block' is displayed") {
onStakingDetailsScreen { rewardsBlock.assertIsDisplayed() }
}
step("Assert 'Rewards block' title is displayed") {
onStakingDetailsScreen { rewardsBlockTitle.assertIsDisplayed() }
}
step("Assert 'Rewards block' text is displayed") {
onStakingDetailsScreen { rewardsBlockText.assertIsDisplayed() }
}
step("Assert 'Active staking block' is displayed") {
onStakingDetailsScreen { activeStakingBlock.assertIsDisplayed() }
}
step("Assert 'Your stakes' title is displayed") {
onStakingDetailsScreen { yourStakesTitle.assertIsDisplayed() }
}
step("Assert 'ToS' text is displayed") {
onStakingDetailsScreen { toSText.assertIsDisplayed() }
}
step("Assert 'Stake more' button is displayed") {
onStakingDetailsScreen { stakeMoreButton.assertIsDisplayed() }
step("Check 'Staking details' screen") {
checkStakingDetailsScreen(withStaking = true)
}
step("Click 'Stake more' button") {
onStakingDetailsScreen { stakeMoreButton.performClick() }
}
step("Assert 'Send' screen is displayed") {
onSendScreen { screenContainer.assertIsDisplayed() }
}
step("Assert 'Send' screen title is displayed") {
onSendScreen { title.assertIsDisplayed() }
}
step("Assert amount container title is displayed") {
onSendScreen { amountContainerTitle.assertIsDisplayed() }
}
step("Assert amount container text is displayed") {
onSendScreen { amountContainerText.assertIsDisplayed() }
}
step("Assert input text field is displayed") {
onSendScreen { amountInputTextField.assertIsDisplayed() }
}
step("Assert secondary amount is displayed") {
onSendScreen { secondaryAmount.assertIsDisplayed() }
}
step("Type '$stakingAmount' in input text field") {
onSendScreen {
amountInputTextField.performClick()
amountInputTextField.performTextReplacement(stakingAmount)
}
}
step("Assert input text field has value: '$stakingAmount'") {
onSendScreen { amountInputTextField.assertTextContains(value = stakingAmount, substring = true) }
}
step("Assert currency button is displayed") {
onSendScreen { currencyButton.assertIsDisplayed() }
}
step("Assert fiat button is displayed") {
onSendScreen { fiatButton.assertIsDisplayed() }
}
step("Assert currency button is displayed") {
onSendScreen { currencyButton.assertIsDisplayed() }
}
step("Assert fiat button is displayed") {
onSendScreen { fiatButton.assertIsDisplayed() }
}
step("Assert 'Max' button is displayed") {
onSendScreen { maxButton.assertIsDisplayed() }
}
step("Assert previous button is displayed") {
onSendScreen { previousButton.assertIsDisplayed() }
}
step("Assert 'Next' button is displayed") {
onSendScreen { nextButton.assertIsDisplayed() }
step("Check 'Staking' screen") {
checkStakingScreen(stakingAmount)
}
step("Click on 'Next' button") {
onSendScreen { nextButton.performClick() }
}
step("Assert 'Send details' screen title is displayed") {
onStakingConfirmScreen { title.assertIsDisplayed() }
}
step("Assert primary amount is displayed") {
onStakingConfirmScreen { primaryAmount.assertIsDisplayed() }
}
step("Assert secondary amount is displayed") {
onStakingConfirmScreen { secondaryAmount.assertIsDisplayed() }
}
step("Assert 'Validator' block is displayed") {
onStakingConfirmScreen { validatorBlock.assertIsDisplayed() }
}
step("Assert 'Network Fee' block is displayed") {
onStakingConfirmScreen { networkFeeBlock.assertIsDisplayed() }
}
step("Assert 'Stake' button is displayed") {
onStakingConfirmScreen { stakeButton.assertIsDisplayed() }
step("Check 'Staking confirm' screen") {
checkStakingConfirmScreen()
}
}
}
@ -228,7 +136,6 @@ class StakingTest : BaseTestCase() {
@Test
fun validateStakingScreensTest() {
val tokenTitle = "POL (ex-MATIC)"
val balance = TOTAL_BALANCE
val scenarioName = "staking_eth_pol_balances_android"
val scenarioState = "Started"
val stakingAmount = "1"
@ -247,11 +154,14 @@ class StakingTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Assert 'Organize tokens' button is displayed") {
onMainScreen { organizeTokensButton().assertIsDisplayed() }
}
step("Swipe up") {
swipeVertical(SwipeDirection.UP)
}
step("Click on token with name: '$tokenTitle'") {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
}
@ -273,107 +183,20 @@ class StakingTest : BaseTestCase() {
step("Click on 'Stake' button") {
onTokenDetailsScreen { stakeButton.clickWithAssertion() }
}
step("Assert 'Title' is displayed") {
onStakingDetailsScreen { stakingTitle.assertIsDisplayed() }
}
step("Assert banner image is displayed") {
onStakingDetailsScreen { bannerImage.assertIsDisplayed() }
}
step("Assert banner text is displayed") {
onStakingDetailsScreen { bannerText.assertIsDisplayed() }
}
step("Assert 'Annual percentage rate' is displayed") {
onStakingDetailsScreen { annualPercentageRate.assertIsDisplayed() }
}
step("Assert 'Available' block is displayed") {
onStakingDetailsScreen { availableBlock.assertIsDisplayed() }
}
step("Assert 'Unbonding Period' block is displayed") {
onStakingDetailsScreen { unbondingPeriodBlock.assertIsDisplayed() }
}
step("Assert 'Reward claiming' block is displayed") {
onStakingDetailsScreen { rewardClaimingBlock.assertIsDisplayed() }
}
step("Assert 'Reward schedule' block is displayed") {
onStakingDetailsScreen { rewardScheduleBlock.assertIsDisplayed() }
}
step("Assert 'ToS' text is displayed") {
onStakingDetailsScreen { toSText.assertIsDisplayed() }
}
step("Assert 'Stake' button is displayed") {
onStakingDetailsScreen { stakeButton.assertIsDisplayed() }
step("Check 'Staking details' screen") {
checkStakingDetailsScreen(withStaking = false)
}
step("Click 'Stake' button") {
onStakingDetailsScreen { stakeButton.performClick() }
}
step("Assert 'Send' screen is displayed") {
onSendScreen { screenContainer.assertIsDisplayed() }
}
step("Assert 'Send' screen title is displayed") {
onSendScreen { title.assertIsDisplayed() }
}
step("Assert amount container title is displayed") {
onSendScreen { amountContainerTitle.assertIsDisplayed() }
}
step("Assert amount container text is displayed") {
onSendScreen { amountContainerText.assertIsDisplayed() }
}
step("Assert input text field is displayed") {
onSendScreen { amountInputTextField.assertIsDisplayed() }
}
step("Assert secondary amount is displayed") {
onSendScreen { secondaryAmount.assertIsDisplayed() }
}
step("Type '$stakingAmount' in input text field") {
onSendScreen {
amountInputTextField.performClick()
amountInputTextField.performTextReplacement(stakingAmount)
}
}
step("Assert input text field has value: '$stakingAmount'") {
onSendScreen { amountInputTextField.assertTextContains(value = stakingAmount, substring = true) }
}
step("Assert currency button is displayed") {
onSendScreen { currencyButton.assertIsDisplayed() }
}
step("Assert fiat button is displayed") {
onSendScreen { fiatButton.assertIsDisplayed() }
}
step("Assert currency button is displayed") {
onSendScreen { currencyButton.assertIsDisplayed() }
}
step("Assert fiat button is displayed") {
onSendScreen { fiatButton.assertIsDisplayed() }
}
step("Assert 'Max' button is displayed") {
onSendScreen { maxButton.assertIsDisplayed() }
}
step("Assert previous button is displayed") {
onSendScreen { previousButton.assertIsDisplayed() }
}
step("Assert 'Next' button is displayed") {
onSendScreen { nextButton.assertIsDisplayed() }
step("Check 'Staking' screen") {
checkStakingScreen(stakingAmount)
}
step("Click on 'Next' button") {
onSendScreen { nextButton.performClick() }
}
step("Assert 'Send details' screen title is displayed") {
onStakingConfirmScreen { title.assertIsDisplayed() }
}
step("Assert primary amount is displayed") {
onStakingConfirmScreen { primaryAmount.assertIsDisplayed() }
}
step("Assert secondary amount is displayed") {
onStakingConfirmScreen { secondaryAmount.assertIsDisplayed() }
}
step("Assert 'Validator' block is displayed") {
onStakingConfirmScreen { validatorBlock.assertIsDisplayed() }
}
step("Assert 'Network Fee' block is displayed") {
onStakingConfirmScreen { networkFeeBlock.assertIsDisplayed() }
}
step("Assert 'Stake' button is displayed") {
onStakingConfirmScreen { stakeButton.assertIsDisplayed() }
step("Check 'Staking confirm' screen") {
checkStakingConfirmScreen()
}
}
}

View file

@ -4,14 +4,15 @@ import androidx.compose.ui.test.hasText
import com.tangem.common.BaseTestCase
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.resetWireMockScenarios
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.screens.*
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.*
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
@ -28,15 +29,16 @@ class SwapTokenTest : BaseTestCase() {
@Test
fun networkFeeTest() {
val inputAmount = "100"
setupHooks().run {
val tokenTitle = "Polygon"
val balance = TOTAL_BALANCE
val tokenTitle = "Polygon"
setupHooks().run {
resetWireMockScenarios()
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Click on token with name: '$tokenTitle'") {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
@ -71,7 +73,7 @@ class SwapTokenTest : BaseTestCase() {
}
}
step("Input swap amount = '$inputAmount'") {
composeTestRule.waitForIdle()
waitForIdle()
onSwapTokenScreen {
textInput.clickWithAssertion()
textInput.performTextReplacement(inputAmount)
@ -89,7 +91,7 @@ class SwapTokenTest : BaseTestCase() {
}
step("Assert 'Network fee' block is displayed") {
onSwapTokenScreen {
flakySafely(WAIT_UNTIL_TIMEOUT) {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
networkFeeBlock.assertIsDisplayed()
}
}
@ -104,15 +106,19 @@ 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
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Click on token with name: '$tokenTitle'") {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
@ -120,6 +126,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() }
}
@ -151,13 +161,12 @@ class SwapTokenTest : BaseTestCase() {
val inputAmount = "100"
setupHooks().run {
val tokenTitle = "Polygon"
val balance = TOTAL_BALANCE
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Click on token with name: '$tokenTitle'") {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
@ -189,7 +198,7 @@ class SwapTokenTest : BaseTestCase() {
}
}
step("Input swap amount = '$inputAmount'") {
composeTestRule.waitForIdle()
waitForIdle()
onSwapTokenScreen {
textInput.clickWithAssertion()
textInput.performTextReplacement(inputAmount)
@ -200,7 +209,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

@ -1,13 +1,16 @@
package com.tangem.tests
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
import com.tangem.common.extensions.SwipeDirection
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.swipeVertical
import com.tangem.common.utils.getWcUri
import com.tangem.common.utils.setClipboardText
import com.tangem.scenarios.*
import com.tangem.screens.*
import com.tangem.screens.onWalletConnectBottomSheet
import com.tangem.screens.onWalletConnectDetailsBottomSheet
import com.tangem.screens.onWalletConnectScanQrScreen
import com.tangem.screens.onWalletConnectScreen
import com.tangem.wallet.BuildConfig
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
@ -21,8 +24,7 @@ class WalletConnectTest : BaseTestCase() {
@DisplayName("WC (React App): open session from deeplink on main screen")
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
@Test
fun openWalletConnectSessionOnMainScreen() {
val balance = TOTAL_BALANCE
fun openWalletConnectSessionOnMainScreenTest() {
val dAppName = "React App"
val deepLinkUri = getWcUri()
@ -31,25 +33,34 @@ class WalletConnectTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Create WC session buy deeplink") {
openAppByDeepLink(deepLinkUri)
}
step("Check 'Wallet Connect' bottom sheet") {
checkWalletConnectBottomSheet()
flakySafely(WAIT_UNTIL_TIMEOUT) {
checkWalletConnectBottomSheet()
}
}
step("Assert 'Connect' button is enabled") {
onWalletConnectBottomSheet { connectButton.assertIsEnabled() }
}
step("Click on 'Connect' button") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.performClick() }
}
step("Click 'More' button on TopBar") {
onTopBar { moreButton.clickWithAssertion() }
step("Assert 'Connect' button is not displayed") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
}
step("Click on 'Wallet Connect' button") {
onDetailsScreen { walletConnectButton.clickWithAssertion() }
step("Open 'Wallet Connect' screen") {
openWalletConnectScreen()
}
step("Check 'Wallet Connect' screen") {
checkWalletConnectScreen()
step("Check 'Wallet Connect' screen with connections") {
flakySafely(WAIT_UNTIL_TIMEOUT) {
checkWalletConnectScreen(withConnections = true)
}
}
step("Click on app icon") {
onWalletConnectScreen { appIcon.performClick() }
@ -57,11 +68,11 @@ class WalletConnectTest : BaseTestCase() {
step("Check 'Wallet Connect' details bottom sheet") {
checkWalletConnectDetailsBottomSheet(dAppName)
}
step("Click on 'Disconnect button' is displayed") {
step("Click on 'Disconnect' button") {
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
}
step("Assert connection is not displayed") {
onWalletConnectScreen { appName.assertIsNotDisplayed() }
step("Check 'Wallet Connect' screen without connections") {
checkWalletConnectScreen(withConnections = false)
}
}
}
@ -70,8 +81,7 @@ class WalletConnectTest : BaseTestCase() {
@DisplayName("WC (React App): open session from deeplink not on main screen")
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
@Test
fun openWalletConnectSessionNotOnMainScreen() {
val balance = TOTAL_BALANCE
fun openWalletConnectSessionNotOnMainScreenTest() {
val dAppName = "React App"
val deepLinkUri = getWcUri()
@ -80,43 +90,46 @@ class WalletConnectTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Click on 'Buy' button") {
onMainScreen { buyButton.clickWithAssertion() }
step("Open 'Wallet Connect' screen") {
openWalletConnectScreen()
checkWalletConnectScreen(false)
}
step("Create WC session buy deeplink") {
openAppByDeepLink(deepLinkUri)
}
step("Check 'Wallet Connect' bottom sheet") {
checkWalletConnectBottomSheet()
flakySafely(WAIT_UNTIL_TIMEOUT) {
checkWalletConnectBottomSheet()
}
}
step("Click on 'Connect' button") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.performClick() }
}
step("Click 'More' button on TopBar") {
onTopBar { moreButton.clickWithAssertion() }
step("Assert 'Connect' button is not displayed") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
}
step("Click on 'Wallet Connect' button") {
onDetailsScreen { walletConnectButton.clickWithAssertion() }
}
step("Assert 'Wallet Connect' bottom sheet is displayed") {
onWalletConnectBottomSheet { connectButton.clickWithAssertion() }
}
step("Check 'Wallet Connect' screen") {
checkWalletConnectScreen()
step("Check 'Wallet Connect' screen with connections") {
flakySafely(WAIT_UNTIL_TIMEOUT) {
checkWalletConnectScreen(withConnections = true)
}
}
step("Click on app icon") {
onWalletConnectScreen { appIcon.performClick() }
}
step("Check 'Wallet Connect' details bottom sheet") {
checkWalletConnectDetailsBottomSheet(dAppName)
flakySafely(WAIT_UNTIL_TIMEOUT) {
checkWalletConnectDetailsBottomSheet(dAppName)
}
}
step("Click on 'Disconnect button' is displayed") {
step("Click on 'Disconnect' button") {
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
}
step("Assert connection is not displayed") {
onWalletConnectScreen { appName.assertIsNotDisplayed() }
step("Check 'Wallet Connect' screen without connections") {
checkWalletConnectScreen(withConnections = false)
}
}
}
@ -125,9 +138,9 @@ class WalletConnectTest : BaseTestCase() {
@DisplayName("WC (React App): open session from deeplink ")
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
@Test
fun openWalletConnectSession() {
val balance = TOTAL_BALANCE
fun openWalletConnectSessionTest() {
val dAppName = "React App"
val packageName = BuildConfig.APPLICATION_ID
val deepLinkUri = getWcUri()
setupHooks().run {
@ -135,34 +148,30 @@ class WalletConnectTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(balance)
synchronizeAddresses()
}
step("Open recent apps") {
device.uiDevice.pressRecentApps()
}
step("Stop app by swipe") {
swipeVertical(SwipeDirection.UP, startHeightRatio = 0.8f)
step("Kill app") {
device.apps.kill(packageName)
}
step("Create WC session buy deeplink") {
openAppByDeepLink(deepLinkUri)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Check 'Wallet Connect' bottom sheet") {
checkWalletConnectBottomSheet()
flakySafely(WAIT_UNTIL_TIMEOUT) {
checkWalletConnectBottomSheet()
}
}
step("Click on 'Connect' button") {
onWalletConnectBottomSheet { connectButton.performClick() }
}
step("Click 'More' button on TopBar") {
onTopBar { moreButton.clickWithAssertion() }
step("Assert 'Connect' button is not displayed") {
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
}
step("Click on 'Wallet Connect' button") {
onDetailsScreen { walletConnectButton.clickWithAssertion() }
step("Open 'Wallet Connect' screen") {
openWalletConnectScreen()
}
step("Check 'Wallet Connect' screen") {
checkWalletConnectScreen()
step("Check 'Wallet Connect' screen with connections") {
checkWalletConnectScreen(withConnections = true)
}
step("Click on app icon") {
onWalletConnectScreen { appIcon.performClick() }
@ -170,11 +179,71 @@ class WalletConnectTest : BaseTestCase() {
step("Check 'Wallet Connect' details bottom sheet") {
checkWalletConnectDetailsBottomSheet(dAppName)
}
step("Click on 'Disconnect button' is displayed") {
step("Click on 'Disconnect' button") {
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
}
step("Assert connection is not displayed") {
onWalletConnectScreen { appName.assertIsNotDisplayed() }
step("Check 'Wallet Connect' screen without connections") {
checkWalletConnectScreen(withConnections = false)
}
}
}
@AllureId("887")
@DisplayName("WC: open session by 'Paste from clipboard' button")
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
@Test
fun openWalletConnectSessionByClipboardLinkTest() {
val dAppName = "React App"
val context = device.context
val deepLinkUri = getWcUri()
setupHooks().run {
step("Set URI to clipboard") {
setClipboardText(context, deepLinkUri)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Open 'Wallet Connect' screen") {
openWalletConnectScreen()
}
step("Click 'New connection' button") {
onWalletConnectScreen { newConnectionButton.performClick() }
}
step("CLick 'Paste from clipboard' button") {
onWalletConnectScanQrScreen { pasteFromClipboardButton.clickWithAssertion() }
}
step("Check 'Wallet Connect' bottom sheet") {
waitForIdle()
flakySafely(WAIT_UNTIL_TIMEOUT) {
checkWalletConnectBottomSheet()
}
}
step("Click on 'Connect' button") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.performClick() }
}
step("Assert 'Connect' button is not displayed") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
}
step("Check 'Wallet Connect' screen with connections") {
checkWalletConnectScreen(withConnections = true)
}
step("Click on app icon") {
onWalletConnectScreen { appIcon.performClick() }
}
step("Check 'Wallet Connect' details bottom sheet") {
checkWalletConnectDetailsBottomSheet(dAppName)
}
step("Click on 'Disconnect' button") {
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
}
step("Check 'Wallet Connect' screen without connections") {
checkWalletConnectScreen(withConnections = false)
}
}
}

View file

@ -0,0 +1,394 @@
package com.tangem.tests.actionButtons
import androidx.compose.ui.test.longClick
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.BITCOIN_ADDRESS
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.assertClipboardTextEquals
import com.tangem.common.utils.clearClipboard
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.*
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Test
@HiltAndroidTest
class MainScreenActionButtonsTest : BaseTestCase() {
@AllureId("79")
@DisplayName("Action buttons (long tap): validate UI")
@Test
fun actionButtonsValidateLongTapUiTest() {
val tokenTitle = "Ethereum"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Analytics' button is displayed") {
onTokenActionsBottomSheet { analyticsButton.assertIsDisplayed() }
}
step("Assert 'Copy address' button is displayed") {
onTokenActionsBottomSheet { copyAddressButton.assertIsDisplayed() }
}
step("Assert 'Receive' button is displayed") {
onTokenActionsBottomSheet { receiveButton.assertIsDisplayed() }
}
step("Assert 'Send' button is displayed") {
onTokenActionsBottomSheet { sendButton.assertIsDisplayed() }
}
step("Assert 'Swap' button is displayed") {
onTokenActionsBottomSheet { swapButton.assertIsDisplayed() }
}
step("Assert 'Buy' button is displayed") {
onTokenActionsBottomSheet { buyButton.assertIsDisplayed() }
}
step("Assert 'Sell' button is displayed") {
onTokenActionsBottomSheet { sellButton.assertIsDisplayed() }
}
step("Assert 'Hide token' button is displayed") {
onTokenActionsBottomSheet { hideTokenButton.assertIsDisplayed() }
}
}
}
@AllureId("84")
@DisplayName("Action buttons (long tap): check 'Copy address' button")
@Test
fun clickOnCopyAddressButtonTest() {
val tokenTitle = "Bitcoin"
val bitcoinAddress = BITCOIN_ADDRESS
setupHooks(
additionalBeforeSection = {
clearClipboard()
},
additionalAfterSection = {
clearClipboard()
}
).run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Copy address' button is displayed") {
onTokenActionsBottomSheet { copyAddressButton.assertIsDisplayed() }
}
step("Click on 'Copy address' button") {
onTokenActionsBottomSheet { copyAddressButton.performClick() }
}
step("Assert snack bar message is displayed") {
onMainScreen { snackbarCopiedAddressMessage.assertIsDisplayed() }
}
step("Check clipboard has '$tokenTitle' address '$bitcoinAddress'") {
waitForIdle()
assertClipboardTextEquals(expected = bitcoinAddress)
}
}
}
@AllureId("82")
@DisplayName("Action buttons (long tap): check 'Buy' button")
@Test
fun clickOnBuyButtonTest() {
val tokenTitle = "Bitcoin"
val tokenSymbol = "BTC"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Buy' button is displayed") {
onTokenActionsBottomSheet { buyButton.assertIsDisplayed() }
}
step("Click on 'Buy' button") {
onTokenActionsBottomSheet { buyButton.performClick() }
}
step("Click on 'Confirm' button in 'Dialog'") {
waitForIdle()
onDialog { confirmButton.clickWithAssertion() }
}
step("Assert top app bar title contains '$tokenTitle'") {
onBuyTokenDetailsScreen { topBarTitle.assertTextContains("Buy $tokenTitle") }
}
step("Assert fiat currency text field is displayed") {
onBuyTokenDetailsScreen { fiatAmountTextField.assertIsDisplayed() }
}
step("Assert fiat currency icon is displayed") {
onBuyTokenDetailsScreen { fiatCurrencyIcon.assertIsDisplayed() }
}
step("Assert token amount field is displayed") {
onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenSymbol, substring = true) }
}
step("Assert 'Continue' button") {
onBuyTokenDetailsScreen { continueButton.assertIsDisplayed() }
}
}
}
@AllureId("87")
@DisplayName("Action buttons (long tap): check 'Swap' button")
@Test
fun clickOnSwapButtonTest() {
val tokenTitle = "Ethereum"
val tokenSymbol = "ETH"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Swap' button is displayed") {
onTokenActionsBottomSheet { swapButton.assertIsDisplayed() }
}
step("Click on 'Swap' button") {
onTokenActionsBottomSheet { swapButton.performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
}
step("Assert 'Swap' screen title is displayed") {
onSwapTokenScreen { title.assertIsDisplayed() }
}
step("Assert token symbol: '$tokenSymbol' is displayed") {
onSwapTokenScreen { tokenSymbol(tokenSymbol).assertIsDisplayed() }
}
}
}
@AllureId("83")
@DisplayName("Action buttons (long tap): check 'Send' button")
@Test
fun clickOnSendButtonTest() {
val tokenTitle = "Ethereum"
val tokenSymbol = "ETH"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Send' button is displayed") {
onTokenActionsBottomSheet { sendButton.assertIsDisplayed() }
}
step("Click on 'Send' button") {
onTokenActionsBottomSheet { sendButton.performClick() }
}
step("Assert amount input text field contains token symbol: '$tokenSymbol'") {
onSendScreen {
amountInputTextField.assertTextContains(value = tokenSymbol, substring = true)
}
}
}
}
@AllureId("86")
@DisplayName("Action buttons (long tap): check 'Receive' button")
@Test
fun clickOnReceiveButtonTest() {
val tokenTitle = "Bitcoin"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Receive' button is displayed") {
onTokenActionsBottomSheet { receiveButton.assertIsDisplayed() }
}
step("Click on 'Receive' button") {
onTokenActionsBottomSheet { receiveButton.performClick() }
}
step("Assert 'Token receive warning' bottom sheet is displayed") {
waitForIdle()
flakySafely(WAIT_UNTIL_TIMEOUT) {
onTokenReceiveWarningBottomSheet {
bottomSheet.assertIsDisplayed()
}
}
}
step("Click on 'Got it' button") {
onTokenReceiveWarningBottomSheet { gotItButton.performClick() }
}
step("Click on 'Show QR code' button") {
onReceiveAssetsBottomSheet { showQrCodeButton.clickWithAssertion() }
}
step("Assert bottom sheet with QR code title is displayed") {
onTokenReceiveQrCodeBottomSheet { title.assertIsDisplayed() }
}
step("Assert QR code is displayed") {
onTokenReceiveQrCodeBottomSheet { qrCode.assertIsDisplayed() }
}
step("Assert address title is displayed") {
onTokenReceiveQrCodeBottomSheet { addressTitle.assertIsDisplayed() }
}
step("Assert address is displayed") {
onTokenReceiveQrCodeBottomSheet { address.assertIsDisplayed() }
}
step("Assert 'Copy' button is displayed") {
onTokenReceiveQrCodeBottomSheet { copyButton.assertIsDisplayed() }
}
step("Assert 'Share' button is displayed") {
onTokenReceiveQrCodeBottomSheet { shareButton.assertIsDisplayed() }
}
}
}
@AllureId("85")
@DisplayName("Action buttons (long tap): check 'Sell' button")
@Test
fun clickOnSellButtonTest() {
val tokenTitle = "Ethereum"
val url = "sell.moonpay.com"
val useWithoutAccount = "Use without an account"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Receive' button is displayed") {
onTokenActionsBottomSheet { sellButton.assertIsDisplayed() }
}
step("Click on 'Receive' button") {
onTokenActionsBottomSheet { sellButton.performClick() }
}
step("Assert Chrome Browser is opened") {
ThirdPartyAppPageObject { assertChromeIsOpened() }
}
if (ThirdPartyAppPageObject.isElementWithTextExists(useWithoutAccount)) {
step("Click on '$useWithoutAccount' button on Chrome browser") {
ThirdPartyAppPageObject { clickOnElementWithText(useWithoutAccount) }
}
}
step("Assert url contains: '$url'") {
ThirdPartyAppPageObject { assertUrlContains(url) }
}
}
}
@AllureId("77")
@DisplayName("Action buttons (long tap): assert 'Sell' button is not displayed if token doesn't support it")
@Test
fun assertSellButtonIsNotDisplayedTest() {
val tokenTitle = "Bitcoin"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Long click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
position = center,
durationMillis = 1000L
)
}
}
}
step("Assert 'Sell' button is not displayed") {
onTokenActionsBottomSheet { sellButton.assertIsNotDisplayed() }
}
}
}
}

View file

@ -2,7 +2,6 @@ package com.tangem.tests.balance
import androidx.compose.ui.test.longClick
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.onMainScreen
@ -23,7 +22,7 @@ class TotalBalanceLongTapTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(TOTAL_BALANCE)
synchronizeAddresses()
}
step("Long tap on total balance block") {
onMainScreen {

View file

@ -51,7 +51,7 @@ class TotalBalanceUnavailableTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(DASH_SIGN)
synchronizeAddresses(isBalanceAvailable = false)
}
step("Assert 'Synchronize addresses' button does not exist") {
onMainScreen {
@ -88,7 +88,7 @@ class TotalBalanceUnavailableTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(DASH_SIGN)
synchronizeAddresses(isBalanceAvailable = false)
}
step("Assert 'Synchronize addresses' button does not exist") {
onMainScreen {
@ -125,7 +125,7 @@ class TotalBalanceUnavailableTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(DASH_SIGN)
synchronizeAddresses(isBalanceAvailable = false)
}
step("Assert 'Synchronize addresses' button does not exist") {
onMainScreen {

View file

@ -33,7 +33,7 @@ class TotalBalanceUpdateTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(TOTAL_BALANCE)
synchronizeAddresses()
}
step("Assert $TOTAL_BALANCE is displayed in total balance") {
onMainScreen { totalBalanceText.assertTextContains(TOTAL_BALANCE) }
@ -67,14 +67,14 @@ class TotalBalanceUpdateTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(TOTAL_BALANCE)
synchronizeAddresses()
}
step("Assert $TOTAL_BALANCE is displayed in total balance") {
onMainScreen { totalBalanceText.assertTextContains(TOTAL_BALANCE) }
}
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'") {
@ -117,7 +117,7 @@ class TotalBalanceUpdateTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(TOTAL_BALANCE)
synchronizeAddresses()
}
step("Assert $TOTAL_BALANCE is displayed in total balance") {
onMainScreen { totalBalanceText.assertTextContains(TOTAL_BALANCE) }
@ -145,13 +145,13 @@ class TotalBalanceUpdateTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(TOTAL_BALANCE)
synchronizeAddresses()
}
step("Assert $TOTAL_BALANCE is displayed in total balance") {
onMainScreen { totalBalanceText.assertTextContains(TOTAL_BALANCE) }
}
step("Long click on token with name: '$tokenTitle'") {
composeTestRule.waitForIdle()
waitForIdle()
onMainScreen {
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
longClick(
@ -162,7 +162,7 @@ class TotalBalanceUpdateTest : BaseTestCase() {
}
}
step("Click 'Hide token' button") {
onBottomSheet { hideButton.clickWithAssertion() }
onTokenActionsBottomSheet { hideTokenButton.clickWithAssertion() }
}
step("Click 'Hide' button in dialog") {
onDialog {
@ -187,7 +187,7 @@ class TotalBalanceUpdateTest : BaseTestCase() {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses(TOTAL_BALANCE)
synchronizeAddresses()
}
step("Assert $TOTAL_BALANCE is displayed in total balance") {
onMainScreen { totalBalanceText.assertTextContains(TOTAL_BALANCE) }

View file

@ -33,6 +33,7 @@
<application
android:name="com.tangem.tap.TangemHiltApplication"
android:theme="@style/AppTheme"
android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"

View file

@ -28,7 +28,7 @@ import com.tangem.domain.apptheme.repository.AppThemeModeRepository
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase

View file

@ -13,9 +13,9 @@ object ForegroundActivityObserver {
val foregroundActivity: AppCompatActivity?
get() = activities.entries
.firstOrNull {
Timber.i("foregroundActivity: ${it.key} | ${it.value.isDestroyed}")
it.value.isDestroyed == false
.firstOrNull { entry ->
Timber.i("foregroundActivity: ${entry.key} | ${entry.value.isDestroyed}")
entry.value.isDestroyed == false
}
?.value
@ -28,9 +28,9 @@ object ForegroundActivityObserver {
override fun onActivityResumed(activity: Activity) {
Timber.i("onActivityResumed ${activity::class}")
(activity as? AppCompatActivity)?.let {
if (activity is AppCompatActivity) {
Timber.i("onActivityResumed store activity")
activities[activity::class] = it
activities[activity::class] = activity
}
}

View file

@ -4,10 +4,10 @@ import android.content.Context
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject

View file

@ -7,10 +7,10 @@ import androidx.lifecycle.LifecycleOwner
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.tangem.common.routing.AppRoute
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.domain.wallets.usecase.ClearAllHotWalletContextualUnlockUseCase
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.tap.LockTimerWorker.Companion.TAG
@ -22,6 +22,7 @@ import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.concurrent.TimeUnit
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes
@Suppress("LongParameterList")
internal class LockUserWalletsTimer(

View file

@ -38,7 +38,7 @@ import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.settings.SetGooglePayAvailabilityUseCase
import com.tangem.domain.settings.SetGoogleServicesAvailabilityUseCase
import com.tangem.domain.settings.ShouldInitiallyAskPermissionUseCase
@ -264,7 +264,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
tangemSdkManager = injectedTangemSdkManager
backupServiceHolder.createAndSetService(cardSdkConfigRepository.sdk, this)
backupService = backupServiceHolder.backupService.get()!! // will be deleted eventually
backupService = requireNotNull(backupServiceHolder.backupService.get()) // will be deleted eventually
lockUserWalletsTimer = LockUserWalletsTimer(
context = this,
@ -371,17 +371,15 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
}
}
override fun onNewIntent(intent: Intent?) {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
val fromPush = intent?.extras?.containsKey(OPENED_FROM_GCM_PUSH) ?: false
if (fromPush) {
val isFromPush = intent.extras?.containsKey(OPENED_FROM_GCM_PUSH) == true
if (isFromPush) {
analyticsEventsHandler.send(Push.PushNotificationOpened)
}
if (intent != null) {
handleDeepLink(intent = intent, isFromOnNewIntent = true)
}
handleDeepLink(intent = intent, isFromOnNewIntent = true)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@ -406,8 +404,8 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
val result = WindowObscurationObserver.dispatchTouchEvent(event, analyticsEventsHandler)
return if (result) super.dispatchTouchEvent(event) else false
val isHandled = WindowObscurationObserver.dispatchTouchEvent(event, analyticsEventsHandler)
return if (isHandled) super.dispatchTouchEvent(event) else false
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
@ -451,9 +449,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
getPolkadotCheckHasImmortalUseCase()
.flowWithLifecycle(lifecycle, minActiveState = Lifecycle.State.CREATED)
.distinctUntilChanged()
.collect {
.collect { (_, hasImmortalTransaction) ->
analyticsEventsHandler.send(
WalletScreenAnalyticsEvent.Token.PolkadotImmortalTransactions(it.second),
WalletScreenAnalyticsEvent.Token.PolkadotImmortalTransactions(hasImmortalTransaction),
)
}
}

View file

@ -88,7 +88,7 @@ lateinit var store: Store<AppState>
val foregroundActivityObserver = ForegroundActivityObserver
internal lateinit var derivationsFinder: DerivationsFinder
abstract class TangemApplication : Application(), ImageLoaderFactory, Configuration.Provider {
open class TangemApplication : Application(), ImageLoaderFactory, Configuration.Provider {
// region DI
private val entryPoint: ApplicationEntryPoint
@ -290,7 +290,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
/**
* Initialize components that need to be initialized before [super.onCreate] is called
*/
private fun preInit() {
fun preInit() {
tangemAppLoggerInitializer.initialize()
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
}

View file

@ -7,7 +7,7 @@ package com.tangem.tap.common
object TestActions {
// It used only for the test actions in debug or debug_beta builds
var testAmountInjectionForWalletManagerEnabled = false
var isTestAmountInjectionForWalletManagerEnabled = false
}
typealias TestAction = Pair<String, () -> Unit>

View file

@ -58,7 +58,7 @@ private class BlockchainSdkErrorConverter(
if (value.customMessage.contains(DemoTransactionSender.ID)) return emptyMap()
if (value is BlockchainSdkError.WrappedTangemError) {
return (value.tangemError as? TangemSdkError)?.let { cardSdkErrorConverter.convert(it) } ?: emptyMap()
return (value.tangemError as? TangemSdkError)?.let { cardSdkErrorConverter.convert(it) }.orEmpty()
}
return mapOf(

View file

@ -7,7 +7,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
*/
sealed class Chat(
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent("Chat", event, params) {
class ScreenOpened : Chat("Chat Screen Opened")

View file

@ -8,7 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
sealed class Onboarding(
category: String,
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(category, event, params) {
class Started : Onboarding("Onboarding", "Onboarding Started")
@ -16,7 +16,7 @@ sealed class Onboarding(
sealed class CreateWallet(
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : Onboarding("Onboarding / Create Wallet", event, params) {
class ScreenOpened : CreateWallet("Create Wallet Screen Opened")
@ -36,7 +36,7 @@ sealed class Onboarding(
sealed class Backup(
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : Onboarding("Onboarding / Backup", event, params) {
class ScreenOpened : Backup("Backup Screen Opened")
@ -64,7 +64,7 @@ sealed class Onboarding(
sealed class Topup(
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : Onboarding("Onboarding / Top Up", event, params) {
class ScreenOpened : Topup("Activation Screen Opened")
@ -79,7 +79,7 @@ sealed class Onboarding(
sealed class Twins(
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : Onboarding("Onboarding / Twins", event, params) {
class ScreenOpened : Twins("Twinning Screen Opened")

View file

@ -8,7 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
sealed class Settings(
category: String = "Settings",
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(category, event, params) {
class ScreenOpened : Settings(event = "Settings Screen Opened")
@ -17,7 +17,7 @@ sealed class Settings(
sealed class CardSettings(
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : Settings("Settings / Card Settings", event, params) {
class ButtonFactoryReset : CardSettings("Button - Factory Reset")
@ -54,7 +54,7 @@ sealed class Settings(
sealed class AppSettings(
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : Settings(category = "Settings / App Settings", event = event, params = params) {
class SaveWalletSwitcherChanged(state: AnalyticsParam.OnOffState) : AppSettings(

View file

@ -7,7 +7,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
*/
sealed class SignIn(
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent("Sign In", event, params) {
class ScreenOpened : SignIn(event = "Sign In Screen Opened")

View file

@ -9,12 +9,12 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
sealed class Token(
category: String,
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(category, event, params) {
sealed class Receive(
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : Token("Token / Receive", event, params) {
class ScreenOpened(
@ -29,7 +29,7 @@ sealed class Token(
sealed class Topup(
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : Token("Token / Topup", event, params) {
class ScreenOpened : Topup("Top Up Screen Opened")
@ -38,7 +38,7 @@ sealed class Token(
sealed class Withdraw(
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : Token("Token / Withdraw", event, params) {
class ScreenOpened : Withdraw("Withdraw Screen Opened")

View file

@ -19,6 +19,7 @@ internal class DefaultClipboardManager(private val clipboardManager: AndroidClip
clipboardManager.setPrimaryClip(clip)
}
@Suppress("UseIsNullOrEmpty")
override fun getText(default: String?): String? {
val clip = clipboardManager.primaryClip

View file

@ -90,6 +90,6 @@ fun Store<AppState>.dispatchNavigationAction(action: AppRouter.() -> Unit) {
inline fun <reified T> Store<AppState>.inject(getDependency: DaggerGraphState.() -> T?): T {
return requireNotNull(state.daggerGraphState.getDependency()) {
"${T::class.simpleName} isn't initialized "
"${T::class.simpleName.orEmpty()} isn't initialized "
}
}

View file

@ -22,9 +22,9 @@ import timber.log.Timber
)
@Suppress("MagicNumber")
suspend fun WalletManager.safeUpdate(isDemoCard: Boolean): Result<Wallet> = try {
if (isDemoCard || TestActions.testAmountInjectionForWalletManagerEnabled) {
if (isDemoCard || TestActions.isTestAmountInjectionForWalletManagerEnabled) {
delay(500)
TestActions.testAmountInjectionForWalletManagerEnabled = false
TestActions.isTestAmountInjectionForWalletManagerEnabled = false
Result.Success(wallet)
} else {
update()
@ -35,7 +35,7 @@ suspend fun WalletManager.safeUpdate(isDemoCard: Boolean): Result<Wallet> = try
val networkConnectionManager = store.inject(DaggerGraphState::networkConnectionManager)
if (!networkConnectionManager.isOnline) {
Result.Failure(TapError.NoInternetConnection)
Result.Failure(TapError.NoInternetConnection())
} else {
val blockchain = wallet.blockchain
val amountToCreateAccount = blockchain.amountToCreateAccount(this, wallet.getFirstToken())

View file

@ -44,6 +44,7 @@ class TangemAppLoggerInitializer(
}
}
@Suppress("BooleanPropertyNaming")
private companion object {
val IS_LOG_ENABLED: Boolean = BuildConfig.LOG_ENABLED
val PERMITTED_PRIORITY = listOf(Log.ERROR, Log.INFO)

View file

@ -20,6 +20,6 @@ data class GlobalState(
typealias CryptoCurrencyName = String
data class OnboardingState(
val onboardingStarted: Boolean = false,
val isOnboardingStarted: Boolean = false,
val shouldResetOnCreate: Boolean = false,
)

View file

@ -61,9 +61,9 @@ internal class AndroidEmailSender : EmailSender {
.setSubject(email.subject)
.setText(email.message)
email.attachment?.let {
email.attachment?.let { file ->
builder.setStream(
FileProvider.getUriForFile(activity, "${activity.packageName}.provider", it),
FileProvider.getUriForFile(activity, "${activity.packageName}.provider", file),
)
}

View file

@ -14,7 +14,7 @@ import javax.inject.Singleton
import kotlin.text.encodeToByteArray
private const val DEFAULT_KEY = "tangem_pay_default_key"
private const val DEFAULT_CUSTOMER_WALLET_ADDRESS_KEY = "tangem_pay_default_customer_wallet_address_key"
private const val ORDER_ID_KEY = "tangem_pay_order_id_key"
@Singleton
internal class DefaultTangemPayStorage @Inject constructor(
@ -54,24 +54,29 @@ internal class DefaultTangemPayStorage @Inject constructor(
?.let(tokensAdapter::fromJson)
}
/**
* Store the only customer wallet address, since for the f&f user can issue only one card tied to one address
*/
override suspend fun storeCustomerWalletAddress(customerWalletAddress: String) =
override suspend fun storeOrderId(customerWalletAddress: String, orderId: String) {
withContext(dispatcherProvider.io) {
secureStorage.store(
customerWalletAddress.encodeToByteArray(throwOnInvalidSequence = true),
DEFAULT_CUSTOMER_WALLET_ADDRESS_KEY,
orderId.encodeToByteArray(throwOnInvalidSequence = true),
createOrderIdKey(customerWalletAddress),
)
}
override suspend fun getCustomerWalletAddress(): String? = withContext(dispatcherProvider.io) {
secureStorage.get(DEFAULT_CUSTOMER_WALLET_ADDRESS_KEY)?.decodeToString(throwOnInvalidSequence = true)
}
override suspend fun clear(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
override suspend fun getOrderId(customerWalletAddress: String): String? = withContext(dispatcherProvider.io) {
secureStorage.get(createOrderIdKey(customerWalletAddress))?.decodeToString(throwOnInvalidSequence = true)
}
override suspend fun clearOrderId(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
secureStorage.delete(createOrderIdKey(customerWalletAddress))
}
override suspend fun clearAll(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
secureStorage.delete(createKey(customerWalletAddress))
secureStorage.delete(createOrderIdKey(customerWalletAddress))
}
private fun createKey(cardId: String): String = "${DEFAULT_KEY}_$cardId"
private fun createKey(address: String): String = "${DEFAULT_KEY}_$address"
private fun createOrderIdKey(address: String): String = "${ORDER_ID_KEY}_$address"
}

View file

@ -3,7 +3,7 @@ package com.tangem.tap.data
import com.tangem.common.CompletionResult
import com.tangem.common.catching
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.coroutines.flow.Flow

View file

@ -1,8 +1,8 @@
package com.tangem.tap.di.data
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.tap.data.RuntimeUserWalletsStore
import com.tangem.tap.data.UserWalletsStoreRepositoryProxy

View file

@ -1,7 +1,9 @@
package com.tangem.tap.di.domain
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.account.tokens.MainAccountTokensMigration
import com.tangem.domain.account.usecase.*
import dagger.Module
import dagger.Provides
@ -15,8 +17,16 @@ internal object AccountDomainModule {
@Provides
@Singleton
fun provideAddCryptoPortfolioUseCase(accountsCRUDRepository: AccountsCRUDRepository): AddCryptoPortfolioUseCase {
return AddCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
fun provideAddCryptoPortfolioUseCase(
accountsCRUDRepository: AccountsCRUDRepository,
singleAccountListFetcher: SingleAccountListFetcher,
mainAccountTokensMigration: MainAccountTokensMigration,
): AddCryptoPortfolioUseCase {
return AddCryptoPortfolioUseCase(
crudRepository = accountsCRUDRepository,
singleAccountListFetcher = singleAccountListFetcher,
mainAccountTokensMigration = mainAccountTokensMigration,
)
}
@Provides
@ -39,8 +49,12 @@ internal object AccountDomainModule {
@Singleton
fun provideRecoverCryptoPortfolioUseCase(
accountsCRUDRepository: AccountsCRUDRepository,
mainAccountTokensMigration: MainAccountTokensMigration,
): RecoverCryptoPortfolioUseCase {
return RecoverCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
return RecoverCryptoPortfolioUseCase(
crudRepository = accountsCRUDRepository,
mainAccountTokensMigration = mainAccountTokensMigration,
)
}
@Provides

View file

@ -1,18 +1,16 @@
package com.tangem.tap.di.domain
import com.tangem.domain.card.*
import com.tangem.domain.card.DeleteSavedAccessCodesUseCase
import com.tangem.domain.card.ResetCardUseCase
import com.tangem.domain.card.SetCardWasScannedUseCase
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase
import com.tangem.domain.wallets.usecase.GetExtendedPublicKeyForCurrencyUseCase
import com.tangem.domain.wallets.usecase.HasMissedDerivationsUseCase
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase
import com.tangem.domain.wallets.usecase.*
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.domain.card.DefaultDeleteSavedAccessCodesUseCase

View file

@ -2,8 +2,8 @@ package com.tangem.tap.di.domain
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles

View file

@ -1,6 +1,5 @@
package com.tangem.tap.di.domain
import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.domain.managetokens.*
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.managetokens.repository.ManageTokensRepository
@ -10,11 +9,14 @@ import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton
@Module
@ -74,6 +76,7 @@ internal object ManageTokensDomainModule {
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
stakingIdFactory: StakingIdFactory,
dispatchers: CoroutineDispatcherProvider,
): SaveManagedTokensUseCase {
return SaveManagedTokensUseCase(
customTokensRepository = customTokensRepository,
@ -84,6 +87,7 @@ internal object ManageTokensDomainModule {
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
stakingIdFactory = stakingIdFactory,
parallelUpdatingScope = CoroutineScope(SupervisorJob() + dispatchers.default),
)
}

View file

@ -1,7 +1,7 @@
package com.tangem.tap.di.domain
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.markets.*
import com.tangem.domain.markets.repositories.MarketsTokenRepository
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
@ -12,13 +12,16 @@ import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton
@Module
@ -67,6 +70,7 @@ object MarketsDomainModule {
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
stakingIdFactory: StakingIdFactory,
dispatchers: CoroutineDispatcherProvider,
): SaveMarketTokensUseCase {
return SaveMarketTokensUseCase(
derivationsRepository = derivationsRepository,
@ -76,6 +80,17 @@ object MarketsDomainModule {
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
stakingIdFactory = stakingIdFactory,
parallelUpdatingScope = CoroutineScope(SupervisorJob() + dispatchers.default),
)
}
@Provides
@Singleton
fun provideGetTokenMarketCryptoCurrency(
marketsTokenRepository: MarketsTokenRepository,
): GetTokenMarketCryptoCurrency {
return GetTokenMarketCryptoCurrency(
marketsTokenRepository = marketsTokenRepository,
)
}

View file

@ -179,6 +179,20 @@ internal object OnrampDomainModule {
)
}
@Provides
@Singleton
fun provideGetOnrampV2QuotesUseCase(
settingsRepository: SettingsRepository,
onrampRepository: OnrampRepository,
onrampErrorResolver: OnrampErrorResolver,
): GetOnrampV2QuotesUseCase {
return GetOnrampV2QuotesUseCase(
settingsRepository = settingsRepository,
repository = onrampRepository,
errorResolver = onrampErrorResolver,
)
}
@Provides
@Singleton
fun provideGetOnrampProviderWithQuoteUseCase(

View file

@ -6,6 +6,7 @@ import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.staking.repositories.StakingTransactionHashRepository
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import dagger.Module
import dagger.Provides
@ -216,4 +217,10 @@ internal object StakingDomainModule {
fun provideStakingIdFactory(walletManagersFacade: WalletManagersFacade): StakingIdFactory {
return StakingIdFactory(walletManagersFacade = walletManagersFacade)
}
@Provides
@Singleton
fun provideStakingApyFlowUseCase(stakingRepository: StakingRepository): StakingApyFlowUseCase {
return StakingApyFlowUseCase(stakingRepository)
}
}

View file

@ -24,6 +24,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
@ -455,4 +456,21 @@ internal object TokensDomainModule {
): SaveViewedTokenReceiveWarningUseCase {
return SaveViewedTokenReceiveWarningUseCase(tokenReceiveWarningsViewedRepository)
}
@Provides
@Singleton
fun provideNeedShowYieldSupplyDepositedWarningUseCase(
yieldSupplyWarningsViewedRepository: YieldSupplyWarningsViewedRepository,
dispatchers: CoroutineDispatcherProvider,
): NeedShowYieldSupplyDepositedWarningUseCase {
return NeedShowYieldSupplyDepositedWarningUseCase(yieldSupplyWarningsViewedRepository, dispatchers)
}
@Provides
@Singleton
fun provideSaveViewedYieldSupplyWarningUseCase(
yieldSupplyWarningsViewedRepository: YieldSupplyWarningsViewedRepository,
): SaveViewedYieldSupplyWarningUseCase {
return SaveViewedYieldSupplyWarningUseCase(yieldSupplyWarningsViewedRepository)
}
}

View file

@ -5,6 +5,7 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
import com.tangem.domain.tokens.GetViewedTokenReceiveWarningUseCase
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.transaction.TransactionRepository
@ -231,6 +232,18 @@ internal object TransactionDomainModule {
)
}
@Provides
@Singleton
fun provideReceiveAddressesFactory(
getEnsNameUseCase: GetEnsNameUseCase,
getViewedTokenReceiveWarningUseCase: GetViewedTokenReceiveWarningUseCase,
): ReceiveAddressesFactory {
return ReceiveAddressesFactory(
getEnsNameUseCase = getEnsNameUseCase,
getViewedTokenReceiveWarningUseCase = getViewedTokenReceiveWarningUseCase,
)
}
@Provides
@Singleton
fun provideGetReverseResolvedEnsAddressUseCase(

Some files were not shown because too many files have changed in this diff Show more