diff --git a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt index 009af28e09..df3927d4fb 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt @@ -147,7 +147,9 @@ abstract class BaseTestCase : TestCase( "NEW_ONRAMP_MAIN_ENABLED" to true, "HOT_WALLET_ENABLED" to true, "YIELD_SUPPLY_FEATURE_ENABLED" to true, - "ACCOUNTS_FEATURE_ENABLED" to true + "ACCOUNTS_FEATURE_ENABLED" to true, + "FEED_ENABLED" to true, + "GASLESS_TRANSACTIONS_ENABLED" to true, ) ) } diff --git a/app/src/androidTest/kotlin/com/tangem/common/extensions/CustomAssertsExt.kt b/app/src/androidTest/kotlin/com/tangem/common/extensions/CustomAssertsExt.kt index e1040b8ffc..827136c346 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/extensions/CustomAssertsExt.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/extensions/CustomAssertsExt.kt @@ -1,5 +1,9 @@ package com.tangem.common.extensions +import androidx.compose.ui.test.SemanticsMatcher +import com.tangem.common.utils.LazyListItemNode +import com.tangem.core.ui.components.buttons.actions.HasBadgeKey +import com.tangem.core.ui.components.buttons.actions.IsDimmedKey import io.github.kakaocup.compose.node.element.KNode fun assertElementDoesNotExist( @@ -22,4 +26,20 @@ fun assertElementDoesNotExist( throw e } } +} + +fun Any.assertIsDimmed(expectedValue: Boolean = true) { + val matcher = SemanticsMatcher.expectValue(IsDimmedKey, expectedValue) + when (this) { + is KNode, is LazyListItemNode -> this.assert(matcher) + else -> throw IllegalArgumentException("Unsupported type: ${this::class}") + } +} + +fun Any.assertHasBadge(expectedValue: Boolean = true) { + val matcher = SemanticsMatcher.expectValue(HasBadgeKey, expectedValue) + when (this) { + is KNode, is LazyListItemNode -> this.assert(matcher) + else -> throw IllegalArgumentException("Unsupported type: ${this::class}") + } } \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt index d1ce4ef047..8d412d40e7 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt @@ -235,6 +235,11 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) useUnmergedTree = true } + val searchThroughMarketPlaceholder: KNode = child { + hasText(getResourceString(R.string.markets_search_header_title)) + useUnmergedTree = true + } + fun tokenNetworkGroupTitle(tokenNetwork: String): KNode { return lazyList.child { hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM) diff --git a/app/src/androidTest/kotlin/com/tangem/screens/MarketsPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/MarketsPageObject.kt index 4f6707a071..cad5a0cdea 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/MarketsPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/MarketsPageObject.kt @@ -48,6 +48,11 @@ class MarketsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : useUnmergedTree = true } + val searchThroughMarketPlaceholder: KNode = child { + hasText(getResourceString(R.string.markets_search_header_title)) + useUnmergedTree = true + } + fun tokenWithTitle(title: String): KNode { return lazyList.child { hasText(title) diff --git a/app/src/androidTest/kotlin/com/tangem/screens/MarketsTokenDetailsPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/MarketsTokenDetailsPageObject.kt new file mode 100644 index 0000000000..237a0f183b --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/MarketsTokenDetailsPageObject.kt @@ -0,0 +1,33 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.test.MarketTokenDetailsBottomSheetTestTags +import com.tangem.core.ui.test.TokenElementsTestTags +import com.tangem.features.onramp.impl.R +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 + +class MarketsTokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + val swapPortfolioQuickActionButton: KNode = child { + hasTestTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_QUICK_ACTION_BUTTON) + hasText(getResourceString(R.string.common_swap), substring = true) + } + + fun tokenWithTitle(title: String): KNode = child { + hasAnyAncestor(withTestTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_TOKEN_ITEM)) + hasTestTag(TokenElementsTestTags.TOKEN_TITLE) + hasAnySibling(withTestTag(TokenElementsTestTags.TOKEN_ICON)) + hasAnyChild(withText(title)) + useUnmergedTree = true + } +} + +internal fun BaseTestCase.onMarketsTokenDetailsScreen(function: MarketsTokenDetailsPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SwapSelectTokenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SwapSelectTokenPageObject.kt new file mode 100644 index 0000000000..83e09a9a09 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/SwapSelectTokenPageObject.kt @@ -0,0 +1,70 @@ +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.SearchBarTestTags +import com.tangem.core.ui.test.SwapSelectTokenScreenTestTags +import com.tangem.core.ui.test.TokenElementsTestTags +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 +import androidx.compose.ui.test.hasText as withText + +class SwapSelectTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + val title: KNode = child { + hasTestTag(TopAppBarTestTags.TITLE) + hasText(getResourceString(R.string.common_swap)) + useUnmergedTree = true + } + + val closeButton: KNode = child { + hasTestTag(TopAppBarTestTags.CLOSE_BUTTON) + } + + val youSwapTitle: KNode = child { + hasText(getResourceString(R.string.swapping_from_title)) + useUnmergedTree = true + } + + val youSwapBlock: KNode = child { + hasTestTag(SwapSelectTokenScreenTestTags.YOU_SWAP_BLOCK) + hasAnyDescendant(withText(getResourceString(R.string.action_buttons_you_want_to_swap))) + useUnmergedTree = true + } + + val youReceiveTitle: KNode = child { + hasText(getResourceString(R.string.swapping_to_title)) + useUnmergedTree = true + } + + val youReceiveBlock: KNode = child { + hasTestTag(SwapSelectTokenScreenTestTags.YOU_SWAP_BLOCK) + hasAnyDescendant(withText(getResourceString(R.string.action_buttons_you_want_to_receive))) + useUnmergedTree = true + } + + val searchBarIcon: KNode = child { + hasTestTag(SearchBarTestTags.ICON) + useUnmergedTree = true + } + + val searchBarPlaceholderText: KNode = child { + hasTestTag(SearchBarTestTags.PLACEHOLDER_TEXT) + useUnmergedTree = true + } + + fun tokenWithName(tokenName: String): KNode = child { + hasTestTag(TokenElementsTestTags.TOKEN_TITLE) + hasAnyChild(withText(tokenName)) + useUnmergedTree = true + } + +} + +internal fun BaseTestCase.onSwapSelectTokenScreen(function: SwapSelectTokenPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt index 188c9ece2e..f31becbd3f 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt @@ -13,6 +13,10 @@ import androidx.compose.ui.test.hasTestTag as withTestTag class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : ComposeScreen(semanticsProvider = semanticsProvider) { + val container: KNode = child { + hasTestTag(SwapTokenScreenTestTags.CONTAINER) + } + val title: KNode = child { hasTestTag(TopAppBarTestTags.TITLE) hasText(getResourceString(R.string.common_swap)) @@ -78,6 +82,7 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) fun tokenSymbol(symbol: String): KNode = child { hasTestTag(SwapTokenScreenTestTags.TOKEN_SYMBOL) + hasText(symbol) } } diff --git a/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/TokenDetailsScreenActionButtonsTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/TokenDetailsScreenActionButtonsTest.kt index d14e14e295..309a1e637c 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/TokenDetailsScreenActionButtonsTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/TokenDetailsScreenActionButtonsTest.kt @@ -3,6 +3,7 @@ package com.tangem.tests.actionButtons import com.tangem.common.BaseTestCase import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT import com.tangem.common.extensions.SwipeDirection +import com.tangem.common.extensions.assertIsDimmed import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.extensions.swipeVertical import com.tangem.common.utils.resetWireMockScenarioState @@ -60,8 +61,6 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() { @Test fun checkActionButtonsStateTest() { val tokenTitle = "Bitcoin" - val actionButtonIsNotDimmed = "Action button is not dimmed" - val actionButtonIsDimmed = "Action button is dimmed" setupHooks().run { step("Open 'Main Screen'") { @@ -75,19 +74,19 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() { onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() } } step("Assert 'Receive' button is not dimmed") { - onTokenDetailsScreen { receiveButton().assertContentDescriptionEquals(actionButtonIsNotDimmed) } + onTokenDetailsScreen { receiveButton().assertIsDimmed(false) } } step("Assert 'Buy' button is not dimmed") { - onTokenDetailsScreen { buyButton().assertContentDescriptionEquals(actionButtonIsNotDimmed) } + onTokenDetailsScreen { buyButton().assertIsDimmed(false) } } step("Assert 'Send' button is not dimmed") { - onTokenDetailsScreen { sendButton().assertContentDescriptionEquals(actionButtonIsNotDimmed) } + onTokenDetailsScreen { sendButton().assertIsDimmed(false) } } step("Assert 'Swap' button is dimmed") { - onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) } + onTokenDetailsScreen { swapButton().assertIsDimmed() } } step("Assert 'Sell' button is dimmed") { - onTokenDetailsScreen { sellButton().assertContentDescriptionEquals(actionButtonIsDimmed) } + onTokenDetailsScreen { sellButton().assertIsDimmed() } } } } @@ -130,7 +129,6 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() { @Test fun checkSwapButtonProviderErrorTest() { val tokenTitle = "POL (ex-MATIC)" - val actionButtonIsDimmed = "Action button is dimmed" setupHooks().run { step("Open 'Main Screen'") { @@ -147,7 +145,7 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() { onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() } } step("Assert 'Swap' button is dimmed") { - onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) } + onTokenDetailsScreen { swapButton().assertIsDimmed() } } step("Click on 'Swap' button") { onTokenDetailsScreen { swapButton().performClick() } @@ -162,7 +160,7 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() { onSwapIsNotSupportedDialog { okButton.performClick() } } step("Assert 'Swap' button is dimmed") { - onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) } + onTokenDetailsScreen { swapButton().assertIsDimmed() } } } } @@ -172,7 +170,6 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() { @Test fun checkSwapButtonExpressErrorTest() { val tokenTitle = "Polygon" - val actionButtonIsDimmed = "Action button is dimmed" val scenarioName = "express_api_assets" val scenarioState = "Error" @@ -198,7 +195,7 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() { onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() } } step("Assert 'Swap' button is dimmed") { - onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) } + onTokenDetailsScreen { swapButton().assertIsDimmed() } } step("Click on 'Swap' button") { onTokenDetailsScreen { swapButton().performClick() } @@ -213,7 +210,7 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() { onOperationIsUnavailableDialog { okButton.performClick() } } step("Assert 'Swap' button is dimmed") { - onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) } + onTokenDetailsScreen { swapButton().assertIsDimmed() } } } } diff --git a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapSelectTokenScreenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapSelectTokenScreenTest.kt new file mode 100644 index 0000000000..e474212036 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapSelectTokenScreenTest.kt @@ -0,0 +1,72 @@ +package com.tangem.tests.swap + +import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.clickWithAssertion +import com.tangem.scenarios.openMainScreen +import com.tangem.scenarios.synchronizeAddresses +import com.tangem.screens.onMainScreen +import com.tangem.screens.onSwapSelectTokenScreen +import com.tangem.screens.onSwapStoriesScreen +import com.tangem.screens.onSwapTokenScreen +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 SwapSelectTokenScreenTest : BaseTestCase() { + + @AllureId("2829") + @DisplayName("Open 'Swap select token' screen from 'Main' screen") + @Test + fun openSwapSelectTokenScreenFromMainScreenTest() { + val swapTokenName = "Ethereum" + val receiveTokenName = "Polygon" + + setupHooks().run { + + step("Open 'Main Screen'") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses() + } + step("Click on 'Swap' button") { + onMainScreen { swapButton.performClick() } + } + step("Close 'Stories' screen") { + onSwapStoriesScreen { closeButton.clickWithAssertion() } + } + step("Assert 'Swap select token' screen title is displayed") { + onSwapSelectTokenScreen { title.assertIsDisplayed() } + } + step("Assert 'You swap' title is displayed") { + onSwapSelectTokenScreen { youSwapTitle.assertIsDisplayed() } + } + step("Assert 'You swap' block is displayed") { + onSwapSelectTokenScreen { youSwapBlock.assertIsDisplayed() } + } + step("Assert search icon is displayed") { + onSwapSelectTokenScreen { searchBarIcon.assertIsDisplayed() } + } + step("Assert search placeholder is displayed") { + onSwapSelectTokenScreen { searchBarPlaceholderText.assertIsDisplayed() } + } + step("Click on token with name '$swapTokenName'") { + onSwapSelectTokenScreen { tokenWithName(swapTokenName).performClick() } + } + step("Assert 'You receive' title is displayed") { + onSwapSelectTokenScreen { youReceiveTitle.assertIsDisplayed() } + } + step("Assert 'You receive' block is displayed") { + onSwapSelectTokenScreen { youReceiveBlock.assertIsDisplayed() } + } + step("Click on token with name '$receiveTokenName'") { + onSwapSelectTokenScreen { tokenWithName(receiveTokenName).performClick() } + } + step("Assert 'Swap token' screen is opened") { + onSwapTokenScreen { container.assertIsDisplayed() } + } + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapStoriesTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapStoriesTest.kt new file mode 100644 index 0000000000..ccbca2b78c --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapStoriesTest.kt @@ -0,0 +1,141 @@ +package com.tangem.tests.swap + +import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.assertHasBadge +import com.tangem.common.extensions.clickWithAssertion +import com.tangem.scenarios.openMainScreen +import com.tangem.scenarios.synchronizeAddresses +import com.tangem.screens.* +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 SwapStoriesTest : BaseTestCase() { + + @AllureId("5453") + @DisplayName("Check 'Swap' button badge on 'Main' screen") + @Test + fun checkMainScreenSwapButtonBadgeTest() { + + setupHooks().run { + + step("Open 'Main Screen'") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses() + } + step("Assert 'Swap' button has badge") { + onMainScreen { swapButton.assertHasBadge() } + } + step("Click on 'Swap' button") { + onMainScreen { swapButton.performClick() } + } + step("Close 'Stories' screen") { + onSwapStoriesScreen { closeButton.clickWithAssertion() } + } + step("Assert 'Swap' screen title is displayed") { + onSwapTokenScreen { title.assertIsDisplayed() } + } + step("Click on 'Close' button") { + onSwapTokenScreen { closeButton.performClick() } + } + step("Assert 'Swap' button has not badge") { + onMainScreen { swapButton.assertHasBadge(false) } + } + } + } + + @AllureId("5454") + @DisplayName("Check 'Swap' button badge on token details screen") + @Test + fun checkTokenDetailsScreenSwapButtonTest() { + val tokenName = "Ethereum" + + setupHooks().run { + + step("Open 'Main Screen'") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses() + } + step("Click on token with name: '$tokenName'") { + onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() } + } + step("Assert 'Swap' button has badge") { + onTokenDetailsScreen { swapButton().assertHasBadge() } + } + step("Click on 'Swap' button") { + onTokenDetailsScreen { swapButton().performClick() } + } + step("Close 'Stories' screen") { + onSwapStoriesScreen { closeButton.clickWithAssertion() } + } + step("Assert 'Swap' screen title is displayed") { + onSwapTokenScreen { title.assertIsDisplayed() } + } + step("Click on 'Close' button") { + onSwapTokenScreen { closeButton.performClick() } + } + step("Assert 'Swap' button has not badge") { + onTokenDetailsScreen { swapButton().assertHasBadge(false) } + } + } + } + + @AllureId("5455") + @DisplayName("Check 'Swap' button badge on token details in 'Market' screen") + @Test + fun checkMarketTokenDetailsScreenSwapButtonTest() { + val tokenName = "Ethereum" + val badgeShown = "Badge shown" + val badgeHidden = "Badge hidden" + + setupHooks().run { + + step("Open 'Main Screen'") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses() + } + step("Open 'Markets' screen") { + onMainScreen { searchThroughMarketPlaceholder.performClick() } + waitForIdle() + } + step("") { + onMarketsScreen { searchThroughMarketPlaceholder.performClick() } + } + step("Click on $tokenName token") { + waitForIdle() + onMarketsScreen { tokenWithTitle(tokenName).clickWithAssertion() } + } + step("Click on $tokenName token") { + waitForIdle() + onMarketsTokenDetailsScreen { tokenWithTitle(tokenName).performClick() } + } + step("Assert 'Swap' button has badge") { + onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertIsDisplayed() } + onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertContentDescriptionEquals(badgeShown) } + } + step("Click on 'Swap' button") { + onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.performClick() } + } + step("Close 'Stories' screen") { + onSwapStoriesScreen { closeButton.clickWithAssertion() } + } + step("Assert 'Swap' screen title is displayed") { + onSwapTokenScreen { title.assertIsDisplayed() } + } + step("Click on 'Close' button") { + onSwapTokenScreen { closeButton.performClick() } + } + step("Assert 'Swap' button has not badge") { + onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertContentDescriptionEquals(badgeHidden) } + } + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/tests/SwapTokenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenTest.kt similarity index 80% rename from app/src/androidTest/kotlin/com/tangem/tests/SwapTokenTest.kt rename to app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenTest.kt index f57ae57734..d0f574aa0b 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/SwapTokenTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/swap/SwapTokenScreenTest.kt @@ -1,4 +1,4 @@ -package com.tangem.tests +package com.tangem.tests.swap import androidx.compose.ui.test.hasText import com.tangem.common.BaseTestCase @@ -19,7 +19,7 @@ import io.qameta.allure.kotlin.junit4.DisplayName import org.junit.Test @HiltAndroidTest -class SwapTokenTest : BaseTestCase() { +class SwapTokenScreenTest : BaseTestCase() { @ApiEnv( ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD) @@ -43,7 +43,7 @@ class SwapTokenTest : BaseTestCase() { step("Click on token with name: '$tokenTitle'") { onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() } } - step("Click on token with name: '$tokenTitle'") { + step("Assert title: '$tokenTitle' is displayed") { onTokenDetailsScreen { title.assertIsDisplayed() } } step("Click on 'Swap' button") { @@ -123,7 +123,7 @@ class SwapTokenTest : BaseTestCase() { step("Click on token with name: '$tokenTitle'") { onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() } } - step("Click on token with name: '$tokenTitle'") { + step("Assert title: '$tokenTitle' is displayed") { onTokenDetailsScreen { title.assertIsDisplayed() } } step("Turn off Wi-Fi and Mobile Data") { @@ -171,7 +171,7 @@ class SwapTokenTest : BaseTestCase() { step("Click on token with name: '$tokenTitle'") { onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() } } - step("Click on token with name: '$tokenTitle'") { + step("Assert title: '$tokenTitle' is displayed") { onTokenDetailsScreen { title.assertIsDisplayed() } } step("Click on 'Swap' button") { @@ -239,4 +239,55 @@ class SwapTokenTest : BaseTestCase() { } } } + + @AllureId("2828") + @DisplayName("Swap: network fee") + @Test + fun goToTokenSwapTest() { + val swapTokenSymbol = "POL" + val receiveTokenSymbol = "ETH" + val tokenTitle = "Polygon" + + setupHooks().run { + + resetWireMockScenarios() + step("Open 'Main Screen'") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses() + } + step("Click on token with name: '$tokenTitle'") { + onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() } + } + step("Assert title: '$tokenTitle' is displayed") { + onTokenDetailsScreen { title.assertIsDisplayed() } + } + step("Click on 'Swap' button") { + onTokenDetailsScreen { swapButton().performClick() } + } + step("Close 'Stories' screen") { + onSwapStoriesScreen { closeButton.clickWithAssertion() } + } + step("Assert 'Swap' screen title is displayed") { + onSwapTokenScreen { title.assertIsDisplayed() } + } + step("Assert 'Close' button is displayed") { + onSwapTokenScreen { closeButton.assertIsDisplayed() } + } + step("Assert 'Swap tokens on screen' button is displayed") { + onSwapTokenScreen { + flakySafely(WAIT_UNTIL_TIMEOUT) { + swapTokensOnscreenButton.assertIsDisplayed() + } + } + } + step("Assert token symbol: '$swapTokenSymbol' is displayed") { + onSwapTokenScreen { tokenSymbol(swapTokenSymbol).assertIsDisplayed() } + } + step("Assert token symbol: '$receiveTokenSymbol' is displayed") { + onSwapTokenScreen { tokenSymbol(receiveTokenSymbol).assertIsDisplayed() } + } + } + } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonTestSemantics.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonTestSemantics.kt new file mode 100644 index 0000000000..feb66508f5 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonTestSemantics.kt @@ -0,0 +1,10 @@ +package com.tangem.core.ui.components.buttons.actions + +import androidx.compose.ui.semantics.SemanticsPropertyKey +import androidx.compose.ui.semantics.SemanticsPropertyReceiver + +val IsDimmedKey = SemanticsPropertyKey("IsDimmed") +val HasBadgeKey = SemanticsPropertyKey("HasBadge") + +var SemanticsPropertyReceiver.isDimmed by IsDimmedKey +var SemanticsPropertyReceiver.hasBadge by HasBadgeKey \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt index ef86d84493..cbeee40848 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -136,11 +135,8 @@ fun ActionBaseButton( } .clip(shape) .semantics { - contentDescription = if (config.shouldDimContent) { - "Action button is dimmed" - } else { - "Action button is not dimmed" - } + isDimmed = config.shouldDimContent + hasBadge = config.shouldShowBadge } .combinedClickable( enabled = config.isEnabled, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SearchBar.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SearchBar.kt index 17ddadc6ff..2e031c6e58 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SearchBar.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SearchBar.kt @@ -38,6 +38,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.test.BaseSearchBarTestTags +import com.tangem.core.ui.test.SearchBarTestTags @Composable fun SearchBar( @@ -126,7 +127,9 @@ private fun DecorationBox( contentPadding = PaddingValues(all = TangemTheme.dimens.spacing12), leadingIcon = { Icon( - modifier = Modifier.size(TangemTheme.dimens.size20), + modifier = Modifier + .size(TangemTheme.dimens.size20) + .testTag(SearchBarTestTags.ICON), painter = painterResource(id = R.drawable.ic_search_24), tint = TangemTheme.colors.icon.informative, contentDescription = null, @@ -137,6 +140,7 @@ private fun DecorationBox( state = state, focusManager = focusManager, keyboardController = keyboardController, + modifier = Modifier.testTag(SearchBarTestTags.CLEAR_BUTTON), ) }, placeholder = { @@ -146,6 +150,7 @@ private fun DecorationBox( style = TangemTheme.typography.body2, maxLines = 1, overflow = TextOverflow.Ellipsis, + modifier = Modifier.testTag(SearchBarTestTags.PLACEHOLDER_TEXT), ) }, ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/MarketTokenDetailsBottomSheetTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/MarketTokenDetailsBottomSheetTestTags.kt new file mode 100644 index 0000000000..0df0d21488 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/test/MarketTokenDetailsBottomSheetTestTags.kt @@ -0,0 +1,8 @@ +package com.tangem.core.ui.test + +object MarketTokenDetailsBottomSheetTestTags { + const val PORTFOLIO_QUICK_ACTION_BUTTON = "MARKET_TOKEN_DETAILS_BOTTOM_SHEET_PORTFOLIO_QUICK_ACTION_BUTTON" + const val PORTFOLIO_QUICK_ACTION_BUTTON_TITLE = "MARKET_TOKEN_DETAILS_BOTTOM_SHEET_PORTFOLIO_QUICK_ACTION_BUTTON_TITLE" + const val PORTFOLIO_QUICK_ACTION_BUTTON_ICON = "MARKET_TOKEN_DETAILS_BOTTOM_SHEET_PORTFOLIO_QUICK_ACTION_BUTTON_ICON" + const val PORTFOLIO_TOKEN_ITEM = "MARKET_TOKEN_DETAILS_BOTTOM_SHEET_PORTFOLIO_TOKEN_ITEM" +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/SearchBarTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/SearchBarTestTags.kt new file mode 100644 index 0000000000..2ce2f7a7dc --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/test/SearchBarTestTags.kt @@ -0,0 +1,7 @@ +package com.tangem.core.ui.test + +object SearchBarTestTags { + const val ICON = "SEARCH_BAR_ICON" + const val CLEAR_BUTTON = "SEARCH_BAR_CLEAR_BUTTON" + const val PLACEHOLDER_TEXT = "SEARCH_BAR_PLACEHOLDER_TEXT" +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/SwapSelectTokenScreenTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/SwapSelectTokenScreenTestTags.kt new file mode 100644 index 0000000000..9583009691 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/test/SwapSelectTokenScreenTestTags.kt @@ -0,0 +1,6 @@ +package com.tangem.core.ui.test + +object SwapSelectTokenScreenTestTags { + const val YOU_SWAP_BLOCK = "SWAP_SELECT_TOKEN_SCREEN_YOU_SWAP_BLOCK" + const val CHOOSE_TOKEN_TEXT = "SWAP_SELECT_TOKEN_SCREEN_CHOOSE_TOKEN_TEXT" +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/SwapTokenScreenTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/SwapTokenScreenTestTags.kt index c64a02bb85..ec8883b577 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/SwapTokenScreenTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/SwapTokenScreenTestTags.kt @@ -1,6 +1,7 @@ package com.tangem.core.ui.test object SwapTokenScreenTestTags { + const val CONTAINER = "SWAP_TOKEN_SCREEN_CONTAINER" const val SWAP_BLOCK_HEADER = "SWAP_TOKEN_SCREEN_SWAP_BLOCK" const val BALANCE = "SWAP_TOKEN_SCREEN_BALANCE" const val SWAP_TEXT_FIELD = "SWAP_TOKEN_SCREEN_SWAP_TEXT_FIELD" diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/PortfolioItem.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/PortfolioItem.kt index 9faba4479e..7d605e621d 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/PortfolioItem.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/PortfolioItem.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider @@ -17,6 +18,7 @@ import com.tangem.core.ui.haptic.TangemHapticEffect import com.tangem.core.ui.res.LocalHapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.test.MarketTokenDetailsBottomSheetTestTags import com.tangem.features.feed.components.market.details.portfolio.impl.ui.preview.PreviewMyPortfolioUMProvider import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.PortfolioTokenUM import com.tangem.features.feed.impl.R @@ -49,6 +51,7 @@ internal fun PortfolioItem(state: PortfolioTokenUM, lastInList: Boolean, modifie start = TangemTheme.dimens.spacing10, end = TangemTheme.dimens.spacing12, ), + modifier = Modifier.testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_TOKEN_ITEM), ) PortfolioQuickActions( diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/PortfolioQuickActions.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/PortfolioQuickActions.kt index ffed264514..2260f52c5d 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/PortfolioQuickActions.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/PortfolioQuickActions.kt @@ -1,45 +1,29 @@ package com.tangem.features.feed.components.market.details.portfolio.impl.ui import android.content.res.Configuration -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.* import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring -import androidx.compose.animation.expandVertically -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.scaleIn -import androidx.compose.animation.scaleOut -import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.Canvas import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.requiredSize -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp @@ -51,6 +35,7 @@ import com.tangem.core.ui.haptic.TangemHapticEffect import com.tangem.core.ui.res.LocalHapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.test.MarketTokenDetailsBottomSheetTestTags import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.QuickActionUM import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -151,7 +136,8 @@ private fun AnimatedVisibilityScope.QuickActionItem( onClick() }, ) - .padding(horizontal = TangemTheme.dimens.spacing14, vertical = TangemTheme.dimens.spacing4), + .padding(horizontal = TangemTheme.dimens.spacing14, vertical = TangemTheme.dimens.spacing4) + .testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_QUICK_ACTION_BUTTON), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18), ) { @@ -168,6 +154,7 @@ private fun AnimatedVisibilityScope.QuickActionItem( text = state.title.resolveReference(), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.primary1, + modifier = Modifier.testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_QUICK_ACTION_BUTTON_TITLE), ) Text( text = state.description.resolveReference(), @@ -192,6 +179,13 @@ private fun AnimatedVisibilityScope.QuickActionIcon(state: QuickActionUM) { shape = CircleShape, ) .size(TangemTheme.dimens.size32) + .semantics { + contentDescription = if (state is QuickActionUM.Exchange && state.shouldShowBadge) { + "Badge shown" + } else { + "Badge hidden" + } + } .drawWithContent { drawContent() if (state is QuickActionUM.Exchange && state.shouldShowBadge) { @@ -202,7 +196,8 @@ private fun AnimatedVisibilityScope.QuickActionIcon(state: QuickActionUM) { ) { Icon( modifier = Modifier - .requiredSize(TangemTheme.dimens.size16), + .requiredSize(TangemTheme.dimens.size16) + .testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_QUICK_ACTION_BUTTON_ICON), imageVector = ImageVector.vectorResource(id = state.icon), contentDescription = null, tint = TangemTheme.colors.button.primary, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt index 4ee9b3eb0e..da564d9d1b 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt @@ -15,6 +15,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -30,6 +31,7 @@ import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.test.SwapSelectTokenScreenTestTags import com.tangem.core.ui.utils.dashedBorder import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.swap.entity.ExchangeCardUM @@ -50,7 +52,8 @@ internal fun ExchangeCard(state: ExchangeCardUM, isBalanceHidden: Boolean, modif .fillMaxWidth() .heightIn(min = 116.dp) .clip(TangemTheme.shapes.roundedCornersXMedium) - .background(TangemTheme.colors.background.primary), + .background(TangemTheme.colors.background.primary) + .testTag(SwapSelectTokenScreenTestTags.YOU_SWAP_BLOCK), verticalArrangement = Arrangement.SpaceBetween, ) { Title( @@ -157,6 +160,7 @@ private fun EmptyTokenBlock(text: TextReference, modifier: Modifier = Modifier) maxLines = 1, overflow = TextOverflow.Ellipsis, style = TangemTheme.typography.body2, + modifier = Modifier.testTag(SwapSelectTokenScreenTestTags.CHOOSE_TOKEN_TEXT), ) } } diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt index 78ad44f4f9..0a5f77d83d 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt @@ -8,11 +8,13 @@ import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.testTag import com.tangem.common.ui.bottomsheet.permission.GiveTxPermissionBottomSheet import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.test.SwapTokenScreenTestTags import com.tangem.core.ui.utils.WindowInsetsZero import com.tangem.feature.swap.component.SwapFeeSelectorBlockComponent import com.tangem.feature.swap.models.SwapStateHolder @@ -50,7 +52,9 @@ internal fun SwapScreen(stateHolder: SwapStateHolder, feeSelectorBlockComponent: } else { null }, - modifier = Modifier.padding(scaffoldPaddings), + modifier = Modifier + .padding(scaffoldPaddings) + .testTag(SwapTokenScreenTestTags.CONTAINER), ) if (stateHolder.bottomSheetConfig != null) {