diff --git a/apps/parent/src/androidTest/assets/test_video.mp4 b/apps/parent/src/androidTest/assets/test_video.mp4 new file mode 100644 index 0000000000..91352b4b07 Binary files /dev/null and b/apps/parent/src/androidTest/assets/test_video.mp4 differ diff --git a/apps/parent/src/androidTest/java/com/instructure/parentapp/ui/e2e/compose/InboxE2ETest.kt b/apps/parent/src/androidTest/java/com/instructure/parentapp/ui/e2e/compose/InboxE2ETest.kt index cb486a2e8e..a3861bc3b4 100644 --- a/apps/parent/src/androidTest/java/com/instructure/parentapp/ui/e2e/compose/InboxE2ETest.kt +++ b/apps/parent/src/androidTest/java/com/instructure/parentapp/ui/e2e/compose/InboxE2ETest.kt @@ -14,12 +14,17 @@ * along with this program. If not, see . * */ + package com.instructure.parentapp.ui.e2e.compose import android.os.SystemClock.sleep import android.util.Log import androidx.test.espresso.Espresso +import androidx.test.espresso.intent.Intents import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiSelector import com.instructure.canvas.espresso.FeatureCategory import com.instructure.canvas.espresso.Priority import com.instructure.canvas.espresso.TestCategory @@ -42,6 +47,7 @@ import com.instructure.parentapp.utils.extensions.seedData import com.instructure.parentapp.utils.extensions.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +import java.io.File import java.util.Date @HiltAndroidTest @@ -619,4 +625,217 @@ class InboxE2ETest: ParentComposeTest() { inboxPage.openConversation(expectedSubjectEvent) inboxDetailsPage.assertMessageDisplayed(expectedMessage) } + + @E2E + @Test + @TestMetaData(Priority.IMPORTANT, FeatureCategory.INBOX, TestCategory.E2E) + fun testInboxMessageReplyWithVideoAttachmentE2E() { + + Log.d(PREPARATION_TAG, "Seeding data.") + val data = seedData(students = 1, teachers = 1, parents = 1, courses = 1) + val parent = data.parentsList[0] + val teacher = data.teachersList[0] + val course = data.coursesList[0] + + Log.d(PREPARATION_TAG, "Copy mp4 file to Downloads folder for attachment.") + val videoFileName = "test_video.mp4" + setupFileOnDevice(videoFileName) + File(InstrumentationRegistry.getInstrumentation().targetContext.cacheDir, "file_upload").deleteRecursively() + + val conversationSubject = "Need Document Help" + val conversationBody = "Can you please send me the course document?" + Log.d(PREPARATION_TAG, "Create a conversation from '${teacher.name}' to '${parent.name}'.") + val seededConversation = ConversationsApi.createConversationForCourse(token = teacher.token, courseId = course.id, recipients = listOf(parent.id.toString()), subject = conversationSubject, body = conversationBody)[0] + + Log.d(STEP_TAG, "Login with user: '${parent.name}', login id: '${parent.loginId}'.") + tokenLogin(parent) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Open the Left Side Navigation Drawer menu.") + dashboardPage.openLeftSideMenu() + + Log.d(STEP_TAG, "Open 'Inbox' menu.") + leftSideNavigationDrawerPage.clickInbox() + + Log.d(ASSERTION_TAG, "Assert that the conversation is displayed.") + inboxPage.assertConversationDisplayed(seededConversation.subject) + + Log.d(STEP_TAG, "Open the conversation.") + inboxPage.openConversation(seededConversation.subject) + + Log.d(ASSERTION_TAG, "Assert that the '${conversationSubject}' and '${conversationBody}' are displayed.") + inboxDetailsPage.assertConversationSubject(conversationSubject) + inboxDetailsPage.assertMessageDisplayed(conversationBody) + + Log.d(STEP_TAG, "Click Reply button to respond to the conversation.") + inboxDetailsPage.pressOverflowMenuItemForConversation("Reply") + + val replyMessage = "Sure! Here is the document." + Log.d(STEP_TAG, "Type reply message: '$replyMessage'") + inboxComposeMessagePage.typeBody(replyMessage) + + Log.d(ASSERTION_TAG, "Assert that send button is enabled after typing message.") + inboxComposeMessagePage.assertIfSendButtonState(true) + + Log.d(STEP_TAG, "Click attachment button to open file picker dialog.") + inboxComposeMessagePage.clickAttachmentButton() + + Log.d(PREPARATION_TAG, "Simulate file picker intent (again).") + Intents.init() + try { + stubFilePickerIntent(videoFileName) + fileChooserPage.chooseDevice() + } + finally { + Intents.release() + } + + Log.d(STEP_TAG, "Click OKAY button to confirm file selection.") + fileChooserPage.clickOkay() + + Log.d(ASSERTION_TAG, "Assert that the video file is displayed as attached in the screen.") + inboxComposeMessagePage.assertAttachmentDisplayed(videoFileName) + + Log.d(STEP_TAG, "Send the reply message with attachment.") + sleep(2000) //Wait for attachment to finish uploading + inboxComposeMessagePage.pressSendButton() + + Log.d(ASSERTION_TAG, "Assert that the reply message, attachment, and original message are displayed in the conversation.") + inboxDetailsPage.assertMessageDisplayed(replyMessage) + inboxDetailsPage.assertAttachmentDisplayed(videoFileName) + inboxDetailsPage.assertMessageDisplayed(conversationBody) + + Log.d(STEP_TAG, "Click on the video attachment to download it.") + inboxDetailsPage.clickAttachment(videoFileName) + + Log.d(STEP_TAG, "Wait for download to complete.") + sleep(5000) + + Log.d(STEP_TAG, "Open the Notification bar.") + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + device.openNotification() + + retryWithIncreasingDelay(times = 10, maxDelay = 3000) { + Log.d(STEP_TAG, "Find download notification.") + val downloadNotification = device.findObject(UiSelector().textContains(videoFileName).className("android.widget.TextView")) + + Log.d(ASSERTION_TAG, "Assert that 'Download complete' text is displayed in notification.") + val downloadCompleteText = device.findObject(UiSelector().textContains("Download complete")) + assert(downloadCompleteText.exists()) { "Download complete text not found in notification" } + + Log.d(ASSERTION_TAG, "Assert that file name '$videoFileName' is displayed in notification.") + assert(downloadNotification.exists()) { "File name '$videoFileName' not found in notification" } + } + + Log.d(STEP_TAG, "Close notification shade.") + device.pressBack() + + Log.d(STEP_TAG, "Assert that the '${conversationSubject}' is displayed.") + inboxDetailsPage.assertConversationSubject(conversationSubject) + + Log.d(STEP_TAG, "Navigate back to inbox.") + Espresso.pressBack() + + Log.d(ASSERTION_TAG, "Assert that the conversation is still displayed in inbox.") + inboxPage.assertConversationDisplayed(seededConversation.subject) + } + + @E2E + @Test + @TestMetaData(Priority.IMPORTANT, FeatureCategory.INBOX, TestCategory.E2E) + fun testInboxMessageForwardE2E() { + + Log.d(PREPARATION_TAG, "Seeding data.") + val data = seedData(students = 1, teachers = 1, parents = 2, courses = 1) + val parent1 = data.parentsList[0] + val parent2 = data.parentsList[1] + val teacher = data.teachersList[0] + val course = data.coursesList[0] + + val conversationSubject = "Important Announcement" + val conversationBody = "Please review this." + Log.d(PREPARATION_TAG, "Create a conversation from '${teacher.name}' to '${parent1.name}'.") + val seededConversation = ConversationsApi.createConversationForCourse(token = teacher.token, courseId = course.id, recipients = listOf(parent1.id.toString()), subject = conversationSubject, body = conversationBody)[0] + + Log.d(STEP_TAG, "Login with user: '${parent1.name}', login id: '${parent1.loginId}'.") + tokenLogin(parent1) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Open the Left Side Navigation Drawer menu.") + dashboardPage.openLeftSideMenu() + + Log.d(STEP_TAG, "Open 'Inbox' menu.") + leftSideNavigationDrawerPage.clickInbox() + + Log.d(ASSERTION_TAG, "Assert that the conversation is displayed.") + inboxPage.assertConversationDisplayed(seededConversation.subject) + + Log.d(STEP_TAG, "Open the conversation.") + inboxPage.openConversation(seededConversation.subject) + + Log.d(ASSERTION_TAG, "Assert that the '${conversationSubject}' and '${conversationBody}' are displayed.") + inboxDetailsPage.assertConversationSubject(conversationSubject) + inboxDetailsPage.assertMessageDisplayed(conversationBody) + + Log.d(STEP_TAG, "Click Forward button to forward the conversation to ${teacher.name}.") + inboxDetailsPage.pressOverflowMenuItemForConversation("Forward") + + val forwardMessage = "Hey, check this out." + Log.d(STEP_TAG, "Type forward message: '$forwardMessage'") + inboxComposeMessagePage.typeBody(forwardMessage) + + Log.d(STEP_TAG, "Select recipient for forwarded message.") + inboxComposeMessagePage.pressAddRecipient() + + Log.d(STEP_TAG, "Open 'Observers' category to verify only ${parent1.name} is visible and '${parent2.name}' is NOT displayed in Observers list.") + inboxRecipientPickerPage.pressLabel("Observers") + inboxRecipientPickerPage.assertRecipientDisplayed(parent1.shortName) + inboxRecipientPickerPage.assertRecipientNotDisplayed(parent2.shortName) + + Log.d(STEP_TAG, "Navigate back from Observers view.") + inboxRecipientPickerPage.pressBack() + + Log.d(STEP_TAG, "Select ${teacher.name} from Teachers category.") + inboxRecipientPickerPage.pressLabel("Teachers") + inboxRecipientPickerPage.pressLabel(teacher.shortName) + inboxRecipientPickerPage.pressDone() + + Log.d(ASSERTION_TAG, "Assert that send button is enabled after selecting recipient.") + inboxComposeMessagePage.assertIfSendButtonState(true) + + Log.d(STEP_TAG, "Send the forwarded message.") + inboxComposeMessagePage.pressSendButton() + + Log.d(ASSERTION_TAG, "Assert that the forward message is displayed in the conversation.") + inboxDetailsPage.assertMessageDisplayed(forwardMessage) + + Log.d(ASSERTION_TAG, "Assert that the original message is still displayed.") + inboxDetailsPage.assertMessageDisplayed(conversationBody) + + Log.d(STEP_TAG, "Navigate back to Inbox conversation list page.") + Espresso.pressBack() + + Log.d(ASSERTION_TAG, "Assert that the conversation is still displayed in inbox.") + inboxPage.assertConversationDisplayed(conversationSubject) + + Log.d(STEP_TAG, "Navigate back to Dashboard page and open the Left Side Navigation Drawer menu (to be able to log out).") + Espresso.pressBack() + dashboardPage.openLeftSideMenu() + + Log.d(STEP_TAG, "Log out from '${parent1.name}' account.") + leftSideNavigationDrawerPage.logout() + + Log.d(STEP_TAG, "Login with user: '${parent2.name}', login id: '${parent2.loginId}'.") + tokenLogin(parent2) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Open Inbox Page.") + dashboardPage.openLeftSideMenu() + leftSideNavigationDrawerPage.clickInbox() + + Log.d(ASSERTION_TAG, "Assert that the forwarded conversation is not displayed for '${parent2.name}' as they were not a recipient and we forwarded the message to ${teacher.name}.") + inboxPage.assertConversationNotDisplayed(conversationSubject) + inboxPage.assertInboxEmpty() + } + } diff --git a/apps/parent/src/androidTest/java/com/instructure/parentapp/ui/espresso/TestAppManager.kt b/apps/parent/src/androidTest/java/com/instructure/parentapp/ui/espresso/TestAppManager.kt index 7f637c07c3..a72ecfdb38 100644 --- a/apps/parent/src/androidTest/java/com/instructure/parentapp/ui/espresso/TestAppManager.kt +++ b/apps/parent/src/androidTest/java/com/instructure/parentapp/ui/espresso/TestAppManager.kt @@ -17,18 +17,21 @@ package com.instructure.parentapp.ui.espresso -import androidx.work.DefaultWorkerFactory +import androidx.work.Configuration import androidx.work.WorkerFactory +import com.instructure.canvas.espresso.WorkManagerTestAppManager +import com.instructure.canvas.espresso.WorkManagerTestHelper import com.instructure.pandautils.features.reminder.AlarmScheduler import com.instructure.parentapp.util.BaseAppManager -open class TestAppManager : BaseAppManager() { +open class TestAppManager : BaseAppManager(), WorkManagerTestAppManager { - private var workerFactory: WorkerFactory? = null + override val workManagerTestHelper = WorkManagerTestHelper() - override fun getWorkManagerFactory(): WorkerFactory { - return workerFactory ?: DefaultWorkerFactory - } + override val workManagerConfiguration: Configuration + get() = workManagerTestHelper.workManagerConfiguration + + override fun getWorkManagerFactory(): WorkerFactory = workManagerTestHelper.getWorkManagerFactory() override fun getScheduler(): AlarmScheduler? { return null diff --git a/apps/parent/src/androidTest/java/com/instructure/parentapp/utils/ParentTest.kt b/apps/parent/src/androidTest/java/com/instructure/parentapp/utils/ParentTest.kt index aa0a57dc0d..9a7cb5424c 100644 --- a/apps/parent/src/androidTest/java/com/instructure/parentapp/utils/ParentTest.kt +++ b/apps/parent/src/androidTest/java/com/instructure/parentapp/utils/ParentTest.kt @@ -17,15 +17,25 @@ package com.instructure.parentapp.utils +import android.app.Activity +import android.app.Instrumentation +import android.content.Intent +import android.net.Uri +import androidx.core.content.FileProvider +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.matcher.IntentMatchers +import androidx.test.platform.app.InstrumentationRegistry import com.instructure.canvas.espresso.CanvasTest import com.instructure.canvas.espresso.common.pages.AboutPage import com.instructure.canvas.espresso.common.pages.CanvasNetworkSignInPage +import com.instructure.canvas.espresso.common.pages.FileChooserPage import com.instructure.canvas.espresso.common.pages.InboxPage import com.instructure.canvas.espresso.common.pages.LegalPage import com.instructure.canvas.espresso.common.pages.LoginFindSchoolPage import com.instructure.canvas.espresso.common.pages.LoginLandingPage import com.instructure.canvas.espresso.common.pages.LoginSignInPage import com.instructure.canvas.espresso.common.pages.WrongDomainPage +import com.instructure.pandautils.utils.Const import com.instructure.parentapp.BuildConfig import com.instructure.parentapp.features.login.LoginActivity import com.instructure.parentapp.ui.pages.classic.DashboardPage @@ -33,6 +43,8 @@ import com.instructure.parentapp.ui.pages.classic.FrontPagePage import com.instructure.parentapp.ui.pages.classic.HelpPage import com.instructure.parentapp.ui.pages.classic.LeftSideNavigationDrawerPage import com.instructure.parentapp.ui.pages.compose.SyllabusPage +import org.hamcrest.core.AllOf +import java.io.File abstract class ParentTest : CanvasTest() { @@ -47,6 +59,7 @@ abstract class ParentTest : CanvasTest() { val helpPage = HelpPage() val syllabusPage = SyllabusPage() val frontPagePage = FrontPagePage() + val fileChooserPage = FileChooserPage() // Common pages (it's common for all apps) val loginLandingPage = LoginLandingPage() @@ -57,4 +70,40 @@ abstract class ParentTest : CanvasTest() { val inboxPage = InboxPage() val legalPage = LegalPage() val aboutPage = AboutPage() + + fun setupFileOnDevice(fileName: String): Uri { + File(InstrumentationRegistry.getInstrumentation().targetContext.cacheDir, "file_upload").deleteRecursively() + copyAssetFileToExternalCache(activityRule.activity, fileName) + + val dir = activityRule.activity.externalCacheDir + val file = File(dir?.path, fileName) + + val instrumentationContext = InstrumentationRegistry.getInstrumentation().context + return FileProvider.getUriForFile( + instrumentationContext, + "com.instructure.parentapp" + Const.FILE_PROVIDER_AUTHORITY, + file + ) + } + + fun stubFilePickerIntent(fileName: String) { + val resultData = Intent() + val dir = activityRule.activity.externalCacheDir + val file = File(dir?.path, fileName) + val newFileUri = FileProvider.getUriForFile( + activityRule.activity, + "com.instructure.parentapp" + Const.FILE_PROVIDER_AUTHORITY, + file + ) + resultData.data = newFileUri + resultData.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + + Intents.intending( + AllOf.allOf( + IntentMatchers.hasAction(Intent.ACTION_GET_CONTENT), + IntentMatchers.hasType("*/*"), + ) + ).respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, resultData)) + } + } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt index 03901eabe0..3392f6ca67 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt @@ -34,6 +34,7 @@ import com.instructure.canvas.espresso.CanvasTest import com.instructure.canvas.espresso.common.pages.AboutPage import com.instructure.canvas.espresso.common.pages.CanvasNetworkSignInPage import com.instructure.canvas.espresso.common.pages.EmailNotificationsPage +import com.instructure.canvas.espresso.common.pages.FileChooserPage import com.instructure.canvas.espresso.common.pages.InboxPage import com.instructure.canvas.espresso.common.pages.LegalPage import com.instructure.canvas.espresso.common.pages.LoginFindSchoolPage @@ -59,7 +60,6 @@ import com.instructure.student.ui.pages.classic.CourseGradesPage import com.instructure.student.ui.pages.classic.DashboardPage import com.instructure.student.ui.pages.classic.DiscussionDetailsPage import com.instructure.student.ui.pages.classic.DiscussionListPage -import com.instructure.student.ui.pages.classic.FileChooserPage import com.instructure.student.ui.pages.classic.FileListPage import com.instructure.student.ui.pages.classic.GoToQuizPage import com.instructure.student.ui.pages.classic.GradesPage diff --git a/apps/teacher/src/androidTest/assets/samplepdf.pdf b/apps/teacher/src/androidTest/assets/samplepdf.pdf new file mode 100644 index 0000000000..4022485fbe Binary files /dev/null and b/apps/teacher/src/androidTest/assets/samplepdf.pdf differ diff --git a/apps/teacher/src/androidTest/assets/test_video.mp4 b/apps/teacher/src/androidTest/assets/test_video.mp4 new file mode 100644 index 0000000000..91352b4b07 Binary files /dev/null and b/apps/teacher/src/androidTest/assets/test_video.mp4 differ diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/compose/InboxE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/compose/InboxE2ETest.kt index 9b4e3d99f5..cca208278e 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/compose/InboxE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/compose/InboxE2ETest.kt @@ -1,8 +1,32 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + package com.instructure.teacher.ui.e2e.compose +import android.os.Environment +import android.os.SystemClock.sleep import android.util.Log +import androidx.media3.ui.R import androidx.test.espresso.Espresso +import androidx.test.espresso.intent.Intents import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiSelector import com.instructure.canvas.espresso.FeatureCategory import com.instructure.canvas.espresso.Priority import com.instructure.canvas.espresso.TestCategory @@ -14,6 +38,7 @@ import com.instructure.dataseeding.api.ConversationsApi import com.instructure.dataseeding.api.GroupsApi import com.instructure.dataseeding.model.CanvasUserApiModel import com.instructure.dataseeding.model.CourseApiModel +import com.instructure.espresso.getVideoPosition import com.instructure.espresso.retry import com.instructure.espresso.retryWithIncreasingDelay import com.instructure.teacher.ui.utils.TeacherComposeTest @@ -21,6 +46,7 @@ import com.instructure.teacher.ui.utils.extensions.seedData import com.instructure.teacher.ui.utils.extensions.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +import java.io.File @HiltAndroidTest class InboxE2ETest : TeacherComposeTest() { @@ -710,4 +736,380 @@ class InboxE2ETest : TeacherComposeTest() { recipientPickerPage.pressDone() } + @E2E + @Test + @TestMetaData(Priority.IMPORTANT, FeatureCategory.INBOX, TestCategory.E2E) + fun testInboxMessageReplyWithVideoAttachmentE2E() { + + Log.d(PREPARATION_TAG, "Seeding data.") + val data = seedData(students = 1, teachers = 1, courses = 1) + val teacher = data.teachersList[0] + val course = data.coursesList[0] + val student = data.studentsList[0] + + Log.d(PREPARATION_TAG, "Copy mp4 file to Downloads folder for attachment.") + val videoFileName = "test_video.mp4" + setupFileOnDevice(videoFileName) + File(InstrumentationRegistry.getInstrumentation().targetContext.cacheDir, "file_upload").deleteRecursively() + + val conversationSubject = "Need Help with Assignment" + val conversationBody = "Can you please send me a demo video?" + Log.d(PREPARATION_TAG, "Create a conversation from '${student.name}' to '${teacher.name}'.") + val seededConversation = ConversationsApi.createConversationForCourse(token = student.token, courseId = course.id, recipients = listOf(teacher.id.toString()), subject = conversationSubject, body = conversationBody)[0] + + Log.d(STEP_TAG, "Login with user: '${teacher.name}', login id: '${teacher.loginId}'.") + tokenLogin(teacher) + dashboardPage.waitForRender() + + Log.d(ASSERTION_TAG, "Assert that the '${course.name}' course is displayed on the Dashboard Page.") + dashboardPage.assertDisplaysCourse(course) + + Log.d(STEP_TAG, "Open Inbox Page.") + dashboardPage.openInbox() + + Log.d(ASSERTION_TAG, "Assert that the conversation is displayed.") + inboxPage.assertConversationDisplayed(seededConversation.subject) + + Log.d(STEP_TAG, "Open the conversation.") + inboxPage.openConversation(seededConversation.subject) + + Log.d(ASSERTION_TAG, "Assert that the '${conversationSubject}' and '${conversationBody}' are displayed.") + inboxDetailsPage.assertConversationSubject(conversationSubject) + inboxDetailsPage.assertMessageDisplayed(conversationBody) + + Log.d(STEP_TAG, "Click Reply button to respond to the conversation.") + inboxDetailsPage.pressOverflowMenuItemForConversation("Reply") + + val replyMessage = "Sure! Here is the demo video." + Log.d(STEP_TAG, "Type reply message: '$replyMessage'") + inboxComposePage.typeBody(replyMessage) + + Log.d(ASSERTION_TAG, "Assert that send button is enabled after typing message.") + inboxComposePage.assertIfSendButtonState(true) + + Log.d(STEP_TAG, "Click attachment button to open file picker dialog.") + inboxComposePage.clickAttachmentButton() + + Log.d(PREPARATION_TAG, "Simulate file picker intent (again).") + Intents.init() + try { + stubFilePickerIntent(videoFileName) + fileChooserPage.chooseDevice() + } + finally { + Intents.release() + } + + Log.d(STEP_TAG, "Click OKAY button to confirm file selection.") + fileChooserPage.clickOkay() + + Log.d(ASSERTION_TAG, "Assert that the video file is displayed as attached in the screen.") + inboxComposePage.assertAttachmentDisplayed(videoFileName) + + Log.d(STEP_TAG, "Send the reply message with attachment.") + sleep(2000) //Wait for attachment to finish uploading + inboxComposePage.pressSendButton() + + Log.d(ASSERTION_TAG, "Assert that the reply message, attachment, and original message are displayed in the conversation.") + inboxDetailsPage.assertMessageDisplayed(replyMessage) + inboxDetailsPage.assertAttachmentDisplayed(videoFileName) + inboxDetailsPage.assertMessageDisplayed(conversationBody) + + Log.d(STEP_TAG, "Click on the attachment to verify it can be opened.") + inboxDetailsPage.clickAttachment(videoFileName) + + Log.d(ASSERTION_TAG, "Wait for video to load and assert that the media play button is visible.") + inboxDetailsPage.assertPlayButtonDisplayed() + + Log.d(STEP_TAG, "Click the play button to start the video and on the screen to show media controls.") + inboxDetailsPage.clickPlayButton() + inboxDetailsPage.clickScreenCenterToShowControls(device) + + Log.d(ASSERTION_TAG, "Assert that the play/pause button is visible in the media controls.") + inboxDetailsPage.assertPlayPauseButtonDisplayed() + + Log.d(STEP_TAG, "Click play/pause button to pause the video.") + inboxDetailsPage.clickPlayPauseButton() + + Log.d(STEP_TAG, "Get the current video position.") + val firstPositionText = getVideoPosition(R.id.exo_position) + + Log.d(STEP_TAG, "Click play/pause button to resume video playback, wait for video to play for 2 seconds then click play/pause button to pause again.") + inboxDetailsPage.clickPlayPauseButton() + sleep(2000) + inboxDetailsPage.clickPlayPauseButton() + + Log.d(STEP_TAG, "Get the video position again.") + val secondPositionText = getVideoPosition(R.id.exo_position) + + Log.d(ASSERTION_TAG, "Assert that the video position has changed, confirming video is playing.") + assert(firstPositionText != secondPositionText) { + "Video position did not change. First: $firstPositionText, Second: $secondPositionText" + } + + Log.d(STEP_TAG, "Navigate back to the conversation details.") + Espresso.pressBack() + + Log.d(ASSERTION_TAG, "Assert that the '${conversationSubject}' is displayed.") + inboxDetailsPage.assertConversationSubject(conversationSubject) + + Log.d(STEP_TAG, "Navigate back to inbox.") + Espresso.pressBack() + + Log.d(ASSERTION_TAG, "Assert that the conversation is still displayed in inbox.") + inboxPage.assertConversationDisplayed(seededConversation.subject) + } + + @E2E + @Test + @TestMetaData(Priority.IMPORTANT, FeatureCategory.INBOX, TestCategory.E2E) + fun testInboxMessageForwardWithPdfAttachmentE2E() { + + Log.d(PREPARATION_TAG, "Seeding data.") + val data = seedData(students = 2, teachers = 1, courses = 1) + val teacher = data.teachersList[0] + val course = data.coursesList[0] + val student1 = data.studentsList[0] + val student2 = data.studentsList[1] + + Log.d(PREPARATION_TAG, "Copy PDF file to device Downloads folder for attachment.") + val pdfFileName = "samplepdf.pdf" + val context = InstrumentationRegistry.getInstrumentation().context + val inputStream = context.assets.open(pdfFileName) + val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + val pdfFile = File(downloadsDir, pdfFileName) + + Log.d(PREPARATION_TAG, "Writing file to: ${pdfFile.absolutePath}") + pdfFile.outputStream().use { inputStream.copyTo(it) } + inputStream.close() + + val conversationSubject = "Project Documentation" + val conversationBody = "Please review the attached document and share it with the team." + Log.d(PREPARATION_TAG, "Create a conversation from '${student1.name}' to '${teacher.name}'.") + val seededConversation = ConversationsApi.createConversationForCourse(token = student1.token, courseId = course.id, recipients = listOf(teacher.id.toString()), subject = conversationSubject, body = conversationBody)[0] + + Log.d(STEP_TAG, "Login with user: '${teacher.name}', login id: '${teacher.loginId}'.") + tokenLogin(teacher) + dashboardPage.waitForRender() + + Log.d(ASSERTION_TAG, "Assert that the '${course.name}' course is displayed on the Dashboard Page.") + dashboardPage.assertDisplaysCourse(course) + + Log.d(STEP_TAG, "Open Inbox Page.") + dashboardPage.openInbox() + + Log.d(ASSERTION_TAG, "Assert that the conversation is displayed.") + inboxPage.assertConversationDisplayed(seededConversation.subject) + + Log.d(STEP_TAG, "Open the conversation.") + inboxPage.openConversation(seededConversation.subject) + + Log.d(ASSERTION_TAG, "Assert that the '${conversationSubject}' and '${conversationBody}' are displayed.") + inboxDetailsPage.assertConversationSubject(conversationSubject) + inboxDetailsPage.assertMessageDisplayed(conversationBody) + + Log.d(STEP_TAG, "Click Forward button to forward the conversation to ${student2.name}") + inboxDetailsPage.pressOverflowMenuItemForConversation("Forward") + + val forwardMessage = "please check this document." + Log.d(STEP_TAG, "Type forward message: '$forwardMessage'") + inboxComposePage.typeBody(forwardMessage) + + Log.d(STEP_TAG, "Select recipient for forwarded message.") + inboxComposePage.pressAddRecipient() + recipientPickerPage.pressLabel("Students") + recipientPickerPage.pressLabel(student2.shortName) + recipientPickerPage.pressDone() + + Log.d(ASSERTION_TAG, "Assert that send button is enabled after selecting recipient.") + inboxComposePage.assertIfSendButtonState(true) + + Log.d(STEP_TAG, "Click attachment button to open file picker dialog.") + inboxComposePage.clickAttachmentButton() + + Log.d(STEP_TAG, "Click on 'Device' option in file picker dialog.") + fileChooserPage.chooseDevice() + + Log.d(STEP_TAG, "Select the PDF file from Android file picker using UIAutomator.") + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + + val pdfFileObject = device.findObject(UiSelector().textContains(pdfFileName)) + if (pdfFileObject.exists()) { + Log.d(STEP_TAG, "Found PDF file with exact name, clicking...") + pdfFileObject.click() + } else { + Log.d(STEP_TAG, "PDF file not immediately visible, trying to navigate to Downloads...") + val showRootsButton = device.findObject(UiSelector().descriptionContains("Show roots")) + if (showRootsButton.exists()) { + showRootsButton.click() + } + + val downloadsItem = device.findObject(UiSelector().textContains("Downloads")) + if (downloadsItem.exists()) { + downloadsItem.click() + } + + val pdfFileObject2 = device.findObject(UiSelector().textContains(pdfFileName)) + if (pdfFileObject2.exists()) { + pdfFileObject2.click() + } + } + + Log.d(STEP_TAG, "Click OKAY button to confirm file selection.") + fileChooserPage.clickOkay() + + Log.d(ASSERTION_TAG, "Assert that the PDF file is displayed as attached in the screen.") + inboxComposePage.assertAttachmentDisplayed(pdfFileName) + + Log.d(STEP_TAG, "Send the forwarded message with attachment.") + sleep(2000) //Wait for attachment to finish uploading + inboxComposePage.pressSendButton() + + Log.d(ASSERTION_TAG, "Assert that the forward message is displayed in the conversation.") + inboxDetailsPage.assertMessageDisplayed(forwardMessage) + + Log.d(ASSERTION_TAG, "Assert that the PDF attachment is displayed in the message.") + inboxDetailsPage.assertAttachmentDisplayed(pdfFileName) + + Log.d(ASSERTION_TAG, "Assert that the original message is still displayed.") + inboxDetailsPage.assertMessageDisplayed(conversationBody) + + Log.d(STEP_TAG, "Click on the PDF attachment to verify it can be opened.") + inboxDetailsPage.clickAttachment(pdfFileName) + + Log.d(ASSERTION_TAG, "Assert that the PDF document view is displayed.") + inboxDetailsPage.assertPdfDocumentViewDisplayed() + + Log.d(STEP_TAG, "Navigate back to conversation details and assert that the '${conversationSubject}' is displayed.") + Espresso.pressBack() + inboxDetailsPage.assertConversationSubject(conversationSubject) + + Log.d(STEP_TAG, "Navigate back to Inbox conversation list page.") + Espresso.pressBack() + + Log.d(ASSERTION_TAG, "Assert that the conversation is still displayed in inbox.") + inboxPage.assertConversationDisplayed(conversationSubject) + + Log.d(STEP_TAG, "Log out with '${teacher.name}' teacher.") + leftSideNavigationDrawerPage.logout() + + Log.d(STEP_TAG, "Login with user: '${student2.name}', login id: '${student2.loginId}'.") + tokenLogin(student2) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Open Inbox Page.") + dashboardPage.openInbox() + + Log.d(ASSERTION_TAG, "Assert that the forwarded conversation is displayed in ${student2.name}'s inbox.") + inboxPage.assertConversationDisplayed(conversationSubject) + + Log.d(STEP_TAG, "Open the forwarded conversation.") + inboxPage.openConversation(conversationSubject) + + Log.d(ASSERTION_TAG, "Assert that the '${conversationSubject}' and '${conversationBody}' are displayed.") + inboxDetailsPage.assertConversationSubject(conversationSubject) + inboxDetailsPage.assertMessageDisplayed(conversationBody) + + Log.d(ASSERTION_TAG, "Assert that the forwarded message from ${teacher.name} is displayed.") + inboxDetailsPage.assertMessageDisplayed(forwardMessage) + + Log.d(ASSERTION_TAG, "Assert that the PDF attachment is displayed to ${student2.name}.") + inboxDetailsPage.assertAttachmentDisplayed(pdfFileName) + } + + @E2E + @Test + @TestMetaData(Priority.IMPORTANT, FeatureCategory.INBOX, TestCategory.E2E) + fun testInboxMessageForwardE2E() { + + Log.d(PREPARATION_TAG, "Seeding data.") + val data = seedData(students = 1, teachers = 2, courses = 1) + val teacher = data.teachersList[0] + val teacher2 = data.teachersList[1] + val course = data.coursesList[0] + val student1 = data.studentsList[0] + + val conversationSubject = "Important Announcement" + val conversationBody = "Please review the course syllabus and share with your classmates." + Log.d(PREPARATION_TAG, "Create a conversation from '${student1.name}' to '${teacher.name}'.") + val seededConversation = ConversationsApi.createConversationForCourse(token = student1.token, courseId = course.id, recipients = listOf(teacher.id.toString()), subject = conversationSubject, body = conversationBody)[0] + + Log.d(STEP_TAG, "Login with user: '${teacher.name}', login id: '${teacher.loginId}'.") + tokenLogin(teacher) + dashboardPage.waitForRender() + + Log.d(ASSERTION_TAG, "Assert that the '${course.name}' course is displayed on the Dashboard Page.") + dashboardPage.assertDisplaysCourse(course) + + Log.d(STEP_TAG, "Open Inbox Page.") + dashboardPage.openInbox() + + Log.d(ASSERTION_TAG, "Assert that the conversation is displayed.") + inboxPage.assertConversationDisplayed(seededConversation.subject) + + Log.d(STEP_TAG, "Open the conversation.") + inboxPage.openConversation(seededConversation.subject) + + Log.d(ASSERTION_TAG, "Assert that the '${conversationSubject}' and '${conversationBody}' are displayed.") + inboxDetailsPage.assertConversationSubject(conversationSubject) + inboxDetailsPage.assertMessageDisplayed(conversationBody) + + Log.d(STEP_TAG, "Click Forward button to forward the conversation to ${teacher2.name}.") + inboxDetailsPage.pressOverflowMenuItemForConversation("Forward") + + val forwardMessage = "Hey, check this out." + Log.d(STEP_TAG, "Type forward message: '$forwardMessage'") + inboxComposePage.typeBody(forwardMessage) + + Log.d(STEP_TAG, "Select recipient for forwarded message.") + inboxComposePage.pressAddRecipient() + recipientPickerPage.pressLabel("Teachers") + recipientPickerPage.pressLabel(teacher2.shortName) + recipientPickerPage.pressDone() + + Log.d(ASSERTION_TAG, "Assert that send button is enabled after selecting recipient.") + inboxComposePage.assertIfSendButtonState(true) + + Log.d(STEP_TAG, "Send the forwarded message.") + inboxComposePage.pressSendButton() + + Log.d(ASSERTION_TAG, "Assert that the forward message is displayed in the conversation.") + inboxDetailsPage.assertMessageDisplayed(forwardMessage) + + Log.d(ASSERTION_TAG, "Assert that the original message is still displayed.") + inboxDetailsPage.assertMessageDisplayed(conversationBody) + + Log.d(STEP_TAG, "Navigate back to Inbox conversation list page.") + Espresso.pressBack() + + Log.d(ASSERTION_TAG, "Assert that the conversation is still displayed in inbox.") + inboxPage.assertConversationDisplayed(conversationSubject) + + Log.d(STEP_TAG, "Log out with '${teacher.name}' teacher.") + leftSideNavigationDrawerPage.logout() + + Log.d(STEP_TAG, "Login with user: '${teacher2.name}', login id: '${teacher2.loginId}'.") + tokenLogin(teacher2) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Open Inbox Page.") + dashboardPage.openInbox() + + Log.d(ASSERTION_TAG, "Assert that the forwarded conversation is displayed in ${teacher2.name}'s inbox.") + inboxPage.assertConversationDisplayed(conversationSubject) + + Log.d(STEP_TAG, "Open the forwarded conversation.") + inboxPage.openConversation(conversationSubject) + + Log.d(ASSERTION_TAG, "Assert that the '${conversationSubject}' and '${conversationBody}' are displayed.") + inboxDetailsPage.assertConversationSubject(conversationSubject) + inboxDetailsPage.assertMessageDisplayed(conversationBody) + + Log.d(ASSERTION_TAG, "Assert that the forwarded message from ${teacher.name} is displayed.") + inboxDetailsPage.assertMessageDisplayed(forwardMessage) + + Log.d(ASSERTION_TAG, "Assert that the original message is also displayed.") + inboxDetailsPage.assertMessageDisplayed(conversationBody) + } + } \ No newline at end of file diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/FileChooserPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/FileChooserPage.kt deleted file mode 100644 index f67a0236b0..0000000000 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/FileChooserPage.kt +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2026 - present Instructure, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package com.instructure.teacher.ui.pages.classic - -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.assertion.ViewAssertions.doesNotExist -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.espresso.matcher.ViewMatchers.hasSibling -import androidx.test.espresso.matcher.ViewMatchers.withChild -import com.instructure.canvas.espresso.containsTextCaseInsensitive -import com.instructure.espresso.OnViewWithId -import com.instructure.espresso.WaitForViewWithId -import com.instructure.espresso.assertDisplayed -import com.instructure.espresso.assertHasText -import com.instructure.espresso.click -import com.instructure.espresso.matchers.WaitForViewMatcher.waitForViewToBeClickable -import com.instructure.espresso.page.BasePage -import com.instructure.espresso.page.plus -import com.instructure.espresso.page.withDescendant -import com.instructure.espresso.page.withId -import com.instructure.espresso.page.withParent -import com.instructure.espresso.page.withText -import com.instructure.espresso.scrollTo -import com.instructure.teacher.R - -class FileChooserPage : BasePage() { - private val cameraButton by OnViewWithId(R.id.fromCamera) - private val galleryButton by OnViewWithId(R.id.fromGallery) - private val deviceButton by OnViewWithId(R.id.fromDevice) - private val chooseFileTitle by OnViewWithId(R.id.chooseFileTitle) - private val chooseFileSubtitle by OnViewWithId(R.id.chooseFileSubtitle) - private val fileChooserTitle by WaitForViewWithId(R.id.alertTitle) - - fun assertFileChooserDetails() { - chooseFileTitle.assertDisplayed().assertHasText(R.string.chooseFile) - chooseFileSubtitle.assertDisplayed().assertHasText(R.string.chooseFileForUploadSubtext) - cameraButton.assertDisplayed() - galleryButton.assertDisplayed() - deviceButton.assertDisplayed() - } - - fun chooseCamera() { - cameraButton.scrollTo().click() - } - - fun chooseGallery() { - galleryButton.scrollTo().click() - } - - fun chooseDevice() { - deviceButton.scrollTo().click() - } - - fun clickUpload() { - onView(withText(R.string.upload)).click() - } - - fun clickTurnIn() { - onView(withText(R.string.turnIn)).click() - } - - fun clickCancel() { - onView(withText(R.string.cancel)).click() - } - - fun removeFile(filename: String) { - val fileItemMatcher = withId(R.id.fileItem) + withDescendant(withId(R.id.fileName) + containsTextCaseInsensitive(filename)) - - val removeMatcher = withId(R.id.removeFile) + ViewMatchers.isDescendantOfA(fileItemMatcher) - waitForViewToBeClickable(removeMatcher).scrollTo().click() - } - - fun assertDialogTitle(title: String) { - fileChooserTitle.assertHasText(title) - } - - fun assertFileDisplayed(filename: String) { - val fileNameMatcher = withId(R.id.fileName) + withText(filename) - onView(fileNameMatcher).assertDisplayed() - onView(withId(R.id.fileSize) + hasSibling(fileNameMatcher)).assertDisplayed() - onView(withId(R.id.fileIcon) + withParent(withId(R.id.iconWrapper) + hasSibling(withId(R.id.content) + withChild(fileNameMatcher)))).assertDisplayed() - onView(withId(R.id.removeFile) + hasSibling(withId(R.id.content) + withChild(fileNameMatcher))).assertDisplayed() - } - - fun assertFileNotDisplayed(filename: String) { - onView(withId(R.id.fileName) + withText(filename)).check(doesNotExist()) - } - -} \ No newline at end of file diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherTest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherTest.kt index d868fe8c7f..cdc00115e4 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherTest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherTest.kt @@ -14,12 +14,19 @@ * limitations under the License. * */ + package com.instructure.teacher.ui.utils import android.app.Activity +import android.app.Instrumentation +import android.content.Intent +import android.net.Uri import android.view.View +import androidx.core.content.FileProvider import androidx.test.espresso.UiController import androidx.test.espresso.ViewAction +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.matcher.IntentMatchers import androidx.test.espresso.matcher.ViewMatchers import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice @@ -27,6 +34,7 @@ import com.instructure.canvas.espresso.CanvasTest import com.instructure.canvas.espresso.common.pages.AboutPage import com.instructure.canvas.espresso.common.pages.CanvasNetworkSignInPage import com.instructure.canvas.espresso.common.pages.EmailNotificationsPage +import com.instructure.canvas.espresso.common.pages.FileChooserPage import com.instructure.canvas.espresso.common.pages.InboxPage import com.instructure.canvas.espresso.common.pages.LegalPage import com.instructure.canvas.espresso.common.pages.LoginFindSchoolPage @@ -36,6 +44,7 @@ import com.instructure.canvas.espresso.common.pages.WrongDomainPage import com.instructure.espresso.InstructureActivityTestRule import com.instructure.espresso.ModuleItemInteractions import com.instructure.espresso.Searchable +import com.instructure.pandautils.utils.Const import com.instructure.teacher.BuildConfig import com.instructure.teacher.R import com.instructure.teacher.activities.LoginActivity @@ -57,7 +66,6 @@ import com.instructure.teacher.ui.pages.classic.EditPageDetailsPage import com.instructure.teacher.ui.pages.classic.EditProfileSettingsPage import com.instructure.teacher.ui.pages.classic.EditQuizDetailsPage import com.instructure.teacher.ui.pages.classic.EditSyllabusPage -import com.instructure.teacher.ui.pages.classic.FileChooserPage import com.instructure.teacher.ui.pages.classic.FileListPage import com.instructure.teacher.ui.pages.classic.HelpPage import com.instructure.teacher.ui.pages.classic.LeftSideNavigationDrawerPage @@ -82,6 +90,8 @@ import com.instructure.teacher.ui.pages.classic.UpdateFilePermissionsPage import com.instructure.teacher.ui.pages.classic.WebViewLoginPage import instructure.rceditor.RCETextEditor import org.hamcrest.Matcher +import org.hamcrest.core.AllOf +import java.io.File abstract class TeacherTest : CanvasTest() { @@ -145,6 +155,41 @@ abstract class TeacherTest : CanvasTest() { val fileListPage = FileListPage(Searchable(R.id.search, R.id.queryInput, R.id.clearButton, R.id.backButton)) val updateFilePermissionsPage = UpdateFilePermissionsPage() val fileChooserPage = FileChooserPage() + + fun setupFileOnDevice(fileName: String): Uri { + File(InstrumentationRegistry.getInstrumentation().targetContext.cacheDir, "file_upload").deleteRecursively() + copyAssetFileToExternalCache(activityRule.activity, fileName) + + val dir = activityRule.activity.externalCacheDir + val file = File(dir?.path, fileName) + + val instrumentationContext = InstrumentationRegistry.getInstrumentation().context + return FileProvider.getUriForFile( + instrumentationContext, + "com.instructure.teacher" + Const.FILE_PROVIDER_AUTHORITY, + file + ) + } + + fun stubFilePickerIntent(fileName: String) { + val resultData = Intent() + val dir = activityRule.activity.externalCacheDir + val file = File(dir?.path, fileName) + val newFileUri = FileProvider.getUriForFile( + activityRule.activity, + "com.instructure.teacher" + Const.FILE_PROVIDER_AUTHORITY, + file + ) + resultData.data = newFileUri + resultData.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + + Intents.intending( + AllOf.allOf( + IntentMatchers.hasAction(Intent.ACTION_GET_CONTENT), + IntentMatchers.hasType("*/*"), + ) + ).respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, resultData)) + } } /* diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/classic/FileChooserPage.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/FileChooserPage.kt similarity index 96% rename from apps/student/src/androidTest/java/com/instructure/student/ui/pages/classic/FileChooserPage.kt rename to automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/FileChooserPage.kt index f436664313..23a00caede 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/classic/FileChooserPage.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/FileChooserPage.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 - present Instructure, Inc. + * Copyright (C) 2026 - present Instructure, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * */ -package com.instructure.student.ui.pages.classic +package com.instructure.canvas.espresso.common.pages import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.doesNotExist @@ -37,7 +37,7 @@ import com.instructure.espresso.page.withParent import com.instructure.espresso.page.withText import com.instructure.espresso.scrollTo import com.instructure.espresso.triggerWorkManagerJobs -import com.instructure.student.R +import com.instructure.pandautils.R class FileChooserPage : BasePage() { private val cameraButton by OnViewWithId(R.id.fromCamera) diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/InboxComposePage.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/InboxComposePage.kt index da96a7c1eb..def3a2568b 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/InboxComposePage.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/InboxComposePage.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextReplacement import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.swipeUp +import androidx.test.espresso.Espresso import com.instructure.canvasapi2.models.Conversation import com.instructure.canvasapi2.models.Message @@ -141,6 +142,7 @@ class InboxComposePage(private val composeTestRule: ComposeTestRule) { composeTestRule.waitForIdle() composeTestRule.onNodeWithTag("textFieldWithHeaderTextField").performClick() composeTestRule.onNodeWithTag("textFieldWithHeaderTextField").performTextReplacement(body) + Espresso.closeSoftKeyboard() } fun clickOnCloseButton() { diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/InboxDetailsPage.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/InboxDetailsPage.kt index 35bb359f3c..cb85f60210 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/InboxDetailsPage.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/InboxDetailsPage.kt @@ -238,4 +238,10 @@ class InboxDetailsPage(private val composeTestRule: ComposeTestRule) { onView(withResourceIdContaining("pspdf__toolbar_main")) .assertDisplayed() } + + // Verifies that the PSPDFKit document view is displayed + fun assertPdfDocumentViewDisplayed() { + onView(withResourceIdContaining("pdfFragmentContainer")) + .assertDisplayed() + } } \ No newline at end of file diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/RecipientPickerPage.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/RecipientPickerPage.kt index 3e02d62df8..cae3a42343 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/RecipientPickerPage.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/RecipientPickerPage.kt @@ -1,15 +1,33 @@ package com.instructure.canvas.espresso.common.pages.compose +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.junit4.ComposeTestRule +import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick class RecipientPickerPage(private val composeTestRule: ComposeTestRule) { fun pressLabel(label: String) { composeTestRule.onNodeWithText(label, useUnmergedTree = true).performClick() + composeTestRule.waitForIdle() } fun pressDone() { composeTestRule.onNodeWithText("Done").performClick() + composeTestRule.waitForIdle() + } + + fun pressBack() { + composeTestRule.onNodeWithTag("navigationButton").performClick() + composeTestRule.waitForIdle() + } + + fun assertRecipientDisplayed(recipientName: String) { + composeTestRule.onNodeWithText(recipientName, useUnmergedTree = true).assertIsDisplayed() + } + + fun assertRecipientNotDisplayed(recipientName: String) { + composeTestRule.onNodeWithText(recipientName, useUnmergedTree = true).assertIsNotDisplayed() } } \ No newline at end of file