Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-14 11:48:39 +03:00
parent 6b770f5d00
commit f1a4be49b3
13 changed files with 337 additions and 103 deletions

View file

@ -34,6 +34,7 @@ object TestConstants {
const val TERRA_RECIPIENT_ADDRESS = "terra148dmp5ccazcwdmrcpvqz5rprnn886kemqen3tj"
const val POLYGON_RECIPIENT_ADDRESS = "0x742d35cc6634c0532925a3b844bc9e7595f2bd18"
const val WAIT_UNTIL_TIMEOUT_SHORT = 5_000L
const val WAIT_UNTIL_TIMEOUT = 20_000L
const val WAIT_UNTIL_TIMEOUT_LONG = 30_000L
const val WAIT_UNTIL_TIMEOUT_VERY_LONG = 60_000L

View file

@ -3,6 +3,9 @@ package com.tangem.common.extensions
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.onAllNodesWithText
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.utils.LazyListItemNode
import com.tangem.core.ui.components.buttons.actions.HasBadgeKey
import com.tangem.core.ui.components.buttons.actions.IsDimmedKey
@ -10,6 +13,15 @@ import io.github.kakaocup.compose.node.element.KNode
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
fun BaseTestCase.assertSnackbarWithText(text: String, timeoutMs: Long = WAIT_UNTIL_TIMEOUT) {
composeTestRule.waitUntil(timeoutMillis = timeoutMs) {
composeTestRule
.onAllNodesWithText(text, substring = true)
.fetchSemanticsNodes()
.isNotEmpty()
}
}
fun assertElementDoesNotExist(
elementProvider: () -> KNode,
elementDescription: String,

View file

@ -1,5 +1,6 @@
package com.tangem.common.utils
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject
@ -21,9 +22,18 @@ private fun redactWcSecrets(text: String): String =
*/
fun getWcUri(
network: String = "ethereum",
dAppUrl: String? = null,
dAppName: String? = null,
baseUrl: String = "[REDACTED_ENV_URL]"
): String? {
val url = "$baseUrl/wc_uri?network=$network"
val url = "$baseUrl/wc_uri".toHttpUrl().newBuilder()
.addQueryParameter("network", network)
.apply {
if (dAppUrl != null) addQueryParameter("dappUrl", dAppUrl)
if (dAppName != null) addQueryParameter("dappName", dAppName)
}
.build()
.toString()
TangemLogger.i("getWcUri: requesting $url")
val client = OkHttpClient.Builder()

View file

@ -1,9 +1,19 @@
package com.tangem.scenarios
import android.content.Context
import androidx.compose.ui.test.onAllNodesWithText
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_SHORT
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.setClipboardText
import com.tangem.core.ui.R
import com.tangem.screens.onScanQrScreen
import com.tangem.screens.onWalletConnectBottomSheet
import com.tangem.screens.onWalletConnectDetailsBottomSheet
import com.tangem.screens.onWalletConnectScreen
import com.tangem.screens.onWarningBottomSheet
import io.github.kakaocup.kakao.common.utilities.getResourceString
import io.qameta.allure.kotlin.Allure.step
fun BaseTestCase.checkWalletConnectBottomSheet() {
@ -17,9 +27,6 @@ fun BaseTestCase.checkWalletConnectBottomSheet() {
step("Assert 'Wallet Connect' bottom sheet app name is displayed") {
onWalletConnectBottomSheet { appName.assertIsDisplayed() }
}
step("Assert 'Wallet Connect' bottom sheet approve icon is displayed") {
onWalletConnectBottomSheet { approveIcon.assertIsDisplayed() }
}
step("Assert 'Wallet Connect' bottom sheet app URL is displayed") {
onWalletConnectBottomSheet { appUrl.assertIsDisplayed() }
}
@ -82,9 +89,6 @@ fun BaseTestCase.checkWalletConnectScreen(withConnections: Boolean) {
step("Assert app name is displayed") {
onWalletConnectScreen { appName.assertIsDisplayed() }
}
step("Assert approve icon is displayed") {
onWalletConnectScreen { approveIcon.assertIsDisplayed() }
}
step("Assert app URL is displayed") {
onWalletConnectScreen { appUrl.assertIsDisplayed() }
}
@ -117,6 +121,73 @@ fun BaseTestCase.checkWalletConnectScreen(withConnections: Boolean) {
}
fun BaseTestCase.establishAndDisconnectWcSession(
context: Context,
deepLinkUri: String?,
dAppName: String,
) {
step("Set URI to clipboard") {
setClipboardText(context, deepLinkUri)
}
step("Create connection via 'Paste from clipboard' button") {
createConnectionViaPasteFromClipboardButton()
}
step("Check 'Wallet Connect' bottom sheet") {
composeTestRule.waitUntil(timeoutMillis = TestConstants.WAIT_UNTIL_TIMEOUT) {
runCatching { checkWalletConnectBottomSheet() }.isSuccess
}
}
step("Click on 'Connect' button and dismiss 'Unknown domain' alert if shown") {
confirmWcConnection()
}
step("Check 'Wallet Connect' screen with connections") {
checkWalletConnectScreen(withConnections = true)
}
step("Click on app icon") {
onWalletConnectScreen { appIcon.performClick() }
}
step("Check 'Wallet Connect' details bottom sheet") {
checkWalletConnectDetailsBottomSheet(dAppName)
}
step("Click on 'Disconnect' button") {
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
}
}
/**
* Clicks 'Connect' in the WalletConnect bottom sheet and dismisses the 'Unknown domain' security
* alert if it appears.
*
* qa-tools URIs are not registered with Reown Verify API, so Reown returns validation=UNKNOWN
* after the production change in DefaultWcPairUseCase that maps UNKNOWN to FAILED_TO_VERIFY, the
* app shows a Security Alert before establishing the session. Tests that drive qa-tools URIs go
* through this helper to consistently accept the warning.
*/
fun BaseTestCase.confirmWcConnection() {
step("Click on 'Connect' button") {
onWalletConnectBottomSheet { connectButton.performClick() }
}
waitForIdle()
val alertText = getResourceString(R.string.wc_alert_connect_anyway)
val alertAppeared = runCatching {
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_SHORT) {
composeTestRule.onAllNodesWithText(alertText).fetchSemanticsNodes().isNotEmpty()
}
}.isSuccess
if (alertAppeared) {
step("Click on 'Connect anyway' button") {
onWarningBottomSheet { connectAnywayButton.clickWithAssertion() }
}
}
step("Assert 'Connect' button is not displayed") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
}
}
fun BaseTestCase.checkWalletConnectDetailsBottomSheet(dAppName: String) {
waitForIdle()
step("Assert connection details title is displayed") {
@ -134,9 +205,6 @@ fun BaseTestCase.checkWalletConnectDetailsBottomSheet(dAppName: String) {
step("Assert app name is displayed") {
onWalletConnectDetailsBottomSheet { appName.assertIsDisplayed() }
}
step("Assert approve icon is displayed") {
onWalletConnectDetailsBottomSheet { approveIcon.assertIsDisplayed() }
}
step("Assert app URL is displayed") {
onWalletConnectDetailsBottomSheet { appUrl.assertIsDisplayed() }
}
@ -158,4 +226,13 @@ fun BaseTestCase.checkWalletConnectDetailsBottomSheet(dAppName: String) {
step("Assert 'Disconnect button' is displayed") {
onWalletConnectDetailsBottomSheet { disconnectButton.assertIsDisplayed() }
}
}
fun BaseTestCase.createConnectionViaPasteFromClipboardButton() {
step("Click on 'New connection' button") {
onWalletConnectScreen { newConnectionButton.performClick() }
}
step("Click on 'Paste from clipboard' button") {
onScanQrScreen { pasteFromClipboardButton.clickWithAssertion() }
}
}

View file

@ -30,11 +30,29 @@ class WarningBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsP
useUnmergedTree = true
}
val gotItButton: KNode = child {
val okGotItButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.warning_button_ok))
useUnmergedTree = true
}
val gotItButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_got_it))
useUnmergedTree = true
}
val cancelButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.common_cancel))
useUnmergedTree = true
}
val connectAnywayButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.wc_alert_connect_anyway))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onWarningBottomSheet(function: WarningBottomSheetPageObject.() -> Unit) =

