Updated on 2026-08-14
This commit is contained in:
parent
c45a6af247
commit
8094af242d
23 changed files with 1001 additions and 16 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -48,3 +48,4 @@ find-latest-release-branch.output
|
|||
|
||||
# Claude
|
||||
/.claude/worktrees/
|
||||
CLAUDE.local.md
|
||||
|
|
|
|||
|
|
@ -183,6 +183,9 @@ abstract class BaseTestCase : TestCase(
|
|||
"GASLESS_APPROVAL_ENABLED" to true,
|
||||
"MAIN_SCREEN_QR_SCANNING_ENABLED" to true,
|
||||
"ADD_AND_MANAGE_TOKENS_ENABLED" to true,
|
||||
"VISA_ONBOARDING_ENABLED" to true,
|
||||
"AND_15101_TANGEM_PAY_HOT_WALLET_ONBOARDING" to true,
|
||||
"AND_15310_ADD_FUNDS_STAGE1" to true,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,4 +59,7 @@ object TestConstants {
|
|||
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"
|
||||
const val SVS_SEED_PHRASE_12 = "diagram thunder merit soup muscle amused refuse usual ring couch popular wash"
|
||||
|
||||
const val TANGEM_PAY_ELIGIBILITY_SCENARIO = "tangem_pay_eligibility"
|
||||
const val TANGEM_PAY_ACCESS_CODE = "517384"
|
||||
}
|
||||
|
|
@ -1,13 +1,20 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.hasText
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.compose.ui.test.waitUntilAtLeastOneExists
|
||||
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.core.ui.R as CoreUiR
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
|
||||
fun BaseTestCase.scanCard(
|
||||
|
|
@ -56,7 +63,8 @@ fun BaseTestCase.openMainScreen(
|
|||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.openMainScreenWithExistingHotWallet(seedPhrase: String) {
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun BaseTestCase.openMainScreenWithExistingHotWallet(seedPhrase: String, accessCode: String = "") {
|
||||
step("Click on 'Get started' button") {
|
||||
onStoriesScreen { getStartedButton.clickWithAssertion() }
|
||||
}
|
||||
|
|
@ -84,11 +92,39 @@ fun BaseTestCase.openMainScreenWithExistingHotWallet(seedPhrase: String) {
|
|||
continueButton.performClick()
|
||||
}
|
||||
}
|
||||
step("Click on 'Skip' button") {
|
||||
onImportWalletScreen { skipButton.performClick() }
|
||||
}
|
||||
step("Click on 'Skip anyway' dialog button") {
|
||||
onDialog { skipAnywayButton.performClick() }
|
||||
if (accessCode.isNotEmpty()) {
|
||||
step("Enter access code '$accessCode' (create)") {
|
||||
onHotWalletAccessCodeScreen {
|
||||
accessCodeInput.performClick()
|
||||
accessCodeInput.performTextInput(accessCode)
|
||||
}
|
||||
}
|
||||
step("Re-enter access code '$accessCode' (confirm)") {
|
||||
// Create+confirm screens share ACCESS_CODE_INPUT — gate on confirm-screen title.
|
||||
composeTestRule.waitUntilAtLeastOneExists(
|
||||
hasText(getResourceString(CoreUiR.string.access_code_confirm_title)),
|
||||
timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG,
|
||||
)
|
||||
onHotWalletAccessCodeScreen {
|
||||
accessCodeInput.performClick()
|
||||
accessCodeInput.performTextInput(accessCode)
|
||||
}
|
||||
}
|
||||
step("Dismiss biometry prompt if shown") {
|
||||
waitForIdle()
|
||||
onBiometryDialog {
|
||||
if (dontAllowButton.isDisplayedSafely()) {
|
||||
dontAllowButton.performClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
step("Click on 'Skip' button") {
|
||||
onImportWalletScreen { skipButton.performClick() }
|
||||
}
|
||||
step("Click on 'Skip anyway' dialog button") {
|
||||
onDialog { skipAnywayButton.performClick() }
|
||||
}
|
||||
}
|
||||
step("Click on 'Finish' button") {
|
||||
onImportWalletScreen {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,20 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
import androidx.compose.ui.test.click
|
||||
import androidx.compose.ui.test.hasTestTag
|
||||
import androidx.compose.ui.test.hasText
|
||||
import androidx.compose.ui.test.longClick
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
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.extensions.assertVisibility
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
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
|
||||
import com.tangem.screens.*
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
|
|
@ -283,6 +293,29 @@ fun BaseTestCase.chooseReceiveToken(tokenName: String) {
|
|||
}
|
||||
}
|
||||
|
||||
/** 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)
|
||||
val buttons = composeTestRule.onAllNodes(buttonMatcher)
|
||||
// HoldToConfirm is always last — withdraw renders an extra BASE_BUTTON for notifications.
|
||||
val swapButton = buttons[buttons.fetchSemanticsNodes().lastIndex]
|
||||
swapButton.performTouchInput { longClick(durationMillis = HOLD_DURATION_MS) }
|
||||
waitForIdle()
|
||||
val accessCodeInput = hasTestTag(HotWalletAccessCodeTestTags.ACCESS_CODE_INPUT)
|
||||
val swapInProgressText = hasText(getResourceString(CoreUiR.string.swap_in_progress))
|
||||
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
composeTestRule.onAllNodes(accessCodeInput).fetchSemanticsNodes().isNotEmpty() ||
|
||||
composeTestRule.onAllNodes(swapInProgressText, useUnmergedTree = true)
|
||||
.fetchSemanticsNodes().isNotEmpty()
|
||||
}
|
||||
val needsAccessCode =
|
||||
composeTestRule.onAllNodes(accessCodeInput).fetchSemanticsNodes().isNotEmpty()
|
||||
if (needsAccessCode && accessCode != null) {
|
||||
composeTestRule.onNode(accessCodeInput).performTextInput(accessCode)
|
||||
waitForIdle()
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SwapEntryPoint {
|
||||
object MainScreen : SwapEntryPoint()
|
||||
object TokenDetails : SwapEntryPoint()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
import androidx.compose.ui.test.hasTestTag
|
||||
import androidx.compose.ui.test.performTouchInput
|
||||
import androidx.compose.ui.test.swipeDown
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.SVS_SEED_PHRASE_12
|
||||
import com.tangem.common.constants.TestConstants.TANGEM_PAY_ACCESS_CODE
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.core.ui.test.TangemPayTestTags
|
||||
import com.tangem.screens.tangempay.*
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
|
||||
fun BaseTestCase.openTangemPay() {
|
||||
step("Import hot wallet from Tangem Pay seed phrase (with access code)") {
|
||||
openMainScreenWithExistingHotWallet(SVS_SEED_PHRASE_12, accessCode = TANGEM_PAY_ACCESS_CODE)
|
||||
}
|
||||
step("Click on Tangem Pay tile") {
|
||||
onTangemPayMainScreen { mainScreenTile.clickWithAssertion() }
|
||||
}
|
||||
step("Assert payment account balance is displayed") {
|
||||
onTangemPayMainScreen { balance.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
|
||||
// Compose Test gesture — UiAutomator swipe doesn't reach Material3 PullToRefreshBox's NestedScrollConnection.
|
||||
fun BaseTestCase.pullToRefreshTangemPay() {
|
||||
val balance = composeTestRule.onNode(hasTestTag(TangemPayTestTags.PAYMENT_ACCOUNT_BALANCE))
|
||||
balance.performTouchInput {
|
||||
swipeDown(startY = 0f, endY = visibleSize.height.toFloat() * 6f, durationMillis = 800)
|
||||
}
|
||||
composeTestRule.mainClock.advanceTimeBy(2_000L)
|
||||
waitForIdle()
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import androidx.compose.ui.test.hasText as withText
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R as CoreUiR
|
||||
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 BiometryDialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<BiometryDialogPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val dontAllowButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasAnyDescendant(withText(getResourceString(CoreUiR.string.save_user_wallet_agreement_dont_allow)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onBiometryDialog(function: BiometryDialogPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.HotWalletAccessCodeTestTags
|
||||
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
|
||||
|
||||
class HotWalletAccessCodePageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<HotWalletAccessCodePageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val accessCodeInput: KNode = child {
|
||||
hasTestTag(HotWalletAccessCodeTestTags.ACCESS_CODE_INPUT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onHotWalletAccessCodeScreen(function: HotWalletAccessCodePageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import androidx.compose.ui.test.hasText as withText
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.TransactionSuccessScreenTestTags
|
||||
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
|
||||
|
||||
// Legacy swap feature's success screen — lacks CONTAINER testTag that SendSuccessPageObject relies on.
|
||||
class SwapSuccessPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SwapSuccessPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(TransactionSuccessScreenTestTags.TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val closeButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasAnyDescendant(withText(getResourceString(R.string.common_close)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onSwapSuccessScreen(function: SwapSuccessPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -38,6 +38,12 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
|
|||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
// Tangem Pay withdraw hides FEE_SELECTOR_BLOCK; gate on the "Network fee" label instead.
|
||||
val networkFeeTitle: KNode = child {
|
||||
hasText(getResourceString(R.string.common_network_fee_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val selectFeeIcon: KNode = child {
|
||||
hasTestTag(FeeSelectorBlockTestTags.SELECT_FEE_ICON)
|
||||
useUnmergedTree = true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.screens.tangempay
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
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 TangemPayAddFundsSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<TangemPayAddFundsSheetPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val swapOption: KNode = child {
|
||||
hasText(getResourceString(CoreResR.string.common_exchange))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val receiveOption: KNode = child {
|
||||
hasText(getResourceString(CoreResR.string.common_receive))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val title: KNode = child {
|
||||
hasText(getResourceString(CoreResR.string.tangempay_card_details_add_funds))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onTangemPayAddFundsSheet(function: TangemPayAddFundsSheetPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.tangem.screens.tangempay
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.TangemPayTestTags
|
||||
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
|
||||
|
||||
class TangemPayCardPagePageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<TangemPayCardPagePageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val changePinRow: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.CHANGE_PIN_ROW)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val freezeCardRow: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.FREEZE_CARD_ROW)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val cardFrozenBadge: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.CARD_FROZEN_BADGE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val showDetailsButton: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.CARD_DETAILS_SHOW_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val hideDetailsButton: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.CARD_DETAILS_HIDE_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val numberValue: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.CARD_DETAILS_NUMBER_VALUE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val expirationValue: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.CARD_DETAILS_EXPIRATION_VALUE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val cvcValue: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.CARD_DETAILS_CVC_VALUE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val copyNumberButton: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.CARD_DETAILS_COPY_NUMBER)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val copyExpirationButton: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.CARD_DETAILS_COPY_EXPIRATION)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val copyCvcButton: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.CARD_DETAILS_COPY_CVC)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onTangemPayCardPageScreen(function: TangemPayCardPagePageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.screens.tangempay
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.TangemPayTestTags
|
||||
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
|
||||
|
||||
class TangemPayChangePinPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<TangemPayChangePinPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.PIN_SCREEN_TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val description: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.PIN_SCREEN_DESCRIPTION)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val inputField: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.PIN_INPUT_FIELD)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val submitButton: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.PIN_SUBMIT_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val errorMessage: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.PIN_ERROR_MESSAGE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val successTitle: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.PIN_SUCCESS_TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val successDescription: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.PIN_SUCCESS_DESCRIPTION)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val doneButton: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.PIN_DONE_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onTangemPayChangePinScreen(function: TangemPayChangePinPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.screens.tangempay
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.WarningBottomSheetTestTags
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
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 TangemPayFreezeConfirmationPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<TangemPayFreezeConfirmationPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val freezeTitle: KNode = child {
|
||||
hasTestTag(WarningBottomSheetTestTags.TITLE)
|
||||
hasText(getResourceString(CoreResR.string.tangem_pay_freeze_card_alert_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val unfreezeTitle: KNode = child {
|
||||
hasTestTag(WarningBottomSheetTestTags.TITLE)
|
||||
hasText(getResourceString(CoreResR.string.tangem_pay_unfreeze_card_alert_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val submitButton: KNode = child {
|
||||
hasTestTag(WarningBottomSheetTestTags.BUTTON_PRIMARY)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onTangemPayFreezeConfirmation(function: TangemPayFreezeConfirmationPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.screens.tangempay
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.BaseActionButtonsBlockTestTags
|
||||
import com.tangem.core.ui.test.TangemPayTestTags
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
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 TangemPayMainPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<TangemPayMainPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val mainScreenTile: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.MAIN_SCREEN_TILE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val balance: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.PAYMENT_ACCOUNT_BALANCE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val cardButton: KNode = child {
|
||||
hasTestTag(TangemPayTestTags.PAYMENT_ACCOUNT_CARD_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val topUpButton: KNode = child {
|
||||
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||
hasAnyDescendant(withText(getResourceString(CoreResR.string.tangempay_card_details_add_funds)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val withdrawButton: KNode = child {
|
||||
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||
hasAnyDescendant(withText(getResourceString(CoreResR.string.tangempay_card_details_withdraw)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun transactionRowWithText(text: String): KNode = child {
|
||||
hasText(text)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onTangemPayMainScreen(function: TangemPayMainPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.screens.tangempay
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.WarningBottomSheetTestTags
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
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 TangemPayWithdrawNoteSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<TangemPayWithdrawNoteSheetPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(WarningBottomSheetTestTags.TITLE)
|
||||
hasText(getResourceString(CoreResR.string.tangempay_withdrawal_note_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val gotItButton: KNode = child {
|
||||
hasTestTag(WarningBottomSheetTestTags.BUTTON_PRIMARY)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onTangemPayWithdrawNoteSheet(function: TangemPayWithdrawNoteSheetPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
package com.tangem.tests.tangempay
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.TANGEM_PAY_ELIGIBILITY_SCENARIO
|
||||
import com.tangem.common.extensions.assertTextContainsSafe
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.extensions.extractText
|
||||
import com.tangem.common.extensions.pullToRefresh
|
||||
import com.tangem.common.utils.assertClipboardTextEquals
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.scenarios.*
|
||||
import com.tangem.screens.tangempay.*
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class TangemPayTest : BaseTestCase() {
|
||||
|
||||
@AllureId("4549")
|
||||
@DisplayName("Tangem Pay: change PIN code from card details")
|
||||
@Test
|
||||
fun changePin_SetsNewPinCode_FromCardDetails() {
|
||||
val newPin = "5217"
|
||||
val pinSetupScenario = "tangem_pay_pin_setup"
|
||||
val pinNotSetState = "PinNotSet"
|
||||
val eligibilityState = "PaeraCustomer"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeSection = {
|
||||
setWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO, eligibilityState)
|
||||
setWireMockScenarioState(pinSetupScenario, pinNotSetState)
|
||||
},
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO)
|
||||
resetWireMockScenarioState(pinSetupScenario)
|
||||
},
|
||||
).run {
|
||||
openTangemPay()
|
||||
step("Click on card button") {
|
||||
onTangemPayMainScreen { cardButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Change PIN' row") {
|
||||
onTangemPayCardPageScreen { changePinRow.clickWithAssertion() }
|
||||
}
|
||||
step("Assert PIN screen is displayed") {
|
||||
onTangemPayChangePinScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Enter PIN '$newPin'") {
|
||||
onTangemPayChangePinScreen { inputField.performTextInput(newPin) }
|
||||
}
|
||||
step("Click on 'Submit' button") {
|
||||
onTangemPayChangePinScreen { submitButton.performClick() }
|
||||
}
|
||||
step("Assert success screen is displayed") {
|
||||
onTangemPayChangePinScreen { successTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Done' button") {
|
||||
onTangemPayChangePinScreen { doneButton.clickWithAssertion() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4969")
|
||||
@DisplayName("Tangem Pay: balance updates after transaction on payment account screen")
|
||||
@Test
|
||||
fun balanceUpdatesAfterTransaction_OnPaymentAccountScreen() {
|
||||
val balanceScenario = "tangem_pay_balance_update"
|
||||
val initialState = "InitialBalance"
|
||||
val afterTransactionState = "AfterTransaction"
|
||||
val eligibilityState = "PaeraCustomer"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeSection = {
|
||||
setWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO, eligibilityState)
|
||||
setWireMockScenarioState(balanceScenario, initialState)
|
||||
},
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO)
|
||||
resetWireMockScenarioState(balanceScenario)
|
||||
},
|
||||
).run {
|
||||
openTangemPay()
|
||||
step("Assert initial balance contains '10'") {
|
||||
onTangemPayMainScreen { balance.assertTextContainsSafe("10", substring = true) }
|
||||
}
|
||||
step("Switch WireMock scenario '$balanceScenario' to '$afterTransactionState'") {
|
||||
setWireMockScenarioState(balanceScenario, afterTransactionState)
|
||||
}
|
||||
step("Pull to refresh") { pullToRefresh() }
|
||||
step("Assert updated balance contains '9'") {
|
||||
onTangemPayMainScreen { balance.assertTextContainsSafe("9", substring = true) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4970")
|
||||
@DisplayName("Tangem Pay: new transaction appears after mocked charge")
|
||||
@Test
|
||||
fun transactionList_NewTransactionAppears_AfterMockedCharge() {
|
||||
val historyScenario = "tangem_pay_transaction_history"
|
||||
val initialState = "InitialEmpty"
|
||||
val afterTransactionState = "AfterTransaction"
|
||||
val eligibilityState = "PaeraCustomer"
|
||||
val merchantName = "Mock Merchant"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeSection = {
|
||||
setWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO, eligibilityState)
|
||||
setWireMockScenarioState(historyScenario, initialState)
|
||||
},
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO)
|
||||
resetWireMockScenarioState(historyScenario)
|
||||
},
|
||||
).run {
|
||||
openTangemPay()
|
||||
step("Assert transaction from '$merchantName' is not displayed") {
|
||||
onTangemPayMainScreen {
|
||||
transactionRowWithText(merchantName).assertDoesNotExist()
|
||||
}
|
||||
}
|
||||
step("Switch WireMock scenario '$historyScenario' to '$afterTransactionState'") {
|
||||
setWireMockScenarioState(historyScenario, afterTransactionState)
|
||||
}
|
||||
step("Pull to refresh") { pullToRefresh() }
|
||||
step("Assert transaction from '$merchantName' is displayed") {
|
||||
onTangemPayMainScreen {
|
||||
transactionRowWithText(merchantName).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4974")
|
||||
@DisplayName("Tangem Pay: reveal and copy card number, expiration and CVC")
|
||||
@Test
|
||||
fun revealAndCopyCardDetails_NumberExpirationCVC() {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val eligibilityState = "PaeraCustomer"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeSection = {
|
||||
setWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO, eligibilityState)
|
||||
},
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO)
|
||||
},
|
||||
).run {
|
||||
openTangemPay()
|
||||
step("Click on card button") {
|
||||
onTangemPayMainScreen { cardButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Show details' button") {
|
||||
onTangemPayCardPageScreen { showDetailsButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert number, expiration and CVC values are visible") {
|
||||
onTangemPayCardPageScreen {
|
||||
numberValue.assertIsDisplayed()
|
||||
expirationValue.assertIsDisplayed()
|
||||
cvcValue.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
var displayedNumber = ""
|
||||
var displayedExpiration = ""
|
||||
var displayedCvc = ""
|
||||
onTangemPayCardPageScreen {
|
||||
displayedNumber = numberValue.extractText()
|
||||
displayedExpiration = expirationValue.extractText()
|
||||
displayedCvc = cvcValue.extractText()
|
||||
}
|
||||
step("Click on 'Copy card number' button") {
|
||||
onTangemPayCardPageScreen { copyNumberButton.clickWithAssertion() }
|
||||
waitForIdle()
|
||||
}
|
||||
step("Assert clipboard contains card number") {
|
||||
// Displayed number has spaces for readability; clipboard copies digits only.
|
||||
assertClipboardTextEquals(displayedNumber.replace(" ", ""), context)
|
||||
}
|
||||
step("Click on 'Copy expiration' button") {
|
||||
onTangemPayCardPageScreen { copyExpirationButton.clickWithAssertion() }
|
||||
waitForIdle()
|
||||
}
|
||||
step("Assert clipboard contains expiration date") {
|
||||
assertClipboardTextEquals(displayedExpiration, context)
|
||||
}
|
||||
step("Click on 'Copy CVC' button") {
|
||||
onTangemPayCardPageScreen { copyCvcButton.clickWithAssertion() }
|
||||
waitForIdle()
|
||||
}
|
||||
step("Assert clipboard contains CVC") {
|
||||
assertClipboardTextEquals(displayedCvc, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4971")
|
||||
@DisplayName("Tangem Pay: freeze card via confirmation sheet")
|
||||
@Test
|
||||
fun freezeUnfreezeCard_TogglesCardState() {
|
||||
val freezeScenario = "tangem_pay_card_freeze"
|
||||
val startedState = "Started"
|
||||
val eligibilityState = "PaeraCustomer"
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeSection = {
|
||||
setWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO, eligibilityState)
|
||||
setWireMockScenarioState(freezeScenario, startedState)
|
||||
},
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO)
|
||||
resetWireMockScenarioState(freezeScenario)
|
||||
},
|
||||
).run {
|
||||
openTangemPay()
|
||||
step("Click on card button") {
|
||||
onTangemPayMainScreen { cardButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on freeze card row (card is active)") {
|
||||
onTangemPayCardPageScreen { freezeCardRow.clickWithAssertion() }
|
||||
}
|
||||
step("Assert freeze confirmation sheet is displayed") {
|
||||
onTangemPayFreezeConfirmation { freezeTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Submit' button (confirm freeze)") {
|
||||
onTangemPayFreezeConfirmation { submitButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert frozen badge is displayed") {
|
||||
onTangemPayCardPageScreen { cardFrozenBadge.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
package com.tangem.tests.tangempay
|
||||
|
||||
import androidx.test.espresso.Espresso
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.TANGEM_PAY_ACCESS_CODE
|
||||
import com.tangem.common.constants.TestConstants.TANGEM_PAY_ELIGIBILITY_SCENARIO
|
||||
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.assertTextContainsSafe
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
import com.tangem.scenarios.*
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.screens.tangempay.*
|
||||
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
|
||||
|
||||
@HiltAndroidTest
|
||||
class TangemPayTopUpTest : BaseTestCase() {
|
||||
|
||||
@AllureId("4973")
|
||||
@DisplayName("Tangem Pay: top up swaps Bitcoin to USDC and appends deposit to history")
|
||||
@Test
|
||||
fun topUpFromTangemPay_SwapsBitcoinToUSDC_AppendsDepositToHistory() {
|
||||
val bitcoinScenario = "bitcoin_utxo"
|
||||
val expressAssetsScenario = "express_api_assets"
|
||||
val balanceScenario = "tangem_pay_balance_update"
|
||||
val historyScenario = "tangem_pay_transaction_history"
|
||||
val eligibilityState = "PaeraCustomer"
|
||||
val bitcoinBalanceState = "BalanceHotWalletSvS"
|
||||
val expressAssetsState = "BitcoinExchangeEnabled"
|
||||
val balanceInitialState = "InitialBalance"
|
||||
val balanceAfterState = "AfterDeposit"
|
||||
val historyInitialState = "InitialEmpty"
|
||||
val historyAfterState = "AfterDeposit"
|
||||
val swapFromAmount = "0.001"
|
||||
val depositLabel = getResourceString(CoreResR.string.tangem_pay_deposit)
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeSection = {
|
||||
setWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO, eligibilityState)
|
||||
setWireMockScenarioState(bitcoinScenario, bitcoinBalanceState)
|
||||
setWireMockScenarioState(expressAssetsScenario, expressAssetsState)
|
||||
setWireMockScenarioState(balanceScenario, balanceInitialState)
|
||||
setWireMockScenarioState(historyScenario, historyInitialState)
|
||||
},
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO)
|
||||
resetWireMockScenarioState(bitcoinScenario)
|
||||
resetWireMockScenarioState(expressAssetsScenario)
|
||||
resetWireMockScenarioState(balanceScenario)
|
||||
resetWireMockScenarioState(historyScenario)
|
||||
},
|
||||
).run {
|
||||
openTangemPay()
|
||||
step("Assert initial balance contains '10'") {
|
||||
onTangemPayMainScreen { balance.assertTextContainsSafe("10", substring = true) }
|
||||
}
|
||||
step("Click on 'Top Up' action chip") {
|
||||
onTangemPayMainScreen { topUpButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Add Funds' sheet is displayed") {
|
||||
onTangemPayAddFundsSheet { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Swap' option") {
|
||||
onTangemPayAddFundsSheet { swapOption.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Close' button on Swap stories") {
|
||||
onSwapStoriesScreen { closeButton.performClick() }
|
||||
}
|
||||
step("Assert 'Swap' screen is displayed (USDC pre-filled as destination)") {
|
||||
onSwapTokenScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Choose token' button (from)") {
|
||||
onSwapTokenScreen { chooseTokenButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Main account'") {
|
||||
onSwapSelectTokenScreen { tokenWithName("Main account").clickWithAssertion() }
|
||||
}
|
||||
step("Click on token 'Bitcoin'") {
|
||||
waitForIdle()
|
||||
onSwapSelectTokenScreen { tokenWithName("Bitcoin").clickWithAssertion() }
|
||||
}
|
||||
step("Enter swap amount '$swapFromAmount'") {
|
||||
onSwapTokenScreen {
|
||||
textInput.performClick()
|
||||
textInput.performTextReplacement(swapFromAmount)
|
||||
}
|
||||
}
|
||||
step("Dismiss keyboard") {
|
||||
Espresso.closeSoftKeyboard()
|
||||
waitForIdle()
|
||||
}
|
||||
step("Wait until provider quote + fee are loaded") {
|
||||
onSwapTokenScreen {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
networkFeeBlock.assertIsDisplayed()
|
||||
feeAmount.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Confirm swap by holding the button") {
|
||||
confirmSwapByHolding(accessCode = TANGEM_PAY_ACCESS_CODE)
|
||||
}
|
||||
step("Wait for 'Swap in progress' screen") {
|
||||
onSwapSuccessScreen {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_VERY_LONG) { title.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
step("Click on 'Close' button") {
|
||||
onSwapSuccessScreen { closeButton.performClick() }
|
||||
}
|
||||
step("Switch WireMock scenario '$balanceScenario' to '$balanceAfterState'") {
|
||||
setWireMockScenarioState(balanceScenario, balanceAfterState)
|
||||
}
|
||||
step("Switch WireMock scenario '$historyScenario' to '$historyAfterState'") {
|
||||
setWireMockScenarioState(historyScenario, historyAfterState)
|
||||
}
|
||||
step("Pull to refresh Tangem Pay") { pullToRefreshTangemPay() }
|
||||
step("Assert balance updated to '\$110.00'") {
|
||||
onTangemPayMainScreen {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
balance.assertTextContainsSafe("110", substring = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Assert '$depositLabel' transaction visible in history") {
|
||||
onTangemPayMainScreen {
|
||||
transactionRowWithText(depositLabel).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
package com.tangem.tests.tangempay
|
||||
|
||||
import androidx.test.espresso.Espresso
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.TANGEM_PAY_ACCESS_CODE
|
||||
import com.tangem.common.constants.TestConstants.TANGEM_PAY_ELIGIBILITY_SCENARIO
|
||||
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.assertTextContainsSafe
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
import com.tangem.scenarios.*
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.screens.tangempay.*
|
||||
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.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class TangemPayWithdrawTest : BaseTestCase() {
|
||||
|
||||
@AllureId("4972")
|
||||
@DisplayName("Tangem Pay: withdraw swaps USDC to Bitcoin and appends withdrawal to history")
|
||||
@Ignore("[REDACTED_JIRA]")
|
||||
@Test
|
||||
fun withdrawFromTangemPay_SwapsUSDCToBitcoin_AppendsWithdrawalToHistory() {
|
||||
val bitcoinScenario = "bitcoin_utxo"
|
||||
val expressAssetsScenario = "express_api_assets"
|
||||
val exchangeStatusScenario = "exchange_status_provider"
|
||||
val balanceScenario = "tangem_pay_balance_update"
|
||||
val historyScenario = "tangem_pay_transaction_history"
|
||||
val eligibilityState = "PaeraCustomer"
|
||||
val bitcoinStartedState = "Started"
|
||||
val expressAssetsState = "BitcoinExchangeEnabled"
|
||||
val exchangeStatusState = "Changelly"
|
||||
val balanceInitialState = "InitialBalance"
|
||||
val balanceAfterState = "AfterWithdraw"
|
||||
val historyInitialState = "InitialEmpty"
|
||||
val historyAfterState = "AfterWithdraw"
|
||||
val withdrawAmount = "5"
|
||||
val withdrawalLabel = getResourceString(CoreResR.string.tangem_pay_withdrawal)
|
||||
|
||||
setupHooks(
|
||||
additionalBeforeSection = {
|
||||
setWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO, eligibilityState)
|
||||
setWireMockScenarioState(bitcoinScenario, bitcoinStartedState)
|
||||
setWireMockScenarioState(expressAssetsScenario, expressAssetsState)
|
||||
setWireMockScenarioState(exchangeStatusScenario, exchangeStatusState)
|
||||
setWireMockScenarioState(balanceScenario, balanceInitialState)
|
||||
setWireMockScenarioState(historyScenario, historyInitialState)
|
||||
},
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(TANGEM_PAY_ELIGIBILITY_SCENARIO)
|
||||
resetWireMockScenarioState(bitcoinScenario)
|
||||
resetWireMockScenarioState(expressAssetsScenario)
|
||||
resetWireMockScenarioState(exchangeStatusScenario)
|
||||
resetWireMockScenarioState(balanceScenario)
|
||||
resetWireMockScenarioState(historyScenario)
|
||||
},
|
||||
).run {
|
||||
openTangemPay()
|
||||
step("Assert initial balance contains '10'") {
|
||||
onTangemPayMainScreen { balance.assertTextContainsSafe("10", substring = true) }
|
||||
}
|
||||
step("Click on 'Withdraw' action chip") {
|
||||
onTangemPayMainScreen { withdrawButton.clickWithAssertion() }
|
||||
}
|
||||
step("Acknowledge withdrawal note sheet") {
|
||||
onTangemPayWithdrawNoteSheet {
|
||||
title.assertIsDisplayed()
|
||||
gotItButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Click on 'Close' button on Swap stories") {
|
||||
onSwapStoriesScreen { closeButton.performClick() }
|
||||
}
|
||||
step("Assert 'Swap' screen is displayed (USDC pre-filled as source)") {
|
||||
onSwapTokenScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Choose token' button (to)") {
|
||||
onSwapTokenScreen { chooseTokenButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Main account'") {
|
||||
onSwapSelectTokenScreen { tokenWithName("Main account").clickWithAssertion() }
|
||||
}
|
||||
step("Click on token 'Bitcoin'") {
|
||||
waitForIdle()
|
||||
onSwapSelectTokenScreen { tokenWithName("Bitcoin").clickWithAssertion() }
|
||||
}
|
||||
step("Enter withdraw amount '$withdrawAmount'") {
|
||||
onSwapTokenScreen {
|
||||
textInput.performClick()
|
||||
textInput.performTextReplacement(withdrawAmount)
|
||||
}
|
||||
}
|
||||
step("Dismiss keyboard") {
|
||||
Espresso.closeSoftKeyboard()
|
||||
waitForIdle()
|
||||
}
|
||||
step("Wait until network fee row is rendered (HoldToConfirm enabled)") {
|
||||
onSwapTokenScreen {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) { networkFeeTitle.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
step("Confirm swap by holding the button") {
|
||||
confirmSwapByHolding(accessCode = TANGEM_PAY_ACCESS_CODE)
|
||||
}
|
||||
step("Wait for 'Swap in progress' screen") {
|
||||
onSwapSuccessScreen {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_VERY_LONG) { title.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
step("Click on 'Close' button") {
|
||||
onSwapSuccessScreen { closeButton.performClick() }
|
||||
}
|
||||
step("Switch WireMock scenario '$balanceScenario' to '$balanceAfterState'") {
|
||||
setWireMockScenarioState(balanceScenario, balanceAfterState)
|
||||
}
|
||||
step("Switch WireMock scenario '$historyScenario' to '$historyAfterState'") {
|
||||
setWireMockScenarioState(historyScenario, historyAfterState)
|
||||
}
|
||||
step("Pull to refresh Tangem Pay") { pullToRefreshTangemPay() }
|
||||
step("Assert balance updated to '\$5.00'") {
|
||||
onTangemPayMainScreen {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
balance.assertTextContainsSafe("5", substring = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Assert '$withdrawalLabel' transaction visible in history") {
|
||||
onTangemPayMainScreen {
|
||||
transactionRowWithText(withdrawalLabel).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ object TangemPayTestTags {
|
|||
// Card management (card page settings)
|
||||
const val CHANGE_PIN_ROW = "TANGEM_PAY_CHANGE_PIN_ROW"
|
||||
const val FREEZE_CARD_ROW = "TANGEM_PAY_FREEZE_CARD_ROW"
|
||||
const val CARD_FROZEN_BADGE = "TANGEM_PAY_CARD_FROZEN_BADGE"
|
||||
|
||||
// Freeze confirmation bottom sheet
|
||||
const val FREEZE_CONFIRMATION_SUBMIT_BUTTON = "TANGEM_PAY_FREEZE_CONFIRMATION_SUBMIT_BUTTON"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.style.LineBreak
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -22,6 +23,7 @@ import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
|||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.fields.PinTextColor
|
||||
import com.tangem.core.ui.components.fields.PinTextField
|
||||
import com.tangem.core.ui.test.HotWalletAccessCodeTestTags
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
import com.tangem.core.ui.res.LocalHapticManager
|
||||
|
|
@ -81,13 +83,15 @@ internal fun HotAccessCodeRequestFullScreenContent(state: HotAccessCodeRequestUM
|
|||
SpacerH24()
|
||||
|
||||
PinTextField(
|
||||
modifier = Modifier.animateEnterExit(
|
||||
enter = slideInVertically(
|
||||
tween(),
|
||||
initialOffsetY = { it + 200 },
|
||||
) + fadeIn(tween()),
|
||||
exit = slideOutVertically(tween(300)) { it - 200 } + fadeOut(tween()),
|
||||
),
|
||||
modifier = Modifier
|
||||
.testTag(HotWalletAccessCodeTestTags.ACCESS_CODE_INPUT)
|
||||
.animateEnterExit(
|
||||
enter = slideInVertically(
|
||||
tween(),
|
||||
initialOffsetY = { it + 200 },
|
||||
) + fadeIn(tween()),
|
||||
exit = slideOutVertically(tween(300)) { it - 200 } + fadeOut(tween()),
|
||||
),
|
||||
length = 6,
|
||||
isPasswordVisual = true,
|
||||
value = state.accessCode,
|
||||
|
|
|
|||
|
|
@ -188,7 +188,8 @@ private fun TangemPayCardDetailsHiddenBlock(state: TangemPayCardDetailsUM, modif
|
|||
bottom.linkTo(cardNumberRef.bottom)
|
||||
}
|
||||
.padding(bottom = 8.dp)
|
||||
.size(16.dp),
|
||||
.size(16.dp)
|
||||
.testTag(TangemPayTestTags.CARD_FROZEN_BADGE),
|
||||
painter = painterResource(id = R.drawable.ic_snow_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.constant,
|
||||
|
|
@ -201,7 +202,8 @@ private fun TangemPayCardDetailsHiddenBlock(state: TangemPayCardDetailsUM, modif
|
|||
bottom.linkTo(cardNumberRef.bottom)
|
||||
}
|
||||
.padding(bottom = 8.dp)
|
||||
.size(16.dp),
|
||||
.size(16.dp)
|
||||
.testTag(TangemPayTestTags.CARD_FROZEN_BADGE),
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
strokeWidth = 1.dp,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -300,7 +300,8 @@ private fun TangemPayCardItem(card: TangemPayDetailsBalanceBlockState.Card, modi
|
|||
Box(
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.clickable(onClick = card.onClick),
|
||||
.clickable(onClick = card.onClick)
|
||||
.testTag(TangemPayTestTags.PAYMENT_ACCOUNT_CARD_BUTTON),
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue