Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-05 14:51:28 +03:00
parent f5b50fcbdd
commit 41171e9e0c
14 changed files with 328 additions and 37 deletions

View file

@ -63,7 +63,6 @@ abstract class BaseTestCase : TestCase(
private val apiEnvironmentRule = ApiEnvironmentRule()
private val permissionRule = GrantPermissionRule.grant(
Manifest.permission.POST_NOTIFICATIONS,
Manifest.permission.CAMERA,
)
/**

View file

@ -36,6 +36,21 @@ fun BaseTestCase.openSendConfirmScreen(
tokenName: String,
inputAmount: String,
recipientAddress: String
) {
step("Open 'Send Address' screen") {
openSendAddressScreen(tokenName, inputAmount)
}
step("Type recipient address") {
onSendAddressScreen { addressTextField.performTextReplacement(recipientAddress) }
}
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
}
}
fun BaseTestCase.openSendAddressScreen(
tokenName: String,
inputAmount: String
) {
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
@ -52,10 +67,22 @@ fun BaseTestCase.openSendConfirmScreen(
step("Click on 'Next' button") {
onSendScreen { nextButton.clickWithAssertion() }
}
step("Type recipient address") {
onSendAddressScreen { addressTextField.performTextReplacement(recipientAddress) }
step("Assert 'Send Address' container is displayed") {
onSendAddressScreen { container.assertIsDisplayed() }
}
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
}
fun BaseTestCase.checkScanQrScreen() {
step("Assert 'Back' button is displayed") {
onScanQrScreen { backTopAppBarButton.assertIsDisplayed() }
}
step("Assert 'Flashlight' button is displayed") {
onScanQrScreen { flashlightButton.assertIsDisplayed() }
}
step("Assert 'Gallery' button is displayed") {
onScanQrScreen { galleryButton.assertIsDisplayed() }
}
step("Assert 'Paste from clipboard' is displayed") {
onScanQrScreen { pasteFromClipboardButton.assertIsDisplayed() }
}
}

View file

@ -0,0 +1,51 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.R
import com.tangem.core.ui.test.BaseBottomSheetTestTags
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
import androidx.compose.ui.test.hasTestTag as withTestTag
import androidx.compose.ui.test.hasText as withText
import com.tangem.feature.qrscanning.impl.R as QrScanningImplR
class CameraAccessDeniedBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<CameraAccessDeniedBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
val title: KNode = child {
hasText(getResourceString(QrScanningImplR.string.qr_scanner_camera_denied_title))
useUnmergedTree = true
}
val subtitle: KNode = child {
hasText(getResourceString(QrScanningImplR.string.qr_scanner_camera_denied_text))
useUnmergedTree = true
}
val settingsButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.qr_scanner_camera_denied_settings_button)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val selectFromGalleryButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.qr_scanner_camera_denied_gallery_button)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
val closeButton: KNode = child {
hasAnyChild(withText(getResourceString(R.string.common_close)))
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onCameraAccessDeniedBottomSheet(function: CameraAccessDeniedBottomSheetPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -0,0 +1,26 @@
package com.tangem.screens
import com.kaspersky.components.kautomator.component.text.UiButton
import com.kaspersky.components.kautomator.component.text.UiTextView
import com.kaspersky.components.kautomator.screen.UiScreen
object PermissionDialogPageObject : UiScreen<PermissionDialogPageObject>() {
override val packageName = "com.android.permissioncontroller"
val allowWhileUsingButton = UiButton {
withId(this@PermissionDialogPageObject.packageName,"permission_allow_foreground_only_button")
}
val allowOnlyThisTimeButton = UiButton {
withId(this@PermissionDialogPageObject.packageName, "permission_allow_one_time_button")
}
val doNotAllowButton = UiButton {
withId(this@PermissionDialogPageObject.packageName, "permission_deny_button")
}
val permissionMessage = UiTextView {
withId(this@PermissionDialogPageObject.packageName, "permission_message")
}
}

View file

@ -0,0 +1,51 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.R
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.QrCodeScreenTestTags
import com.tangem.core.ui.test.TopAppBarTestTags
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
class ScanQrPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<ScanQrPageObject>(semanticsProvider = semanticsProvider) {
val backTopAppBarButton: KNode = child {
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
useUnmergedTree = true
}
val sendTitle: KNode = child {
hasTestTag(TopAppBarTestTags.TITLE)
hasText(getResourceString(R.string.common_send))
useUnmergedTree = true
}
val flashlightButton: KNode = child {
hasTestTag(QrCodeScreenTestTags.FLASHLIGHT_BUTTON)
useUnmergedTree = true
}
val galleryButton: KNode = child {
hasTestTag(QrCodeScreenTestTags.GALLERY_BUTTON)
useUnmergedTree = true
}
fun scanText(tokenName: String): KNode = child {
hasText(getResourceString(R.string.send_qrcode_scan_info, tokenName))
useUnmergedTree = true
}
val pasteFromClipboardButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.wallet_connect_paste_from_clipboard))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onScanQrScreen(function: ScanQrPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -16,6 +16,11 @@ import com.tangem.core.ui.R as CoreUiR
class SendAddressPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<SendAddressPageObject>(semanticsProvider = semanticsProvider) {
val container: KNode = child {
hasTestTag(SendAddressScreenTestTags.CONTAINER)
useUnmergedTree = true
}
val addressTextFieldTitle: KNode = child {
hasTestTag(SendAddressScreenTestTags.ADDRESS_TEXT_FIELD_TITLE)
useUnmergedTree = true
@ -31,6 +36,11 @@ class SendAddressPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
useUnmergedTree = true
}
val qrButton: KNode = child {
hasTestTag(SendAddressScreenTestTags.QR_BUTTON)
useUnmergedTree = true
}
val clearTextFieldButton: KNode = child {
hasContentDescription(getResourceString(CoreUiR.string.common_close))
useUnmergedTree = true

View file

@ -1,23 +0,0 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.R
import com.tangem.core.ui.test.BaseButtonTestTags
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
class WalletConnectScanQrPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<WalletConnectScanQrPageObject>(semanticsProvider = semanticsProvider) {
val pasteFromClipboardButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.wallet_connect_paste_from_clipboard))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onWalletConnectScanQrScreen(function: WalletConnectScanQrPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -1,5 +1,6 @@
package com.tangem.tests
import android.Manifest
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.extensions.clickWithAssertion
@ -8,7 +9,7 @@ import com.tangem.common.utils.setClipboardText
import com.tangem.scenarios.*
import com.tangem.screens.onWalletConnectBottomSheet
import com.tangem.screens.onWalletConnectDetailsBottomSheet
import com.tangem.screens.onWalletConnectScanQrScreen
import com.tangem.screens.onScanQrScreen
import com.tangem.screens.onWalletConnectScreen
import com.tangem.wallet.BuildConfig
import dagger.hilt.android.testing.HiltAndroidTest
@ -196,8 +197,14 @@ class WalletConnectTest : BaseTestCase() {
val dAppName = "React App"
val context = device.context
val deepLinkUri = getWcUri()
val packageName = BuildConfig.APPLICATION_ID
val permissionName = Manifest.permission.CAMERA
setupHooks().run {
setupHooks(
additionalBeforeSection = {
device.uiDevice.executeShellCommand("pm grant $packageName $permissionName")
},
).run {
step("Set URI to clipboard") {
setClipboardText(context, deepLinkUri)
}
@ -214,7 +221,7 @@ class WalletConnectTest : BaseTestCase() {
onWalletConnectScreen { newConnectionButton.performClick() }
}
step("CLick 'Paste from clipboard' button") {
onWalletConnectScanQrScreen { pasteFromClipboardButton.clickWithAssertion() }
onScanQrScreen { pasteFromClipboardButton.clickWithAssertion() }
}
step("Check 'Wallet Connect' bottom sheet") {
waitForIdle()

View file

@ -1,5 +1,6 @@
package com.tangem.tests.send.addressScreen
import android.Manifest
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.ENS_ETHEREUM_RECIPIENT_ADDRESS
import com.tangem.common.constants.TestConstants.ENS_ETHEREUM_RECIPIENT_SHORTENED_ADDRESS
@ -9,15 +10,17 @@ import com.tangem.common.constants.TestConstants.ETHEREUM_RECIPIENT_ADDRESS
import com.tangem.common.constants.TestConstants.ETHEREUM_RECIPIENT_SHORTENED_ADDRESS
import com.tangem.common.constants.TestConstants.XRP_RECIPIENT_ADDRESS
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.*
import com.tangem.common.utils.clearClipboard
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setClipboardText
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.core.ui.R
import com.tangem.scenarios.checkScanQrScreen
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.openSendAddressScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.*
import com.tangem.screens.onMainScreen
import com.tangem.screens.onSendAddressScreen
import com.tangem.screens.onSendScreen
import com.tangem.screens.onTokenDetailsScreen
import com.tangem.wallet.BuildConfig
import dagger.hilt.android.testing.HiltAndroidTest
import io.github.kakaocup.kakao.common.utilities.getResourceString
import io.qameta.allure.kotlin.AllureId
@ -335,4 +338,126 @@ class SendAddressScreenTest : BaseTestCase() {
}
}
}
@AllureId("4574")
@DisplayName("Send (address screen): check scan QR code screen")
@Test
fun checkQrCodeScreenTest() {
val tokenName = "Ethereum"
val sendAmount = "1"
val packageName = BuildConfig.APPLICATION_ID
val permissionName = Manifest.permission.CAMERA
setupHooks(
additionalBeforeSection = {
device.uiDevice.executeShellCommand("pm grant $packageName $permissionName")
}
).run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Open 'Send Address' screen") {
openSendAddressScreen(tokenName, sendAmount)
}
step("Click on 'QR' button") {
onSendAddressScreen { qrButton.clickWithAssertion() }
}
step("Check 'Scan QR code' screen") {
checkScanQrScreen()
}
}
}
@AllureId("4576")
@DisplayName("Send (address screen): give camera permission")
@Test
fun giveCameraPermissionScreenTest() {
val tokenName = "Ethereum"
val sendAmount = "1"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Open 'Send Address' screen") {
openSendAddressScreen(tokenName, sendAmount)
}
step("Click on 'QR' button") {
onSendAddressScreen { qrButton.clickWithAssertion() }
}
step("Assert 'While using app' button is displayed") {
waitForIdle()
PermissionDialogPageObject { allowWhileUsingButton.isDisplayed() }
}
step("Assert 'Allow only this time' button is displayed") {
PermissionDialogPageObject { allowOnlyThisTimeButton.isDisplayed() }
}
step("Assert 'Don't allow' button is displayed") {
PermissionDialogPageObject { doNotAllowButton.isDisplayed() }
}
step("CLick on 'While using app' button") {
PermissionDialogPageObject { allowWhileUsingButton.click() }
}
step("Check 'Scan QR code' screen") {
checkScanQrScreen()
}
}
}
@AllureId("4578")
@DisplayName("Send (address screen): don't give camera permission")
@Test
fun doNotGiveCameraPermissionScreenTest() {
val tokenName = "Ethereum"
val sendAmount = "1"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Open 'Send Address' screen") {
openSendAddressScreen(tokenName, sendAmount)
}
step("Click on 'QR' button") {
onSendAddressScreen { qrButton.clickWithAssertion() }
}
step("Click on 'Don't allow' button") {
waitForIdle()
PermissionDialogPageObject { doNotAllowButton.click() }
}
step("Assert 'Camera access denied' bottom sheet title is displayed") {
waitForIdle()
onCameraAccessDeniedBottomSheet { title.assertIsDisplayed() }
}
step("Assert 'Camera access denied' bottom sheet subtitle is displayed") {
onCameraAccessDeniedBottomSheet { subtitle.assertIsDisplayed() }
}
step("Assert 'Settings' button is displayed") {
onCameraAccessDeniedBottomSheet { settingsButton.assertIsDisplayed() }
}
step("Assert 'Select from the gallery' button is displayed") {
onCameraAccessDeniedBottomSheet { selectFromGalleryButton.assertIsDisplayed() }
}
step("Assert 'Close' button is displayed") {
onCameraAccessDeniedBottomSheet { closeButton.assertIsDisplayed() }
}
step("Click on 'Close' button") {
onCameraAccessDeniedBottomSheet { closeButton.performClick() }
}
step("Assert 'Address' screen is displayed") {
onSendAddressScreen { container.assertIsDisplayed() }
}
}
}
}

View file

@ -134,6 +134,7 @@ fun InputRowRecipient(
QrButton(
visible = value.isBlank(),
onQrCodeClick = onQrCodeClick,
modifier = Modifier.testTag(SendAddressScreenTestTags.QR_BUTTON),
)
PasteButton(
isPasteButtonVisible = value.isBlank(),

View file

@ -0,0 +1,6 @@
package com.tangem.core.ui.test
object QrCodeScreenTestTags {
const val FLASHLIGHT_BUTTON = "QR_CODE_SCREEN_FLASHLIGHT_BUTTON"
const val GALLERY_BUTTON = "QR_CODE_SCREEN_GALLERY_BUTTON"
}

View file

@ -1,8 +1,11 @@
package com.tangem.core.ui.test
object SendAddressScreenTestTags {
const val CONTAINER = "SEND_ADDRESS_SCREEN_CONTAINER"
const val ADDRESS_TEXT_FIELD_TITLE = "SEND_ADDRESS_SCREEN_TEXT_FIELD_TITLE"
const val ADDRESS_TEXT_FIELD = "SEND_ADDRESS_SCREEN_TEXT_FIELD"
const val QR_BUTTON = "SEND_ADDRESS_SCREEN_QR_BUTTON"
const val ADDRESS_PASTE_BUTTON = "SEND_ADDRESS_SCREEN_ADDRESS_PASTE_BUTTON"
const val RESOLVED_ADDRESS = "SEND_ADDRESS_SCREEN_RESOLVED_ADDRESS"

View file

@ -16,6 +16,7 @@ import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.style.TextAlign
@ -32,6 +33,7 @@ import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.QrCodeScreenTestTags
import com.tangem.feature.qrscanning.impl.R
import com.tangem.feature.qrscanning.inner.MLKitBarcodeAnalyzer
import java.util.concurrent.ExecutorService
@ -87,6 +89,7 @@ internal fun QrScanningContent(
},
),
tint = TangemColorPalette.White,
modifier = Modifier.testTag(QrCodeScreenTestTags.FLASHLIGHT_BUTTON),
)
}
@ -96,6 +99,7 @@ internal fun QrScanningContent(
onClicked = uiState.onGalleryClick,
),
tint = TangemColorPalette.White,
modifier = Modifier.testTag(QrCodeScreenTestTags.GALLERY_BUTTON),
)
},
)

View file

@ -16,6 +16,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
@ -27,6 +28,7 @@ import com.tangem.core.ui.components.containers.FooterContainer
import com.tangem.core.ui.components.inputrow.InputRowRecipient
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.SendAddressScreenTestTags
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationRecipientListUM
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationTextFieldUM
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
@ -38,6 +40,7 @@ import kotlinx.collections.immutable.ImmutableList
private const val ADDRESS_FIELD_KEY = "ADDRESS_FIELD_KEY"
private const val MEMO_FIELD_KEY = "MEMO_FIELD_KEY"
@Suppress("LongMethod")
@Composable
internal fun SendDestinationContent(
state: DestinationUM,
@ -53,7 +56,8 @@ internal fun SendDestinationContent(
LazyColumn(
modifier = Modifier // Do not put fillMaxSize() in here
.background(TangemTheme.colors.background.tertiary)
.padding(horizontal = 16.dp),
.padding(horizontal = 16.dp)
.testTag(SendAddressScreenTestTags.CONTAINER),
) {
addressItem(
address = address,