From 539457dcf81ca352b67f2c6c0d7dfd480da3b7eb Mon Sep 17 00:00:00 2001 From: francelu Date: Sat, 14 Dec 2024 16:14:19 +0100 Subject: [PATCH 01/11] test: e2e timer navigate to sign-up --- .../periodpals/endtoend/EndToEndTimer.kt | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt diff --git a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt new file mode 100644 index 000000000..4aa630127 --- /dev/null +++ b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt @@ -0,0 +1,64 @@ +package com.android.periodpals.endtoend + +import android.Manifest +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onAllNodesWithTag +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performScrollTo +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.rule.ActivityTestRule +import androidx.test.rule.GrantPermissionRule +import com.android.periodpals.MainActivity +import com.android.periodpals.resources.C.Tag.AuthenticationScreens.SignInScreen +import com.android.periodpals.resources.C.Tag.AuthenticationScreens.SignUpScreen +import com.kaspersky.kaspresso.testcases.api.testcase.TestCase +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +private const val TAG = "EndToEndTimer" +private const val TIMEOUT = 60000L // 60 seconds, adjust for slower devices, networks and CI + +@RunWith(AndroidJUnit4::class) +class EndToEndTimer : TestCase() { + @get:Rule val composeTestRule = createComposeRule() + @get:Rule val activityRule = ActivityTestRule(MainActivity::class.java) + @get:Rule + val permissionRule: GrantPermissionRule = + GrantPermissionRule.grant(Manifest.permission.POST_NOTIFICATIONS) + + companion object SignUpData { + private val signUpName = ('a'..'z').map { it }.shuffled().subList(0, 8).joinToString("") + private val signUpEmail = "$signUpName@example.com" + private const val PASSWORD = "iLoveSwent1234!" + private val randomNumber = (1..9).random() + private val dob = "0$randomNumber/0$randomNumber/200$randomNumber" + private val signUpDescription = "Short bio containing my name to identify me: $signUpName" + } + + @Before + fun setUp() { + composeTestRule.setContent { MainActivity() } + } + + @Test + fun timerEndToEnd() { + // Navigate to the sign-up screen + composeTestRule.waitForIdle() + composeTestRule.onNodeWithTag(SignInScreen.SCREEN).assertIsDisplayed() + composeTestRule + .onNodeWithTag(SignInScreen.NOT_REGISTERED_NAV_LINK) + .performScrollTo() + .assertIsDisplayed() + .performClick() + + // Register a new user + composeTestRule.waitForIdle() + assertEquals( + 1, composeTestRule.onAllNodesWithTag(SignUpScreen.SCREEN).fetchSemanticsNodes().size) + } +} From e108c4299592971bb5e8d37f0b3afabeca02d839 Mon Sep 17 00:00:00 2001 From: francelu Date: Sat, 14 Dec 2024 16:28:44 +0100 Subject: [PATCH 02/11] test: e2e timer use timeout --- .../com/android/periodpals/endtoend/EndToEndTimer.kt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt index 4aa630127..25e584294 100644 --- a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt +++ b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt @@ -14,7 +14,6 @@ import com.android.periodpals.MainActivity import com.android.periodpals.resources.C.Tag.AuthenticationScreens.SignInScreen import com.android.periodpals.resources.C.Tag.AuthenticationScreens.SignUpScreen import com.kaspersky.kaspresso.testcases.api.testcase.TestCase -import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test @@ -35,8 +34,7 @@ class EndToEndTimer : TestCase() { private val signUpName = ('a'..'z').map { it }.shuffled().subList(0, 8).joinToString("") private val signUpEmail = "$signUpName@example.com" private const val PASSWORD = "iLoveSwent1234!" - private val randomNumber = (1..9).random() - private val dob = "0$randomNumber/0$randomNumber/200$randomNumber" + private const val DOB = "31/01/2000" private val signUpDescription = "Short bio containing my name to identify me: $signUpName" } @@ -55,10 +53,8 @@ class EndToEndTimer : TestCase() { .performScrollTo() .assertIsDisplayed() .performClick() - - // Register a new user - composeTestRule.waitForIdle() - assertEquals( - 1, composeTestRule.onAllNodesWithTag(SignUpScreen.SCREEN).fetchSemanticsNodes().size) + composeTestRule.waitUntil(TIMEOUT) { + composeTestRule.onAllNodesWithTag(SignUpScreen.SCREEN).fetchSemanticsNodes().size == 1 + } } } From 9232715a07063c6346b77af25c9fa470fe1eb3c2 Mon Sep 17 00:00:00 2001 From: francelu Date: Sat, 14 Dec 2024 16:35:00 +0100 Subject: [PATCH 03/11] test: e2e timer register --- .../periodpals/endtoend/EndToEndTimer.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt index 25e584294..2131e3398 100644 --- a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt +++ b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt @@ -7,12 +7,15 @@ import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollTo +import androidx.compose.ui.test.performTextInput import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.rule.ActivityTestRule import androidx.test.rule.GrantPermissionRule import com.android.periodpals.MainActivity +import com.android.periodpals.resources.C.Tag.AuthenticationScreens import com.android.periodpals.resources.C.Tag.AuthenticationScreens.SignInScreen import com.android.periodpals.resources.C.Tag.AuthenticationScreens.SignUpScreen +import com.android.periodpals.resources.C.Tag.ProfileScreens.CreateProfileScreen import com.kaspersky.kaspresso.testcases.api.testcase.TestCase import org.junit.Before import org.junit.Rule @@ -56,5 +59,31 @@ class EndToEndTimer : TestCase() { composeTestRule.waitUntil(TIMEOUT) { composeTestRule.onAllNodesWithTag(SignUpScreen.SCREEN).fetchSemanticsNodes().size == 1 } + + // Register a new user + composeTestRule.waitForIdle() + composeTestRule + .onNodeWithTag(AuthenticationScreens.EMAIL_FIELD) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(signUpEmail) + composeTestRule + .onNodeWithTag(AuthenticationScreens.PASSWORD_FIELD) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(PASSWORD) + composeTestRule + .onNodeWithTag(SignUpScreen.CONFIRM_PASSWORD_FIELD) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(PASSWORD) + composeTestRule + .onNodeWithTag(SignUpScreen.SIGN_UP_BUTTON) + .performScrollTo() + .assertIsDisplayed() + .performClick() + composeTestRule.waitUntil(TIMEOUT) { + composeTestRule.onAllNodesWithTag(CreateProfileScreen.SCREEN).fetchSemanticsNodes().size == 1 + } } } From 980de68221914b35cfb4d24b87772546d6b4c40f Mon Sep 17 00:00:00 2001 From: francelu Date: Sat, 14 Dec 2024 16:54:17 +0100 Subject: [PATCH 04/11] test: e2e timer create profile --- .../periodpals/endtoend/EndToEndTimer.kt | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt index 2131e3398..53f7e1225 100644 --- a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt +++ b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt @@ -15,7 +15,9 @@ import com.android.periodpals.MainActivity import com.android.periodpals.resources.C.Tag.AuthenticationScreens import com.android.periodpals.resources.C.Tag.AuthenticationScreens.SignInScreen import com.android.periodpals.resources.C.Tag.AuthenticationScreens.SignUpScreen +import com.android.periodpals.resources.C.Tag.ProfileScreens import com.android.periodpals.resources.C.Tag.ProfileScreens.CreateProfileScreen +import com.android.periodpals.resources.C.Tag.ProfileScreens.ProfileScreen import com.kaspersky.kaspresso.testcases.api.testcase.TestCase import org.junit.Before import org.junit.Rule @@ -85,5 +87,31 @@ class EndToEndTimer : TestCase() { composeTestRule.waitUntil(TIMEOUT) { composeTestRule.onAllNodesWithTag(CreateProfileScreen.SCREEN).fetchSemanticsNodes().size == 1 } + + // Create a new profile + composeTestRule.waitForIdle() + composeTestRule + .onNodeWithTag(ProfileScreens.NAME_INPUT_FIELD) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(signUpName) + composeTestRule + .onNodeWithTag(ProfileScreens.DOB_INPUT_FIELD) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(DOB) + composeTestRule + .onNodeWithTag(ProfileScreens.DESCRIPTION_INPUT_FIELD) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(signUpDescription) + composeTestRule + .onNodeWithTag(ProfileScreens.SAVE_BUTTON) + .performScrollTo() + .assertIsDisplayed() + .performClick() + composeTestRule.waitUntil(TIMEOUT) { + composeTestRule.onAllNodesWithTag(ProfileScreen.SCREEN).fetchSemanticsNodes().size == 1 + } } } From 2a82b3707292018acae59273e9ffd168c95a77b3 Mon Sep 17 00:00:00 2001 From: francelu Date: Sat, 14 Dec 2024 17:08:31 +0100 Subject: [PATCH 05/11] test: e2e timer navigate to timer screen --- .../com/android/periodpals/endtoend/EndToEndTimer.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt index 53f7e1225..0e242bfa8 100644 --- a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt +++ b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt @@ -15,9 +15,11 @@ import com.android.periodpals.MainActivity import com.android.periodpals.resources.C.Tag.AuthenticationScreens import com.android.periodpals.resources.C.Tag.AuthenticationScreens.SignInScreen import com.android.periodpals.resources.C.Tag.AuthenticationScreens.SignUpScreen +import com.android.periodpals.resources.C.Tag.BottomNavigationMenu import com.android.periodpals.resources.C.Tag.ProfileScreens import com.android.periodpals.resources.C.Tag.ProfileScreens.CreateProfileScreen import com.android.periodpals.resources.C.Tag.ProfileScreens.ProfileScreen +import com.android.periodpals.resources.C.Tag.TimerScreen import com.kaspersky.kaspresso.testcases.api.testcase.TestCase import org.junit.Before import org.junit.Rule @@ -113,5 +115,15 @@ class EndToEndTimer : TestCase() { composeTestRule.waitUntil(TIMEOUT) { composeTestRule.onAllNodesWithTag(ProfileScreen.SCREEN).fetchSemanticsNodes().size == 1 } + + // Navigate to the timer screen + composeTestRule.waitForIdle() + composeTestRule + .onNodeWithTag(BottomNavigationMenu.BOTTOM_NAVIGATION_MENU_ITEM + "Timer") + .assertIsDisplayed() + .performClick() + composeTestRule.waitUntil(TIMEOUT) { + composeTestRule.onAllNodesWithTag(TimerScreen.SCREEN).fetchSemanticsNodes().size == 1 + } } } From 2dbb180b0526870302968536fb6e7acfa9286b1c Mon Sep 17 00:00:00 2001 From: francelu Date: Sat, 14 Dec 2024 17:17:22 +0100 Subject: [PATCH 06/11] test: e2e timer start then stop --- .../periodpals/endtoend/EndToEndTimer.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt index 0e242bfa8..e8682c102 100644 --- a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt +++ b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt @@ -1,6 +1,7 @@ package com.android.periodpals.endtoend import android.Manifest +import android.os.SystemClock import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onAllNodesWithTag @@ -12,6 +13,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.rule.ActivityTestRule import androidx.test.rule.GrantPermissionRule import com.android.periodpals.MainActivity +import com.android.periodpals.model.timer.HOUR +import com.android.periodpals.model.timer.MINUTE import com.android.periodpals.resources.C.Tag.AuthenticationScreens import com.android.periodpals.resources.C.Tag.AuthenticationScreens.SignInScreen import com.android.periodpals.resources.C.Tag.AuthenticationScreens.SignUpScreen @@ -125,5 +128,21 @@ class EndToEndTimer : TestCase() { composeTestRule.waitUntil(TIMEOUT) { composeTestRule.onAllNodesWithTag(TimerScreen.SCREEN).fetchSemanticsNodes().size == 1 } + + // Start the timer + composeTestRule.waitForIdle() + composeTestRule + .onNodeWithTag(TimerScreen.START_BUTTON) + .performScrollTo() + .assertIsDisplayed() + .performClick() + + // Stop the timer + SystemClock.setCurrentTimeMillis(System.currentTimeMillis() + 6L * HOUR + 30L * MINUTE) + composeTestRule + .onNodeWithTag(TimerScreen.STOP_BUTTON) + .performScrollTo() + .assertIsDisplayed() + .performClick() } } From b9af7aa709d501ce8c9416de3cf7ddd3b6178207 Mon Sep 17 00:00:00 2001 From: francelu Date: Sat, 14 Dec 2024 17:18:31 +0100 Subject: [PATCH 07/11] test: e2e timer start then reset --- .../android/periodpals/endtoend/EndToEndTimer.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt index e8682c102..62a8d8ebb 100644 --- a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt +++ b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt @@ -144,5 +144,19 @@ class EndToEndTimer : TestCase() { .performScrollTo() .assertIsDisplayed() .performClick() + + // Start the timer again + composeTestRule + .onNodeWithTag(TimerScreen.START_BUTTON) + .performScrollTo() + .assertIsDisplayed() + .performClick() + + // Reset the timer + composeTestRule + .onNodeWithTag(TimerScreen.RESET_BUTTON) + .performScrollTo() + .assertIsDisplayed() + .performClick() } } From fe87a2791935231227c297404b3858d8a96b25a9 Mon Sep 17 00:00:00 2001 From: francelu Date: Sat, 14 Dec 2024 17:39:16 +0100 Subject: [PATCH 08/11] docs: add documentation to e2e timer --- .../com/android/periodpals/endtoend/EndToEndTimer.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt index 62a8d8ebb..049baece9 100644 --- a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt +++ b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt @@ -53,6 +53,16 @@ class EndToEndTimer : TestCase() { composeTestRule.setContent { MainActivity() } } + /** + * End-to-end test for the + * [timer flow](https://www.figma.com/design/r6jgyWnwTQ6e5X1eLpeHwN/PeriodsPals?node-id=579-5989&node-type=canvas&m=dev). + * + * The "user" lands on the SignIn screen then navigates to the SignUp screen. They (correctly) + * fill in the fields and click on the "Sign Up" button and get redirected to the CreateProfile + * screen. They (correctly) fill in the fields and get redirected to the Profile screen that + * displays the info they just entered. They navigate to the Timer screen and start the timer. + * They stop the timer, start it again, and reset it. + */ @Test fun timerEndToEnd() { // Navigate to the sign-up screen From a8e40cf16e5ffa05b3ec4569978406da0b905c6d Mon Sep 17 00:00:00 2001 From: francelu Date: Sat, 14 Dec 2024 17:50:39 +0100 Subject: [PATCH 09/11] test: e2e timer add logs and profile screen checks --- .../periodpals/endtoend/EndToEndTimer.kt | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt index 049baece9..09b92a260 100644 --- a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt +++ b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt @@ -2,7 +2,9 @@ package com.android.periodpals.endtoend import android.Manifest import android.os.SystemClock +import android.util.Log import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onNodeWithTag @@ -67,6 +69,7 @@ class EndToEndTimer : TestCase() { fun timerEndToEnd() { // Navigate to the sign-up screen composeTestRule.waitForIdle() + Log.d(TAG, "User arrives on SignIn Screen") composeTestRule.onNodeWithTag(SignInScreen.SCREEN).assertIsDisplayed() composeTestRule .onNodeWithTag(SignInScreen.NOT_REGISTERED_NAV_LINK) @@ -79,6 +82,7 @@ class EndToEndTimer : TestCase() { // Register a new user composeTestRule.waitForIdle() + Log.d(TAG, "User arrives on SignUp Screen") composeTestRule .onNodeWithTag(AuthenticationScreens.EMAIL_FIELD) .performScrollTo() @@ -105,6 +109,7 @@ class EndToEndTimer : TestCase() { // Create a new profile composeTestRule.waitForIdle() + Log.d(TAG, "User arrives on Create Profile Screen") composeTestRule .onNodeWithTag(ProfileScreens.NAME_INPUT_FIELD) .performScrollTo() @@ -129,8 +134,31 @@ class EndToEndTimer : TestCase() { composeTestRule.onAllNodesWithTag(ProfileScreen.SCREEN).fetchSemanticsNodes().size == 1 } - // Navigate to the timer screen + // Check the profile screen composeTestRule.waitForIdle() + Log.d(TAG, "User arrives on Profile Screen") + composeTestRule + .onNodeWithTag(ProfileScreen.NAME_FIELD) + .performScrollTo() + .assertIsDisplayed() + .assertTextEquals(signUpName) + composeTestRule + .onNodeWithTag(ProfileScreen.DESCRIPTION_FIELD) + .performScrollTo() + .assertIsDisplayed() + .assertTextEquals(signUpDescription) + composeTestRule + .onNodeWithTag(ProfileScreen.NO_REVIEWS_CARD) + .performScrollTo() + .assertIsDisplayed() + composeTestRule + .onNodeWithTag(ProfileScreen.CONTRIBUTION_FIELD) + .performScrollTo() + .assertIsDisplayed() + .assertTextEquals("New user") + + // Navigate to the Timer screen + Log.d(TAG, "User navigates to Timer Screen") composeTestRule .onNodeWithTag(BottomNavigationMenu.BOTTOM_NAVIGATION_MENU_ITEM + "Timer") .assertIsDisplayed() @@ -141,6 +169,7 @@ class EndToEndTimer : TestCase() { // Start the timer composeTestRule.waitForIdle() + Log.d(TAG, "User starts the timer") composeTestRule .onNodeWithTag(TimerScreen.START_BUTTON) .performScrollTo() @@ -149,6 +178,7 @@ class EndToEndTimer : TestCase() { // Stop the timer SystemClock.setCurrentTimeMillis(System.currentTimeMillis() + 6L * HOUR + 30L * MINUTE) + Log.d(TAG, "User stops the timer") composeTestRule .onNodeWithTag(TimerScreen.STOP_BUTTON) .performScrollTo() @@ -156,6 +186,7 @@ class EndToEndTimer : TestCase() { .performClick() // Start the timer again + Log.d(TAG, "User starts the timer again") composeTestRule .onNodeWithTag(TimerScreen.START_BUTTON) .performScrollTo() @@ -163,6 +194,7 @@ class EndToEndTimer : TestCase() { .performClick() // Reset the timer + Log.d(TAG, "User resets the timer") composeTestRule .onNodeWithTag(TimerScreen.RESET_BUTTON) .performScrollTo() From d023ddfec124504bf733c0dcf6d800a3bc11b7b2 Mon Sep 17 00:00:00 2001 From: francelu Date: Fri, 20 Dec 2024 05:00:57 +0100 Subject: [PATCH 10/11] test: set up and delete in e2e timer --- .../periodpals/endtoend/EndToEndTimer.kt | 352 +++++++++++------- 1 file changed, 209 insertions(+), 143 deletions(-) diff --git a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt index 09b92a260..38e1835c0 100644 --- a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt +++ b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndTimer.kt @@ -5,59 +5,128 @@ import android.os.SystemClock import android.util.Log import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertTextEquals -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollTo import androidx.compose.ui.test.performTextInput import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.rule.ActivityTestRule import androidx.test.rule.GrantPermissionRule +import com.android.periodpals.BuildConfig import com.android.periodpals.MainActivity +import com.android.periodpals.model.authentication.AuthenticationModelSupabase +import com.android.periodpals.model.authentication.AuthenticationViewModel import com.android.periodpals.model.timer.HOUR import com.android.periodpals.model.timer.MINUTE +import com.android.periodpals.model.timer.SECOND +import com.android.periodpals.model.user.User +import com.android.periodpals.model.user.UserRepositorySupabase +import com.android.periodpals.model.user.UserViewModel import com.android.periodpals.resources.C.Tag.AuthenticationScreens import com.android.periodpals.resources.C.Tag.AuthenticationScreens.SignInScreen -import com.android.periodpals.resources.C.Tag.AuthenticationScreens.SignUpScreen import com.android.periodpals.resources.C.Tag.BottomNavigationMenu -import com.android.periodpals.resources.C.Tag.ProfileScreens -import com.android.periodpals.resources.C.Tag.ProfileScreens.CreateProfileScreen import com.android.periodpals.resources.C.Tag.ProfileScreens.ProfileScreen +import com.android.periodpals.resources.C.Tag.SettingsScreen import com.android.periodpals.resources.C.Tag.TimerScreen +import com.android.periodpals.resources.C.Tag.TopAppBar +import com.android.periodpals.ui.navigation.TopLevelDestinations.PROFILE +import com.android.periodpals.ui.navigation.TopLevelDestinations.TIMER import com.kaspersky.kaspresso.testcases.api.testcase.TestCase +import io.github.jan.supabase.SupabaseClient +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.createSupabaseClient +import io.github.jan.supabase.postgrest.Postgrest +import io.github.jan.supabase.storage.Storage +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith private const val TAG = "EndToEndTimer" -private const val TIMEOUT = 60000L // 60 seconds, adjust for slower devices, networks and CI +private const val TIMEOUT = 60_000L @RunWith(AndroidJUnit4::class) class EndToEndTimer : TestCase() { - @get:Rule val composeTestRule = createComposeRule() - @get:Rule val activityRule = ActivityTestRule(MainActivity::class.java) + @get:Rule val composeTestRule = createAndroidComposeRule() @get:Rule val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.POST_NOTIFICATIONS) + private lateinit var supabaseClient: SupabaseClient + private lateinit var authenticationViewModel: AuthenticationViewModel + private lateinit var userViewModel: UserViewModel - companion object SignUpData { - private val signUpName = ('a'..'z').map { it }.shuffled().subList(0, 8).joinToString("") - private val signUpEmail = "$signUpName@example.com" + companion object { + private val randomNumber = (0..999).random() + private val EMAIL = "e2e.timer.$randomNumber@test.ch" private const val PASSWORD = "iLoveSwent1234!" - private const val DOB = "31/01/2000" - private val signUpDescription = "Short bio containing my name to identify me: $signUpName" + private val name = "E2E Timer $randomNumber" + private const val IMAGE_URL = "" + private val description = "I am a test user $randomNumber for the timer end-to-end test" + private const val DOB = "31/01/1998" + private const val PREFERRED_DISTANCE = 500 + private val user = + User( + name = name, + imageUrl = IMAGE_URL, + description = description, + dob = DOB, + preferredDistance = PREFERRED_DISTANCE, + ) } + /** + * Set up the Supabase client and the authentication view model. Check if the user is already + * logged in and log them out if they are. Create a new account and its profile for the test. + */ @Before - fun setUp() { - composeTestRule.setContent { MainActivity() } + fun setUp() = runBlocking { + supabaseClient = + createSupabaseClient( + supabaseUrl = BuildConfig.SUPABASE_URL, + supabaseKey = BuildConfig.SUPABASE_KEY, + ) { + install(Auth) + install(Postgrest) + install(Storage) + } + val authenticationModel = AuthenticationModelSupabase(supabaseClient) + authenticationViewModel = AuthenticationViewModel(authenticationModel) + val userModel = UserRepositorySupabase(supabaseClient) + userViewModel = UserViewModel(userModel) + + authenticationViewModel.isUserLoggedIn( + onSuccess = { + Log.d(TAG, "User is already logged in") + authenticationViewModel.logOut( + onSuccess = { Log.d(TAG, "Successfully logged out previous user") }, + onFailure = { e: Exception -> Log.e(TAG, "Failed to log out previous user: $e") }, + ) + }, + onFailure = { e: Exception -> Log.e(TAG, "Failed to check if user is logged in: $e") }, + ) + + authenticationViewModel.signUpWithEmail( + EMAIL, + PASSWORD, + onSuccess = { + Log.d(TAG, "Successfully signed up with email and password") + userViewModel.saveUser( + user, + onSuccess = { + Log.d(TAG, "Successfully saved user") + authenticationViewModel.logOut() + }, + onFailure = { e: Exception -> Log.e(TAG, "Failed to save user: $e") }, + ) + }, + onFailure = { e: Exception -> Log.e(TAG, "Failed to sign up with email and password: $e") }, + ) } /** - * End-to-end test for the - * [timer flow](https://www.figma.com/design/r6jgyWnwTQ6e5X1eLpeHwN/PeriodsPals?node-id=579-5989&node-type=canvas&m=dev). + * End-to-end test for the timer flow. * * The "user" lands on the SignIn screen then navigates to the SignUp screen. They (correctly) * fill in the fields and click on the "Sign Up" button and get redirected to the CreateProfile @@ -66,139 +135,136 @@ class EndToEndTimer : TestCase() { * They stop the timer, start it again, and reset it. */ @Test - fun timerEndToEnd() { - // Navigate to the sign-up screen - composeTestRule.waitForIdle() - Log.d(TAG, "User arrives on SignIn Screen") - composeTestRule.onNodeWithTag(SignInScreen.SCREEN).assertIsDisplayed() - composeTestRule - .onNodeWithTag(SignInScreen.NOT_REGISTERED_NAV_LINK) - .performScrollTo() - .assertIsDisplayed() - .performClick() - composeTestRule.waitUntil(TIMEOUT) { - composeTestRule.onAllNodesWithTag(SignUpScreen.SCREEN).fetchSemanticsNodes().size == 1 + fun test() = run { + step("User signs in") { + composeTestRule.waitForIdle() + composeTestRule.onNodeWithTag(SignInScreen.SCREEN).assertIsDisplayed() + + composeTestRule + .onNodeWithTag(AuthenticationScreens.EMAIL_FIELD) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(EMAIL) + composeTestRule + .onNodeWithTag(AuthenticationScreens.PASSWORD_FIELD) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(PASSWORD) + composeTestRule + .onNodeWithTag(SignInScreen.SIGN_IN_BUTTON) + .performScrollTo() + .assertIsDisplayed() + .performClick() } - // Register a new user - composeTestRule.waitForIdle() - Log.d(TAG, "User arrives on SignUp Screen") - composeTestRule - .onNodeWithTag(AuthenticationScreens.EMAIL_FIELD) - .performScrollTo() - .assertIsDisplayed() - .performTextInput(signUpEmail) - composeTestRule - .onNodeWithTag(AuthenticationScreens.PASSWORD_FIELD) - .performScrollTo() - .assertIsDisplayed() - .performTextInput(PASSWORD) - composeTestRule - .onNodeWithTag(SignUpScreen.CONFIRM_PASSWORD_FIELD) - .performScrollTo() - .assertIsDisplayed() - .performTextInput(PASSWORD) - composeTestRule - .onNodeWithTag(SignUpScreen.SIGN_UP_BUTTON) - .performScrollTo() - .assertIsDisplayed() - .performClick() - composeTestRule.waitUntil(TIMEOUT) { - composeTestRule.onAllNodesWithTag(CreateProfileScreen.SCREEN).fetchSemanticsNodes().size == 1 + step("User arrives on Profile Screen and navigates to Edit Profile Screen") { + composeTestRule.waitForIdle() + composeTestRule.waitUntil(TIMEOUT) { + try { + composeTestRule + .onNodeWithTag(ProfileScreen.NAME_FIELD) + .performScrollTo() + .assertIsDisplayed() + .assertTextEquals(name) + true + } catch (e: AssertionError) { + false + } + } + + composeTestRule + .onNodeWithTag(ProfileScreen.DESCRIPTION_FIELD) + .performScrollTo() + .assertIsDisplayed() + .assertTextEquals(description) + + composeTestRule + .onNodeWithTag(BottomNavigationMenu.BOTTOM_NAVIGATION_MENU_ITEM + TIMER.textId) + .assertIsDisplayed() + .performClick() + composeTestRule.waitUntil(TIMEOUT) { + composeTestRule.onAllNodesWithTag(TimerScreen.SCREEN).fetchSemanticsNodes().size == 1 + } } - // Create a new profile - composeTestRule.waitForIdle() - Log.d(TAG, "User arrives on Create Profile Screen") - composeTestRule - .onNodeWithTag(ProfileScreens.NAME_INPUT_FIELD) - .performScrollTo() - .assertIsDisplayed() - .performTextInput(signUpName) - composeTestRule - .onNodeWithTag(ProfileScreens.DOB_INPUT_FIELD) - .performScrollTo() - .assertIsDisplayed() - .performTextInput(DOB) - composeTestRule - .onNodeWithTag(ProfileScreens.DESCRIPTION_INPUT_FIELD) - .performScrollTo() - .assertIsDisplayed() - .performTextInput(signUpDescription) - composeTestRule - .onNodeWithTag(ProfileScreens.SAVE_BUTTON) - .performScrollTo() - .assertIsDisplayed() - .performClick() - composeTestRule.waitUntil(TIMEOUT) { - composeTestRule.onAllNodesWithTag(ProfileScreen.SCREEN).fetchSemanticsNodes().size == 1 + step("User starts the timer, stops it, starts it again, and resets it") { + composeTestRule.waitForIdle() + composeTestRule + .onNodeWithTag(TimerScreen.START_BUTTON) + .performScrollTo() + .assertIsDisplayed() + .performClick() + SystemClock.setCurrentTimeMillis(System.currentTimeMillis() + 6L * HOUR + 30L * MINUTE) + composeTestRule + .onNodeWithTag(TimerScreen.STOP_BUTTON) + .performScrollTo() + .assertIsDisplayed() + .performClick() + + composeTestRule.waitForIdle() + composeTestRule + .onNodeWithTag(TimerScreen.START_BUTTON) + .performScrollTo() + .assertIsDisplayed() + .performClick() + SystemClock.setCurrentTimeMillis(System.currentTimeMillis() + 5 * SECOND) + composeTestRule + .onNodeWithTag(TimerScreen.RESET_BUTTON) + .performScrollTo() + .assertIsDisplayed() + .performClick() + } + + step("Navigate back to Profile Screen") { + composeTestRule.waitForIdle() + composeTestRule + .onNodeWithTag(BottomNavigationMenu.BOTTOM_NAVIGATION_MENU_ITEM + PROFILE.textId) + .assertIsDisplayed() + .performClick() + composeTestRule.waitUntil(TIMEOUT) { + try { + composeTestRule + .onNodeWithTag(ProfileScreen.NAME_FIELD) + .performScrollTo() + .assertIsDisplayed() + .assertTextEquals(name) + true + } catch (e: AssertionError) { + false + } + } } - // Check the profile screen - composeTestRule.waitForIdle() - Log.d(TAG, "User arrives on Profile Screen") - composeTestRule - .onNodeWithTag(ProfileScreen.NAME_FIELD) - .performScrollTo() - .assertIsDisplayed() - .assertTextEquals(signUpName) - composeTestRule - .onNodeWithTag(ProfileScreen.DESCRIPTION_FIELD) - .performScrollTo() - .assertIsDisplayed() - .assertTextEquals(signUpDescription) - composeTestRule - .onNodeWithTag(ProfileScreen.NO_REVIEWS_CARD) - .performScrollTo() - .assertIsDisplayed() - composeTestRule - .onNodeWithTag(ProfileScreen.CONTRIBUTION_FIELD) - .performScrollTo() - .assertIsDisplayed() - .assertTextEquals("New user") - - // Navigate to the Timer screen - Log.d(TAG, "User navigates to Timer Screen") - composeTestRule - .onNodeWithTag(BottomNavigationMenu.BOTTOM_NAVIGATION_MENU_ITEM + "Timer") - .assertIsDisplayed() - .performClick() - composeTestRule.waitUntil(TIMEOUT) { - composeTestRule.onAllNodesWithTag(TimerScreen.SCREEN).fetchSemanticsNodes().size == 1 + step("User navigates to Settings Screen to delete their account") { + composeTestRule.onNodeWithTag(TopAppBar.SETTINGS_BUTTON).assertIsDisplayed().performClick() + + composeTestRule.waitForIdle() + composeTestRule.waitUntil(TIMEOUT) { + try { + composeTestRule.onAllNodesWithTag(SettingsScreen.SCREEN).fetchSemanticsNodes().size == 1 + } catch (e: AssertionError) { + false + } + } + composeTestRule.onNodeWithTag(SettingsScreen.SCREEN).assertIsDisplayed() + + composeTestRule + .onNodeWithTag(SettingsScreen.DELETE_ACCOUNT_ICON) + .performScrollTo() + .assertIsDisplayed() + .performClick() + composeTestRule.onNodeWithTag(SettingsScreen.DELETE_BUTTON).assertIsDisplayed().performClick() } - // Start the timer - composeTestRule.waitForIdle() - Log.d(TAG, "User starts the timer") - composeTestRule - .onNodeWithTag(TimerScreen.START_BUTTON) - .performScrollTo() - .assertIsDisplayed() - .performClick() - - // Stop the timer - SystemClock.setCurrentTimeMillis(System.currentTimeMillis() + 6L * HOUR + 30L * MINUTE) - Log.d(TAG, "User stops the timer") - composeTestRule - .onNodeWithTag(TimerScreen.STOP_BUTTON) - .performScrollTo() - .assertIsDisplayed() - .performClick() - - // Start the timer again - Log.d(TAG, "User starts the timer again") - composeTestRule - .onNodeWithTag(TimerScreen.START_BUTTON) - .performScrollTo() - .assertIsDisplayed() - .performClick() - - // Reset the timer - Log.d(TAG, "User resets the timer") - composeTestRule - .onNodeWithTag(TimerScreen.RESET_BUTTON) - .performScrollTo() - .assertIsDisplayed() - .performClick() + step("User is lead back to the Sign In Screen") { + composeTestRule.waitForIdle() + composeTestRule.waitUntil(TIMEOUT) { + try { + composeTestRule.onAllNodesWithTag(SignInScreen.SCREEN).fetchSemanticsNodes().size == 1 + } catch (e: AssertionError) { + false + } + } + } } } From 13d67248c16014553ad713b2537ef82ff88592d5 Mon Sep 17 00:00:00 2001 From: francelu Date: Fri, 20 Dec 2024 05:25:57 +0100 Subject: [PATCH 11/11] test: add forgotten runBlocking in e2e sign-up --- .../java/com/android/periodpals/endtoend/EndToEndSignUp.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndSignUp.kt b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndSignUp.kt index caafff534..5138a1410 100644 --- a/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndSignUp.kt +++ b/app/src/androidTest/java/com/android/periodpals/endtoend/EndToEndSignUp.kt @@ -31,6 +31,7 @@ import io.github.jan.supabase.auth.Auth import io.github.jan.supabase.createSupabaseClient import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.storage.Storage +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test @@ -64,8 +65,7 @@ class EndToEndSignUp : TestCase() { * logged in and log them out if they are. Create a new account and its profile for the test. */ @Before - fun setUp() { - + fun setUp() = runBlocking { supabaseClient = createSupabaseClient( supabaseUrl = BuildConfig.SUPABASE_URL,