Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-15 22:43:11 +03:00
parent 6148b6b523
commit af16eb3d94
4 changed files with 633 additions and 0 deletions

View file

@ -192,6 +192,7 @@ abstract class BaseTestCase : TestCase(
"AND_15101_TANGEM_PAY_HOT_WALLET_ONBOARDING" to true,
"AND_15310_ADD_FUNDS_STAGE1" to true,
"APP_REDESIGN_ENABLED" to true,
"AND_15122_SWAP_PREDEFINED_BUTTONS_ENABLED" to true,
)
)
}

View file

@ -9,6 +9,7 @@ import androidx.compose.ui.test.performTouchInput
import com.tangem.common.BaseTestCase
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.clickWhenEnabled
import com.tangem.common.extensions.clickWithAssertion
@ -294,6 +295,84 @@ fun BaseTestCase.chooseReceiveToken(tokenName: String) {
}
}
/**
* 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
* enter [amount]. Scenario states stay in the test body.
*/
fun BaseTestCase.openSwapAmountScreen(
fromTokenName: String,
receiveTokenName: String,
amount: String,
seedPhrase: String? = null,
) {
if (seedPhrase == null) {
step("Open 'Main' screen") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
} else {
step("Open 'Main' screen with existing hot wallet") {
openMainScreenWithExistingHotWallet(seedPhrase)
}
}
step("Click on token with name: '$fromTokenName'") {
onMainScreen { tokenWithTitleAndAddress(fromTokenName).clickWithAssertion() }
}
step("Open 'Swap' screen") {
openSwapScreen(from = SwapEntryPoint.TokenDetails)
}
step("Choose receive token '$receiveTokenName'") {
chooseReceiveToken(receiveTokenName)
}
step("Input swap amount '$amount'") {
waitForIdle()
onSwapTokenScreen {
textInput.clickWithAssertion()
textInput.performTextReplacement(amount)
}
}
step("Wait for the receive amount to load") {
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) {
runCatching { onSwapTokenScreen { receiveAmount.assertIsDisplayed() } }.isSuccess
}
}
}
/**
* Opens the swap 'Network fee' bottom sheet, retrying the click until the fee selector shows.
* Single action without its own step wrap the call in a `step(...)`.
*/
fun BaseTestCase.openSwapNetworkFeeSelector() {
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_VERY_LONG) {
runCatching { onSwapTokenScreen { networkFeeBlock.performClick() } }
runCatching { onSendFeeSelectorBottomSheet { networkFeeTitle.assertIsDisplayed() } }.isSuccess
}
}
/**
* Opens the fee selector and switches the fee-paying token from [currentFeeToken] to [newFeeToken],
* then applies. Works both ways coin -> stablecoin and back.
*/
fun BaseTestCase.switchFeeTokenAndApply(currentFeeToken: String, newFeeToken: String) {
step("Open the 'Network fee' bottom sheet") {
openSwapNetworkFeeSelector()
}
step("Click on '$currentFeeToken' fee token to open 'Choose token'") {
onSendFeeSelectorBottomSheet { feeTokenItem(currentFeeToken).performClick() }
}
step("Select '$newFeeToken' as the fee-paying token") {
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) {
runCatching { onSendFeeSelectorBottomSheet { feeTokenItem(newFeeToken).performClick() } }.isSuccess
}
}
step("Click on 'Apply' button") {
onSendFeeSelectorBottomSheet { applyButton.performClick() }
}
}
/** Holds the last BASE_BUTTON; enters [accessCode] if a hot wallet prompts for it. */
fun BaseTestCase.confirmSwapByHolding(accessCode: String? = null) {
val buttonMatcher = hasTestTag(BaseButtonTestTags.BUTTON)

View file

@ -54,6 +54,12 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
useUnmergedTree = true
}
fun feeBlockCurrency(symbol: String): KNode = child {
hasTestTag(FeeSelectorBlockTestTags.SELECTOR_BLOCK)
hasAnyDescendant(withText(symbol))
useUnmergedTree = true
}
val receiveAmountShimmer: KNode = child {
hasTestTag(SwapTokenScreenTestTags.RECEIVE_AMOUNT_SHIMMER)
}
@ -72,6 +78,11 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
useUnmergedTree = true
}
val bestRateText: KNode = child {
hasText(getResourceString(R.string.express_provider_best_rate))
useUnmergedTree = true
}
val errorNotificationTitle: KNode = child {
hasTestTag(NotificationTestTags.TITLE)
useUnmergedTree = true

View file

@ -0,0 +1,542 @@
package com.tangem.tests.swap
import com.tangem.common.BaseTestCase
import com.tangem.common.R as CommonR
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.extensions.extractText
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.core.ui.R
import com.tangem.scenarios.*
import com.tangem.screens.*
import dagger.hilt.android.testing.HiltAndroidTest
import io.github.kakaocup.kakao.common.utilities.getResourceString
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Test
/**
* Gasless swap on a CEX route, paying the swap network fee with a stablecoin. Covers the best-rate
* block / unchanged rate (5117), the fee-token selector bottom sheet (5111), the network-fee
* selection for a USDC -> POL swap (5110), the signed swap reaching the provider (5116, hot wallet),
* the insufficient-stablecoin-balance-for-fee error (5118), the stablecoin fee shown with its
* fiat equivalent (5112), switching the fee token between the coin and the stablecoin (5114), and the
* fee-selection options when paying with a token (5115), and the max amount reserving the fee (5113).
* Validation cases run on the default (cold) wallet without signing.
*/
@HiltAndroidTest
class GaslessSwapTest : BaseTestCase() {
private val tokenName = "USDC"
private val currencySymbol = "USDC"
private val swapTokenName = "Ethereum"
private val nativeTokenName = "Polygon"
private val inputAmount = "50"
private val userTokensState = "PolygonUSDCEthereum"
private val quotesState = "PolygonUSDC"
private val assetsScenarioName = "express_api_assets"
private val assetsExchangeEnabledState = "BitcoinExchangeEnabled"
@AllureId("5117")
@DisplayName("Gasless Swap: CEX best rate stays the same when the fee is paid with a stablecoin")
@Test
fun checkBestRateUnchangedWithStablecoinFeeTest() {
var capturedReceiveAmount: String? = null
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
resetWireMockScenarioState(assetsScenarioName)
}
).run {
step("Set WireMock scenario '$USER_TOKENS_API_SCENARIO' to '$userTokensState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
}
step("Set WireMock scenario '$QUOTES_API_SCENARIO' to '$quotesState'") {
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = quotesState)
}
step("Set WireMock scenario '$assetsScenarioName' to '$assetsExchangeEnabledState'") {
setWireMockScenarioState(scenarioName = assetsScenarioName, state = assetsExchangeEnabledState)
}
step("Open the swap amount screen for '$tokenName' -> '$swapTokenName'") {
openSwapAmountScreen(fromTokenName = tokenName, receiveTokenName = swapTokenName, amount = inputAmount)
}
step("Assert 'Best rate' label is displayed") {
onSwapTokenScreen {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) { bestRateText.assertIsDisplayed() }
}
}
step("Capture the received amount (exchange rate) before changing the fee token") {
onSwapTokenScreen { capturedReceiveAmount = receiveAmount.extractText() }
}
step("Pay the network fee with the stablecoin '$tokenName'") {
switchFeeTokenAndApply(currentFeeToken = nativeTokenName, newFeeToken = tokenName)
}
step("Assert the received amount (rate) is unchanged after selecting the stablecoin fee") {
val expected = requireNotNull(capturedReceiveAmount) { "Receive amount was not captured" }
onSwapTokenScreen {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) { receiveAmount.assertTextEquals(expected) }
}
}
}
}
@AllureId("5111")
@DisplayName("Gasless Swap: fee-token selector — coin has a speed choice, stablecoin only Market, token is selectable")
@Test
fun checkFeeSelectorBottomSheetTest() {
val marketSpeed = getResourceString(R.string.common_fee_selector_option_market)
val fastSpeed = getResourceString(R.string.common_fee_selector_option_fast)
val slowSpeed = getResourceString(R.string.common_fee_selector_option_slow)
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
resetWireMockScenarioState(assetsScenarioName)
}
).run {
step("Set WireMock scenario '$USER_TOKENS_API_SCENARIO' to '$userTokensState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
}
step("Set WireMock scenario '$QUOTES_API_SCENARIO' to '$quotesState'") {
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = quotesState)
}
step("Set WireMock scenario '$assetsScenarioName' to '$assetsExchangeEnabledState'") {
setWireMockScenarioState(scenarioName = assetsScenarioName, state = assetsExchangeEnabledState)
}
step("Open the swap amount screen for '$tokenName' -> '$swapTokenName'") {
openSwapAmountScreen(fromTokenName = tokenName, receiveTokenName = swapTokenName, amount = inputAmount)
}
step("Open the 'Network fee' bottom sheet") {
openSwapNetworkFeeSelector()
}
step("Assert the '$nativeTokenName' fee coin is displayed") {
onSendFeeSelectorBottomSheet { feeTokenItem(nativeTokenName).assertIsDisplayed() }
}
step("Open 'Choose speed' for the '$nativeTokenName' fee") {
onSendFeeSelectorBottomSheet { feeSpeedItemTitle(marketSpeed).performClick() }
}
step("Assert 'Choose speed' offers multiple speeds ('$marketSpeed', '$fastSpeed') for the coin") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSendSelectNetworkFeeBottomSheet {
chooseSpeedTitle.assertIsDisplayed()
regularFeeSelectorItem(marketSpeed).assertIsDisplayed()
regularFeeSelectorItem(fastSpeed).assertIsDisplayed()
}
}
}
step("Select '$marketSpeed' speed") {
onSendSelectNetworkFeeBottomSheet { regularFeeSelectorItem(marketSpeed).performClick() }
}
step("Click on '$nativeTokenName' fee token to open 'Choose token'") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSendFeeSelectorBottomSheet { feeTokenItem(nativeTokenName).performClick() }
}
}
step("Assert 'Choose token' is displayed and '$tokenName' is available for the fee") {
onSendFeeSelectorBottomSheet {
chooseTokenTitle.assertIsDisplayed()
feeTokenItem(tokenName).assertIsDisplayed()
}
}
step("Select '$tokenName' as the fee-paying token") {
onSendFeeSelectorBottomSheet { feeTokenItem(tokenName).performClick() }
}
step("Assert only '$marketSpeed' speed is available for the stablecoin fee") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSendFeeSelectorBottomSheet {
feeSpeedItemTitle(marketSpeed).assertIsDisplayed()
feeSpeedItemTitle(fastSpeed).assertIsNotDisplayed()
feeSpeedItemTitle(slowSpeed).assertIsNotDisplayed()
}
}
}
step("Click on '$marketSpeed' fee row") {
onSendFeeSelectorBottomSheet { feeSpeedItemTitle(marketSpeed).performClick() }
}
step("Assert 'Choose speed' bottom sheet did not open for the stablecoin fee") {
onSendSelectNetworkFeeBottomSheet { chooseSpeedTitle.assertIsNotDisplayed() }
}
step("Click on 'Apply' button") {
onSendFeeSelectorBottomSheet { applyButton.performClick() }
}
step("Assert the network fee is shown in '$currencySymbol' on the swap screen") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSwapTokenScreen { feeBlockCurrency(currencySymbol).assertIsDisplayed() }
}
}
}
}
@AllureId("5110")
@DisplayName("Gasless Swap: network fee with fee-token selection is shown for a USDC -> POL swap")
@Test
fun checkNetworkFeeSelectionForSwapTest() {
val receiveTokenName = "Polygon"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
resetWireMockScenarioState(assetsScenarioName)
}
).run {
step("Set WireMock scenario '$USER_TOKENS_API_SCENARIO' to '$userTokensState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
}
step("Set WireMock scenario '$QUOTES_API_SCENARIO' to '$quotesState'") {
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = quotesState)
}
step("Set WireMock scenario '$assetsScenarioName' to '$assetsExchangeEnabledState'") {
setWireMockScenarioState(scenarioName = assetsScenarioName, state = assetsExchangeEnabledState)
}
step("Open the swap amount screen for '$tokenName' -> '$receiveTokenName'") {
openSwapAmountScreen(fromTokenName = tokenName, receiveTokenName = receiveTokenName, amount = inputAmount)
}
step("Assert 'Network fee' block with token selection is displayed") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSwapTokenScreen {
networkFeeBlock.assertIsDisplayed()
selectFeeIcon.assertIsDisplayed()
}
}
}
step("Open the 'Network fee' bottom sheet") {
openSwapNetworkFeeSelector()
}
step("Click on '$nativeTokenName' fee token to open 'Choose token'") {
onSendFeeSelectorBottomSheet { feeTokenItem(nativeTokenName).performClick() }
}
step("Assert 'Choose token' is displayed and '$tokenName' is available for the fee") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSendFeeSelectorBottomSheet {
chooseTokenTitle.assertIsDisplayed()
feeTokenItem(tokenName).assertIsDisplayed()
}
}
}
}
}
@AllureId("5116")
@DisplayName("Gasless Swap: sign a swap paying the fee with the stablecoin and reach the provider")
@Test
fun checkSignSwapWithStablecoinFeeTest() {
val hotWalletTokensState = "PolygonUSDCHotWallet"
val receiveTokenName = "Polygon"
val providerName = "Changelly"
val exchangeStatusScenario = "exchange_status_provider"
val changellyStatusState = "Changelly"
val expressStatusItemTitle = getResourceString(CommonR.string.express_exchange_by, providerName)
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
resetWireMockScenarioState(assetsScenarioName)
resetWireMockScenarioState(exchangeStatusScenario)
}
).run {
step("Set WireMock scenario '$USER_TOKENS_API_SCENARIO' to '$hotWalletTokensState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = hotWalletTokensState)
}
step("Set WireMock scenario '$QUOTES_API_SCENARIO' to '$quotesState'") {
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = quotesState)
}
step("Set WireMock scenario '$assetsScenarioName' to '$assetsExchangeEnabledState'") {
setWireMockScenarioState(scenarioName = assetsScenarioName, state = assetsExchangeEnabledState)
}
step("Set WireMock scenario '$exchangeStatusScenario' to '$changellyStatusState'") {
setWireMockScenarioState(scenarioName = exchangeStatusScenario, state = changellyStatusState)
}
step("Open the swap amount screen for '$tokenName' -> '$receiveTokenName' on an existing hot wallet") {
openSwapAmountScreen(
fromTokenName = tokenName,
receiveTokenName = receiveTokenName,
amount = inputAmount,
seedPhrase = SVS_SEED_PHRASE_12,
)
}
step("Pay the network fee with the stablecoin '$tokenName'") {
switchFeeTokenAndApply(currentFeeToken = nativeTokenName, newFeeToken = tokenName)
}
step("Assert the network fee is paid in '$currencySymbol'") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSwapTokenScreen {
feeBlockCurrency(currencySymbol).assertIsDisplayed()
}
}
}
step("Confirm the swap by holding the 'Swap' button and sign") {
confirmSwapByHolding()
}
step("Assert the 'Swap in progress' screen is displayed (transaction sent to provider)") {
onSwapSuccessScreen {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) { title.assertIsDisplayed() }
}
}
step("Click on 'Close' button") {
onSwapSuccessScreen { closeButton.performClick() }
}
step("Assert 'Express status' item with title '$expressStatusItemTitle' is displayed") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onTokenDetailsScreen { expressStatusItem(expressStatusItemTitle).assertIsDisplayed() }
}
}
}
}
@AllureId("5118")
@DisplayName("Gasless Swap: insufficient stablecoin balance to cover the fee shows an error and blocks the swap")
@Test
fun checkInsufficientBalanceForFeeTest() {
val usdcBalanceScenario = "polygon_usdc_balance"
val lowBalanceState = "LowBalance"
val lowAmount = "0.0005"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
resetWireMockScenarioState(assetsScenarioName)
resetWireMockScenarioState(usdcBalanceScenario)
}
).run {
step("Set WireMock scenario '$usdcBalanceScenario' to '$lowBalanceState'") {
setWireMockScenarioState(scenarioName = usdcBalanceScenario, state = lowBalanceState)
}
step("Set WireMock scenario '$USER_TOKENS_API_SCENARIO' to '$userTokensState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
}
step("Set WireMock scenario '$QUOTES_API_SCENARIO' to '$quotesState'") {
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = quotesState)
}
step("Set WireMock scenario '$assetsScenarioName' to '$assetsExchangeEnabledState'") {
setWireMockScenarioState(scenarioName = assetsScenarioName, state = assetsExchangeEnabledState)
}
step("Open the swap amount screen for '$tokenName' -> '$swapTokenName'") {
openSwapAmountScreen(fromTokenName = tokenName, receiveTokenName = swapTokenName, amount = lowAmount)
}
step("Open the 'Network fee' bottom sheet") {
openSwapNetworkFeeSelector()
}
step("Click on '$nativeTokenName' fee token to open 'Choose token'") {
onSendFeeSelectorBottomSheet { feeTokenItem(nativeTokenName).performClick() }
}
step("Select '$tokenName' as the fee-paying token") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSendFeeSelectorBottomSheet { feeTokenItem(tokenName).performClick() }
}
}
step("Assert 'Not enough funds' error is displayed in the fee selector") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSendFeeSelectorBottomSheet { notEnoughFundsError.assertIsDisplayed() }
}
}
step("Assert 'Apply' button is disabled (cannot pay the fee with insufficient balance)") {
onSendFeeSelectorBottomSheet { applyButton.assertIsNotEnabled() }
}
}
}
@AllureId("5112")
@DisplayName("Gasless Swap: the stablecoin network fee is shown with its fiat (dollar) equivalent")
@Test
fun checkStablecoinFeeShownWithFiatTest() {
val fiatSign = "$"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
resetWireMockScenarioState(assetsScenarioName)
}
).run {
step("Set WireMock scenario '$USER_TOKENS_API_SCENARIO' to '$userTokensState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
}
step("Set WireMock scenario '$QUOTES_API_SCENARIO' to '$quotesState'") {
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = quotesState)
}
step("Set WireMock scenario '$assetsScenarioName' to '$assetsExchangeEnabledState'") {
setWireMockScenarioState(scenarioName = assetsScenarioName, state = assetsExchangeEnabledState)
}
step("Open the swap amount screen for '$tokenName' -> '$swapTokenName'") {
openSwapAmountScreen(fromTokenName = tokenName, receiveTokenName = swapTokenName, amount = inputAmount)
}
step("Pay the network fee with the stablecoin '$tokenName'") {
switchFeeTokenAndApply(currentFeeToken = nativeTokenName, newFeeToken = tokenName)
}
step("Assert the network fee is shown in '$currencySymbol' with its fiat ('$fiatSign') equivalent") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSwapTokenScreen {
feeBlockCurrency(currencySymbol).assertIsDisplayed()
feeAmount.assertTextContains(fiatSign, substring = true)
}
}
}
}
}
@AllureId("5114")
@DisplayName("Gasless Swap: switching the fee token from the coin to the stablecoin and back updates the summary")
@Test
fun checkSwitchFeeTokenBetweenCoinAndStablecoinTest() {
val nativeSymbol = "POL"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
resetWireMockScenarioState(assetsScenarioName)
}
).run {
step("Set WireMock scenario '$USER_TOKENS_API_SCENARIO' to '$userTokensState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
}
step("Set WireMock scenario '$QUOTES_API_SCENARIO' to '$quotesState'") {
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = quotesState)
}
step("Set WireMock scenario '$assetsScenarioName' to '$assetsExchangeEnabledState'") {
setWireMockScenarioState(scenarioName = assetsScenarioName, state = assetsExchangeEnabledState)
}
step("Open the swap amount screen for '$tokenName' -> '$swapTokenName'") {
openSwapAmountScreen(fromTokenName = tokenName, receiveTokenName = swapTokenName, amount = inputAmount)
}
step("Switch the fee token from the coin '$nativeTokenName' to the stablecoin '$tokenName'") {
switchFeeTokenAndApply(currentFeeToken = nativeTokenName, newFeeToken = tokenName)
}
step("Assert the network fee is now paid in '$currencySymbol' on the summary") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSwapTokenScreen { feeBlockCurrency(currencySymbol).assertIsDisplayed() }
}
}
step("Switch the fee token from the stablecoin '$tokenName' back to the coin '$nativeTokenName'") {
switchFeeTokenAndApply(currentFeeToken = tokenName, newFeeToken = nativeTokenName)
}
step("Assert the network fee is back to the coin ('$nativeSymbol') on the summary") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSwapTokenScreen { feeBlockCurrency(nativeSymbol).assertIsDisplayed() }
}
}
}
}
@AllureId("5115")
@DisplayName("Gasless Swap: fee selector offers token selection and no speed choice when paying with a token")
@Test
fun checkFeeSelectionOptionsTest() {
val marketSpeed = getResourceString(R.string.common_fee_selector_option_market)
val fastSpeed = getResourceString(R.string.common_fee_selector_option_fast)
val slowSpeed = getResourceString(R.string.common_fee_selector_option_slow)
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
resetWireMockScenarioState(assetsScenarioName)
}
).run {
step("Set WireMock scenario '$USER_TOKENS_API_SCENARIO' to '$userTokensState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
}
step("Set WireMock scenario '$QUOTES_API_SCENARIO' to '$quotesState'") {
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = quotesState)
}
step("Set WireMock scenario '$assetsScenarioName' to '$assetsExchangeEnabledState'") {
setWireMockScenarioState(scenarioName = assetsScenarioName, state = assetsExchangeEnabledState)
}
step("Open the swap amount screen for '$tokenName' -> '$swapTokenName'") {
openSwapAmountScreen(fromTokenName = tokenName, receiveTokenName = swapTokenName, amount = inputAmount)
}
step("Open the 'Network fee' bottom sheet") {
openSwapNetworkFeeSelector()
}
step("Click on '$nativeTokenName' fee token to open 'Choose token'") {
onSendFeeSelectorBottomSheet { feeTokenItem(nativeTokenName).performClick() }
}
step("Assert 'Choose token' is displayed and '$tokenName' is available for the fee") {
onSendFeeSelectorBottomSheet {
chooseTokenTitle.assertIsDisplayed()
feeTokenItem(tokenName).assertIsDisplayed()
}
}
step("Select '$tokenName' as the fee-paying token") {
onSendFeeSelectorBottomSheet { feeTokenItem(tokenName).performClick() }
}
step("Assert only '$marketSpeed' speed is available for the token fee") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSendFeeSelectorBottomSheet {
feeSpeedItemTitle(marketSpeed).assertIsDisplayed()
feeSpeedItemTitle(fastSpeed).assertIsNotDisplayed()
feeSpeedItemTitle(slowSpeed).assertIsNotDisplayed()
}
}
}
step("Click on '$marketSpeed' fee row") {
onSendFeeSelectorBottomSheet { feeSpeedItemTitle(marketSpeed).performClick() }
}
step("Assert 'Choose speed' bottom sheet did not open when paying with a token") {
onSendSelectNetworkFeeBottomSheet { chooseSpeedTitle.assertIsNotDisplayed() }
}
step("Click on 'Apply' button") {
onSendFeeSelectorBottomSheet { applyButton.performClick() }
}
step("Assert the network fee is shown in '$currencySymbol' on the summary") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSwapTokenScreen { feeBlockCurrency(currencySymbol).assertIsDisplayed() }
}
}
}
}
@AllureId("5113")
@DisplayName("Gasless Swap: the max amount reserves the stablecoin fee and stays valid without an insufficient error")
@Test
fun checkMaxAmountReservesFeeTest() {
val maxInputAmount = "100"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
resetWireMockScenarioState(assetsScenarioName)
}
).run {
step("Set WireMock scenario '$USER_TOKENS_API_SCENARIO' to '$userTokensState'") {
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = userTokensState)
}
step("Set WireMock scenario '$QUOTES_API_SCENARIO' to '$quotesState'") {
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = quotesState)
}
step("Set WireMock scenario '$assetsScenarioName' to '$assetsExchangeEnabledState'") {
setWireMockScenarioState(scenarioName = assetsScenarioName, state = assetsExchangeEnabledState)
}
step("Open the swap amount screen for '$tokenName' -> '$swapTokenName' with the max amount '$maxInputAmount'") {
openSwapAmountScreen(fromTokenName = tokenName, receiveTokenName = swapTokenName, amount = maxInputAmount)
}
step("Pay the network fee with the stablecoin '$tokenName'") {
switchFeeTokenAndApply(currentFeeToken = nativeTokenName, newFeeToken = tokenName)
}
step("Assert no insufficient-balance error and 'Swap' is enabled (the fee is reserved from the max amount)") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
onSwapTokenScreen {
insufficientFundsErrorTitle.assertIsNotDisplayed()
swapButton.assertIsEnabled()
}
}
}
}
}
}