diff --git a/.gitignore b/.gitignore index 5469e50266..161d081d55 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,4 @@ private.properties # Test users credentials **/users.json +**/internal_api.json diff --git a/README.md b/README.md index 28a7087d1c..7b79b2269b 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ will show all the possible actions that are available. ## UI Tests UI tests are executed on firebase through the CI. Firebase test lab can be triggered also locally with `bundle exec fastlane uiTests` or tests can be run in a local emulator through android studio. -The `app/src/uiTest/assets/users.json` file will be needed for UI tests to work, its value can be found in confluence or in the CI env vars +The `app/src/uiTest/assets/users.json` and `app/src/uiTest/assets/internal_api.json` files will be needed for UI tests to work, their value can be found in confluence or in the CI env vars **Currently, only the ui tests that are included in the `SmokeSuite` class are run on firebase** diff --git a/app/src/main/kotlin/ch/protonmail/android/sidebar/Sidebar.kt b/app/src/main/kotlin/ch/protonmail/android/sidebar/Sidebar.kt index a5f86ed4c1..036c4be9e8 100644 --- a/app/src/main/kotlin/ch/protonmail/android/sidebar/Sidebar.kt +++ b/app/src/main/kotlin/ch/protonmail/android/sidebar/Sidebar.kt @@ -28,6 +28,7 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -63,6 +64,8 @@ import me.proton.core.domain.entity.UserId import me.proton.core.label.domain.entity.LabelId import timber.log.Timber +const val TEST_TAG_SIDEBAR_MENU = "SidebarMenuTestTag" + @Composable @Suppress("ComplexMethod") fun Sidebar( @@ -166,7 +169,7 @@ fun Sidebar( viewState: SidebarState ) { ProtonSidebarLazy( - modifier = modifier, + modifier = modifier.testTag(TEST_TAG_SIDEBAR_MENU), drawerState = viewState.drawerState, ) { item { diff --git a/app/src/uiTest/kotlin/ch/protonmail/android/uitest/BaseTest.kt b/app/src/uiTest/kotlin/ch/protonmail/android/uitest/BaseTest.kt index 5db237efa8..14f9368d79 100644 --- a/app/src/uiTest/kotlin/ch/protonmail/android/uitest/BaseTest.kt +++ b/app/src/uiTest/kotlin/ch/protonmail/android/uitest/BaseTest.kt @@ -18,13 +18,21 @@ package ch.protonmail.android.uitest +import android.app.Application import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.core.app.ApplicationProvider import ch.protonmail.android.MainActivity import ch.protonmail.android.di.AppDatabaseModule +import ch.protonmail.android.test.BuildConfig import kotlinx.coroutines.runBlocking +import me.proton.core.auth.presentation.testing.ProtonTestEntryPoint import me.proton.core.test.android.instrumented.ProtonTest.Companion.getTargetContext +import me.proton.core.test.android.instrumented.utils.Shell.setupDeviceForAutomation +import me.proton.core.test.android.plugins.Quark +import me.proton.core.test.android.plugins.data.User import me.proton.core.test.android.plugins.data.User.Users import org.junit.After +import org.junit.BeforeClass import org.junit.Rule import org.junit.rules.RuleChain import org.junit.rules.TestName @@ -43,17 +51,33 @@ open class BaseTest( .around(composeTestRule) @After - fun tearDown() { + fun cleanup() { if (clearAppDatabaseOnTearDown) { + Timber.d("Finishing Testing: Clearing all database tables") runBlocking { appDatabase.accountDao().deleteAll() } } - Timber.d("Finishing Testing: Clearing all database tables") + } + + fun login(user: User) { + Timber.d("Login user: ${user.name}") + authHelper.login(user.name, user.password) } companion object { val users = Users("users.json") + val quark = Quark(BuildConfig.HOST, BuildConfig.PROXY_TOKEN, "internal_api.json") val appDatabase = AppDatabaseModule.provideAppDatabase(getTargetContext()) + val authHelper = ProtonTestEntryPoint.provide( + ApplicationProvider.getApplicationContext() + ) + + @JvmStatic + @BeforeClass + fun prepare() { + setupDeviceForAutomation(true) + authHelper.logoutAll() + } } } diff --git a/app/src/uiTest/kotlin/ch/protonmail/android/uitest/robot/menu/MenuRobot.kt b/app/src/uiTest/kotlin/ch/protonmail/android/uitest/robot/menu/MenuRobot.kt index e17720b7ae..5aa7ca52e9 100644 --- a/app/src/uiTest/kotlin/ch/protonmail/android/uitest/robot/menu/MenuRobot.kt +++ b/app/src/uiTest/kotlin/ch/protonmail/android/uitest/robot/menu/MenuRobot.kt @@ -19,6 +19,18 @@ package ch.protonmail.android.uitest.robot.menu import androidx.annotation.IdRes +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.onChild +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performScrollToNode +import androidx.compose.ui.test.performTouchInput +import androidx.compose.ui.test.swipeRight +import ch.protonmail.android.sidebar.TEST_TAG_SIDEBAR_MENU import ch.protonmail.android.uitest.robot.contacts.ContactsRobot import ch.protonmail.android.uitest.robot.mailbox.archive.ArchiveRobot import ch.protonmail.android.uitest.robot.mailbox.drafts.DraftsRobot @@ -34,14 +46,30 @@ import ch.protonmail.android.uitest.robot.settings.SettingsRobot * [MenuRobot] class contains actions and verifications for menu functionality. */ @Suppress("unused", "TooManyFunctions", "ExpressionBodySyntax") -class MenuRobot { +class MenuRobot( + private val composeTestRule: ComposeContentTestRule? = null +) { fun archive(): ArchiveRobot { return ArchiveRobot() } fun settings(): SettingsRobot { - return SettingsRobot() + composeTestRule!! + .onRoot() + .performTouchInput { swipeRight() } + + composeTestRule + .onNodeWithTag(TEST_TAG_SIDEBAR_MENU) + .onChild() + .performScrollToNode(hasText("Settings")) + .assertIsDisplayed() + + composeTestRule + .onNodeWithText("Settings") + .performClick() + + return SettingsRobot(composeTestRule) } fun drafts(): DraftsRobot { diff --git a/app/src/uiTest/kotlin/ch/protonmail/android/uitest/robot/settings/SettingsRobot.kt b/app/src/uiTest/kotlin/ch/protonmail/android/uitest/robot/settings/SettingsRobot.kt index 1439ea71e5..0232334beb 100644 --- a/app/src/uiTest/kotlin/ch/protonmail/android/uitest/robot/settings/SettingsRobot.kt +++ b/app/src/uiTest/kotlin/ch/protonmail/android/uitest/robot/settings/SettingsRobot.kt @@ -18,6 +18,10 @@ */ package ch.protonmail.android.uitest.robot.settings +import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import ch.protonmail.android.mailsettings.presentation.settings.TEST_TAG_SETTINGS_SCREEN_ACCOUNT_ITEM import ch.protonmail.android.uitest.robot.mailbox.inbox.InboxRobot import ch.protonmail.android.uitest.robot.settings.account.AccountSettingsRobot import ch.protonmail.android.uitest.robot.settings.autolock.AutoLockRobot @@ -26,7 +30,9 @@ import ch.protonmail.android.uitest.robot.settings.autolock.AutoLockRobot * [SettingsRobot] class contains actions and verifications for Settings view. */ @Suppress("unused", "ExpressionBodySyntax") -class SettingsRobot { +class SettingsRobot( + private val composeTestRule: ComposeContentTestRule? = null +) { fun navigateUpToInbox(): InboxRobot { return InboxRobot() @@ -37,7 +43,10 @@ class SettingsRobot { } fun openUserAccountSettings(): AccountSettingsRobot { - return AccountSettingsRobot() + composeTestRule!! + .onNodeWithTag(TEST_TAG_SETTINGS_SCREEN_ACCOUNT_ITEM) + .performClick() + return AccountSettingsRobot(composeTestRule) } fun selectAutoLock(): AutoLockRobot { diff --git a/app/src/uiTest/kotlin/ch/protonmail/android/uitest/robot/settings/account/AccountSettingsRobot.kt b/app/src/uiTest/kotlin/ch/protonmail/android/uitest/robot/settings/account/AccountSettingsRobot.kt index ce63c51f95..c76f5bfb79 100644 --- a/app/src/uiTest/kotlin/ch/protonmail/android/uitest/robot/settings/account/AccountSettingsRobot.kt +++ b/app/src/uiTest/kotlin/ch/protonmail/android/uitest/robot/settings/account/AccountSettingsRobot.kt @@ -19,6 +19,11 @@ package ch.protonmail.android.uitest.robot.settings.account import androidx.annotation.IdRes +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.onAllNodesWithTag +import androidx.compose.ui.test.onNodeWithTag +import ch.protonmail.android.mailsettings.presentation.accountsettings.TEST_TAG_ACCOUNT_SETTINGS_SCREEN import ch.protonmail.android.uitest.robot.settings.SettingsRobot import ch.protonmail.android.uitest.robot.settings.account.labelsandfolders.LabelsAndFoldersRobot import ch.protonmail.android.uitest.robot.settings.account.privacy.PrivacySettingsRobot @@ -29,7 +34,9 @@ import ch.protonmail.android.uitest.robot.settings.account.swipinggestures.Swipi * Account settings functionality. */ @Suppress("unused", "ExpressionBodySyntax") -class AccountSettingsRobot { +class AccountSettingsRobot( + private val composeTestRule: ComposeContentTestRule? = null +) { fun subscription(): SubscriptionRobot { return SubscriptionRobot() @@ -67,8 +74,17 @@ class AccountSettingsRobot { */ class Verify { - @SuppressWarnings("EmptyFunctionBlock") - fun accountSettingsOpened() {} + fun accountSettingsOpened(composeRule: ComposeContentTestRule) { + composeRule.waitUntil(timeoutMillis = 5000) { + composeRule + .onAllNodesWithTag(TEST_TAG_ACCOUNT_SETTINGS_SCREEN) + .fetchSemanticsNodes(false) + .isNotEmpty() + } + composeRule + .onNodeWithTag(TEST_TAG_ACCOUNT_SETTINGS_SCREEN) + .assertIsDisplayed() + } } inline fun verify(block: Verify.() -> Unit) = Verify().apply(block) diff --git a/app/src/uiTest/kotlin/ch/protonmail/android/uitest/test/settings/SettingsTest.kt b/app/src/uiTest/kotlin/ch/protonmail/android/uitest/test/settings/SettingsTest.kt new file mode 100644 index 0000000000..c4b1a0612a --- /dev/null +++ b/app/src/uiTest/kotlin/ch/protonmail/android/uitest/test/settings/SettingsTest.kt @@ -0,0 +1,32 @@ +package ch.protonmail.android.uitest.test.settings + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import ch.protonmail.android.uitest.BaseTest +import ch.protonmail.android.uitest.annotation.SmokeTest +import ch.protonmail.android.uitest.robot.menu.MenuRobot +import org.junit.Before +import org.junit.Test +import org.junit.experimental.categories.Category +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SettingsTest : BaseTest(clearAppDatabaseOnTearDown = false) { + + private val user = quark.userCreate() + private val menuRobot = MenuRobot(composeTestRule) + + @Before + fun setUp() { + login(user) + } + + @Test + @Category(SmokeTest::class) + fun openAccountSettings() { + menuRobot + .settings() + .openUserAccountSettings() + .verify { accountSettingsOpened(composeTestRule) } + } + +} diff --git a/app/src/uiTest/kotlin/ch/protonmail/android/uitest/test/suite/SmokeSuite.kt b/app/src/uiTest/kotlin/ch/protonmail/android/uitest/test/suite/SmokeSuite.kt index 165eb01665..0d9d119889 100644 --- a/app/src/uiTest/kotlin/ch/protonmail/android/uitest/test/suite/SmokeSuite.kt +++ b/app/src/uiTest/kotlin/ch/protonmail/android/uitest/test/suite/SmokeSuite.kt @@ -2,6 +2,7 @@ package ch.protonmail.android.uitest.test.suite import ch.protonmail.android.uitest.annotation.SmokeTest import ch.protonmail.android.uitest.test.login.LoginTests +import ch.protonmail.android.uitest.test.settings.SettingsTest import org.junit.experimental.categories.Categories import org.junit.runner.RunWith import org.junit.runners.Suite @@ -9,6 +10,7 @@ import org.junit.runners.Suite @RunWith(Categories::class) @Categories.IncludeCategory(SmokeTest::class) @Suite.SuiteClasses( - LoginTests::class + LoginTests::class, + SettingsTest::class ) class SmokeSuite diff --git a/mail-settings/presentation/src/main/kotlin/ch/protonmail/android/mailsettings/presentation/settings/SettingsScreen.kt b/mail-settings/presentation/src/main/kotlin/ch/protonmail/android/mailsettings/presentation/settings/SettingsScreen.kt index 4b63aab53c..6a624623e6 100644 --- a/mail-settings/presentation/src/main/kotlin/ch/protonmail/android/mailsettings/presentation/settings/SettingsScreen.kt +++ b/mail-settings/presentation/src/main/kotlin/ch/protonmail/android/mailsettings/presentation/settings/SettingsScreen.kt @@ -40,6 +40,7 @@ import me.proton.core.compose.component.ProtonSettingsTopBar import me.proton.core.compose.flow.rememberAsState const val TEST_TAG_SETTINGS_SCREEN = "SettingsScreenTestTag" +const val TEST_TAG_SETTINGS_SCREEN_ACCOUNT_ITEM = "AccountSettingsItemTestTag" @Composable fun MainSettingsScreen( @@ -101,6 +102,7 @@ fun MainSettingsScreen( item { ProtonSettingsHeader(title = R.string.account_settings) } item { AccountSettingsItem( + modifier = Modifier.testTag(TEST_TAG_SETTINGS_SCREEN_ACCOUNT_ITEM), accountInfo = state.account, onAccountClicked = onAccountClick )