Updated on 2026-08-14
This commit is contained in:
parent
ecb1c6d272
commit
822ed8eb80
14 changed files with 570 additions and 9 deletions
|
|
@ -8,6 +8,8 @@ import androidx.test.core.app.ActivityScenario
|
|||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import com.kaspersky.components.alluresupport.interceptors.step.ScreenshotStepInterceptor
|
||||
import com.kaspersky.components.alluresupport.withForcedAllureSupport
|
||||
import com.kaspersky.components.composesupport.config.addComposeSupport
|
||||
|
|
@ -20,13 +22,14 @@ import com.tangem.common.rules.ApiEnvironmentRule
|
|||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.walletmanager.WalletManagersStore
|
||||
import com.tangem.datasource.utils.WireMockRedirectInterceptor
|
||||
import com.tangem.domain.promo.PromoRepository
|
||||
import com.tangem.domain.promo.models.PromoId
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.tap.MainActivity
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import io.qameta.allure.kotlin.Allure
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TestRule
|
||||
|
|
@ -58,6 +61,12 @@ abstract class BaseTestCase : TestCase(
|
|||
@Inject
|
||||
lateinit var promoRepository: PromoRepository
|
||||
|
||||
@Inject
|
||||
lateinit var walletManagersStore: WalletManagersStore
|
||||
|
||||
@Inject
|
||||
lateinit var getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase
|
||||
|
||||
private val hiltRule = HiltAndroidRule(this)
|
||||
private val apiEnvironmentRule = ApiEnvironmentRule()
|
||||
private val permissionRule = GrantPermissionRule.grant(
|
||||
|
|
@ -72,7 +81,11 @@ abstract class BaseTestCase : TestCase(
|
|||
|
||||
private val semanticTreePrinterRule = object : TestWatcher() {
|
||||
override fun failed(e: Throwable?, description: Description?) {
|
||||
runCatching { printAllRoots() }
|
||||
runCatching {
|
||||
runBlocking {
|
||||
withTimeoutOrNull(SEMANTIC_TREE_PRINT_TIMEOUT_MS) { printAllRoots() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,5 +187,6 @@ abstract class BaseTestCase : TestCase(
|
|||
|
||||
private companion object {
|
||||
const val WIREMOCK_BASE_URL_ARG = "wiremockBaseUrl"
|
||||
const val SEMANTIC_TREE_PRINT_TIMEOUT_MS = 5_000L
|
||||
}
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ object TestConstants {
|
|||
|
||||
const val WAIT_UNTIL_TIMEOUT = 20_000L
|
||||
const val WAIT_UNTIL_TIMEOUT_LONG = 30_000L
|
||||
const val WAIT_UNTIL_TIMEOUT_VERY_LONG = 60_000L
|
||||
|
||||
const val MARKETS_MAIN_NETWORK_SUFFIX = "MAIN"
|
||||
|
||||
|
|
@ -43,4 +44,14 @@ object TestConstants {
|
|||
|
||||
const val USER_TOKENS_API_SCENARIO = "user_tokens_api"
|
||||
const val QUOTES_API_SCENARIO = "quotes_api"
|
||||
|
||||
const val SEED_PHRASE_12 = "they cram join fantasy unfair observe true theory buffalo bus exchange walk"
|
||||
const val SEED_PHRASE_15 = "genuine try deer upset connect sausage diary rule price shallow fit faculty leopard " +
|
||||
"hawk when"
|
||||
const val SEED_PHRASE_18 = "crush idle include refuse expose kiss slot budget uphold when dinner certain holiday " +
|
||||
"slow word armor butter suffer"
|
||||
const val SEED_PHRASE_21 = "employ space oval venue wash clog zebra cover icon wash assist word debris inform " +
|
||||
"cable meadow add game meat rigid pride"
|
||||
const val SEED_PHRASE_24 = "force visit fresh brown razor target ill scissors figure cave feel genre cargo category " +
|
||||
"bread much nature basic fun iron benefit egg error prosper"
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.tangem.common.utils
|
||||
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import org.junit.Assert.assertTrue
|
||||
|
||||
/**
|
||||
* Compares addresses from the app (clipboard JSON) with reference addresses from the QA tools API.
|
||||
*
|
||||
* Both JSON arrays contain objects with fields: blockchain, derivationPath, token (nullable), addresses (array).
|
||||
* Comparison normalizes and sorts both arrays before diffing.
|
||||
*/
|
||||
object AddressComparisonHelper {
|
||||
|
||||
fun compareAddresses(appJson: String, apiJson: String) {
|
||||
val appEntries = parseAndNormalize(appJson)
|
||||
val apiEntries = parseAndNormalize(apiJson)
|
||||
|
||||
val missingInApp = apiEntries - appEntries.toSet()
|
||||
val extraInApp = appEntries - apiEntries.toSet()
|
||||
|
||||
if (missingInApp.isEmpty() && extraInApp.isEmpty()) {
|
||||
TangemLogger.i("Address comparison passed: ${appEntries.size} entries match")
|
||||
return
|
||||
}
|
||||
|
||||
val report = buildString {
|
||||
appendLine("Address comparison FAILED")
|
||||
if (missingInApp.isNotEmpty()) {
|
||||
appendLine("\nMissing in app (expected from API but not found):")
|
||||
missingInApp.forEach { appendLine(" - $it") }
|
||||
}
|
||||
if (extraInApp.isNotEmpty()) {
|
||||
appendLine("\nExtra in app (found in app but not in API):")
|
||||
extraInApp.forEach { appendLine(" - $it") }
|
||||
}
|
||||
appendLine("\nApp entries: ${appEntries.size}, API entries: ${apiEntries.size}")
|
||||
}
|
||||
|
||||
TangemLogger.e(report)
|
||||
assertTrue(report, false)
|
||||
}
|
||||
|
||||
private fun parseAndNormalize(json: String): List<AddressEntry> {
|
||||
val array = JSONArray(json)
|
||||
val entries = mutableListOf<AddressEntry>()
|
||||
|
||||
for (i in 0 until array.length()) {
|
||||
val obj = array.getJSONObject(i)
|
||||
entries.add(
|
||||
AddressEntry(
|
||||
blockchain = normalizeBlockchainName(obj.getString("blockchain")),
|
||||
derivationPath = obj.getString("derivationPath").trim(),
|
||||
token = obj.optString("token", null)?.trim()?.lowercase(),
|
||||
addresses = parseAddresses(obj).sorted(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return entries.sortedWith(
|
||||
compareBy<AddressEntry> { it.blockchain }
|
||||
.thenBy { it.derivationPath }
|
||||
.thenBy { it.token },
|
||||
)
|
||||
}
|
||||
|
||||
private val blockchainNameOverrides = mapOf(
|
||||
"chia network" to "chia",
|
||||
)
|
||||
|
||||
private fun normalizeBlockchainName(name: String): String {
|
||||
val normalized = name.trim().lowercase()
|
||||
return blockchainNameOverrides[normalized] ?: normalized
|
||||
}
|
||||
|
||||
private fun parseAddresses(obj: JSONObject): List<String> {
|
||||
val addressesArray = obj.getJSONArray("addresses")
|
||||
return (0 until addressesArray.length()).map { addressesArray.getString(it) }
|
||||
}
|
||||
|
||||
private data class AddressEntry(
|
||||
val blockchain: String,
|
||||
val derivationPath: String,
|
||||
val token: String?,
|
||||
val addresses: List<String>,
|
||||
)
|
||||
}
|
||||
|
|
@ -16,10 +16,10 @@ fun getWcUri(
|
|||
TangemLogger.i("Getting WC URI for network: $network")
|
||||
|
||||
val client = OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS) // Таймаут подключения
|
||||
.readTimeout(60, TimeUnit.SECONDS) // Таймаут чтения ответа
|
||||
.writeTimeout(30, TimeUnit.SECONDS) // Таймаут записи
|
||||
.callTimeout(90, TimeUnit.SECONDS) // Общий таймаут запроса
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.callTimeout(90, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
val request = Request.Builder()
|
||||
|
|
@ -58,6 +58,59 @@ fun getWcUri(
|
|||
}
|
||||
}
|
||||
|
||||
fun getAddressesFromApi(
|
||||
seedKey: String,
|
||||
baseUrl: String = "[REDACTED_ENV_URL]",
|
||||
): String? {
|
||||
TangemLogger.i("Getting addresses for seed key: $seedKey")
|
||||
|
||||
val client = OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.callTimeout(90, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$baseUrl/addresses")
|
||||
.get()
|
||||
.build()
|
||||
|
||||
return try {
|
||||
client.newCall(request).execute().use { response ->
|
||||
TangemLogger.i("Response code: ${response.code}")
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val body = response.body?.string() ?: ""
|
||||
|
||||
val contentType = response.header("Content-Type") ?: ""
|
||||
if (!contentType.contains("application/json") && !body.trimStart().startsWith("{")) {
|
||||
TangemLogger.e("Unexpected response (not JSON), Content-Type: $contentType, body: $body")
|
||||
return null
|
||||
}
|
||||
|
||||
val jsonObject = JSONObject(body)
|
||||
val data = jsonObject.optJSONObject("data") ?: jsonObject
|
||||
val seedData = data.optJSONArray(seedKey)
|
||||
|
||||
if (seedData != null) {
|
||||
TangemLogger.i("Got addresses for $seedKey: ${seedData.length()} entries")
|
||||
seedData.toString()
|
||||
} else {
|
||||
TangemLogger.e("No data found for seed key: $seedKey")
|
||||
null
|
||||
}
|
||||
} else {
|
||||
val errorBody = response.body?.string() ?: "No error body"
|
||||
TangemLogger.e("Request failed: ${response.code}, body: $errorBody")
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
TangemLogger.e("Error getting addresses", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun checkServiceHealth(
|
||||
baseUrl: String = "[REDACTED_ENV_URL]"
|
||||
): String? {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
import android.view.KeyEvent
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_VERY_LONG
|
||||
import com.tangem.common.utils.AddressComparisonHelper
|
||||
import com.tangem.common.utils.getClipboardText
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.screens.onTesterMenuScreen
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.junit.Assert.assertNotNull
|
||||
|
||||
private const val WALLET_MANAGERS_SETTLE_MS = 5_000L
|
||||
private const val WALLET_MANAGERS_POLL_INTERVAL_MS = 500L
|
||||
|
||||
fun BaseTestCase.verifyAddresses(seedPhrase: String, apiAddressesJson: String) {
|
||||
var appAddressesJson: String? = null
|
||||
|
||||
step("Open 'Main Screen' with existing hot wallet") {
|
||||
openMainScreenWithExistingHotWallet(seedPhrase)
|
||||
}
|
||||
step("Assert wallet balance = '$DASH_SIGN'") {
|
||||
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_VERY_LONG) {
|
||||
runCatching { onMainScreen { totalBalanceText.assertTextContains(DASH_SIGN) } }.isSuccess
|
||||
}
|
||||
}
|
||||
step("Assert 'Organize tokens' button is enabled") {
|
||||
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_VERY_LONG) {
|
||||
runCatching { onMainScreen { organizeTokensButton().assertIsEnabled() } }.isSuccess
|
||||
}
|
||||
}
|
||||
step("Wait for all wallet managers to initialize") {
|
||||
awaitWalletManagersStabilized()
|
||||
}
|
||||
step("Open tester menu") {
|
||||
openTesterMenu()
|
||||
}
|
||||
step("Click on 'Addresses info' button") {
|
||||
onTesterMenuScreen { addressesInfoButton.performClick() }
|
||||
}
|
||||
step("Click on 'JSON' tab") {
|
||||
onTesterMenuScreen { jsonTab.performClick() }
|
||||
}
|
||||
step("Click on 'Copy' button") {
|
||||
onTesterMenuScreen { copyButton.performClick() }
|
||||
}
|
||||
step("Get addresses JSON from clipboard") {
|
||||
appAddressesJson = getClipboardText(ApplicationProvider.getApplicationContext())
|
||||
assertNotNull("Clipboard is empty after copying addresses", appAddressesJson)
|
||||
}
|
||||
step("Compare app addresses with API reference") {
|
||||
AddressComparisonHelper.compareAddresses(
|
||||
appJson = requireNotNull(appAddressesJson),
|
||||
apiJson = apiAddressesJson,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val TESTER_MENU_MAX_ATTEMPTS = 3
|
||||
|
||||
/**
|
||||
* Presses 'Volume Down' twice to open tester menu.
|
||||
* Retries up to [TESTER_MENU_MAX_ATTEMPTS] times if the menu doesn't appear.
|
||||
*/
|
||||
private fun BaseTestCase.openTesterMenu() {
|
||||
repeat(TESTER_MENU_MAX_ATTEMPTS) { attempt ->
|
||||
waitForIdle()
|
||||
device.uiDevice.pressKeyCode(KeyEvent.KEYCODE_VOLUME_DOWN)
|
||||
device.uiDevice.pressKeyCode(KeyEvent.KEYCODE_VOLUME_DOWN)
|
||||
|
||||
val opened = runCatching {
|
||||
onTesterMenuScreen { addressesInfoButton.assertIsDisplayed() }
|
||||
}.isSuccess
|
||||
|
||||
if (opened) {
|
||||
TangemLogger.i("Tester menu opened on attempt ${attempt + 1}")
|
||||
return
|
||||
}
|
||||
TangemLogger.w("Tester menu not opened on attempt ${attempt + 1}, retrying...")
|
||||
}
|
||||
error("Failed to open tester menu after $TESTER_MENU_MAX_ATTEMPTS attempts")
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls [walletManagersStore] until the wallet manager count stops growing for [WALLET_MANAGERS_SETTLE_MS].
|
||||
|
||||
*
|
||||
* Uses [getAllSync] with a polling interval instead of Flow, because the Flow only emits on changes —
|
||||
* if the count stabilizes, there would be no new emission to check the settle timeout against.
|
||||
*/
|
||||
private fun BaseTestCase.awaitWalletManagersStabilized() {
|
||||
val walletId = getSelectedWalletSyncUseCase().getOrNull()?.walletId
|
||||
?: error("No selected wallet found")
|
||||
var lastSize = -1
|
||||
var stableStart = System.currentTimeMillis()
|
||||
|
||||
runBlocking {
|
||||
withTimeout(WAIT_UNTIL_TIMEOUT_VERY_LONG) {
|
||||
while (true) {
|
||||
val currentSize = walletManagersStore.getAllSync(walletId).size
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
if (currentSize != lastSize) {
|
||||
TangemLogger.i("Wallet managers count: $currentSize (was $lastSize)")
|
||||
lastSize = currentSize
|
||||
stableStart = now
|
||||
} else if (now - stableStart >= WALLET_MANAGERS_SETTLE_MS) {
|
||||
TangemLogger.i("Wallet managers stabilized at $currentSize entries")
|
||||
return@withTimeout
|
||||
}
|
||||
|
||||
delay(WALLET_MANAGERS_POLL_INTERVAL_MS)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -59,6 +59,57 @@ fun BaseTestCase.openMainScreen(
|
|||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.openMainScreenWithExistingHotWallet(seedPhrase: String) {
|
||||
step("Click on 'Accept' button") {
|
||||
onDisclaimerScreen { acceptButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Get started' button") {
|
||||
onStoriesScreen { getStartedButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Start with Mobile Wallet' button") {
|
||||
onCreateWalletStartScreen { startWithMobileWalletButton.performClick() }
|
||||
}
|
||||
step("Click on 'Import existing wallet' button") {
|
||||
onCreateMobileWalletScreen { importExistingWalletButton.performClick() }
|
||||
}
|
||||
step("Click on 'Phrase text field'") {
|
||||
onImportWalletScreen { phraseTextField.performClick() }
|
||||
}
|
||||
step("Type seed phrase in 'Phrase text field'") {
|
||||
onImportWalletScreen { phraseTextField.performTextReplacement(seedPhrase) }
|
||||
}
|
||||
step("Click on 'Import' button") {
|
||||
onImportWalletScreen {
|
||||
importButton.assertIsEnabled()
|
||||
importButton.performClick()
|
||||
}
|
||||
}
|
||||
step("Click on 'Continue' button") {
|
||||
onImportWalletScreen {
|
||||
continueButton.assertIsEnabled()
|
||||
continueButton.performClick()
|
||||
}
|
||||
}
|
||||
step("Click on 'Skip' button") {
|
||||
onImportWalletScreen { skipButton.performClick() }
|
||||
}
|
||||
step("Click on 'Skip anyway' dialog button") {
|
||||
onDialog { skipAnywayButton.performClick() }
|
||||
}
|
||||
step("Click on 'Finish' button") {
|
||||
onImportWalletScreen {
|
||||
finishButton.assertIsEnabled()
|
||||
finishButton.performClick()
|
||||
}
|
||||
}
|
||||
step("Assert 'Main' screen is displayed") {
|
||||
onMainScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
step("Dismiss Market Tooltip by clicking close button") {
|
||||
onMarketsTooltipScreen { closeButton.clickWithAssertion() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.synchronizeAddresses(
|
||||
balance: String? = null,
|
||||
isBalanceAvailable: Boolean = true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.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 CreateMobileWalletPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<CreateMobileWalletPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val importExistingWalletButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.hw_import_existing_wallet))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onCreateMobileWalletScreen(function: CreateMobileWalletPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -17,6 +17,11 @@ class CreateWalletStartPageObject(semanticsProvider: SemanticsNodeInteractionsPr
|
|||
hasText(getResourceString(OnboardingImplR.string.welcome_unlock_card))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val startWithMobileWalletButton: KNode = child {
|
||||
hasText(getResourceString(OnboardingImplR.string.welcome_create_wallet_mobile_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onCreateWalletStartScreen(function: CreateWalletStartPageObject.() -> Unit) =
|
||||
|
|
|
|||
|
|
@ -64,6 +64,11 @@ class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_change))
|
||||
}
|
||||
|
||||
val skipAnywayButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.access_code_alert_skip_ok))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onDialog(function: DialogPageObject.() -> Unit) =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.R
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.ImportWalletScreenTestTags
|
||||
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
|
||||
|
||||
class ImportWalletPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<ImportWalletPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val phraseTextField: KNode = child {
|
||||
hasTestTag(ImportWalletScreenTestTags.PHRASE_TEXT_FIELD)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val passphraseTextField: KNode = child {
|
||||
hasTestTag(ImportWalletScreenTestTags.PASSPHRASE_TEXT_FIELD)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val importButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasAnyChild(withText(getResourceString(R.string.common_import)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val continueButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasAnyChild(withText(getResourceString(R.string.common_continue)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val skipButton: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.MORE_BUTTON)
|
||||
hasText(getResourceString(R.string.common_skip))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val finishButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasAnyChild(withText(getResourceString(R.string.common_finish)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onImportWalletScreen(function: ImportWalletPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.feature.tester.impl.R as TesterImplR
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
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
|
||||
|
||||
class TesterMenuPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<TesterMenuPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val backButton: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val addressesInfoButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(TesterImplR.string.addresses_info))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val jsonTab: KNode = child {
|
||||
hasText("JSON")
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val copyButton: KNode = child {
|
||||
hasContentDescription("Copy")
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onTesterMenuScreen(function: TesterMenuPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package com.tangem.tests.hotWallet
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.annotations.ApiEnv
|
||||
import com.tangem.common.annotations.ApiEnvConfig
|
||||
import com.tangem.common.constants.TestConstants.SEED_PHRASE_12
|
||||
import com.tangem.common.constants.TestConstants.SEED_PHRASE_15
|
||||
import com.tangem.common.constants.TestConstants.SEED_PHRASE_18
|
||||
import com.tangem.common.constants.TestConstants.SEED_PHRASE_21
|
||||
import com.tangem.common.constants.TestConstants.SEED_PHRASE_24
|
||||
import com.tangem.common.utils.checkServiceHealth
|
||||
import com.tangem.common.utils.getAddressesFromApi
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiEnvironment
|
||||
import com.tangem.scenarios.verifyAddresses
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class AddressesTest : BaseTestCase() {
|
||||
|
||||
private var apiAddressesJson: String? = null
|
||||
|
||||
private fun setupAddressTestHooks(seedKey: String) = setupHooks(
|
||||
additionalBeforeAppLaunchSection = {
|
||||
val status = checkServiceHealth()
|
||||
assertNotNull("QA tools service is unreachable ([REDACTED_ENV_URL]", status)
|
||||
|
||||
apiAddressesJson = getAddressesFromApi(seedKey)
|
||||
assertNotNull("Failed to fetch reference addresses for '$seedKey'", apiAddressesJson)
|
||||
},
|
||||
)
|
||||
|
||||
@ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD))
|
||||
@AllureId("1792")
|
||||
@DisplayName("Hot wallet: auto derivation addresses for seed 12")
|
||||
@Test
|
||||
fun seed12AddressesTest() {
|
||||
setupAddressTestHooks("twelve").run {
|
||||
verifyAddresses(SEED_PHRASE_12, requireNotNull(apiAddressesJson))
|
||||
}
|
||||
}
|
||||
|
||||
@ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD))
|
||||
@AllureId("5106")
|
||||
@DisplayName("Hot wallet: auto derivation addresses for seed 15")
|
||||
@Test
|
||||
fun seed15AddressesTest() {
|
||||
setupAddressTestHooks("fifteen").run {
|
||||
verifyAddresses(SEED_PHRASE_15, requireNotNull(apiAddressesJson))
|
||||
}
|
||||
}
|
||||
|
||||
@ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD))
|
||||
@AllureId("5107")
|
||||
@DisplayName("Hot wallet: auto derivation addresses for seed 18")
|
||||
@Test
|
||||
fun seed18AddressesTest() {
|
||||
setupAddressTestHooks("eighteen").run {
|
||||
verifyAddresses(SEED_PHRASE_18, requireNotNull(apiAddressesJson))
|
||||
}
|
||||
}
|
||||
|
||||
@ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD))
|
||||
@AllureId("5108")
|
||||
@DisplayName("Hot wallet: auto derivation addresses for seed 21")
|
||||
@Test
|
||||
fun seed21AddressesTest() {
|
||||
setupAddressTestHooks("twenty_one").run {
|
||||
verifyAddresses(SEED_PHRASE_21, requireNotNull(apiAddressesJson))
|
||||
}
|
||||
}
|
||||
|
||||
@ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD))
|
||||
@AllureId("5109")
|
||||
@DisplayName("Hot wallet: auto derivation addresses for seed 24")
|
||||
@Test
|
||||
fun seed24AddressesTest() {
|
||||
setupAddressTestHooks("twenty_four").run {
|
||||
verifyAddresses(SEED_PHRASE_24, requireNotNull(apiAddressesJson))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.core.ui.test
|
||||
|
||||
object ImportWalletScreenTestTags {
|
||||
const val PHRASE_TEXT_FIELD = "IMPORT_WALLET_PHRASE_TEXT_FIELD"
|
||||
const val PASSPHRASE_TEXT_FIELD = "IMPORT_WALLET_PASSPHRASE_TEXT_FIELD"
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
|
|
@ -39,6 +40,7 @@ import com.tangem.core.ui.components.PrimaryButton
|
|||
import com.tangem.core.ui.components.TangemTextFieldsDefault
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.test.ImportWalletScreenTestTags
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.ui.utils.InvalidWordsColorTransformation
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -73,9 +75,10 @@ internal fun AddExistingWalletImportContent(state: AddExistingWalletImportUM, mo
|
|||
)
|
||||
|
||||
OutlineTextFieldWithIcon(
|
||||
modifier = modifier
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
.testTag(ImportWalletScreenTestTags.PASSPHRASE_TEXT_FIELD),
|
||||
value = state.passPhrase,
|
||||
onValueChange = state.passPhraseChange,
|
||||
iconResId = R.drawable.ic_information_24,
|
||||
|
|
@ -130,7 +133,8 @@ private fun PhraseBlock(state: AddExistingWalletImportUM, modifier: Modifier = M
|
|||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(TangemTheme.dimens.size142),
|
||||
.height(TangemTheme.dimens.size142)
|
||||
.testTag(ImportWalletScreenTestTags.PHRASE_TEXT_FIELD),
|
||||
value = state.words,
|
||||
onValueChange = state.wordsChange,
|
||||
textStyle = TangemTheme.typography.body1,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue