diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/CourseBrowserE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/CourseBrowserE2ETest.kt new file mode 100644 index 0000000000..2dc3d7e01c --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/CourseBrowserE2ETest.kt @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2025 - 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.student.ui.e2e + +import android.util.Log +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performImeAction +import androidx.compose.ui.test.performTextInput +import androidx.compose.ui.test.requestFocus +import androidx.test.espresso.Espresso +import com.instructure.canvas.espresso.E2E +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.SecondaryFeatureCategory +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData +import com.instructure.canvasapi2.models.SmartSearchContentType +import com.instructure.canvasapi2.models.SmartSearchFilter +import com.instructure.dataseeding.api.AssignmentsApi +import com.instructure.dataseeding.api.DiscussionTopicsApi +import com.instructure.dataseeding.api.PagesApi +import com.instructure.dataseeding.model.SubmissionType +import com.instructure.dataseeding.util.days +import com.instructure.dataseeding.util.fromNow +import com.instructure.dataseeding.util.iso8601 +import com.instructure.student.ui.utils.StudentComposeTest +import com.instructure.student.ui.utils.seedData +import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest +import org.junit.Test + +@HiltAndroidTest +class CourseBrowserE2ETest : StudentComposeTest() { + + override fun displaysPageObjects() = Unit + + override fun enableAndConfigureAccessibilityChecks() = Unit + + @E2E + @Test + @TestMetaData(Priority.MANDATORY, FeatureCategory.COURSE_BROWSER, TestCategory.E2E, SecondaryFeatureCategory.SMART_SEARCH) + fun testSmartSearchE2E() { + + Log.d(PREPARATION_TAG,"Seeding data.") + val data = seedData(students = 1, teachers = 1, courses = 1) + val student = data.studentsList[0] + val teacher = data.teachersList[0] + val course = data.coursesList[0] + + Log.d(PREPARATION_TAG,"Seeding 'Text Entry' assignment for '${course.name}' course with 2 days ahead due date.") + val testAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, dueAt = 2.days.fromNow.iso8601, pointsPossible = 15.0, submissionTypes = listOf( + SubmissionType.ONLINE_TEXT_ENTRY), assignmentName = "Test SmartSearch Assignment") + + Log.d(STEP_TAG,"Seed an announcement for '${course.name}' course.") + val testAnnouncement = DiscussionTopicsApi.createAnnouncement(course.id, teacher.token, announcementTitle = "Test Announcement") + + Log.d(PREPARATION_TAG,"Create a discussion topic for '${course.name}' course.") + val testDiscussion = DiscussionTopicsApi.createDiscussion(courseId = course.id, token = teacher.token, discussionTitle = "Test Discussion") + + Log.d(PREPARATION_TAG,"Create a published page for course: '${course.name}'.") + val testPage = PagesApi.createCoursePage(course.id, teacher.token, published = true, frontPage = false, pageTitle = "Test SmartSearch Page", body = "

Test SmartSearch Page

") + + Log.d(STEP_TAG,"Login with user: '${student.name}', login id: '${student.loginId}'.") + tokenLogin(student) + + Log.d(STEP_TAG,"Wait for the Dashboard Page to be rendered. Select course: '${course.name}'.") + dashboardPage.waitForRender() + Thread.sleep(10000) // Wait for the API creations to complete, sometimes the creation processes are too slow on the API side + dashboardPage.selectCourse(course) + + Log.d(STEP_TAG,"Assert that the Course Browser Page is displayed.") + courseBrowserPage.assertPageObjects() + + Log.d(STEP_TAG, "Click on the 'Smart Search' (Magnifying glass) icon on the top right corner of the Course Browser page.") + courseBrowserPage.clickOnSmartSearch() + + val smartSearchText = testAssignment.name.take(4) // "Test" will be the search text. + Log.d(STEP_TAG, "Type the '$smartSearchText' into the search input field.") + composeTestRule.onNodeWithTag("searchField") + .requestFocus() + .performClick() + .performTextInput(smartSearchText) + composeTestRule.onNodeWithTag("searchField").performImeAction() + composeTestRule.waitForIdle() + + Log.d(ASSERTION_TAG, "Assert that the '$smartSearchText' text is displayed in the search input field and the filter button is displayed on the search bar.") + smartSearchPage.assertQuery(smartSearchText) + composeTestRule.onNodeWithTag("filterButton").assertIsDisplayed() + + Log.d(ASSERTION_TAG, "Assert that the '${course.name}' is displayed under the 'Results in course' section.") + smartSearchPage.assertCourse(course.name) + + Log.d(ASSERTION_TAG, "Assert that the '${testAssignment.name}' assignment is displayed and it's type is 'Assignment'.") + smartSearchPage.assertItemDisplayed(testAssignment.name, "Assignment") + + Log.d(ASSERTION_TAG, "Assert that the '${testAnnouncement.title}' announcement is displayed and it's type is 'Announcement'.") + smartSearchPage.assertItemDisplayed(testAnnouncement.title, "Announcement") + + Log.d(ASSERTION_TAG, "Assert that the '${testDiscussion.title}' discussion is displayed and it's type is 'Discussion'.") + smartSearchPage.assertItemDisplayed(testDiscussion.title, "Discussion") + + Log.d(ASSERTION_TAG, "Assert that the '${testPage.title}' page is displayed and it's type is 'Page'.") + smartSearchPage.assertItemDisplayed(testPage.title, "Page") + + Log.d(STEP_TAG, "Click on the '${testAssignment.name}' assignment.") + smartSearchPage.clickOnItem(testAssignment.name) + + Log.d(ASSERTION_TAG, "Assert that the previous click action will navigate to the '${testAssignment.name}' assignment's details page.") + assignmentDetailsPage.assertAssignmentDetails(testAssignment) + + Log.d(STEP_TAG, "Navigate back to the Smart Search Result List page.") + Espresso.pressBack() + + Log.d(STEP_TAG, "Click on the '${testAnnouncement.title}' announcement.") + smartSearchPage.clickOnItem(testAnnouncement.title) + + Log.d(ASSERTION_TAG, "Assert that the previous click action will navigate to the '${testAnnouncement.title}' announcement's details page.") + discussionDetailsPage.assertToolbarDiscussionTitle(testAnnouncement.title) + + Log.d(STEP_TAG, "Navigate back to the Smart Search Result List page.") + Espresso.pressBack() + + Log.d(STEP_TAG, "Click on the '${testDiscussion.title}' discussion.") + smartSearchPage.clickOnItem(testDiscussion.title) + + Log.d(ASSERTION_TAG, "Assert that the previous click action will navigate to the '${testDiscussion.title}' discussion's details page.") + discussionDetailsPage.assertToolbarDiscussionTitle(testDiscussion.title) + + Log.d(STEP_TAG, "Navigate back to the Smart Search Result List page.") + Espresso.pressBack() + + Log.d(STEP_TAG, "Click on the '${testPage.title}' page.") + smartSearchPage.clickOnItem(testPage.title) + + Log.d(ASSERTION_TAG, "Assert that the previous click action will navigate to the '${testPage.title}' page's details page. (Assert for the URL as we are displaying the URL of the page when navigating to it's details page from a link.") + pageDetailsPage.assertToolbarTitle(testPage.url) + + Log.d(STEP_TAG, "Navigate back to the Smart Search Result List page.") + Espresso.pressBack() + + Log.d(STEP_TAG, "Click on the 'Filters' icon on the top-right corner.") + smartSearchPage.clickOnFilters() + + Log.d(ASSERTION_TAG, "Assert that all of the types (Pages, Discussion Topics, Announcements, Assignments) are checked by default.") + smartSearchPreferencesPage.assertFilterChecked(SmartSearchFilter.PAGES) + smartSearchPreferencesPage.assertFilterChecked(SmartSearchFilter.DISCUSSION_TOPICS) + smartSearchPreferencesPage.assertFilterChecked(SmartSearchFilter.ANNOUNCEMENTS) + smartSearchPreferencesPage.assertFilterChecked(SmartSearchFilter.ASSIGNMENTS) + + Log.d(STEP_TAG, "Click on the 'Pages' and 'Announcements' filters to turn them off. Apply the filters.") + smartSearchPreferencesPage.clickOnFilter(SmartSearchFilter.PAGES) + smartSearchPreferencesPage.clickOnFilter(SmartSearchFilter.ANNOUNCEMENTS) + smartSearchPreferencesPage.applyFilters() + + Log.d(ASSERTION_TAG, "Assert that the 'Page' and 'Announcement' result items are not displayed any more on the Smart Search Results page but the 'Discussion Topic' and 'Assignment' are still displayed.") + smartSearchPage.assertItemNotDisplayed(testPage.title, "Page") + smartSearchPage.assertItemNotDisplayed(testAnnouncement.title, "Announcement") + smartSearchPage.assertItemDisplayed(testDiscussion.title, "Discussion") + smartSearchPage.assertItemDisplayed(testAssignment.name, "Assignment") + + Log.d(STEP_TAG, "Click on the 'Filters' icon on the top-right corner.") + smartSearchPage.clickOnFilters() + + Log.d(ASSERTION_TAG, "Assert that the 'Pages' and 'Announcements' filters are unchecked (as we modified it recently) and the 'Discussion Topics' and 'Assignments' are remain checked.") + smartSearchPreferencesPage.assertFilterNotChecked(SmartSearchFilter.PAGES) + smartSearchPreferencesPage.assertFilterNotChecked(SmartSearchFilter.ANNOUNCEMENTS) + smartSearchPreferencesPage.assertFilterChecked(SmartSearchFilter.DISCUSSION_TOPICS) + smartSearchPreferencesPage.assertFilterChecked(SmartSearchFilter.ASSIGNMENTS) + + Log.d(STEP_TAG, "Click on the 'Discussion Topic' and 'Assignment' filters to turn them off as well. Apply the new filters.") + smartSearchPreferencesPage.clickOnFilter(SmartSearchFilter.DISCUSSION_TOPICS) + smartSearchPreferencesPage.clickOnFilter(SmartSearchFilter.ASSIGNMENTS) + smartSearchPreferencesPage.applyFilters() + + Log.d(ASSERTION_TAG, "Assert that all the types of result items are displayed as we have a logic if nothing is selected then we show every item on the Smart Search Result page.") + smartSearchPage.assertItemDisplayed(testPage.title, "Page") + smartSearchPage.assertItemDisplayed(testAnnouncement.title, "Announcement") + smartSearchPage.assertItemDisplayed(testDiscussion.title, "Discussion") + smartSearchPage.assertItemDisplayed(testAssignment.name, "Assignment") + + Log.d(STEP_TAG, "Click on the 'Filters' icon on the top-right corner.") + smartSearchPage.clickOnFilters() + + Log.d(ASSERTION_TAG, "Assert that none of the type filters are checked.") + smartSearchPreferencesPage.assertFilterNotChecked(SmartSearchFilter.PAGES) + smartSearchPreferencesPage.assertFilterNotChecked(SmartSearchFilter.ANNOUNCEMENTS) + smartSearchPreferencesPage.assertFilterNotChecked(SmartSearchFilter.DISCUSSION_TOPICS) + smartSearchPreferencesPage.assertFilterNotChecked(SmartSearchFilter.ASSIGNMENTS) + + Log.d(STEP_TAG, "Click on the 'Select All' button.") + smartSearchPreferencesPage.toggleAll() + + Log.d(ASSERTION_TAG, "Assert that all of the type filters are checked.") + smartSearchPreferencesPage.assertFilterChecked(SmartSearchFilter.PAGES) + smartSearchPreferencesPage.assertFilterChecked(SmartSearchFilter.ANNOUNCEMENTS) + smartSearchPreferencesPage.assertFilterChecked(SmartSearchFilter.DISCUSSION_TOPICS) + smartSearchPreferencesPage.assertFilterChecked(SmartSearchFilter.ASSIGNMENTS) + + Log.d(ASSERTION_TAG, "Assert that the 'Sort By' section displayed properly. By default, the 'Relevance' radiobutton should be selected.") + smartSearchPreferencesPage.assertSortByDetails() + smartSearchPreferencesPage.assertRadioButtonSelected("Relevance") + + Log.d(STEP_TAG, "Select the 'Type' sorting type and apply the filters.") + smartSearchPreferencesPage.selectTypeSortType() + smartSearchPreferencesPage.applyFilters() + + Log.d(ASSERTION_TAG, "Assert that the four different group header titles (Pages, Discussion Topics, Announcements, Assignments) are displayed.") + smartSearchPage.assertGroupHeaderDisplayed(SmartSearchContentType.WIKI_PAGE) + smartSearchPage.assertGroupHeaderDisplayed(SmartSearchContentType.DISCUSSION_TOPIC) + smartSearchPage.assertGroupHeaderDisplayed(SmartSearchContentType.ANNOUNCEMENT) + smartSearchPage.assertGroupHeaderDisplayed(SmartSearchContentType.ASSIGNMENT) + + Log.d(ASSERTION_TAG, "Assert that all the types of result items are displayed on the Smart Search Result page.") + smartSearchPage.assertItemDisplayed(testPage.title, "Page") + smartSearchPage.assertItemDisplayed(testAnnouncement.title, "Announcement") + smartSearchPage.assertItemDisplayed(testDiscussion.title, "Discussion") + smartSearchPage.assertItemDisplayed(testAssignment.name, "Assignment") + } + +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CourseBrowserPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CourseBrowserPage.kt index f462ea98f7..5a3e147703 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CourseBrowserPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CourseBrowserPage.kt @@ -43,6 +43,7 @@ import com.instructure.espresso.click import com.instructure.espresso.page.BasePage import com.instructure.espresso.page.onView import com.instructure.espresso.page.plus +import com.instructure.espresso.page.withAncestor import com.instructure.espresso.page.withId import com.instructure.espresso.scrollTo import com.instructure.espresso.swipeUp @@ -127,6 +128,10 @@ open class CourseBrowserPage : BasePage(R.id.courseBrowserPage) { onView(matcher).click() } + fun clickOnSmartSearch() { + onView(withId(R.id.searchBar) + withAncestor(R.id.courseBrowserPage)).click() + } + fun assertTitleCorrect(course: Course) { // You might have multiple of these if you navigate from one course to another. // In that event, we'll have to choose the one that is displayed. diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentComposeTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentComposeTest.kt index 8ca9a65d74..89eacca2ba 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentComposeTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentComposeTest.kt @@ -31,6 +31,8 @@ import com.instructure.canvas.espresso.common.pages.compose.InboxDetailsPage import com.instructure.canvas.espresso.common.pages.compose.RecipientPickerPage import com.instructure.canvas.espresso.common.pages.compose.SelectContextPage import com.instructure.canvas.espresso.common.pages.compose.SettingsPage +import com.instructure.canvas.espresso.common.pages.compose.SmartSearchPage +import com.instructure.canvas.espresso.common.pages.compose.SmartSearchPreferencesPage import com.instructure.student.activity.LoginActivity import org.junit.Rule @@ -51,4 +53,6 @@ abstract class StudentComposeTest : StudentTest() { val inboxComposePage = InboxComposePage(composeTestRule) val recipientPickerPage = RecipientPickerPage(composeTestRule) val selectContextPage = SelectContextPage(composeTestRule) + val smartSearchPage = SmartSearchPage(composeTestRule) + val smartSearchPreferencesPage = SmartSearchPreferencesPage(composeTestRule) } \ No newline at end of file diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/ModulesE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/ModulesE2ETest.kt index 0d083d2fd3..62c02690d0 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/ModulesE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/ModulesE2ETest.kt @@ -31,7 +31,7 @@ import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test @HiltAndroidTest -class ModulesE2ETest : TeacherComposeTest() { +class ModulesE2ETest : TeacherComposeTest() { override fun displaysPageObjects() = Unit diff --git a/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/api/AssignmentsApi.kt b/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/api/AssignmentsApi.kt index b665471602..ce3e036a2d 100644 --- a/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/api/AssignmentsApi.kt +++ b/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/api/AssignmentsApi.kt @@ -17,7 +17,13 @@ package com.instructure.dataseeding.api -import com.instructure.dataseeding.model.* +import com.instructure.dataseeding.model.AssignmentApiModel +import com.instructure.dataseeding.model.AssignmentOverrideApiModel +import com.instructure.dataseeding.model.CreateAssignmentOverrideForStudents +import com.instructure.dataseeding.model.CreateAssignmentOverrideForStudentsWrapper +import com.instructure.dataseeding.model.CreateAssignmentWrapper +import com.instructure.dataseeding.model.GradingType +import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.util.CanvasNetworkAdapter import com.instructure.dataseeding.util.Randomizer import retrofit2.Call @@ -48,6 +54,7 @@ object AssignmentsApi { val gradingType: GradingType = GradingType.POINTS, val allowedExtensions: List? = null, val teacherToken: String, + val assignmentName: String? = null, val groupCategoryId: Long? = null, val pointsPossible: Double? = null, val importantDate: Boolean? = null, @@ -64,6 +71,7 @@ object AssignmentsApi { request.submissionTypes, request.gradingType, request.allowedExtensions, + request.assignmentName, request.groupCategoryId, request.pointsPossible, request.importantDate, @@ -81,11 +89,13 @@ object AssignmentsApi { submissionTypes: List = emptyList(), gradingType: GradingType = GradingType.POINTS, allowedExtensions: List? = null, + assignmentName: String? = null, groupCategoryId: Long? = null, pointsPossible: Double? = null, importantDate: Boolean? = null, assignmentGroupId: Long? = null): AssignmentApiModel { val assignment = CreateAssignmentWrapper(Randomizer.randomAssignment( + assignmentName, withDescription, lockAt, unlockAt, diff --git a/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/api/DiscussionTopicsApi.kt b/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/api/DiscussionTopicsApi.kt index d15c38a850..33846d98e7 100644 --- a/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/api/DiscussionTopicsApi.kt +++ b/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/api/DiscussionTopicsApi.kt @@ -62,16 +62,16 @@ object DiscussionTopicsApi { .body()!! } - fun createDiscussion(courseId: Long, token: String, isAnnouncement: Boolean = false, lockedForUser: Boolean = false, locked: Boolean = false): DiscussionApiModel { - val discussionTopic = Randomizer.randomDiscussion(isAnnouncement, lockedForUser, locked) + fun createDiscussion(courseId: Long, token: String, isAnnouncement: Boolean = false, lockedForUser: Boolean = false, locked: Boolean = false, discussionTitle: String? = null): DiscussionApiModel { + val discussionTopic = Randomizer.randomDiscussion(discussionTitle, isAnnouncement, lockedForUser, locked) return discussionTopicsService(token) .createDiscussionTopic(courseId, discussionTopic) .execute() .body()!! } - fun createAnnouncement(courseId: Long, token: String, lockedForUser: Boolean = false, locked: Boolean = false): DiscussionApiModel { - val discussion = createDiscussion(courseId, token, true, lockedForUser, locked, ) + fun createAnnouncement(courseId: Long, token: String, lockedForUser: Boolean = false, locked: Boolean = false, announcementTitle: String? = null): DiscussionApiModel { + val discussion = createDiscussion(courseId, token, true, lockedForUser, locked, announcementTitle) if(!lockedForUser) { return DiscussionApiModel( diff --git a/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/api/PagesApi.kt b/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/api/PagesApi.kt index 5c5353a4a0..209a942393 100644 --- a/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/api/PagesApi.kt +++ b/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/api/PagesApi.kt @@ -44,8 +44,9 @@ object PagesApi { frontPage: Boolean = false, body: String = Randomizer.randomPageBody(), editingRoles: String? = null, + pageTitle: String? = null ): PageApiModel { - val page = CreatePageWrapper(CreatePage(Randomizer.randomPageTitle(), body, published, frontPage, editingRoles)) + val page = CreatePageWrapper(CreatePage(pageTitle ?: Randomizer.randomPageTitle(), body, published, frontPage, editingRoles)) return pagesService(token) .createCoursePage(courseId, page) diff --git a/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/util/Randomizer.kt b/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/util/Randomizer.kt index 85a536e81d..0cb7b0813d 100644 --- a/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/util/Randomizer.kt +++ b/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/util/Randomizer.kt @@ -27,7 +27,9 @@ import com.instructure.dataseeding.model.CreateSubmissionComment import com.instructure.dataseeding.model.GradingType import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.model.SubmitCourseAssignmentSubmission -import java.util.* +import java.util.Date +import java.util.Locale +import java.util.UUID object Randomizer { private val faker = Faker() @@ -56,9 +58,9 @@ object Randomizer { fun randomImageUrlSmall(): String = faker.internet().image(64, 64, false, null) - fun randomDiscussion(isAnnouncement: Boolean = false, lockedForUser: Boolean = false, locked: Boolean = false): CreateDiscussionTopic = + fun randomDiscussion(discussionTitle: String?, isAnnouncement: Boolean = false, lockedForUser: Boolean = false, locked: Boolean = false): CreateDiscussionTopic = CreateDiscussionTopic( - title = faker.lorem().sentence(), + title = discussionTitle ?: faker.lorem().sentence(), message = faker.lorem().paragraph(), isAnnouncement = isAnnouncement, lockedForUser = lockedForUser, @@ -77,9 +79,9 @@ object Randomizer { fun randomGradingPeriodSetTitle(): String = "${faker.pokemon().location()} Set" fun randomGradingPeriodName(): String = "${faker.pokemon().name()} Grading Period" - fun randomAssignment(withDescription: Boolean = false, lockAt: String, unlockAt: String, dueAt: String, submissionTypes: List, gradingType: GradingType?, groupCategoryId: Long?, pointsPossible: Double?, allowedExtensions: List?, importantDate: Boolean?, assignmentGroupId: Long? = null): CreateAssignment = + fun randomAssignment(assignmentName: String?, withDescription: Boolean = false, lockAt: String, unlockAt: String, dueAt: String, submissionTypes: List, gradingType: GradingType?, groupCategoryId: Long?, pointsPossible: Double?, allowedExtensions: List?, importantDate: Boolean?, assignmentGroupId: Long? = null): CreateAssignment = CreateAssignment( - name = faker.lorem().sentence(), + name = assignmentName ?: faker.lorem().sentence(), description = if (withDescription) faker.lorem().paragraph() else null, lockAt = if (lockAt.isNotBlank()) lockAt else null, unlockAt = if (unlockAt.isNotBlank()) unlockAt else null, @@ -87,7 +89,7 @@ object Randomizer { submissionTypes = if (submissionTypes.isEmpty()) null else submissionTypes.map { if (it.name == "NO_TYPE") "none" else it.name.lowercase(Locale.getDefault()) }, - gradingType = if (gradingType != null) gradingType.toString().lowercase(Locale.getDefault()) else "points", + gradingType = gradingType?.toString()?.lowercase(Locale.getDefault()) ?: "points", groupCategoryId = groupCategoryId, pointsPossible = pointsPossible, allowedExtensions = allowedExtensions, diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/TestMetaData.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/TestMetaData.kt index 5b97f23d01..773eb53e39 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/TestMetaData.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/TestMetaData.kt @@ -34,7 +34,7 @@ enum class FeatureCategory { ASSIGNMENTS, SUBMISSIONS, LOGIN, COURSE, DASHBOARD, GROUPS, SETTINGS, PAGES, DISCUSSIONS, MODULES, CALENDAR, INBOX, GRADES, FILES, EVENTS, PEOPLE, CONFERENCES, COLLABORATIONS, SYLLABUS, TODOS, QUIZZES, NOTIFICATIONS, ANNOTATIONS, ANNOUNCEMENTS, COMMENTS, BOOKMARKS, NONE, CANVAS_FOR_ELEMENTARY, SPEED_GRADER, SYNC_SETTINGS, SYNC_PROGRESS, OFFLINE_CONTENT, LEFT_SIDE_MENU, - COURSE_LIST + COURSE_LIST, COURSE_BROWSER } enum class SecondaryFeatureCategory { @@ -43,7 +43,7 @@ enum class SecondaryFeatureCategory { ASSIGNMENT_COMMENTS, ASSIGNMENT_QUIZZES, ASSIGNMENT_DISCUSSIONS, HOMEROOM, K5_GRADES, IMPORTANT_DATES, RESOURCES, SCHEDULE, GROUPS_DASHBOARD, GROUPS_FILES, GROUPS_ANNOUNCEMENTS, GROUPS_DISCUSSIONS, GROUPS_PAGES, GROUPS_PEOPLE, EVENTS_DISCUSSIONS, EVENTS_QUIZZES, EVENTS_ASSIGNMENTS, EVENTS_NOTIFICATIONS, SETTINGS_EMAIL_NOTIFICATIONS, - MODULES_ASSIGNMENTS, MODULES_DISCUSSIONS, MODULES_FILES, MODULES_PAGES, MODULES_QUIZZES, OFFLINE_MODE, ALL_COURSES, CHANGE_USER, ASSIGNMENT_REMINDER, ASSIGNMENT_DETAILS, ADD_STUDENT + MODULES_ASSIGNMENTS, MODULES_DISCUSSIONS, MODULES_FILES, MODULES_PAGES, MODULES_QUIZZES, OFFLINE_MODE, ALL_COURSES, CHANGE_USER, ASSIGNMENT_REMINDER, ASSIGNMENT_DETAILS, SMART_SEARCH, ADD_STUDENT } enum class TestCategory { diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/interaction/SmartSearchInteractionTest.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/interaction/SmartSearchInteractionTest.kt index 99d505b912..6c801164a8 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/interaction/SmartSearchInteractionTest.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/interaction/SmartSearchInteractionTest.kt @@ -180,14 +180,14 @@ abstract class SmartSearchInteractionTest : CanvasComposeTest() { smartSearchPage.assertItemDisplayed("Test Announcement for query", "Announcement") smartSearchPage.assertItemDisplayed("Test Assignment for query", "Assignment") - smartSearchPage.openFilters() + smartSearchPage.clickOnFilters() smartSearchPreferencesPage.assertFilterChecked(SmartSearchFilter.PAGES) smartSearchPreferencesPage.assertFilterChecked(SmartSearchFilter.DISCUSSION_TOPICS) smartSearchPreferencesPage.assertFilterChecked(SmartSearchFilter.ANNOUNCEMENTS) smartSearchPreferencesPage.assertFilterChecked(SmartSearchFilter.ASSIGNMENTS) - smartSearchPreferencesPage.toggleFilter(SmartSearchFilter.PAGES) + smartSearchPreferencesPage.clickOnFilter(SmartSearchFilter.PAGES) smartSearchPreferencesPage.applyFilters() smartSearchPage.assertItemNotDisplayed("Test Page for query", "Page") @@ -195,11 +195,11 @@ abstract class SmartSearchInteractionTest : CanvasComposeTest() { smartSearchPage.assertItemDisplayed("Test Announcement for query", "Announcement") smartSearchPage.assertItemDisplayed("Test Assignment for query", "Assignment") - smartSearchPage.openFilters() + smartSearchPage.clickOnFilters() smartSearchPreferencesPage.assertFilterNotChecked(SmartSearchFilter.PAGES) - smartSearchPreferencesPage.toggleFilter(SmartSearchFilter.ASSIGNMENTS) + smartSearchPreferencesPage.clickOnFilter(SmartSearchFilter.ASSIGNMENTS) smartSearchPreferencesPage.applyFilters() smartSearchPage.assertItemNotDisplayed("Test Page for query", "Page") @@ -207,7 +207,7 @@ abstract class SmartSearchInteractionTest : CanvasComposeTest() { smartSearchPage.assertItemDisplayed("Test Announcement for query", "Announcement") smartSearchPage.assertItemNotDisplayed("Test Assignment for query", "Assignment") - smartSearchPage.openFilters() + smartSearchPage.clickOnFilters() smartSearchPreferencesPage.toggleAll() smartSearchPreferencesPage.applyFilters() @@ -216,7 +216,7 @@ abstract class SmartSearchInteractionTest : CanvasComposeTest() { smartSearchPage.assertItemDisplayed("Test Announcement for query", "Announcement") smartSearchPage.assertItemDisplayed("Test Assignment for query", "Assignment") - smartSearchPage.openFilters() + smartSearchPage.clickOnFilters() smartSearchPreferencesPage.toggleAll() smartSearchPreferencesPage.applyFilters() @@ -260,7 +260,7 @@ abstract class SmartSearchInteractionTest : CanvasComposeTest() { composeTestRule.waitForIdle() - smartSearchPage.openFilters() + smartSearchPage.clickOnFilters() smartSearchPreferencesPage.selectTypeSortType() smartSearchPreferencesPage.applyFilters() @@ -300,7 +300,7 @@ abstract class SmartSearchInteractionTest : CanvasComposeTest() { composeTestRule.waitForIdle() - smartSearchPage.openFilters() + smartSearchPage.clickOnFilters() smartSearchPreferencesPage.selectTypeSortType() smartSearchPreferencesPage.applyFilters() @@ -357,7 +357,7 @@ abstract class SmartSearchInteractionTest : CanvasComposeTest() { composeTestRule.waitForIdle() - smartSearchPage.openFilters() + smartSearchPage.clickOnFilters() smartSearchPreferencesPage.selectTypeSortType() smartSearchPreferencesPage.applyFilters() @@ -366,7 +366,7 @@ abstract class SmartSearchInteractionTest : CanvasComposeTest() { smartSearchPage.assertGroupHeaderDisplayed(SmartSearchContentType.ANNOUNCEMENT) smartSearchPage.assertGroupHeaderDisplayed(SmartSearchContentType.ASSIGNMENT) - smartSearchPage.openFilters() + smartSearchPage.clickOnFilters() smartSearchPreferencesPage.selectRelevanceSortType() smartSearchPreferencesPage.applyFilters() diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/SmartSearchPage.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/SmartSearchPage.kt index c40a5c2210..06772dd936 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/SmartSearchPage.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/SmartSearchPage.kt @@ -90,7 +90,7 @@ class SmartSearchPage(private val composeTestRule: ComposeTestRule) : BasePage() .performClick() } - fun openFilters() { + fun clickOnFilters() { composeTestRule.onNode( hasTestTag("filterButton"), useUnmergedTree = true diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/SmartSearchPreferencesPage.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/SmartSearchPreferencesPage.kt index 3f4e5491ba..246f14a6f4 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/SmartSearchPreferencesPage.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/SmartSearchPreferencesPage.kt @@ -15,19 +15,24 @@ */ package com.instructure.canvas.espresso.common.pages.compose +import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsOff import androidx.compose.ui.test.assertIsOn +import androidx.compose.ui.test.assertIsSelected import androidx.compose.ui.test.hasParent import androidx.compose.ui.test.hasTestTag 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 import androidx.compose.ui.test.performScrollToNode import com.instructure.canvasapi2.models.SmartSearchFilter class SmartSearchPreferencesPage(private val composeTestRule: ComposeTestRule) { - fun toggleFilter(filter: SmartSearchFilter) { + // Actions + + fun clickOnFilter(filter: SmartSearchFilter) { composeTestRule.onNodeWithTag("preferencesScreen", useUnmergedTree = true) .performScrollToNode(hasTestTag("${filter.name.lowercase()}FilterRow")) composeTestRule.onNodeWithTag("${filter.name.lowercase()}FilterRow") @@ -39,6 +44,23 @@ class SmartSearchPreferencesPage(private val composeTestRule: ComposeTestRule) { .performClick() } + fun toggleAll() { + composeTestRule.onNodeWithTag("toggleAllButton", useUnmergedTree = true) + .performClick() + } + + fun selectRelevanceSortType() { + composeTestRule.onNodeWithTag("relevanceTypeSelector") + .performClick() + } + + fun selectTypeSortType() { + composeTestRule.onNodeWithTag("typeTypeSelector") + .performClick() + } + + // Assertions + fun assertFilterChecked(filter: SmartSearchFilter) { composeTestRule.onNodeWithTag("preferencesScreen", useUnmergedTree = true) .performScrollToNode(hasTestTag("${filter.name.lowercase()}FilterRow")) @@ -59,18 +81,14 @@ class SmartSearchPreferencesPage(private val composeTestRule: ComposeTestRule) { .assertIsOff() } - fun toggleAll() { - composeTestRule.onNodeWithTag("toggleAllButton", useUnmergedTree = true) - .performClick() - } - - fun selectRelevanceSortType() { - composeTestRule.onNodeWithTag("relevanceTypeSelector") - .performClick() + fun assertSortByDetails() { + composeTestRule.onNodeWithText("Sort By").assertIsDisplayed() + composeTestRule.onNodeWithTag("relevanceTypeSelector").assertIsDisplayed() + composeTestRule.onNodeWithTag("typeTypeSelector").assertIsDisplayed() } - fun selectTypeSortType() { - composeTestRule.onNodeWithTag("typeTypeSelector") - .performClick() + fun assertRadioButtonSelected(sortText: String) { + if(sortText == "Relevance") composeTestRule.onNodeWithTag("relevanceRadioButton").assertIsSelected() + else if(sortText == "Type") composeTestRule.onNodeWithTag("typeRadioButton").assertIsSelected() } } \ No newline at end of file