diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/classic/GradesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/classic/GradesE2ETest.kt index 0878f955e8..e0f42a3526 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/classic/GradesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/classic/GradesE2ETest.kt @@ -24,6 +24,7 @@ import com.instructure.canvas.espresso.TestCategory import com.instructure.canvas.espresso.TestMetaData import com.instructure.canvas.espresso.annotations.E2E import com.instructure.dataseeding.api.AssignmentsApi +import com.instructure.dataseeding.api.CoursesApi import com.instructure.dataseeding.api.QuizzesApi import com.instructure.dataseeding.api.SubmissionsApi import com.instructure.dataseeding.model.GradingType @@ -219,6 +220,103 @@ class GradesE2ETest: StudentComposeTest() { gradesPage.assertAllAssignmentItemCount(3) } + @E2E + @Test + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.E2E) + fun testShowOnlyLetterGradeOnGradesPageE2E() { + Log.d(PREPARATION_TAG, "Seeding data.") + val data = seedData(teachers = 1, courses = 1, students = 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.") + val pointsTextAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.POINTS, pointsPossible = 15.0, dueAt = 1.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) + + Log.d(STEP_TAG, "Login with user: '${student.name}', login id: '${student.loginId}'.") + tokenLogin(student) + dashboardPage.waitForRender() + + Log.d(PREPARATION_TAG, "Grade submission: '${pointsTextAssignment.name}' with 12 points.") + SubmissionsApi.gradeSubmission(teacher.token, course.id, pointsTextAssignment.id, student.id, postedGrade = "12") + + Log.d(ASSERTION_TAG, "Assert that the grade is not displayed on the course's card by default.") + dashboardPage.assertCourseGradeNotDisplayed(course.name, "N/A", false) + + Log.d(STEP_TAG, "Toggle ON 'Show Grades' and navigate back to Dashboard Page.") + leftSideNavigationDrawerPage.setShowGrades(true) + + Log.d(ASSERTION_TAG, "Assert that the grade is displayed on the course's card.") + dashboardPage.assertCourseGrade(course.name, "N/A") + + Log.d(PREPARATION_TAG, "Update '${course.name}' course's settings: Enable restriction for quantitative data.") + val restrictQuantitativeDataMap = mutableMapOf() + restrictQuantitativeDataMap["restrict_quantitative_data"] = true + CoursesApi.updateCourseSettings(course.id, restrictQuantitativeDataMap) + + Log.d(ASSERTION_TAG, "Refresh the Dashboard page. Assert that the course grade is B-, as it is converted to letter grade because of the restriction.") + retryWithIncreasingDelay(times = 15, maxDelay = 5000) { + dashboardPage.refresh() + dashboardPage.assertCourseGrade(course.name, "B-") + } + + Log.d(PREPARATION_TAG, "Seeding 'Text Entry' assignment for '${course.name}' course.") + val percentageAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.PERCENT, pointsPossible = 15.0, dueAt = 1.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) + + Log.d(PREPARATION_TAG, "Grade submission: '${percentageAssignment.name}' with 66% of the maximum points (aka. 10).") + SubmissionsApi.gradeSubmission(teacher.token, course.id, percentageAssignment.id, student.id, postedGrade = "10") + + Log.d(PREPARATION_TAG, "Seeding 'Text Entry' assignment for '${course.name}' course.") + val letterGradeAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.LETTER_GRADE, pointsPossible = 15.0, dueAt = 1.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) + + Log.d(PREPARATION_TAG, "Grade submission: '${letterGradeAssignment.name}' with C.") + SubmissionsApi.gradeSubmission(teacher.token, course.id, letterGradeAssignment.id, student.id, postedGrade = "C") + + Log.d(PREPARATION_TAG, "Seeding 'Text Entry' assignment for '${course.name}' course.") + val passFailAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.PASS_FAIL, pointsPossible = 15.0, dueAt = 1.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) + + Log.d(PREPARATION_TAG, "Grade submission: '${passFailAssignment.name}' with 'Incomplete'.") + SubmissionsApi.gradeSubmission(teacher.token, course.id, passFailAssignment.id, student.id, postedGrade = "Incomplete") + + Log.d(PREPARATION_TAG, "Seeding 'Text Entry' assignment for '${course.name}' course.") + val gpaScaleAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.GPA_SCALE, pointsPossible = 15.0, dueAt = 1.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) + + Log.d(PREPARATION_TAG, "Grade submission: '${gpaScaleAssignment.name}' with 3.7.") + SubmissionsApi.gradeSubmission(teacher.token, course.id, gpaScaleAssignment.id, student.id, postedGrade = "3.7") + + Log.d(STEP_TAG, "Refresh the Dashboard page to let the newly added submissions and their grades propagate.") + dashboardPage.refresh() + + Log.d(STEP_TAG, "Select course: '${course.name}'. Select 'Grades' menu.") + dashboardPage.selectCourse(course) + courseBrowserPage.selectGrades() + + Log.d(ASSERTION_TAG, "Assert that the Total Grade is 'F' and all of the assignment grades are displayed properly (so they have been converted to letter grade).") + gradesPage.assertTotalGradeText("F") + gradesPage.assertAssignmentGradeText(pointsTextAssignment.name, "B-") + gradesPage.assertAssignmentGradeText(percentageAssignment.name, "D") + gradesPage.assertAssignmentGradeText(letterGradeAssignment.name, "C") + gradesPage.assertAssignmentGradeText(passFailAssignment.name, "Incomplete") + gradesPage.assertAssignmentGradeText(gpaScaleAssignment.name, "F") + + Log.d(PREPARATION_TAG, "Update '${course.name}' course's settings: Disable restriction for quantitative data.") + restrictQuantitativeDataMap["restrict_quantitative_data"] = false + CoursesApi.updateCourseSettings(course.id, restrictQuantitativeDataMap) + + Log.d(STEP_TAG, "Swipe to the top of the Course Grades Page and refresh it.") + gradesPage.scrollDownScreen() // First go to the top of the recycler view + gradesPage.refresh() // Actual refresh + + Log.d(ASSERTION_TAG, "Assert that the Total Grade is '49.47%' and all of the assignment grades are displayed properly. We now show numeric grades because restriction to quantitative data has been disabled.") + gradesPage.assertTotalGradeText("49.47%") + gradesPage.assertAssignmentGradeText(pointsTextAssignment.name, "12/15") + gradesPage.assertAssignmentGradeText(percentageAssignment.name, "66.67%") + gradesPage.assertAssignmentGradeText(letterGradeAssignment.name, "11.4/15 (C)") + gradesPage.assertAssignmentGradeText(passFailAssignment.name, "Incomplete") + gradesPage.scrollDownScreen() + gradesPage.assertAssignmentGradeText(gpaScaleAssignment.name, "3.7/15 (F)") + } + private fun makeQuizQuestions() = listOf( QuizQuestion( pointsPossible = 5, diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/compose/AssignmentsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/compose/AssignmentsE2ETest.kt index c3f3a4ac1f..883b0110e9 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/compose/AssignmentsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/compose/AssignmentsE2ETest.kt @@ -35,6 +35,7 @@ import com.instructure.dataseeding.api.AssignmentGroupsApi import com.instructure.dataseeding.api.AssignmentsApi import com.instructure.dataseeding.api.CoursesApi import com.instructure.dataseeding.api.FileUploadsApi +import com.instructure.dataseeding.api.GradingPeriodsApi import com.instructure.dataseeding.api.SubmissionsApi import com.instructure.dataseeding.model.FileUploadType import com.instructure.dataseeding.model.GradingType @@ -690,7 +691,7 @@ class AssignmentsE2ETest: StudentComposeTest() { @E2E @Test @TestMetaData(Priority.MANDATORY, FeatureCategory.ASSIGNMENTS, TestCategory.E2E) - fun testMultipleAssignmentsE2E() { + fun testMultipleAssignmentsWithSearchE2E() { Log.d(PREPARATION_TAG, "Seeding data.") val data = seedData(teachers = 1, courses = 1, students = 1) @@ -729,6 +730,36 @@ class AssignmentsE2ETest: StudentComposeTest() { Log.d(ASSERTION_TAG, "Assert that '${letterGradeTextAssignment.name}' assignment is displayed with the corresponding grade: 16.") assignmentListPage.assertHasAssignment(letterGradeTextAssignment, "16") + + Log.d(STEP_TAG, "Click on the 'Search' (magnifying glass) icon at the toolbar.") + assignmentListPage.searchBar.clickOnSearchButton() + + Log.d(STEP_TAG, "Type the name of the '${letterGradeTextAssignment.name}' assignment into the search bar.") + assignmentListPage.searchBar.typeToSearchBar(letterGradeTextAssignment.name) + + Log.d(ASSERTION_TAG, "Assert that only '${letterGradeTextAssignment.name}' is displayed and '${pointsTextAssignment.name}' is not.") + assignmentListPage.assertHasAssignment(letterGradeTextAssignment) + assignmentListPage.assertAssignmentNotDisplayed(pointsTextAssignment.name) + + Log.d(STEP_TAG, "Clear the search input.") + assignmentListPage.searchBar.clickOnClearSearchButton() + + Log.d(ASSERTION_TAG, "Assert that both assignments are visible again after clearing the search.") + assignmentListPage.assertHasAssignment(pointsTextAssignment, "13") + assignmentListPage.assertHasAssignment(letterGradeTextAssignment, "16") + + Log.d(STEP_TAG, "Type a search query that does not match any assignment.") + assignmentListPage.searchBar.typeToSearchBar("xxxxxxxxxxx") + + Log.d(ASSERTION_TAG, "Assert that the empty state ('No Assignments') view is displayed when no assignments match the search query.") + assignmentListPage.assertDisplaysNoAssignmentsView() + + Log.d(STEP_TAG, "Close the search bar.") + assignmentListPage.searchBar.pressSearchBarButton() + + Log.d(ASSERTION_TAG, "Assert that both assignments are displayed again after closing the search bar.") + assignmentListPage.assertHasAssignment(pointsTextAssignment, "13") + assignmentListPage.assertHasAssignment(letterGradeTextAssignment, "16") } @E2E @@ -741,13 +772,13 @@ class AssignmentsE2ETest: StudentComposeTest() { val teacher = data.teachersList[0] val course = data.coursesList[0] - Log.d(PREPARATION_TAG, "Seeding assignment for '${course.name}' course.") - val upcomingAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.LETTER_GRADE, pointsPossible = 20.0, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) + Log.d(PREPARATION_TAG, "Seeding an upcoming assignment (future due date) for '${course.name}' course.") + val upcomingAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.LETTER_GRADE, pointsPossible = 20.0, dueAt = 1.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) - Log.d(PREPARATION_TAG, "Seeding assignment for '${course.name}' course.") + Log.d(PREPARATION_TAG, "Seeding an overdue assignment (past due date) for '${course.name}' course.") val missingAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.LETTER_GRADE, pointsPossible = 20.0, dueAt = 2.days.ago.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) - Log.d(PREPARATION_TAG, "Seeding a GRADED assignment for '${course.name}' course.") + Log.d(PREPARATION_TAG, "Seeding a GRADED assignment (no due date) for '${course.name}' course.") val gradedAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.LETTER_GRADE, pointsPossible = 20.0, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) Log.d(PREPARATION_TAG, "Grade the '${gradedAssignment.name}' with '11' points out of 20.") @@ -756,7 +787,7 @@ class AssignmentsE2ETest: StudentComposeTest() { Log.d(PREPARATION_TAG, "Create an Assignment Group for '${course.name}' course.") val assignmentGroup = AssignmentGroupsApi.createAssignmentGroup(teacher.token, course.id, name = "Discussions") - Log.d(PREPARATION_TAG, "Seeding assignment for '${course.name}' course.") + Log.d(PREPARATION_TAG, "Seeding an assignment in the 'Discussions' group (no due date) for '${course.name}' course.") val otherTypeAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.LETTER_GRADE, pointsPossible = 20.0, assignmentGroupId = assignmentGroup.id, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) Log.d(STEP_TAG, "Login with user: '${student.name}', login id: '${student.loginId}'.") @@ -773,22 +804,22 @@ class AssignmentsE2ETest: StudentComposeTest() { assignmentListPage.assertHasAssignment(otherTypeAssignment) assignmentListPage.assertHasAssignment(gradedAssignment) - Log.d(STEP_TAG, "Filter the 'Not Yet Submitted' assignments.") + Log.d(STEP_TAG, "Filter to show only 'Not Yet Submitted' assignments.") assignmentListPage.filterAssignments("Assignment Filter", AssignmentListPage.FilterOption.ToBeGraded) assignmentListPage.filterAssignments("Assignment Filter", AssignmentListPage.FilterOption.Graded) assignmentListPage.filterAssignments("Assignment Filter", AssignmentListPage.FilterOption.Other) - Log.d(ASSERTION_TAG, "Assert that the '${missingAssignment.name}' 'Not Yet Submitted' assignment is displayed and the others at NOT.") + Log.d(ASSERTION_TAG, "Assert that only the '${missingAssignment.name}' 'Not Yet Submitted' assignment is displayed and the others are NOT.") assignmentListPage.assertHasAssignment(missingAssignment) assignmentListPage.assertAssignmentNotDisplayed(upcomingAssignment.name) assignmentListPage.assertAssignmentNotDisplayed(otherTypeAssignment.name) assignmentListPage.assertAssignmentNotDisplayed(gradedAssignment.name) - Log.d(STEP_TAG, "Filter the 'GRADED' assignments.") + Log.d(STEP_TAG, "Filter to show only 'Graded' assignments.") assignmentListPage.filterAssignments("Assignment Filter", AssignmentListPage.FilterOption.NotYetSubmitted) assignmentListPage.filterAssignments("Assignment Filter", AssignmentListPage.FilterOption.Graded) - Log.d(ASSERTION_TAG, "Assert that the '${gradedAssignment.name}' GRADED assignment is displayed.") + Log.d(ASSERTION_TAG, "Assert that only the '${gradedAssignment.name}' 'Graded' assignment is displayed and the others are NOT.") assignmentListPage.assertHasAssignment(gradedAssignment) assignmentListPage.assertAssignmentNotDisplayed(upcomingAssignment.name) assignmentListPage.assertAssignmentNotDisplayed(otherTypeAssignment.name) @@ -799,8 +830,10 @@ class AssignmentsE2ETest: StudentComposeTest() { assignmentListPage.filterAssignments("Assignment Filter", AssignmentListPage.FilterOption.ToBeGraded) assignmentListPage.filterAssignments("Assignment Filter", AssignmentListPage.FilterOption.Other) - Log.d(ASSERTION_TAG, "Assert that still all the assignment are displayed and the corresponding groups (Assignments, Discussions) as well.") + Log.d(STEP_TAG, "Group assignments by 'Assignment Group'.") assignmentListPage.groupByAssignments(AssignmentListPage.GroupByOption.AssignmentGroup) + + Log.d(ASSERTION_TAG, "Assert that the 'Assignments' and 'Discussions' groups are displayed with all assignments inside.") assignmentListPage.assertAssignmentGroupDisplayed("Assignments") assignmentListPage.assertAssignmentGroupDisplayed("Discussions") assignmentListPage.assertHasAssignment(upcomingAssignment) @@ -811,31 +844,120 @@ class AssignmentsE2ETest: StudentComposeTest() { Log.d(STEP_TAG, "Collapse the 'Assignments' assignment group.") assignmentListPage.expandCollapseAssignmentGroup("Assignments") - Log.d(ASSERTION_TAG, "Assert that it's items are not displayed when the group is collapsed.") + Log.d(ASSERTION_TAG, "Assert that the items inside 'Assignments' group are NOT displayed when collapsed.") assignmentListPage.assertAssignmentNotDisplayed(upcomingAssignment.name) assignmentListPage.assertAssignmentNotDisplayed(missingAssignment.name) assignmentListPage.assertAssignmentNotDisplayed(gradedAssignment.name) - Log.d(ASSERTION_TAG, "Assert that the other group's item is still displayed.") + Log.d(ASSERTION_TAG, "Assert that the 'Discussions' group item is still displayed.") assignmentListPage.assertHasAssignment(otherTypeAssignment) - Log.d(STEP_TAG, "Expand the 'Assignments' assignment group.") + Log.d(STEP_TAG, "Expand the 'Assignments' group back.") assignmentListPage.expandCollapseAssignmentGroup("Assignments") - Log.d(STEP_TAG, "Click on the 'Search' (magnifying glass) icon at the toolbar.") - assignmentListPage.searchBar.clickOnSearchButton() + Log.d(ASSERTION_TAG, "Assert that all assignments are visible again after expanding.") + assignmentListPage.assertHasAssignment(upcomingAssignment) + assignmentListPage.assertHasAssignment(missingAssignment) + assignmentListPage.assertHasAssignment(gradedAssignment) + + Log.d(STEP_TAG, "Group assignments by 'Due Date'.") + assignmentListPage.groupByAssignments(AssignmentListPage.GroupByOption.DueDate) + + Log.d(ASSERTION_TAG, "Assert that the three due date groups are displayed.") + assignmentListPage.assertAssignmentGroupDisplayed("Overdue Assignments") + assignmentListPage.assertAssignmentGroupDisplayed("Upcoming Assignments") + assignmentListPage.assertAssignmentGroupDisplayed("Undated Assignments") + + Log.d(ASSERTION_TAG, "Assert that all assignments are visible in the correct due date groups: '${missingAssignment.name}' overdue, '${upcomingAssignment.name}' upcoming, '${gradedAssignment.name}' and '${otherTypeAssignment.name}' undated.") + assignmentListPage.assertHasAssignment(missingAssignment) + assignmentListPage.assertHasAssignment(upcomingAssignment) + assignmentListPage.assertHasAssignment(gradedAssignment) + assignmentListPage.assertHasAssignment(otherTypeAssignment) + + Log.d(STEP_TAG, "Collapse the 'Overdue Assignments' group.") + assignmentListPage.expandCollapseAssignmentGroup("Overdue Assignments") + + Log.d(ASSERTION_TAG, "Assert that the '${missingAssignment.name}' overdue assignment is NOT displayed when the group is collapsed.") + assignmentListPage.assertAssignmentNotDisplayed(missingAssignment.name) + + Log.d(ASSERTION_TAG, "Assert that assignments in the other groups are still displayed.") + assignmentListPage.assertHasAssignment(upcomingAssignment) + assignmentListPage.assertHasAssignment(gradedAssignment) + assignmentListPage.assertHasAssignment(otherTypeAssignment) - Log.d(STEP_TAG, "Type the name of the '${missingAssignment.name}' assignment.") - assignmentListPage.searchBar.typeToSearchBar(missingAssignment.name.drop(5)) + Log.d(STEP_TAG, "Expand the 'Overdue Assignments' group back.") + assignmentListPage.expandCollapseAssignmentGroup("Overdue Assignments") - Log.d(ASSERTION_TAG, "Assert that the '${missingAssignment.name}' assignment has been found by previously typed search string.") - sleep(3000) // Allow the search input to propagate + Log.d(ASSERTION_TAG, "Assert that the '${missingAssignment.name}' overdue assignment is visible again.") assignmentListPage.assertHasAssignment(missingAssignment) + + Log.d(STEP_TAG, "Collapse all three due date groups.") + assignmentListPage.expandCollapseAssignmentGroup("Overdue Assignments") + assignmentListPage.expandCollapseAssignmentGroup("Upcoming Assignments") + assignmentListPage.expandCollapseAssignmentGroup("Undated Assignments") + + Log.d(ASSERTION_TAG, "Assert that none of the assignments is displayed when all groups are collapsed.") + assignmentListPage.assertAssignmentNotDisplayed(missingAssignment.name) assignmentListPage.assertAssignmentNotDisplayed(upcomingAssignment.name) assignmentListPage.assertAssignmentNotDisplayed(otherTypeAssignment.name) assignmentListPage.assertAssignmentNotDisplayed(gradedAssignment.name) } + @E2E + @Test + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.E2E) + fun testGradingPeriodFilterE2E() { + Log.d(PREPARATION_TAG, "Seeding data with a grading period enabled.") + val data = seedData(teachers = 1, courses = 1, students = 1, gradingPeriods = true) + val student = data.studentsList[0] + val teacher = data.teachersList[0] + val course = data.coursesList[0] + + Log.d(PREPARATION_TAG, "Fetching the grading period for '${course.name}' course.") + val gradingPeriod = GradingPeriodsApi.getGradingPeriodsOfCourse(course.id).gradingPeriods[0] + + Log.d(PREPARATION_TAG, "Seeding an assignment due within the grading period (2 days from now) for '${course.name}' course.") + val assignmentInPeriod = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.POINTS, pointsPossible = 10.0, dueAt = 2.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) + + Log.d(PREPARATION_TAG, "Seeding an assignment due outside the grading period (30 days ago) for '${course.name}' course.") + val assignmentOutsidePeriod = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.POINTS, pointsPossible = 10.0, dueAt = 30.days.ago.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) + + Log.d(STEP_TAG, "Login with user: '${student.name}', login id: '${student.loginId}'.") + tokenLogin(student) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Select '${course.name}' course and navigate to its Assignments Page.") + dashboardPage.selectCourse(course) + courseBrowserPage.selectAssignments() + + Log.d(ASSERTION_TAG, "Assert that the grading period header shows '${gradingPeriod.title}' as the current grading period is pre-selected by default.") + assignmentListPage.assertGradingPeriodLabel(gradingPeriod.title) + + Log.d(ASSERTION_TAG, "Assert that only '${assignmentInPeriod.name}' is displayed by default (within the grading period) and '${assignmentOutsidePeriod.name}' is NOT.") + assignmentListPage.assertHasAssignment(assignmentInPeriod) + assignmentListPage.assertAssignmentNotDisplayed(assignmentOutsidePeriod.name) + + Log.d(STEP_TAG, "Switch the grading period filter to 'All Grading Periods'.") + assignmentListPage.filterAssignments("Grading Period", AssignmentListPage.FilterOption.GradingPeriod("All Grading Periods")) + + Log.d(ASSERTION_TAG, "Assert that the grading period header now shows 'All'.") + assignmentListPage.assertGradingPeriodLabel() + + Log.d(ASSERTION_TAG, "Assert that both assignments are visible when 'All Grading Periods' is selected.") + assignmentListPage.assertHasAssignment(assignmentInPeriod) + assignmentListPage.assertHasAssignment(assignmentOutsidePeriod) + + Log.d(STEP_TAG, "Switch back to the '${gradingPeriod.title}' grading period.") + assignmentListPage.filterAssignments("Grading Period", AssignmentListPage.FilterOption.GradingPeriod(gradingPeriod.title)) + + Log.d(ASSERTION_TAG, "Assert that the grading period header shows '${gradingPeriod.title}' again.") + assignmentListPage.assertGradingPeriodLabel(gradingPeriod.title) + + Log.d(ASSERTION_TAG, "Assert that only '${assignmentInPeriod.name}' is displayed again and '${assignmentOutsidePeriod.name}' is NOT.") + assignmentListPage.assertHasAssignment(assignmentInPeriod) + assignmentListPage.assertAssignmentNotDisplayed(assignmentOutsidePeriod.name) + } + @E2E @Test @TestMetaData(Priority.MANDATORY, FeatureCategory.COMMENTS, TestCategory.E2E) @@ -1453,101 +1575,4 @@ class AssignmentsE2ETest: StudentComposeTest() { "Video position did not change. First: $firstVideoPositionText, Second: $secondVideoPositionText" } } - - @E2E - @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.E2E) - fun testShowOnlyLetterGradeOnGradesPageE2E() { - Log.d(PREPARATION_TAG, "Seeding data.") - val data = seedData(teachers = 1, courses = 1, students = 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.") - val pointsTextAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.POINTS, pointsPossible = 15.0, dueAt = 1.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) - - Log.d(STEP_TAG, "Login with user: '${student.name}', login id: '${student.loginId}'.") - tokenLogin(student) - dashboardPage.waitForRender() - - Log.d(PREPARATION_TAG, "Grade submission: '${pointsTextAssignment.name}' with 12 points.") - SubmissionsApi.gradeSubmission(teacher.token, course.id, pointsTextAssignment.id, student.id, postedGrade = "12") - - Log.d(ASSERTION_TAG, "Assert that the grade is not displayed on the course's card by default.") - dashboardPage.assertCourseGradeNotDisplayed(course.name, "N/A", false) - - Log.d(STEP_TAG, "Toggle ON 'Show Grades' and navigate back to Dashboard Page.") - leftSideNavigationDrawerPage.setShowGrades(true) - - Log.d(ASSERTION_TAG, "Assert that the grade is displayed on the course's card.") - dashboardPage.assertCourseGrade(course.name, "N/A") - - Log.d(PREPARATION_TAG, "Update '${course.name}' course's settings: Enable restriction for quantitative data.") - val restrictQuantitativeDataMap = mutableMapOf() - restrictQuantitativeDataMap["restrict_quantitative_data"] = true - CoursesApi.updateCourseSettings(course.id, restrictQuantitativeDataMap) - - Log.d(ASSERTION_TAG, "Refresh the Dashboard page. Assert that the course grade is B-, as it is converted to letter grade because of the restriction.") - retryWithIncreasingDelay(times = 15, maxDelay = 5000) { - dashboardPage.refresh() - dashboardPage.assertCourseGrade(course.name, "B-") - } - - Log.d(PREPARATION_TAG, "Seeding 'Text Entry' assignment for '${course.name}' course.") - val percentageAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.PERCENT, pointsPossible = 15.0, dueAt = 1.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) - - Log.d(PREPARATION_TAG, "Grade submission: '${percentageAssignment.name}' with 66% of the maximum points (aka. 10).") - SubmissionsApi.gradeSubmission(teacher.token, course.id, percentageAssignment.id, student.id, postedGrade = "10") - - Log.d(PREPARATION_TAG, "Seeding 'Text Entry' assignment for '${course.name}' course.") - val letterGradeAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.LETTER_GRADE, pointsPossible = 15.0, dueAt = 1.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) - - Log.d(PREPARATION_TAG, "Grade submission: '${letterGradeAssignment.name}' with C.") - SubmissionsApi.gradeSubmission(teacher.token, course.id, letterGradeAssignment.id, student.id, postedGrade = "C") - - Log.d(PREPARATION_TAG, "Seeding 'Text Entry' assignment for '${course.name}' course.") - val passFailAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.PASS_FAIL, pointsPossible = 15.0, dueAt = 1.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) - - Log.d(PREPARATION_TAG, "Grade submission: '${passFailAssignment.name}' with 'Incomplete'.") - SubmissionsApi.gradeSubmission(teacher.token, course.id, passFailAssignment.id, student.id, postedGrade = "Incomplete") - - Log.d(PREPARATION_TAG, "Seeding 'Text Entry' assignment for '${course.name}' course.") - val gpaScaleAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.GPA_SCALE, pointsPossible = 15.0, dueAt = 1.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) - - Log.d(PREPARATION_TAG, "Grade submission: '${gpaScaleAssignment.name}' with 3.7.") - SubmissionsApi.gradeSubmission(teacher.token, course.id, gpaScaleAssignment.id, student.id, postedGrade = "3.7") - - Log.d(STEP_TAG, "Refresh the Dashboard page to let the newly added submissions and their grades propagate.") - dashboardPage.refresh() - - Log.d(STEP_TAG, "Select course: '${course.name}'. Select 'Grades' menu.") - dashboardPage.selectCourse(course) - courseBrowserPage.selectGrades() - - Log.d(ASSERTION_TAG, "Assert that the Total Grade is 'F' and all of the assignment grades are displayed properly (so they have been converted to letter grade).") - gradesPage.assertTotalGradeText("F") - gradesPage.assertAssignmentGradeText(pointsTextAssignment.name, "B-") - gradesPage.assertAssignmentGradeText(percentageAssignment.name, "D") - gradesPage.assertAssignmentGradeText(letterGradeAssignment.name, "C") - gradesPage.assertAssignmentGradeText(passFailAssignment.name, "Incomplete") - gradesPage.assertAssignmentGradeText(gpaScaleAssignment.name, "F") - - Log.d(PREPARATION_TAG, "Update '${course.name}' course's settings: Enable restriction for quantitative data.") - restrictQuantitativeDataMap["restrict_quantitative_data"] = false - CoursesApi.updateCourseSettings(course.id, restrictQuantitativeDataMap) - - Log.d(STEP_TAG, "Swipe to the top of the Course Grades Page and refresh it.") - gradesPage.scrollDownScreen() // First go to the top of the recycler view - gradesPage.refresh() // Actual refresh - - Log.d(ASSERTION_TAG, "Assert that the Total Grade is '49.47%' and all of the assignment grades are displayed properly. We now show numeric grades because restriction to quantitative data has been disabled.") - gradesPage.assertTotalGradeText("49.47%") - gradesPage.assertAssignmentGradeText(pointsTextAssignment.name, "12/15") - gradesPage.assertAssignmentGradeText(percentageAssignment.name, "66.67%") - gradesPage.assertAssignmentGradeText(letterGradeAssignment.name, "11.4/15 (C)") - gradesPage.assertAssignmentGradeText(passFailAssignment.name, "Incomplete") - gradesPage.scrollDownScreen() - gradesPage.assertAssignmentGradeText(gpaScaleAssignment.name, "3.7/15 (F)") - } } \ No newline at end of file diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/compose/AssignmentE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/compose/AssignmentE2ETest.kt index 0236863495..d8f019cbd5 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/compose/AssignmentE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/compose/AssignmentE2ETest.kt @@ -28,14 +28,18 @@ import com.instructure.canvas.espresso.SecondaryFeatureCategory import com.instructure.canvas.espresso.TestCategory import com.instructure.canvas.espresso.TestMetaData import com.instructure.canvas.espresso.annotations.E2E +import com.instructure.canvas.espresso.common.pages.compose.AssignmentListPage +import com.instructure.dataseeding.api.AssignmentGroupsApi import com.instructure.canvas.espresso.utils.refresh import com.instructure.dataseeding.api.AssignmentsApi import com.instructure.dataseeding.api.FileUploadsApi +import com.instructure.dataseeding.api.GradingPeriodsApi import com.instructure.dataseeding.api.SectionsApi import com.instructure.dataseeding.api.SubmissionsApi import com.instructure.dataseeding.model.FileUploadType import com.instructure.dataseeding.model.GradingType import com.instructure.dataseeding.model.SubmissionType +import com.instructure.dataseeding.util.ago import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 @@ -51,6 +55,9 @@ import com.instructure.teacher.ui.utils.extensions.uploadTextFile import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Rule import org.junit.Test +import java.text.SimpleDateFormat +import java.util.Locale +import com.instructure.teacher.R as TeacherR @HiltAndroidTest class AssignmentE2ETest : TeacherComposeTest() { @@ -92,9 +99,10 @@ class AssignmentE2ETest : TeacherComposeTest() { assignmentListPage.assertDisplaysNoAssignmentsView() Log.d(PREPARATION_TAG, "Seeding 'Text Entry' assignment for '${course.name}' course.") + val assignmentDueCal = 1.days.fromNow val assignment = seedAssignments( courseId = course.id, - dueAt = 1.days.fromNow.iso8601, + dueAt = assignmentDueCal.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY), teacherToken = teacher.token, pointsPossible = 15.0 @@ -304,16 +312,14 @@ class AssignmentE2ETest : TeacherComposeTest() { editAssignmentDetailsPage.clickOnDisplayGradeAsSpinner() editAssignmentDetailsPage.selectGradeType("Percentage") - // TODO: Fix this - MBL-19110 - /* - Log.d(STEP_TAG, "Click on the 'Due Time' section and edit the hour and minutes to 1:30 PM.") + Log.d(STEP_TAG, "Edit the due date to Dec 12, 2030 and the due time to 1:30 AM.") editAssignmentDetailsPage.clickEditDueDate() - editAssignmentDetailsPage.editDate(2022,12,12) + editAssignmentDetailsPage.editDate(2030, 12, 12) editAssignmentDetailsPage.clickEditDueTime() editAssignmentDetailsPage.editTime(1, 30) - Log.d(ASSERTION_TAG, "Assert that the changes have been applied on Edit Assignment Details page.") - editAssignmentDetailsPage.assertTimeChanged(1, 30, R.id.dueTime) + Log.d(ASSERTION_TAG, "Assert that the due time change has been applied on the Edit Assignment Details page.") + editAssignmentDetailsPage.assertTimeChanged(1, 30, TeacherR.id.dueTime) Log.d(STEP_TAG, "Click on 'Assigned To' spinner and select '${student.name}' besides 'Everyone'.") editAssignmentDetailsPage.editAssignees() @@ -321,43 +327,41 @@ class AssignmentE2ETest : TeacherComposeTest() { assigneeListPage.toggleAssignees(listOf(student.name)) val expectedAssignees = listOf(student.name, "Everyone else") - Log.d(ASSERTION_TAG, "Assert that '${student.name}' and 'Everyone else' is selected as well.") + Log.d(ASSERTION_TAG, "Assert that '${student.name}' and 'Everyone else' are selected.") assigneeListPage.assertAssigneesSelected(expectedAssignees) Log.d(STEP_TAG, "Save and close the assignee list.") assigneeListPage.saveAndClose() - val assignText = editAssignmentDetailsPage.onViewWithId(R.id.assignTo) - Log.d(ASSERTION_TAG, "Assert that on the Assignment Details Page both the '${student.name}' and the 'Everyone else' values are set.") - for (assignee in expectedAssignees) assignText.assertContainsText(assignee) + Log.d(ASSERTION_TAG, "Assert that on the Edit Assignment Details Page both '${student.name}' and 'Everyone else' are set as assignees.") + expectedAssignees.forEach { editAssignmentDetailsPage.assertContainsAssignee(it) } - Log.d(STEP_TAG, "Save the assignment.") + Log.d(STEP_TAG, "Save the assignment and refresh the page.") editAssignmentDetailsPage.saveAssignment() - - Log.d(ASSERTION_TAG, "Refresh the page. Assert that the points of '$newAssignmentName' assignment has been changed to 20.") assignmentDetailsPage.refresh() assignmentDetailsPage.waitForRender() + + Log.d(ASSERTION_TAG, "Assert that the points of '$newAssignmentName' assignment has been changed to 20.") assignmentDetailsPage.assertAssignmentPointsChanged("20") Log.d(ASSERTION_TAG, "Assert that there are multiple due dates set, so the 'Multiple Due Dates' string is displayed on the 'Due Dates' section.") assignmentDetailsPage.assertMultipleDueDates() - Log.d(STEP_TAG, "Open Due Dates Page.") - assignmentDetailsPage.openAllDatesPage() + Log.d(STEP_TAG, "Open the Due Dates Page.") + assignmentDetailsPage.openDueDatesPage() Log.d(ASSERTION_TAG, "Assert that there are 2 different due dates set.") assignmentDueDatesPage.assertDueDatesCount(2) - Log.d(ASSERTION_TAG, "Assert that there is a due date set for '${student.name}' student especially and another one for everyone else.") + Log.d(ASSERTION_TAG, "Assert that there is a due date set for '${student.name}' specifically and another one for 'Everyone else'.") assignmentDueDatesPage.assertDueFor(student.name) - assignmentDueDatesPage.assertDueFor(R.string.everyone_else) - - val dueDateForEveryoneElse = "Dec 12 at 1:30 AM" - val dueDateForStudentSpecially = "Dec 12 at 9:30 AM" - Log.d(ASSERTION_TAG, "Assert that the there is a due date with '$dueDateForEveryoneElse' value and another one with '$dueDateForStudentSpecially'.") - assignmentDueDatesPage.assertDueDateTime("Due $dueDateForEveryoneElse") - assignmentDueDatesPage.assertDueDateTime("Due $dueDateForStudentSpecially") - */ + assignmentDueDatesPage.assertDueFor(TeacherR.string.everyone_else) + + val dueDateForEveryoneElse = "Dec 12, 2030 at 1:30 AM" + val dueDateForStudentSpecially = SimpleDateFormat("MMM d, yyyy 'at' h:mm a", Locale.getDefault()).format(assignmentDueCal.time) + Log.d(ASSERTION_TAG, "Assert that the due date for 'Everyone else' is '$dueDateForEveryoneElse' and for '${student.name}' is '$dueDateForStudentSpecially'.") + assignmentDueDatesPage.assertDueDateTime(dueDateForEveryoneElse) + assignmentDueDatesPage.assertDueDateTime(dueDateForStudentSpecially) } @E2E @@ -817,4 +821,245 @@ class AssignmentE2ETest : TeacherComposeTest() { } } + @E2E + @Test + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.E2E) + fun testGradingPeriodFiltersAndGroupByAssignmentsE2E() { + + Log.d(PREPARATION_TAG, "Seeding data with a grading period enabled.") + val data = seedData(teachers = 1, courses = 1, students = 1, gradingPeriods = true) + val student = data.studentsList[0] + val teacher = data.teachersList[0] + val course = data.coursesList[0] + + Log.d(PREPARATION_TAG, "Fetching the grading period for '${course.name}' course.") + val gradingPeriod = GradingPeriodsApi.getGradingPeriodsOfCourse(course.id).gradingPeriods.last() + + Log.d(PREPARATION_TAG, "Seeding an assignment due within the grading period (2 days from now) for '${course.name}' course.") + val assignmentInPeriod = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.POINTS, pointsPossible = 10.0, dueAt = 2.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) + + Log.d(PREPARATION_TAG, "Submit '${assignmentInPeriod.name}' assignment for '${student.name}' student.") + SubmissionsApi.seedAssignmentSubmission(course.id, student.token, assignmentInPeriod.id, submissionSeedsList = listOf(SubmissionsApi.SubmissionSeedInfo(amount = 1, submissionType = SubmissionType.ONLINE_TEXT_ENTRY))) + + Log.d(PREPARATION_TAG, "Creating a custom assignment group 'Custom Group' for '${course.name}' course.") + val customGroup = AssignmentGroupsApi.createAssignmentGroup(teacher.token, course.id, name = "Custom Group") + + Log.d(PREPARATION_TAG, "Seeding an assignment due outside the grading period (30 days ago) in the 'Custom Group' assignment group for '${course.name}' course.") + val assignmentOutsidePeriod = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.POINTS, pointsPossible = 10.0, dueAt = 30.days.ago.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY), assignmentGroupId = customGroup.id) + + Log.d(PREPARATION_TAG, "Seeding a quiz assignment due within the grading period (2 days from now) for '${course.name}' course.") + val quizAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.POINTS, pointsPossible = 10.0, dueAt = 2.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_QUIZ)) + + Log.d(STEP_TAG, "Login with user: '${teacher.name}', login id: '${teacher.loginId}'.") + tokenLogin(teacher) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Open '${course.name}' course and navigate to its Assignments Tab.") + dashboardPage.openCourse(course.name) + courseBrowserPage.openAssignmentsTab() + + Log.d(ASSERTION_TAG, "Assert that the grading period header shows '${gradingPeriod.title}' as the current grading period is pre-selected by default.") + assignmentListPage.assertGradingPeriodLabel(gradingPeriod.title) + + Log.d(ASSERTION_TAG, "Assert that '${assignmentInPeriod.name}' and '${quizAssignment.name}' are displayed by default (within the grading period) and '${assignmentOutsidePeriod.name}' is NOT.") + assignmentListPage.assertHasAssignment(assignmentInPeriod, needsGradingCount = 1) + assignmentListPage.assertHasAssignment(quizAssignment) + assignmentListPage.assertAssignmentNotDisplayed(assignmentOutsidePeriod.name) + + Log.d(STEP_TAG, "Switch the grading period filter to 'All Grading Periods'.") + assignmentListPage.filterAssignments("Grading Period", AssignmentListPage.FilterOption.GradingPeriod("All Grading Periods")) + + Log.d(ASSERTION_TAG, "Assert that the grading period header now shows 'All'.") + assignmentListPage.assertGradingPeriodLabel() + + Log.d(ASSERTION_TAG, "Assert that all assignments are visible when 'All Grading Periods' is selected.") + assignmentListPage.assertHasAssignment(assignmentInPeriod) + assignmentListPage.assertHasAssignment(assignmentOutsidePeriod) + assignmentListPage.assertHasAssignment(quizAssignment) + + Log.d(STEP_TAG, "Filter the Assignment Filter to 'Needs Grading'.") + assignmentListPage.filterAssignments("Assignment Filter", AssignmentListPage.FilterOption.NeedsGrading) + + Log.d(ASSERTION_TAG, "Assert that only '${assignmentInPeriod.name}' is displayed (it has an ungraded submission) and '${assignmentOutsidePeriod.name}' and '${quizAssignment.name}' are NOT.") + assignmentListPage.assertHasAssignment(assignmentInPeriod, needsGradingCount = 1) + assignmentListPage.assertAssignmentNotDisplayed(assignmentOutsidePeriod.name) + assignmentListPage.assertAssignmentNotDisplayed(quizAssignment.name) + + Log.d(STEP_TAG, "Filter the Assignment Filter to 'Not Submitted'.") + assignmentListPage.filterAssignments("Assignment Filter", AssignmentListPage.FilterOption.NoSubmission) + + Log.d(ASSERTION_TAG, "Assert that '${assignmentOutsidePeriod.name}' and '${quizAssignment.name}' are displayed (no submission) and '${assignmentInPeriod.name}' is NOT.") + assignmentListPage.assertHasAssignment(assignmentOutsidePeriod) + assignmentListPage.assertHasAssignment(quizAssignment) + assignmentListPage.assertAssignmentNotDisplayed(assignmentInPeriod.name) + + Log.d(STEP_TAG, "Reset the Assignment Filter back to 'All Assignments'.") + assignmentListPage.filterAssignments("Assignment Filter", AssignmentListPage.FilterOption.All) + + Log.d(STEP_TAG, "Click on '${quizAssignment.name}' assignment to navigate to its details page.") + assignmentListPage.clickAssignment(quizAssignment) + quizDetailsPage.waitForRender() + + Log.d(STEP_TAG, "Open the Edit Page and unpublish '${quizAssignment.name}' by toggling the publish switch.") + quizDetailsPage.openEditPage() + editAssignmentDetailsPage.clickPublishSwitch() + editAssignmentDetailsPage.saveAssignment() + quizDetailsPage.assertQuizUnpublished() + + Log.d(STEP_TAG, "Navigate back to the Assignment List Page.") + Espresso.pressBack() + composeTestRule.waitForIdle() + + Log.d(ASSERTION_TAG, "Assert that '${quizAssignment.name}' is now shown as 'Unpublished' on the assignment list.") + assignmentListPage.refreshAssignmentList() + assignmentListPage.assertPublishedState(quizAssignment.name, false) + + Log.d(STEP_TAG, "Filter the Status Filter to 'Unpublished'.") + assignmentListPage.filterAssignments("Status Filter", AssignmentListPage.FilterOption.Unpublished) + + Log.d(ASSERTION_TAG, "Assert that only '${quizAssignment.name}' is displayed (it is unpublished) and the published assignments are NOT.") + assignmentListPage.assertHasAssignment(quizAssignment) + assignmentListPage.assertAssignmentNotDisplayed(assignmentInPeriod.name) + assignmentListPage.assertAssignmentNotDisplayed(assignmentOutsidePeriod.name) + + Log.d(STEP_TAG, "Filter the Status Filter to 'Published'.") + assignmentListPage.filterAssignments("Status Filter", AssignmentListPage.FilterOption.Published) + + Log.d(ASSERTION_TAG, "Assert that '${assignmentInPeriod.name}' and '${assignmentOutsidePeriod.name}' are displayed (published) and '${quizAssignment.name}' is NOT (unpublished).") + assignmentListPage.assertHasAssignment(assignmentInPeriod) + assignmentListPage.assertHasAssignment(assignmentOutsidePeriod) + assignmentListPage.assertAssignmentNotDisplayed(quizAssignment.name) + + Log.d(STEP_TAG, "Reset the Status Filter back to 'All Assignments'.") + assignmentListPage.filterAssignments("Status Filter", AssignmentListPage.FilterOption.All) + + Log.d(STEP_TAG, "Group assignments by 'Assignment Group'.") + assignmentListPage.groupByAssignments(AssignmentListPage.GroupByOption.AssignmentGroup) + + Log.d(ASSERTION_TAG, "Assert that the 'Assignments' and '${customGroup.name}' groups are displayed with the corresponding assignments inside.") + assignmentListPage.assertAssignmentGroupDisplayed("Assignments") + assignmentListPage.assertAssignmentGroupDisplayed(customGroup.name) + assignmentListPage.assertHasAssignment(assignmentInPeriod) + assignmentListPage.assertHasAssignment(quizAssignment) + assignmentListPage.assertHasAssignment(assignmentOutsidePeriod) + + Log.d(STEP_TAG, "Collapse the '${customGroup.name}' assignment group.") + assignmentListPage.expandCollapseAssignmentGroup(customGroup.name) + + Log.d(ASSERTION_TAG, "Assert that '${assignmentOutsidePeriod.name}' is NOT displayed when its group is collapsed.") + assignmentListPage.assertAssignmentNotDisplayed(assignmentOutsidePeriod.name) + + Log.d(ASSERTION_TAG, "Assert that '${assignmentInPeriod.name}' and '${quizAssignment.name}' in the 'Assignments' group are still displayed.") + assignmentListPage.assertHasAssignment(assignmentInPeriod) + assignmentListPage.assertHasAssignment(quizAssignment) + + Log.d(STEP_TAG, "Expand the '${customGroup.name}' group back.") + assignmentListPage.expandCollapseAssignmentGroup(customGroup.name) + + Log.d(ASSERTION_TAG, "Assert that '${assignmentOutsidePeriod.name}' is visible again.") + assignmentListPage.assertHasAssignment(assignmentOutsidePeriod) + + Log.d(STEP_TAG, "Group assignments by 'Assignment Type'.") + assignmentListPage.groupByAssignments(AssignmentListPage.GroupByOption.AssignmentType) + + Log.d(ASSERTION_TAG, "Assert that the 'Assignments' and 'Quizzes' type groups are displayed with the corresponding assignments inside.") + assignmentListPage.assertAssignmentGroupDisplayed("Assignments") + assignmentListPage.assertAssignmentGroupDisplayed("Quizzes") + assignmentListPage.assertHasAssignment(assignmentInPeriod) + assignmentListPage.assertHasAssignment(assignmentOutsidePeriod) + assignmentListPage.assertHasAssignment(quizAssignment) + + Log.d(STEP_TAG, "Collapse the 'Quizzes' type group.") + assignmentListPage.expandCollapseAssignmentGroup("Quizzes") + + Log.d(ASSERTION_TAG, "Assert that '${quizAssignment.name}' is NOT displayed when the 'Quizzes' type group is collapsed.") + assignmentListPage.assertAssignmentNotDisplayed(quizAssignment.name) + + Log.d(ASSERTION_TAG, "Assert that the text entry assignments in the 'Assignments' group are still displayed.") + assignmentListPage.assertHasAssignment(assignmentInPeriod) + assignmentListPage.assertHasAssignment(assignmentOutsidePeriod) + + Log.d(STEP_TAG, "Expand the 'Quizzes' type group back.") + assignmentListPage.expandCollapseAssignmentGroup("Quizzes") + + Log.d(ASSERTION_TAG, "Assert that '${quizAssignment.name}' is visible again.") + assignmentListPage.assertHasAssignment(quizAssignment) + + Log.d(STEP_TAG, "Switch back to the '${gradingPeriod.title}' grading period.") + assignmentListPage.filterAssignments("Grading Period", AssignmentListPage.FilterOption.GradingPeriod(gradingPeriod.title)) + + Log.d(ASSERTION_TAG, "Assert that the grading period header shows '${gradingPeriod.title}' again.") + assignmentListPage.assertGradingPeriodLabel(gradingPeriod.title) + + Log.d(ASSERTION_TAG, "Assert that '${assignmentInPeriod.name}' and '${quizAssignment.name}' are displayed (within the period) and '${assignmentOutsidePeriod.name}' is NOT.") + assignmentListPage.assertHasAssignment(assignmentInPeriod, needsGradingCount = 1) + assignmentListPage.assertHasAssignment(quizAssignment) + assignmentListPage.assertAssignmentNotDisplayed(assignmentOutsidePeriod.name) + } + + @E2E + @Test + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.E2E) + fun testMultipleAssignmentsWithSearchE2E() { + + Log.d(PREPARATION_TAG, "Seeding data.") + val data = seedData(teachers = 1, courses = 1, students = 1) + val student = data.studentsList[0] + val teacher = data.teachersList[0] + val course = data.coursesList[0] + + Log.d(PREPARATION_TAG, "Seeding a 'Letter Grade' assignment for '${course.name}' course.") + val letterGradeAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.LETTER_GRADE, pointsPossible = 20.0, dueAt = 1.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) + + Log.d(PREPARATION_TAG, "Submit '${letterGradeAssignment.name}' assignment for '${student.name}' student.") + SubmissionsApi.seedAssignmentSubmission(course.id, student.token, letterGradeAssignment.id, submissionSeedsList = listOf(SubmissionsApi.SubmissionSeedInfo(amount = 1, submissionType = SubmissionType.ONLINE_TEXT_ENTRY))) + + Log.d(PREPARATION_TAG, "Seeding a 'Points' assignment for '${course.name}' course.") + val pointsAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.POINTS, pointsPossible = 15.0, dueAt = 1.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY)) + + Log.d(PREPARATION_TAG, "Submit '${pointsAssignment.name}' assignment for '${student.name}' student.") + SubmissionsApi.seedAssignmentSubmission(course.id, student.token, pointsAssignment.id, submissionSeedsList = listOf(SubmissionsApi.SubmissionSeedInfo(amount = 1, submissionType = SubmissionType.ONLINE_TEXT_ENTRY))) + + Log.d(STEP_TAG, "Login with user: '${teacher.name}', login id: '${teacher.loginId}'.") + tokenLogin(teacher) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Open '${course.name}' course and navigate to its Assignments Tab.") + dashboardPage.openCourse(course.name) + courseBrowserPage.openAssignmentsTab() + + Log.d(ASSERTION_TAG, "Assert that both '${letterGradeAssignment.name}' and '${pointsAssignment.name}' assignments are displayed, each with 1 submission needing grading.") + assignmentListPage.assertHasAssignment(letterGradeAssignment, needsGradingCount = 1) + assignmentListPage.assertHasAssignment(pointsAssignment, needsGradingCount = 1) + + Log.d(STEP_TAG, "Click on the 'Search' (magnifying glass) icon at the toolbar.") + assignmentListPage.searchBar.clickOnSearchButton() + + Log.d(STEP_TAG, "Type the name of the '${letterGradeAssignment.name}' assignment into the search bar.") + assignmentListPage.searchBar.typeToSearchBar(letterGradeAssignment.name) + + Log.d(ASSERTION_TAG, "Assert that only '${letterGradeAssignment.name}' is displayed and '${pointsAssignment.name}' is not.") + assignmentListPage.assertHasAssignment(letterGradeAssignment) + assignmentListPage.assertAssignmentNotDisplayed(pointsAssignment.name) + + Log.d(STEP_TAG, "Clear the search input.") + assignmentListPage.searchBar.clickOnClearSearchButton() + + Log.d(ASSERTION_TAG, "Assert that both assignments are visible again after clearing the search.") + assignmentListPage.assertHasAssignment(letterGradeAssignment, needsGradingCount = 1) + assignmentListPage.assertHasAssignment(pointsAssignment, needsGradingCount = 1) + + Log.d(STEP_TAG, "Type a search query that does not match any assignment.") + assignmentListPage.searchBar.typeToSearchBar("xxxxxxxxxxx") + + Log.d(ASSERTION_TAG, "Assert that the empty state ('No Assignments') view is displayed when no assignments match the search query.") + assignmentListPage.assertDisplaysNoAssignmentsView() + + Log.d(STEP_TAG, "Close the search bar.") + assignmentListPage.searchBar.pressSearchBarButton() + + Log.d(ASSERTION_TAG, "Assert that both assignments are displayed again after closing the search bar.") + assignmentListPage.assertHasAssignment(letterGradeAssignment, needsGradingCount = 1) + assignmentListPage.assertHasAssignment(pointsAssignment, needsGradingCount = 1) + } } \ No newline at end of file diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/EditAssignmentDetailsPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/EditAssignmentDetailsPage.kt index f2ccc6a553..aaa84dca74 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/EditAssignmentDetailsPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/EditAssignmentDetailsPage.kt @@ -29,6 +29,7 @@ import com.instructure.canvas.espresso.utils.withIndex import com.instructure.canvasapi2.utils.DateHelper import com.instructure.espresso.OnViewWithId import com.instructure.espresso.WaitForViewWithId +import com.instructure.espresso.assertContainsText import com.instructure.espresso.assertHasText import com.instructure.espresso.click import com.instructure.espresso.page.BasePage @@ -116,6 +117,13 @@ class EditAssignmentDetailsPage : BasePage() { */ fun editAssignees() = waitScrollClick(R.id.assignTo) + /** + * Asserts that the assignee field contains the specified [assignee] text. + */ + fun assertContainsAssignee(assignee: String) { + onViewWithId(R.id.assignTo).assertContainsText(assignee) + } + /** * Clicks on the edit due date field. */ diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/AssignmentReminderPage.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/AssignmentReminderPage.kt index b55beb1507..960b225de4 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/AssignmentReminderPage.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/AssignmentReminderPage.kt @@ -80,6 +80,7 @@ class AssignmentReminderPage(private val composeTestRule: ComposeTestRule) { fun clickAddReminder() { composeTestRule.onNodeWithContentDescription(reminderAdd).performClick() + composeTestRule.waitForIdle() } fun assertReminderDisplayedWithText(text: String) { @@ -92,6 +93,7 @@ class AssignmentReminderPage(private val composeTestRule: ComposeTestRule) { hasAnySibling(hasText(text)) ) ).performClick() + composeTestRule.waitForIdle() Thread.sleep(1000) onView(withText(R.string.yes)).scrollTo().click() } diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/AssignmentListPage.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/AssignmentListPage.kt index dc667a1233..c1691b6876 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/AssignmentListPage.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/AssignmentListPage.kt @@ -63,6 +63,7 @@ class AssignmentListPage(private val composeTestRule: ComposeTestRule) { composeTestRule.onNodeWithText(assignment.name) .performClick() + composeTestRule.waitForIdle() } fun clickAssignment(assignment: Assignment) { @@ -91,6 +92,7 @@ class AssignmentListPage(private val composeTestRule: ComposeTestRule) { composeTestRule.onNodeWithText(quiz.title) .performClick() + composeTestRule.waitForIdle() } fun assertDisplaysNoAssignmentsView() { @@ -143,6 +145,7 @@ class AssignmentListPage(private val composeTestRule: ComposeTestRule) { ) .performScrollTo() .performClick() + composeTestRule.waitForIdle() } fun clickDiscussionCheckpointExpandCollapseIcon(discussionTitle: String) { @@ -264,32 +267,35 @@ class AssignmentListPage(private val composeTestRule: ComposeTestRule) { fun assertGradingPeriodLabel(gradingPeriodName: String? = null) { composeTestRule.onNode( - hasText("Grading Period:").and(hasParent(hasAnyDescendant(hasText(gradingPeriodName ?: "All")))) + hasContentDescription("Grading Period: ${gradingPeriodName ?: "All"}") ) .assertIsDisplayed() } fun assertGradingPeriodLabelDoesNotExist(gradingPeriodName: String? = null) { composeTestRule.onNode( - hasText("Grading Period:").and(hasParent(hasAnyDescendant(hasText(gradingPeriodName ?: "All")))) + hasContentDescription("Grading Period: ${gradingPeriodName ?: "All"}") ) .assertDoesNotExistWithTimeout(5) } private fun clickFilterMenu() { composeTestRule.onNodeWithContentDescription("Filter Assignments").performClick() + composeTestRule.waitForIdle() } fun filterAssignments(groupName: String, option: FilterOption) { clickFilterMenu() selectFilterOption(groupName, option) composeTestRule.onNodeWithText("Done").performClick() + composeTestRule.waitForIdle() } fun groupByAssignments(option: GroupByOption) { clickFilterMenu() selectGroupByOption("Grouped By", option) composeTestRule.onNodeWithText("Done").performClick() + composeTestRule.waitForIdle() } fun assertPublishedState(assignmentName: String, published: Boolean) { @@ -322,7 +328,9 @@ class AssignmentListPage(private val composeTestRule: ComposeTestRule) { hasText(filterText).and(hasParent(hasParent(hasAnyDescendant(hasText(groupName))))), useUnmergedTree = true ) - .performClick() + .performScrollTo() + .performClick() + composeTestRule.waitForIdle() } private fun selectGroupByOption(groupName: String, option: GroupByOption) { @@ -336,6 +344,7 @@ class AssignmentListPage(private val composeTestRule: ComposeTestRule) { useUnmergedTree = true ) .performClick() + composeTestRule.waitForIdle() } private fun String?.toDate(): Date? { diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/GradesPage.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/GradesPage.kt index 0bb16d3be5..1c406acc7d 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/GradesPage.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/GradesPage.kt @@ -14,7 +14,6 @@ * along with this program. If not, see . * */ - package com.instructure.canvas.espresso.common.pages.compose import androidx.compose.ui.test.assertCountEquals @@ -55,6 +54,7 @@ class GradesPage(private val composeTestRule: ComposeTestRule) { .performScrollToNode(hasText(name)) composeTestRule.onNodeWithText(name) .performClick() + composeTestRule.waitForIdle() } fun assertAssignmentIsDisplayed(name: String) { @@ -93,6 +93,7 @@ class GradesPage(private val composeTestRule: ComposeTestRule) { fun clickFilterButton() { composeTestRule.onNodeWithContentDescription("Filter") .performClick() + composeTestRule.waitForIdle() } fun assertFilterNotApplied() { @@ -106,11 +107,13 @@ class GradesPage(private val composeTestRule: ComposeTestRule) { fun clickFilterOption(option: String) { composeTestRule.onNodeWithText(option) .performClick() + composeTestRule.waitForIdle() } fun clickSaveButton() { composeTestRule.onNodeWithText("Save") .performClick() + composeTestRule.waitForIdle() } fun clickAssignment(name: String) { @@ -118,6 +121,7 @@ class GradesPage(private val composeTestRule: ComposeTestRule) { .performScrollToNode(hasText(name)) composeTestRule.onNodeWithText(name) .performClick() + composeTestRule.waitForIdle() } fun assertEmptyStateIsDisplayed() { @@ -129,11 +133,13 @@ class GradesPage(private val composeTestRule: ComposeTestRule) { fun scrollDownScreen() { composeTestRule.onNodeWithTag("gradesList") .performTouchInput { swipeUp() } + composeTestRule.waitForIdle() } fun scrollUpScreen() { composeTestRule.onNodeWithTag("gradesList") .performTouchInput { swipeDown() } + composeTestRule.waitForIdle() } fun assertCardText(text: String) { diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/SearchableToolbar.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/SearchableToolbar.kt index 4db918aeda..59cd8d1c0a 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/SearchableToolbar.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/common/pages/compose/SearchableToolbar.kt @@ -18,29 +18,33 @@ package com.instructure.canvas.espresso.common.pages.compose import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.compose.ui.test.onAllNodesWithTag -import androidx.compose.ui.test.onFirst +import androidx.compose.ui.test.onLast import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextReplacement class SearchableToolbar(private val composeTestRule: ComposeTestRule) { fun clickOnSearchButton() { - composeTestRule.onAllNodesWithTag("searchButton").onFirst() + composeTestRule.onAllNodesWithTag("searchButton").onLast() .performClick() + composeTestRule.waitForIdle() } fun typeToSearchBar(textToType: String) { - composeTestRule.onAllNodesWithTag("searchField").onFirst() + composeTestRule.onAllNodesWithTag("searchField").onLast() .performClick() .performTextReplacement(textToType) + composeTestRule.waitForIdle() } fun clickOnClearSearchButton() { - composeTestRule.onAllNodesWithTag("clearButton").onFirst() + composeTestRule.onAllNodesWithTag("clearButton").onLast() .performClick() + composeTestRule.waitForIdle() } fun pressSearchBarButton() { - composeTestRule.onAllNodesWithTag("closeButton").onFirst() + composeTestRule.onAllNodesWithTag("closeButton").onLast() .performClick() + composeTestRule.waitForIdle() } } \ No newline at end of file