View file

@ -75,7 +75,7 @@ class SendViaSwapTest : BaseTestCase() {
onWarningBottomSheet { message(warningMessage).assertIsDisplayed() }
}
step("Click on 'Ok, Got it!' button") {
onWarningBottomSheet { gotItButton.performClick() }
onWarningBottomSheet { okGotItButton.performClick() }
}
}
}

View file

@ -3,17 +3,9 @@ package com.tangem.tests.walletConnect
import android.Manifest
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.getWcUri
import com.tangem.common.utils.setClipboardText
import com.tangem.scenarios.checkWalletConnectBottomSheet
import com.tangem.scenarios.checkWalletConnectDetailsBottomSheet
import com.tangem.scenarios.checkWalletConnectScreen
import com.tangem.scenarios.openAppByDeepLink
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.openWalletConnectScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.onScanQrScreen
import com.tangem.scenarios.*
import com.tangem.screens.onWalletConnectBottomSheet
import com.tangem.screens.onWalletConnectDetailsBottomSheet
import com.tangem.screens.onWalletConnectScreen
@ -51,13 +43,8 @@ class EthereumWalletConnectTest : BaseTestCase() {
step("Assert 'Connect' button is enabled") {
onWalletConnectBottomSheet { connectButton.assertIsEnabled() }
}
step("Click on 'Connect' button") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.performClick() }
}
step("Assert 'Connect' button is not displayed") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
step("Click on 'Connect' button and dismiss 'Unknown domain' alert if shown") {
confirmWcConnection()
}
step("Open 'Wallet Connect' screen") {
openWalletConnectScreen()
@ -108,13 +95,8 @@ class EthereumWalletConnectTest : BaseTestCase() {
checkWalletConnectBottomSheet()
}
}
step("Click on 'Connect' button") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.performClick() }
}
step("Assert 'Connect' button is not displayed") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
step("Click on 'Connect' button and dismiss 'Unknown domain' alert if shown") {
confirmWcConnection()
}
step("Check 'Wallet Connect' screen with connections") {
flakySafely(TestConstants.WAIT_UNTIL_TIMEOUT) {
@ -164,11 +146,8 @@ class EthereumWalletConnectTest : BaseTestCase() {
checkWalletConnectBottomSheet()
}
}
step("Click on 'Connect' button") {
onWalletConnectBottomSheet { connectButton.performClick() }
}
step("Assert 'Connect' button is not displayed") {
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
step("Click on 'Connect' button and dismiss 'Unknown domain' alert if shown") {
confirmWcConnection()
}
step("Open 'Wallet Connect' screen") {
openWalletConnectScreen()
@ -218,11 +197,8 @@ class EthereumWalletConnectTest : BaseTestCase() {
step("Open 'Wallet Connect' screen") {
openWalletConnectScreen()
}
step("Click 'New connection' button") {
onWalletConnectScreen { newConnectionButton.performClick() }
}
step("CLick 'Paste from clipboard' button") {
onScanQrScreen { pasteFromClipboardButton.clickWithAssertion() }
step("Create connection via 'Paste from clipboard' button") {
createConnectionViaPasteFromClipboardButton()
}
step("Check 'Wallet Connect' bottom sheet") {
waitForIdle()
@ -230,13 +206,8 @@ class EthereumWalletConnectTest : BaseTestCase() {
checkWalletConnectBottomSheet()
}
}
step("Click on 'Connect' button") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.performClick() }
}
step("Assert 'Connect' button is not displayed") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
step("Click on 'Connect' button and dismiss 'Unknown domain' alert if shown") {
confirmWcConnection()
}
step("Check 'Wallet Connect' screen with connections") {
checkWalletConnectScreen(withConnections = true)

View file

@ -4,19 +4,11 @@ import android.Manifest
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.getWcUri
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setClipboardText
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.checkWalletConnectBottomSheet
import com.tangem.scenarios.checkWalletConnectDetailsBottomSheet
import com.tangem.scenarios.checkWalletConnectScreen
import com.tangem.scenarios.openAppByDeepLink
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.openWalletConnectScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.onScanQrScreen
import com.tangem.scenarios.*
import com.tangem.screens.onWalletConnectBottomSheet
import com.tangem.screens.onWalletConnectDetailsBottomSheet
import com.tangem.screens.onWalletConnectScreen
@ -64,13 +56,8 @@ class SolanaWalletConnectTest : BaseTestCase() {
step("Assert 'Connect' button is enabled") {
onWalletConnectBottomSheet { connectButton.assertIsEnabled() }
}
step("Click on 'Connect' button") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.performClick() }
}
step("Assert 'Connect' button is not displayed") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
step("Click on 'Connect' button and dismiss 'Unknown domain' alert if shown") {
confirmWcConnection()
}
step("Open 'Wallet Connect' screen") {
openWalletConnectScreen()
@ -131,13 +118,8 @@ class SolanaWalletConnectTest : BaseTestCase() {
checkWalletConnectBottomSheet()
}
}
step("Click on 'Connect' button") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.performClick() }
}
step("Assert 'Connect' button is not displayed") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
step("Click on 'Connect' button and dismiss 'Unknown domain' alert if shown") {
confirmWcConnection()
}
step("Check 'Wallet Connect' screen with connections") {
flakySafely(TestConstants.WAIT_UNTIL_TIMEOUT) {
@ -197,11 +179,8 @@ class SolanaWalletConnectTest : BaseTestCase() {
checkWalletConnectBottomSheet()
}
}
step("Click on 'Connect' button") {
onWalletConnectBottomSheet { connectButton.performClick() }
}
step("Assert 'Connect' button is not displayed") {
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
step("Click on 'Connect' button and dismiss 'Unknown domain' alert if shown") {
confirmWcConnection()
}
step("Open 'Wallet Connect' screen") {
openWalletConnectScreen()
@ -261,11 +240,8 @@ class SolanaWalletConnectTest : BaseTestCase() {
step("Open 'Wallet Connect' screen") {
openWalletConnectScreen()
}
step("Click 'New connection' button") {
onWalletConnectScreen { newConnectionButton.performClick() }
}
step("CLick 'Paste from clipboard' button") {
onScanQrScreen { pasteFromClipboardButton.clickWithAssertion() }
step("Create connection via 'Paste from clipboard' button") {
createConnectionViaPasteFromClipboardButton()
}
step("Check 'Wallet Connect' bottom sheet") {
waitForIdle()
@ -273,13 +249,8 @@ class SolanaWalletConnectTest : BaseTestCase() {
checkWalletConnectBottomSheet()
}
}
step("Click on 'Connect' button") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.performClick() }
}
step("Assert 'Connect' button is not displayed") {
waitForIdle()
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
step("Click on 'Connect' button and dismiss 'Unknown domain' alert if shown") {
confirmWcConnection()
}
step("Check 'Wallet Connect' screen with connections") {
checkWalletConnectScreen(withConnections = true)

View file

@ -0,0 +1,143 @@
package com.tangem.tests.walletConnect
import android.Manifest
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants
import com.tangem.common.extensions.assertSnackbarWithText
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.getWcUri
import com.tangem.common.utils.setClipboardText
import com.tangem.scenarios.*
import com.tangem.screens.onWarningBottomSheet
import com.tangem.wallet.BuildConfig
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 WalletConnectTest : BaseTestCase() {
@AllureId("9037")
@DisplayName("WC: invalid wallet connect link")
@Test
fun invalidWalletConnectLinkTest() {
val context = device.context
val deepLinkUri = "wc:384617d590a47f11c26311b5cf2418859682920aa0ad52"
val packageName = BuildConfig.APPLICATION_ID
val permissionName = Manifest.permission.CAMERA
setupHooks(
additionalBeforeSection = {
device.uiDevice.executeShellCommand("pm grant $packageName $permissionName")
},
).run {
step("Set URI to clipboard") {
setClipboardText(context, deepLinkUri)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Open 'Wallet Connect' screen") {
openWalletConnectScreen()
}
step("Create connection via 'Paste from clipboard' button") {
createConnectionViaPasteFromClipboardButton()
}
step("Assert error snackbar about invalid WC URI is displayed") {
assertSnackbarWithText("getUserInfo")
}
}
}
@AllureId("9040")
@DisplayName("WC (React App): repeat open/close session")
@Test
fun repeatedConnectByWalletConnectDeeplinkScreenTest() {
val dAppName = "Tangem QA Tools"
val context = device.context
val packageName = BuildConfig.APPLICATION_ID
val permissionName = Manifest.permission.CAMERA
val sessionsCount = 3
setupHooks(
additionalBeforeSection = {
device.uiDevice.executeShellCommand("pm grant $packageName $permissionName")
},
).run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Open 'Wallet Connect' screen") {
openWalletConnectScreen()
}
repeat(sessionsCount) { iteration ->
step("Session #${iteration + 1}: connect and disconnect") {
establishAndDisconnectWcSession(
context = context,
deepLinkUri = getWcUri(),
dAppName = dAppName,
)
}
step("Check 'Wallet Connect' screen without connections") {
checkWalletConnectScreen(withConnections = false)
}
}
}
}
@AllureId("9066")
@DisplayName("WC: connect to unsupported dApp shows error")
@Test
fun connectToUnsupportedDAppShowsUnsupportedErrorTest() {
val unsupportedDAppUrl = "https://dydx.trade/test"
val dAppName = "dYdX"
val context = device.context
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 'Wallet Connect' screen") {
openWalletConnectScreen()
}
step("Set unsupported dApp URI to clipboard") {
setClipboardText(
context = context,
text = getWcUri(dAppUrl = unsupportedDAppUrl, dAppName = dAppName),
)
}
step("Create connection via 'Paste from clipboard' button") {
createConnectionViaPasteFromClipboardButton()
}
step("Wait for unsupported dApp error bottom sheet") {
composeTestRule.waitUntil(timeoutMillis = TestConstants.WAIT_UNTIL_TIMEOUT) {
runCatching {
onWarningBottomSheet { gotItButton.assertIsDisplayed() }
}.isSuccess
}
}
step("Click on 'Got it' button") {
onWarningBottomSheet { gotItButton.clickWithAssertion() }
}
step("Check 'Wallet Connect' screen without connections") {
checkWalletConnectScreen(withConnections = false)
}
}
}
}