Updated on 2026-08-14
This commit is contained in:
parent
dacc49b17e
commit
d412fc1473
6 changed files with 576 additions and 27 deletions
|
|
@ -140,12 +140,15 @@ fun BaseTestCase.openMainScreenWithExistingHotWallet(seedPhrase: String, accessC
|
|||
|
||||
fun BaseTestCase.synchronizeAddresses(
|
||||
balance: String? = null,
|
||||
isBalanceAvailable: Boolean = true
|
||||
isBalanceAvailable: Boolean = true,
|
||||
assertBalance: Boolean = true,
|
||||
) {
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
|
||||
if (!assertBalance) return
|
||||
|
||||
when {
|
||||
!isBalanceAvailable -> step("Assert wallet balance = '$DASH_SIGN'") {
|
||||
onMainScreen { totalBalanceText.assertTextContains(DASH_SIGN) }
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ import com.tangem.common.constants.TestConstants.HOLD_DURATION_MS
|
|||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
|
||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_VERY_LONG
|
||||
import com.tangem.common.extensions.assertVisibility
|
||||
import com.tangem.common.extensions.clickAndWaitFor
|
||||
import com.tangem.common.extensions.clickWhenEnabled
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.extensions.extractText
|
||||
import com.tangem.common.extensions.isDisplayedSafely
|
||||
import com.tangem.core.ui.R as CoreUiR
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.HotWalletAccessCodeTestTags
|
||||
|
|
@ -262,6 +262,24 @@ fun BaseTestCase.checkSwapWarning(
|
|||
}
|
||||
}
|
||||
|
||||
/** Scans a card wallet and opens Swap for [tokenName] in [fromAccountName] without choosing the receive token yet. */
|
||||
fun BaseTestCase.openSwapForTokenInAccount(
|
||||
tokenName: String,
|
||||
fromAccountName: String = "Account 1",
|
||||
mockContent: MockContent? = null,
|
||||
) {
|
||||
step("Open 'Main' screen") {
|
||||
openMainScreen(mockContent = mockContent)
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(assertBalance = false)
|
||||
}
|
||||
step("Wait for addresses to be generated") {
|
||||
waitForAddressesGenerated()
|
||||
}
|
||||
navigateToSwapForToken(tokenName, fromAccountName)
|
||||
}
|
||||
|
||||
/** Opens Swap for [tokenName] in [fromAccountName] and picks it again in [toAccountName] to enter Transfer mode; needs a two-accounts-same-token mock. */
|
||||
fun BaseTestCase.openSwapInTransferMode(
|
||||
tokenName: String,
|
||||
|
|
@ -269,36 +287,59 @@ fun BaseTestCase.openSwapInTransferMode(
|
|||
toAccountName: String = "Account 2",
|
||||
mockContent: MockContent? = null,
|
||||
) {
|
||||
step("Open 'Main' screen") {
|
||||
openMainScreen(mockContent = mockContent)
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Scroll '$fromAccountName' into view (semantics, not touch — avoids the Markets sheet)") {
|
||||
onMainScreen { scrollToAccount(fromAccountName) }
|
||||
}
|
||||
step("Expand account '$fromAccountName' and reveal token '$tokenName'") {
|
||||
onMainScreen {
|
||||
findAccountSectionByName(fromAccountName).clickAndWaitFor(
|
||||
rule = composeTestRule,
|
||||
expectedCondition = {
|
||||
onMainScreen { findTokenInAnyAccountByName(tokenName).assertIsDisplayed() }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
step("Click on token with name: '$tokenName'") {
|
||||
onMainScreen { findTokenInAnyAccountByName(tokenName).clickWithAssertion() }
|
||||
}
|
||||
step("Open 'Swap' screen") {
|
||||
openSwapScreen(from = SwapEntryPoint.TokenDetails, storiesExist = false)
|
||||
}
|
||||
openSwapForTokenInAccount(tokenName, fromAccountName, mockContent)
|
||||
step("Choose identical receive token '$tokenName' from '$toAccountName'") {
|
||||
chooseIdenticalReceiveToken(tokenName = tokenName, receiveAccountName = toAccountName)
|
||||
}
|
||||
}
|
||||
|
||||
/** Like [openSwapInTransferMode] but imports a hot wallet first — required for broadcasting flows (the mock card can't sign). */
|
||||
fun BaseTestCase.openSwapInTransferModeWithHotWallet(
|
||||
tokenName: String,
|
||||
seedPhrase: String,
|
||||
fromAccountName: String = "Account 1",
|
||||
toAccountName: String = "Account 2",
|
||||
) {
|
||||
step("Open 'Main' screen with existing hot wallet") {
|
||||
openMainScreenWithExistingHotWallet(seedPhrase)
|
||||
}
|
||||
step("Generate missing addresses") {
|
||||
generateMissingHotWalletAddresses()
|
||||
}
|
||||
step("Wait for addresses to be generated") {
|
||||
waitForAddressesGenerated()
|
||||
}
|
||||
navigateToSwapForToken(tokenName, fromAccountName)
|
||||
step("Choose identical receive token '$tokenName' from '$toAccountName'") {
|
||||
chooseIdenticalReceiveToken(tokenName = tokenName, receiveAccountName = toAccountName)
|
||||
}
|
||||
}
|
||||
|
||||
// Mirrors iOS: enter Swap via the main action button (source auto-selected) — avoids the account list under the Markets promo.
|
||||
private fun BaseTestCase.navigateToSwapForToken(tokenName: String, fromAccountName: String) {
|
||||
step("Open 'Swap' for '$tokenName' from '$fromAccountName' via the main 'Swap' button") {
|
||||
openSwapScreen(from = SwapEntryPoint.MainScreen, storiesExist = false)
|
||||
}
|
||||
}
|
||||
|
||||
// Hot wallets derive locally, so the second account's missing addresses are generated without a card scan when prompted.
|
||||
fun BaseTestCase.generateMissingHotWalletAddresses() {
|
||||
var notificationShown = false
|
||||
onMainScreen { notificationShown = synchronizeAddressesButton.isDisplayedSafely() }
|
||||
if (notificationShown) {
|
||||
onMainScreen { synchronizeAddressesButton.performClick() }
|
||||
}
|
||||
}
|
||||
|
||||
// The receive selector shows "No address" until the second account's derivation lands; the prompt disappears when it does.
|
||||
fun BaseTestCase.waitForAddressesGenerated() {
|
||||
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
var generated = false
|
||||
onMainScreen { generated = !synchronizeAddressesButton.isDisplayedSafely() }
|
||||
generated
|
||||
}
|
||||
}
|
||||
|
||||
/** Picks the identical [tokenName] in [receiveAccountName]; the receive list collapses the other account, so its header is expanded first. */
|
||||
fun BaseTestCase.chooseIdenticalReceiveToken(tokenName: String, receiveAccountName: String) {
|
||||
step("Click on 'Choose token' button") {
|
||||
|
|
@ -321,6 +362,16 @@ fun BaseTestCase.chooseReceiveToken(tokenName: String) {
|
|||
}
|
||||
}
|
||||
|
||||
/** Reopens the receive selector via the receive-card icon and picks [tokenName] directly — the reopened selector keeps the account expanded. */
|
||||
fun BaseTestCase.changeReceiveToken(tokenName: String) {
|
||||
step("Open receive token selector") {
|
||||
onSwapTokenScreen { receiveSelectTokenIcon.performClick() }
|
||||
}
|
||||
step("Click on token with name '$tokenName'") {
|
||||
onSwapSelectTokenScreen { tokenWithName(tokenName).performClick() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From a clean start: open the main screen (cold by default, or an existing hot wallet when
|
||||
* [seedPhrase] is given), open Swap for [fromTokenName], choose [receiveTokenName] to receive and
|
||||
|
|
@ -423,6 +474,14 @@ fun BaseTestCase.confirmSwapByHolding(accessCode: String? = null) {
|
|||
}
|
||||
}
|
||||
|
||||
/** Holds the last BASE_BUTTON to confirm a transfer; the caller asserts the outcome (transfer mode has no in-progress marker to wait on). */
|
||||
fun BaseTestCase.holdToConfirmTransfer() {
|
||||
val buttons = composeTestRule.onAllNodes(hasTestTag(BaseButtonTestTags.BUTTON))
|
||||
val confirmButton = buttons[buttons.fetchSemanticsNodes().lastIndex]
|
||||
confirmButton.performTouchInput { longClick(durationMillis = HOLD_DURATION_MS) }
|
||||
waitForIdle()
|
||||
}
|
||||
|
||||
sealed class SwapEntryPoint {
|
||||
object MainScreen : SwapEntryPoint()
|
||||
object TokenDetails : SwapEntryPoint()
|
||||
|
|
|
|||
|
|
@ -20,6 +20,13 @@ class SwapSuccessPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
|
|||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
// App Transfers reuses the swap success screen; in transfer mode its title is "Transfer in progress".
|
||||
val transferInProgressTitle: KNode = child {
|
||||
hasTestTag(TransactionSuccessScreenTestTags.TITLE)
|
||||
hasText(getResourceString(R.string.transfer_in_progress_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val closeButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasAnyDescendant(withText(getResourceString(R.string.common_close)))
|
||||
|
|
|
|||
|
|
@ -245,6 +245,12 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
|
|||
hasText(getResourceString(R.string.common_choose_token))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
// Transfer mode auto-fills the memo/destination tag — the manual Send-address field must never render here.
|
||||
val destinationTagField: KNode = child {
|
||||
hasTestTag(SendAddressScreenTestTags.DESTINATION_TAG_TEXT_FIELD)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onSwapTokenScreen(function: SwapTokenPageObject.() -> Unit) =
|
||||
|
|
|
|||
|
|
@ -4,17 +4,21 @@ import com.tangem.common.BaseTestCase
|
|||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.extensions.extractText
|
||||
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||
import com.tangem.common.constants.TestConstants.SVS_SEED_PHRASE_12
|
||||
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.core.ui.R as CoreUiR
|
||||
import com.tangem.scenarios.*
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.tap.domain.sdk.mocks.content.Wallet2WithDerivationsMockContent
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
|
|
@ -454,4 +458,453 @@ class AppTransfersTest : BaseTestCase() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("9841")
|
||||
@DisplayName("App transfers: full transfer reaches 'Transfer in progress' screen")
|
||||
@Test
|
||||
fun fullTransferReachesTransferInProgressScreenTest() {
|
||||
val token = "Ethereum"
|
||||
val amount = "0.001"
|
||||
val userTokensState = "TwoAccountsSameToken"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeAppLaunchSection = { setWireMockScenarioState(storiesScenario, storiesErrorState) },
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(storiesScenario)
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(ethCallScenario)
|
||||
resetWireMockScenarioState(ethBalanceScenario)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
|
||||
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
|
||||
}
|
||||
step("Set WireMock scenario: '$ethCallScenario' to state: '$started'") {
|
||||
setWireMockScenarioState(scenarioName = ethCallScenario, state = started)
|
||||
}
|
||||
step("Set WireMock scenario: '$ethBalanceScenario' to state: '$started'") {
|
||||
setWireMockScenarioState(scenarioName = ethBalanceScenario, state = started)
|
||||
}
|
||||
|
||||
step("Open Swap in Transfer mode for '$token' with existing hot wallet") {
|
||||
openSwapInTransferModeWithHotWallet(tokenName = token, seedPhrase = SVS_SEED_PHRASE_12)
|
||||
}
|
||||
step("Enter amount '$amount'") { inputAmount(amount) }
|
||||
step("Assert Transfer mode is ready") { assertTransferReady() }
|
||||
step("Assert network fee is displayed") { waitForFeeDisplayed() }
|
||||
step("Hold to confirm the transfer") { holdToConfirmTransfer() }
|
||||
step("Assert 'Transfer in progress' screen is displayed") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
onSwapSuccessScreen { transferInProgressTitle.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("9999")
|
||||
@DisplayName("App transfers: broadcast error shows alert without finish screen")
|
||||
@Test
|
||||
fun broadcastErrorShowsAlertWithoutFinishScreenTest() {
|
||||
val token = "Ethereum"
|
||||
val amount = "0.001"
|
||||
val userTokensState = "TwoAccountsSameToken"
|
||||
val sendRawTransactionScenario = "eth_sendRawTransaction"
|
||||
val broadcastErrorState = "BroadcastError"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeAppLaunchSection = { setWireMockScenarioState(storiesScenario, storiesErrorState) },
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(storiesScenario)
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(ethCallScenario)
|
||||
resetWireMockScenarioState(ethBalanceScenario)
|
||||
resetWireMockScenarioState(sendRawTransactionScenario)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
|
||||
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
|
||||
}
|
||||
step("Set WireMock scenario: '$ethCallScenario' to state: '$started'") {
|
||||
setWireMockScenarioState(scenarioName = ethCallScenario, state = started)
|
||||
}
|
||||
step("Set WireMock scenario: '$ethBalanceScenario' to state: '$started'") {
|
||||
setWireMockScenarioState(scenarioName = ethBalanceScenario, state = started)
|
||||
}
|
||||
step("Set WireMock scenario: '$sendRawTransactionScenario' to state: '$broadcastErrorState'") {
|
||||
setWireMockScenarioState(scenarioName = sendRawTransactionScenario, state = broadcastErrorState)
|
||||
}
|
||||
|
||||
step("Open Swap in Transfer mode for '$token' with existing hot wallet") {
|
||||
openSwapInTransferModeWithHotWallet(tokenName = token, seedPhrase = SVS_SEED_PHRASE_12)
|
||||
}
|
||||
step("Enter amount '$amount'") { inputAmount(amount) }
|
||||
step("Assert Transfer mode is ready") { assertTransferReady() }
|
||||
step("Assert network fee is displayed") { waitForFeeDisplayed() }
|
||||
step("Hold to confirm the transfer") { holdToConfirmTransfer() }
|
||||
step("Assert 'Transaction failed' dialog is displayed") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
onFailedTransactionDialog { dialogContainer.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
step("Assert 'Transfer in progress' screen is not displayed") {
|
||||
onSwapSuccessScreen { transferInProgressTitle.assertDoesNotExist() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("9989")
|
||||
@DisplayName("App transfers: receive list allows identical token on another account")
|
||||
@Test
|
||||
fun receiveListAllowsIdenticalTokenOnAnotherAccountTest() {
|
||||
val token = "Ethereum"
|
||||
val receiveAccountName = "Account 2"
|
||||
val userTokensState = "TwoAccountsSameToken"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeAppLaunchSection = { setWireMockScenarioState(storiesScenario, storiesErrorState) },
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(storiesScenario)
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(ethCallScenario)
|
||||
resetWireMockScenarioState(ethBalanceScenario)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
|
||||
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
|
||||
}
|
||||
step("Set WireMock scenario: '$ethCallScenario' to state: '$started'") {
|
||||
setWireMockScenarioState(scenarioName = ethCallScenario, state = started)
|
||||
}
|
||||
step("Set WireMock scenario: '$ethBalanceScenario' to state: '$started'") {
|
||||
setWireMockScenarioState(scenarioName = ethBalanceScenario, state = started)
|
||||
}
|
||||
|
||||
step("Open Swap for '$token' in 'Account 1'") { openSwapForTokenInAccount(token) }
|
||||
step("Open receive token selector") {
|
||||
onSwapTokenScreen { chooseTokenButton.performClick() }
|
||||
}
|
||||
step("Expand account '$receiveAccountName' in receive selector") {
|
||||
onSwapSelectTokenScreen { tokenWithName(receiveAccountName).performClick() }
|
||||
}
|
||||
step("Assert token '$token' is displayed in receive selector") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
onSwapSelectTokenScreen { tokenWithName(token).assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("9998")
|
||||
@DisplayName("App transfers: fee calculation error disables Transfer")
|
||||
@Test
|
||||
fun feeCalculationErrorDisablesTransferTest() {
|
||||
val token = "Ethereum"
|
||||
val amount = "0.001"
|
||||
val userTokensState = "TwoAccountsSameToken"
|
||||
val feeHistoryScenario = "eth_fee_history"
|
||||
val estimateGasScenario = "eth_estimate_gas"
|
||||
val unreachable = "Unreachable"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeAppLaunchSection = { setWireMockScenarioState(storiesScenario, storiesErrorState) },
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(storiesScenario)
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(ethCallScenario)
|
||||
resetWireMockScenarioState(ethBalanceScenario)
|
||||
resetWireMockScenarioState(feeHistoryScenario)
|
||||
resetWireMockScenarioState(estimateGasScenario)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
|
||||
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
|
||||
}
|
||||
step("Set WireMock scenario: '$ethCallScenario' to state: '$started'") {
|
||||
setWireMockScenarioState(scenarioName = ethCallScenario, state = started)
|
||||
}
|
||||
step("Set WireMock scenario: '$ethBalanceScenario' to state: '$started'") {
|
||||
setWireMockScenarioState(scenarioName = ethBalanceScenario, state = started)
|
||||
}
|
||||
step("Set WireMock scenario: '$feeHistoryScenario' to state: '$unreachable'") {
|
||||
setWireMockScenarioState(scenarioName = feeHistoryScenario, state = unreachable)
|
||||
}
|
||||
step("Set WireMock scenario: '$estimateGasScenario' to state: '$unreachable'") {
|
||||
setWireMockScenarioState(scenarioName = estimateGasScenario, state = unreachable)
|
||||
}
|
||||
|
||||
step("Open Swap in Transfer mode for '$token'") { openSwapInTransferMode(token) }
|
||||
step("Enter amount '$amount'") { inputAmount(amount) }
|
||||
// Unreachable fee endpoints leave the fee unresolved (shown as '—'); the transfer stays blocked.
|
||||
step("Assert 'Transfer' button is disabled") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
onSwapTokenScreen { transferButton.assertIsNotEnabled() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("9994")
|
||||
@DisplayName("App transfers: mode switches reactively without screen reload")
|
||||
@Test
|
||||
fun modeSwitchesReactivelyWithoutScreenReloadTest() {
|
||||
val token = "Solana"
|
||||
val swapReceiveToken = "USDC"
|
||||
val userTokensState = "TwoAccountsSameSolanaWithUsdc"
|
||||
val solanaBalanceScenario = "solana_balance"
|
||||
val assetsScenario = "express_api_assets"
|
||||
val fromPairsScenario = "solana_from_pairs"
|
||||
val dexProviderState = "DexProvider"
|
||||
val quotesSolanaState = "Solana"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeAppLaunchSection = { setWireMockScenarioState(storiesScenario, storiesErrorState) },
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(storiesScenario)
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(solanaBalanceScenario)
|
||||
resetWireMockScenarioState(assetsScenario)
|
||||
resetWireMockScenarioState(fromPairsScenario)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
|
||||
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
|
||||
}
|
||||
step("Set WireMock scenario: '$solanaBalanceScenario' to state: '$started'") {
|
||||
setWireMockScenarioState(scenarioName = solanaBalanceScenario, state = started)
|
||||
}
|
||||
step("Set WireMock scenario: '$assetsScenario' to state: '$started'") {
|
||||
setWireMockScenarioState(scenarioName = assetsScenario, state = started)
|
||||
}
|
||||
step("Set WireMock scenario: '$fromPairsScenario' to state: '$dexProviderState'") {
|
||||
setWireMockScenarioState(scenarioName = fromPairsScenario, state = dexProviderState)
|
||||
}
|
||||
// Non-zero SOL price keeps total fiat > 0 so the empty-wallet banner doesn't push the account list under the Markets sheet.
|
||||
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$quotesSolanaState'") {
|
||||
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = quotesSolanaState)
|
||||
}
|
||||
|
||||
step("Open Swap in Transfer mode for '$token'") { openSwapInTransferMode(token) }
|
||||
step("Assert Transfer mode is ready") { assertTransferReady() }
|
||||
step("Change receive token to '$swapReceiveToken' to switch to Swap mode") {
|
||||
changeReceiveToken(swapReceiveToken)
|
||||
}
|
||||
step("Assert 'Swap' button is displayed") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
onSwapTokenScreen { swapButton.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
step("Change receive token back to identical '$token' to switch to Transfer mode") {
|
||||
changeReceiveToken(token)
|
||||
}
|
||||
step("Assert Transfer mode is ready") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) { assertTransferReady() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("10001")
|
||||
@DisplayName("App transfers: memo field is not entered manually in Transfer mode")
|
||||
@Test
|
||||
fun memoFieldIsNotEnteredManuallyInTransferModeTest() {
|
||||
val token = "XRP Ledger"
|
||||
val amount = "0.001"
|
||||
val userTokensState = "TwoAccountsSameXRP"
|
||||
val rippleAccountInfoScenario = "ripple_account_info"
|
||||
val quotesRippleState = "Ripple"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeAppLaunchSection = { setWireMockScenarioState(storiesScenario, storiesErrorState) },
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(storiesScenario)
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(rippleAccountInfoScenario)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
|
||||
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
|
||||
}
|
||||
step("Set WireMock scenario: '$rippleAccountInfoScenario' to state: '$started'") {
|
||||
setWireMockScenarioState(scenarioName = rippleAccountInfoScenario, state = started)
|
||||
}
|
||||
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$quotesRippleState'") {
|
||||
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = quotesRippleState)
|
||||
}
|
||||
|
||||
step("Open Swap in Transfer mode for '$token'") { openSwapInTransferMode(token) }
|
||||
step("Enter amount '$amount'") { inputAmount(amount) }
|
||||
step("Assert Transfer mode is ready") { assertTransferReady() }
|
||||
step("Assert manual 'Destination tag' field is not displayed") {
|
||||
onSwapTokenScreen { destinationTagField.assertDoesNotExist() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("10009")
|
||||
@DisplayName("App transfers: XRP network fee")
|
||||
@Test
|
||||
fun xrpNetworkFeeTest() {
|
||||
val token = "XRP Ledger"
|
||||
val amount = "0.001"
|
||||
val userTokensState = "TwoAccountsSameXRP"
|
||||
val rippleAccountInfoScenario = "ripple_account_info"
|
||||
val quotesRippleState = "Ripple"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeAppLaunchSection = { setWireMockScenarioState(storiesScenario, storiesErrorState) },
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(storiesScenario)
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(rippleAccountInfoScenario)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
|
||||
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
|
||||
}
|
||||
step("Set WireMock scenario: '$rippleAccountInfoScenario' to state: '$started'") {
|
||||
setWireMockScenarioState(scenarioName = rippleAccountInfoScenario, state = started)
|
||||
}
|
||||
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$quotesRippleState'") {
|
||||
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = quotesRippleState)
|
||||
}
|
||||
|
||||
step("Open Swap in Transfer mode for '$token'") { openSwapInTransferMode(token) }
|
||||
step("Enter amount '$amount'") { inputAmount(amount) }
|
||||
step("Assert Transfer mode is ready") { assertTransferReady() }
|
||||
step("Assert network fee is displayed") { waitForFeeDisplayed() }
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("10011")
|
||||
@DisplayName("App transfers: Stellar network fee")
|
||||
@Test
|
||||
fun stellarNetworkFeeTest() {
|
||||
val token = "Stellar"
|
||||
val amount = "0.001"
|
||||
val userTokensState = "TwoAccountsSameXLM"
|
||||
val quotesXlmState = "XLM"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeAppLaunchSection = { setWireMockScenarioState(storiesScenario, storiesErrorState) },
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(storiesScenario)
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
|
||||
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
|
||||
}
|
||||
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$quotesXlmState'") {
|
||||
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = quotesXlmState)
|
||||
}
|
||||
|
||||
step("Open Swap in Transfer mode for '$token'") { openSwapInTransferMode(token) }
|
||||
step("Enter amount '$amount'") { inputAmount(amount) }
|
||||
step("Assert Transfer mode is ready") { assertTransferReady() }
|
||||
step("Assert network fee is displayed") { waitForFeeDisplayed() }
|
||||
}
|
||||
}
|
||||
|
||||
// [REDACTED_TASK_KEY]: transfer mode never runs tx validation, so the destination rent-exemption notification never shows.
|
||||
@Ignore("[REDACTED_JIRA]")
|
||||
@AllureId("9852")
|
||||
@DisplayName("App transfers: amount below destination reserve disables Transfer")
|
||||
@Test
|
||||
fun amountBelowDestinationReserveDisablesTransferTest() {
|
||||
val token = "Solana"
|
||||
val belowReserveAmount = "0.0001"
|
||||
val userTokensState = "TwoAccountsSameSolana"
|
||||
val solanaBalanceScenario = "solana_balance"
|
||||
val recipientAccountScenario = "solana_recipient_account"
|
||||
val notExistState = "NotExist"
|
||||
val quotesSolanaState = "Solana"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeAppLaunchSection = { setWireMockScenarioState(storiesScenario, storiesErrorState) },
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(storiesScenario)
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(solanaBalanceScenario)
|
||||
resetWireMockScenarioState(recipientAccountScenario)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
|
||||
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
|
||||
}
|
||||
step("Set WireMock scenario: '$solanaBalanceScenario' to state: '$started'") {
|
||||
setWireMockScenarioState(scenarioName = solanaBalanceScenario, state = started)
|
||||
}
|
||||
step("Set WireMock scenario: '$recipientAccountScenario' to state: '$notExistState'") {
|
||||
setWireMockScenarioState(scenarioName = recipientAccountScenario, state = notExistState)
|
||||
}
|
||||
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$quotesSolanaState'") {
|
||||
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = quotesSolanaState)
|
||||
}
|
||||
|
||||
step("Open Swap in Transfer mode for '$token'") { openSwapInTransferMode(token) }
|
||||
step("Enter amount '$belowReserveAmount'") { inputAmount(belowReserveAmount) }
|
||||
step("Assert error notification is displayed") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
onSwapTokenScreen { errorNotificationTitle.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
step("Assert 'Transfer' button is disabled") {
|
||||
onSwapTokenScreen { transferButton.assertIsNotEnabled() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("9997")
|
||||
@DisplayName("App transfers: amount below minimum disables Transfer")
|
||||
@Test
|
||||
fun amountBelowMinimumDisablesTransferTest() {
|
||||
val token = "Kaspa"
|
||||
val belowMinimumAmount = "0.00000001"
|
||||
val userTokensState = "TwoAccountsSameKaspa"
|
||||
val kaspaUtxoScenario = "kaspa_utxo"
|
||||
// Android-specific UTXO body — addresses differ from the iOS fixture (see kaspa-utxo.json).
|
||||
val kaspaUtxoState = "more_than_84_android"
|
||||
val quotesKaspaState = "Kaspa"
|
||||
val invalidAmountTitle = getResourceString(CoreUiR.string.send_notification_invalid_amount_title)
|
||||
val minimumAmountMessagePrefix =
|
||||
getResourceString(CoreUiR.string.send_notification_invalid_minimum_amount_text).substringBefore("%1")
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeAppLaunchSection = { setWireMockScenarioState(storiesScenario, storiesErrorState) },
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(storiesScenario)
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(kaspaUtxoScenario)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$userTokensState'") {
|
||||
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
|
||||
}
|
||||
step("Set WireMock scenario: '$kaspaUtxoScenario' to state: '$kaspaUtxoState'") {
|
||||
setWireMockScenarioState(scenarioName = kaspaUtxoScenario, state = kaspaUtxoState)
|
||||
}
|
||||
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$quotesKaspaState'") {
|
||||
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = quotesKaspaState)
|
||||
}
|
||||
|
||||
step("Open Swap in Transfer mode for '$token'") { openSwapInTransferMode(token) }
|
||||
step("Enter amount '$belowMinimumAmount'") { inputAmount(belowMinimumAmount) }
|
||||
step("Assert '$invalidAmountTitle' notification title is displayed") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
onSwapTokenScreen { warningTitle(invalidAmountTitle).assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
step("Assert notification message contains the minimum-amount text") {
|
||||
onSwapTokenScreen { errorNotificationText.assertTextContains(minimumAmountMessagePrefix, substring = true) }
|
||||
}
|
||||
step("Assert 'Transfer' button is disabled") {
|
||||
onSwapTokenScreen { transferButton.assertIsNotEnabled() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -297,6 +297,13 @@ object WalletMockContent : MockContent {
|
|||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/144'/1'/0/0") to ExtendedPublicKey( // XRP (account 2)
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/1729'/0'/0'") to ExtendedPublicKey( // Tezos
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
|
|
@ -396,6 +403,13 @@ object WalletMockContent : MockContent {
|
|||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/148'/1'") to ExtendedPublicKey( // Stellar (account 2)
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/330'/0'/0/0") to ExtendedPublicKey( // Terra
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
|
|
@ -564,6 +578,13 @@ object WalletMockContent : MockContent {
|
|||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/148'/1'") to ExtendedPublicKey( // Stellar (account 2)
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue