Updated on 2026-08-14
This commit is contained in:
parent
383d42f6b3
commit
2110b5f541
10 changed files with 87 additions and 74 deletions
|
|
@ -45,6 +45,16 @@ When the user asks to **port** an iOS test to Android:
|
|||
XCUITest/accessibility identifiers → Compose `testTag`; iOS `*Screen` page objects → Kotlin page
|
||||
objects in `com/tangem/screens/`; XCTest assertions → Kaspresso/Truth assertions. Re-derive the real
|
||||
Android `testTag`s and string resources from production source — never reuse iOS identifier strings.
|
||||
- **Card/wallet mock mapping — where iOS uses `wallet2`, Android uses the default `Wallet`**
|
||||
(`openMainScreen()` with no `productType` → `ProductType.Wallet`). Do NOT port iOS `.wallet2` to
|
||||
`ProductType.Wallet2`. Other cards map directly: iOS `.twin` → `ProductType.Twins`, `.xrpNote` →
|
||||
`ProductType.Note`, `.four12` → `Firmware412MockContent` (via the `mockContent` param). `ProductType.Wallet2`
|
||||
exists but is a distinct Wallet-2.0-card case, not the iOS-`wallet2` analog.
|
||||
- **A wallet has no balance until you sync.** The default `Wallet` mock starts with missing derivations
|
||||
("Some addresses are missing"); the fiat balance shows `—` until you call `synchronizeAddresses()` after
|
||||
`openMainScreen()` (mirror `TotalBalanceUpdateTest`). Any test asserting a balance/fiat-equivalent must
|
||||
sync first, then `waitUntil` the value loads — balances re-load asynchronously (e.g. after an app-currency
|
||||
change the equivalent repaints with a delay).
|
||||
- The WireMock scenarios are usually shared across platforms, but the branch may differ
|
||||
(see `reference/running-and-debugging.md`).
|
||||
|
||||
|
|
|
|||
|
|
@ -175,7 +175,10 @@ fun BaseTestCase.openDeviceSettingsScreen() {
|
|||
onDetailsScreen { walletNameButton.performClick() }
|
||||
}
|
||||
step("Click on 'Device settings' button") {
|
||||
onWalletSettingsScreen { deviceSettingsButton.clickWithAssertion() }
|
||||
onWalletSettingsScreen {
|
||||
scrollToDeviceSettings()
|
||||
deviceSettingsButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,17 +23,11 @@ class DetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
hasText(getResourceString(R.string.wallet_connect_title))
|
||||
}
|
||||
|
||||
private val walletBlock: KNode = child {
|
||||
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
|
||||
}
|
||||
|
||||
val walletNameButton: KNode = walletBlock.child {
|
||||
hasClickAction()
|
||||
hasPosition(0)
|
||||
}
|
||||
|
||||
val scanCardButton: KNode = walletBlock.child {
|
||||
hasText(getResourceString(R.string.scan_card_settings_button))
|
||||
// Match the wallet row by its own tag (not position-0 clickable, which races with the async-loading
|
||||
// "Add Wallet" button and otherwise triggers a re-scan → "already saved" dialog).
|
||||
val walletNameButton: KNode = child {
|
||||
hasTestTag(DetailsScreenTestTags.USER_WALLET_ITEM)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val buyTangemButton: KNode = child {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ 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
|
||||
|
||||
class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<DialogPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
|
@ -25,8 +26,10 @@ class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
hasTestTag(BaseDialogTestTags.TEXT)
|
||||
}
|
||||
|
||||
// Tag is on the OutlineTextField wrapper; the editable node is its descendant with a SetText action.
|
||||
val inputField: KNode = child {
|
||||
hasTestTag(BaseDialogTestTags.TEXT_INPUT_FIELD)
|
||||
hasSetTextAction()
|
||||
hasAnyAncestor(withTestTag(BaseDialogTestTags.TEXT_INPUT_FIELD))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
|
|
@ -9,6 +10,7 @@ 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 WalletSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
|
|
@ -22,6 +24,31 @@ class WalletSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvi
|
|||
hasTestTag(WalletSettingsScreenTestTags.SCREEN_ITEM)
|
||||
}
|
||||
|
||||
// The Accounts section loads async and can push rows below the fold — scroll before asserting/clicking.
|
||||
private val scrollableContainer: KNode = child {
|
||||
hasTestTag(WalletSettingsScreenTestTags.SCREEN_CONTAINER)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun scrollToText(text: String) = scrollableContainer { performScrollToNode(withText(text)) }
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun scrollToDeviceSettings() = scrollToText(getResourceString(R.string.card_settings_title))
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun scrollToLinkMoreCards() = scrollToText(getResourceString(R.string.details_row_title_create_backup))
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun scrollToReferralProgram() = scrollToText(getResourceString(R.string.details_referral_title))
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun scrollToForgetWallet() = scrollToText(getResourceString(R.string.settings_forget_wallet))
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun scrollToRenameButton() = scrollableContainer {
|
||||
performScrollToNode(withTestTag(WalletSettingsScreenTestTags.RENAME_BUTTON))
|
||||
}
|
||||
|
||||
val linkMoreCardsButton: KNode = walletSettingsItem.child {
|
||||
hasText(getResourceString(R.string.details_row_title_create_backup))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.extensions.isDisplayedSafely
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
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
|
||||
|
|
@ -23,7 +25,7 @@ class AppCurrencyTest : BaseTestCase() {
|
|||
val appSettingsState = "AppSettings"
|
||||
val targetCurrency = "EUR"
|
||||
val targetSymbol = "€"
|
||||
val token = "Polygon"
|
||||
val token = "Bitcoin"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = { resetWireMockScenarioState(currenciesScenario) },
|
||||
|
|
@ -32,8 +34,9 @@ class AppCurrencyTest : BaseTestCase() {
|
|||
setWireMockScenarioState(scenarioName = currenciesScenario, state = appSettingsState)
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen(productType = ProductType.Wallet2)
|
||||
openMainScreen()
|
||||
}
|
||||
synchronizeAddresses()
|
||||
step("Open wallet details") {
|
||||
onMainScreenTopBar { moreButton.clickWithAssertion() }
|
||||
}
|
||||
|
|
@ -53,18 +56,30 @@ class AppCurrencyTest : BaseTestCase() {
|
|||
onAppCurrencySelectorScreen { currencyItem(targetCurrency).performClick() }
|
||||
}
|
||||
step("Return to 'Main' screen") {
|
||||
device.uiDevice.pressBack()
|
||||
device.uiDevice.pressBack()
|
||||
waitForIdle()
|
||||
var displayed = false
|
||||
var attempts = 0
|
||||
while (!displayed && attempts < 4) {
|
||||
onMainScreen { displayed = screenContainer.isDisplayedSafely() }
|
||||
if (!displayed) {
|
||||
device.uiDevice.pressBack()
|
||||
waitForIdle()
|
||||
attempts++
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Assert total balance contains '$targetSymbol' on 'Main' screen") {
|
||||
onMainScreen { totalBalanceText.assertTextContains(targetSymbol) }
|
||||
// Balance re-loads in the new currency async after the switch — wait for the € equivalent.
|
||||
composeTestRule.waitUntil(WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
runCatching { onMainScreen { totalBalanceText.assertTextContains(targetSymbol) } }.isSuccess
|
||||
}
|
||||
}
|
||||
step("Click on token '$token'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(token).clickWithAssertion() }
|
||||
}
|
||||
step("Assert token fiat balance contains '$targetSymbol'") {
|
||||
onTokenDetailsScreen { fiatBalance.assertTextContains(targetSymbol) }
|
||||
composeTestRule.waitUntil(WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
runCatching { onTokenDetailsScreen { fiatBalance.assertTextContains(targetSymbol) } }.isSuccess
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,66 +49,19 @@ class DetailsTest : BaseTestCase() {
|
|||
}
|
||||
onWalletSettingsScreen {
|
||||
step("Assert 'Link more cards' button is visible") {
|
||||
scrollToLinkMoreCards()
|
||||
linkMoreCardsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Card Settings' button is visible") {
|
||||
scrollToDeviceSettings()
|
||||
deviceSettingsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Referral program' button is visible") {
|
||||
scrollToReferralProgram()
|
||||
referralProgramButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Forget wallet' button is visible") {
|
||||
forgetWalletButton.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("Details: (Wallet 2.0) fields")
|
||||
@Test
|
||||
fun wallet2DetailsTest() =
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen(productType = ProductType.Wallet2)
|
||||
}
|
||||
onMainScreenTopBar {
|
||||
step("Open wallet details") {
|
||||
moreButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
onDetailsScreen {
|
||||
step("Assert 'Wallet connect' button is visible") {
|
||||
walletConnectButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Scan card' button is visible") {
|
||||
scanCardButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Buy Tangem card' button is visible") {
|
||||
buyTangemButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'App settings' button is visible") {
|
||||
appSettingsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Contact support' button is visible") {
|
||||
contactSupportButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Terms or service' button is visible") {
|
||||
toSButton.assertIsDisplayed()
|
||||
}
|
||||
step("Open 'Wallet settings' screen") {
|
||||
walletNameButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
onWalletSettingsScreen {
|
||||
step("Assert 'Link more cards' button does not exist") {
|
||||
linkMoreCardsButton.assertIsNotDisplayed()
|
||||
}
|
||||
step("Assert 'Card Settings' button is visible") {
|
||||
deviceSettingsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Referral program' button is visible") {
|
||||
referralProgramButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Forget wallet' button is visible") {
|
||||
scrollToForgetWallet()
|
||||
forgetWalletButton.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,10 @@ class WalletRenameTest : BaseTestCase() {
|
|||
onDetailsScreen { walletNameButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Rename' button") {
|
||||
onWalletSettingsScreen { renameWalletButton.clickWithAssertion() }
|
||||
onWalletSettingsScreen {
|
||||
scrollToRenameButton()
|
||||
renameWalletButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Enter new wallet name '$newWalletName'") {
|
||||
onDialog { inputField.performTextReplacement(newWalletName) }
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@ object DetailsScreenTestTags {
|
|||
const val SCREEN_CONTAINER = "DETAILS_SCREEN_CONTAINER"
|
||||
const val SCREEN_ITEM = "DETAILS_SCREEN_ITEM"
|
||||
const val VERSION_NAME = "DETAILS_SCREEN_VERSION_NAME"
|
||||
const val USER_WALLET_ITEM = "DETAILS_SCREEN_USER_WALLET_ITEM"
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
|
|
@ -34,6 +35,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.DetailsScreenTestTags
|
||||
import com.tangem.features.details.component.UserWalletListComponent
|
||||
import com.tangem.features.details.component.preview.PreviewUserWalletListComponent
|
||||
import com.tangem.features.details.entity.UserWalletListUM
|
||||
|
|
@ -69,7 +71,9 @@ internal fun UserWalletListBlock(state: UserWalletListUM, modifier: Modifier = M
|
|||
model = walletState,
|
||||
reorderableListState = reorderableListState,
|
||||
walletReorderUM = state.walletReorderUM,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(DetailsScreenTestTags.USER_WALLET_ITEM),
|
||||
)
|
||||
}
|
||||
item(key = "add_wallet_button") {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue