Skip to content

Commit 5de30cb

Browse files
[MBL-19445][Teacher] - Differentiation Tags and Custom Statuses Filtering E2E Test Implementation (#3489)
* Seeding differentiation tags via hybrid (GraphQL + Rest) API. Add first steps of e2e test of custom status and differentiation tag filtering. refs: MBL-19445 affects: Teacher release note: - * Implement filter custom statuses and differentiation test E2E test fully. refs: MBL-19445 affects: Teacher release note: - * Correct copyright year in graphql seeding files.
1 parent b8b932c commit 5de30cb

File tree

6 files changed

+457
-11
lines changed

6 files changed

+457
-11
lines changed

apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/compose/CustomStatusesE2ETest.kt

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.instructure.canvas.espresso.annotations.E2E
99
import com.instructure.canvas.espresso.pressBackButton
1010
import com.instructure.dataseeding.api.AssignmentsApi
1111
import com.instructure.dataseeding.api.CustomStatusApi
12+
import com.instructure.dataseeding.api.DifferentiationTagsApi
1213
import com.instructure.dataseeding.api.SubmissionsApi
1314
import com.instructure.dataseeding.model.GradingType
1415
import com.instructure.dataseeding.model.SubmissionType
@@ -131,6 +132,218 @@ class CustomStatusesE2ETest: TeacherComposeTest() {
131132
speedGraderGradePage.assertCurrentStatus("Graded", student.name)
132133
}
133134

135+
@E2E
136+
@Test
137+
@TestMetaData(Priority.COMMON, FeatureCategory.CUSTOM_STATUSES, TestCategory.E2E)
138+
fun testFilterCustomStatusesAndDifferentiationTagsE2E() {
139+
140+
Log.d(PREPARATION_TAG, "Seeding data.")
141+
val data = seedData(teachers = 1, courses = 1, students = 3)
142+
val student = data.studentsList[0]
143+
val student2 = data.studentsList[1]
144+
val studentWithoutTag = data.studentsList[2]
145+
val teacher = data.teachersList[0]
146+
val course = data.coursesList[0]
147+
148+
Log.d(PREPARATION_TAG, "Seeding a custom status ('AMAZING') with the admin user.")
149+
customStatusId = CustomStatusApi.upsertCustomGradeStatus(adminToken, name = "AMAZING", color = "#FF0000")
150+
151+
Log.d(PREPARATION_TAG, "Create 'Differentiation Tags Group Set' differentiation group set for '${course.name}' course.")
152+
val groupSetId = DifferentiationTagsApi.createGroupSet(
153+
token = teacher.token,
154+
courseId = course.id.toString(),
155+
name = "Differentiation Tags Group Set",
156+
nonCollaborative = true
157+
)
158+
159+
Log.d(PREPARATION_TAG, "Seeding 'First Diff Tag' differentiation tags for '${course.name}' course.")
160+
val firstDifferentiationTag = DifferentiationTagsApi.createGroup(
161+
token = teacher.token,
162+
groupSetId = groupSetId,
163+
name = "First Diff Tag"
164+
)
165+
166+
Log.d(PREPARATION_TAG, "Seeding 'Second Diff Tag' differentiation tags for '${course.name}' course.")
167+
val secondDifferentiationTag = DifferentiationTagsApi.createGroup(
168+
token = teacher.token,
169+
groupSetId = groupSetId,
170+
name = "Second Diff Tag"
171+
)
172+
173+
Log.d(PREPARATION_TAG, "Seeding 'Third Diff Tag' differentiation tags for '${course.name}' course.")
174+
val thirdDifferentiationTag = DifferentiationTagsApi.createGroup(
175+
token = teacher.token,
176+
groupSetId = groupSetId,
177+
name = "Third Diff Tag"
178+
)
179+
180+
Log.d(PREPARATION_TAG, "Assigning 'First Diff Tag' differentiation tag to '${student.name}' student.")
181+
DifferentiationTagsApi.addUserToGroup(
182+
token = teacher.token,
183+
groupId = firstDifferentiationTag.toLong(),
184+
userId = student.id
185+
)
186+
187+
Log.d(PREPARATION_TAG, "Assigning 'Second Diff Tag' differentiation tag to '${student2.name}' student.")
188+
DifferentiationTagsApi.addUserToGroup(
189+
token = teacher.token,
190+
groupId = secondDifferentiationTag.toLong(),
191+
userId = student2.id
192+
)
193+
194+
Log.d(PREPARATION_TAG, "Seeding 'Text Entry' assignment for '${course.name}' course.")
195+
val testAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.POINTS, pointsPossible = 15.0, dueAt = 1.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY))
196+
197+
Log.d(PREPARATION_TAG, "Student submits the assignment.")
198+
SubmissionsApi.submitCourseAssignment(
199+
courseId = course.id,
200+
studentToken = student.token,
201+
assignmentId = testAssignment.id,
202+
submissionType = SubmissionType.ONLINE_TEXT_ENTRY
203+
)
204+
205+
Log.d(PREPARATION_TAG, "Teacher grades submission with custom status 'AMAZING' for '${student.name}' student.")
206+
SubmissionsApi.gradeSubmission(
207+
teacherToken = teacher.token,
208+
courseId = course.id,
209+
assignmentId = testAssignment.id,
210+
studentId = student.id,
211+
postedGrade = "12",
212+
customGradeStatusId = customStatusId
213+
)
214+
215+
Log.d(STEP_TAG, "Login with user: '${teacher.name}', login id: '${teacher.loginId}'.")
216+
tokenLogin(teacher)
217+
dashboardPage.waitForRender()
218+
219+
Log.d(STEP_TAG, "Open '${course.name}' course.")
220+
dashboardPage.openCourse(course.name)
221+
222+
Log.d(STEP_TAG, "Navigate to '${course.name}' course's Assignments Tab.")
223+
courseBrowserPage.openAssignmentsTab()
224+
225+
Log.d(STEP_TAG, "Click on '${testAssignment.name}' assignment.")
226+
assignmentListPage.clickAssignment(testAssignment)
227+
228+
Log.d(STEP_TAG, "Open the 'All Submissions' page.")
229+
assignmentDetailsPage.clickAllSubmissions()
230+
231+
Log.d(ASSERTION_TAG, "Assert that all the 3 students are displayed by default on the 'All Submissions' page before applying any filter.")
232+
assignmentSubmissionListPage.assertHasSubmission(3)
233+
assignmentSubmissionListPage.assertHasStudentSubmission(student)
234+
assignmentSubmissionListPage.assertHasStudentSubmission(student2)
235+
assignmentSubmissionListPage.assertHasStudentSubmission(studentWithoutTag)
236+
237+
Log.d(STEP_TAG, "Click on the filter icon on the top-right corner again.")
238+
assignmentSubmissionListPage.clickFilterButton()
239+
240+
Log.d(ASSERTION_TAG, "Assert that all the custom status filter text is displayed among the filtering options.")
241+
assignmentSubmissionListPage.assertCustomStatusFilterOption("AMAZING")
242+
243+
Log.d(ASSERTION_TAG, "Assert that all the corresponding differentiation tag filter texts are displayed among the filtering options.")
244+
assignmentSubmissionListPage.assertDifferentiationTagFilterOption("Students without Differentiation tags")
245+
assignmentSubmissionListPage.assertDifferentiationTagFilterOption("First Diff Tag")
246+
assignmentSubmissionListPage.assertDifferentiationTagFilterOption("Second Diff Tag")
247+
assignmentSubmissionListPage.assertDifferentiationTagFilterOption("Third Diff Tag")
248+
249+
// Check 'First Diff Tag' differentiation tag filter option
250+
Log.d(STEP_TAG, "Select the 'First Diff Tag' differentiation tag filter and click on 'Done'.")
251+
assignmentSubmissionListPage.clickDifferentiationTagFilter("First Diff Tag")
252+
assignmentSubmissionListPage.clickFilterDialogDone()
253+
254+
Log.d(ASSERTION_TAG, "Assert that there is 1 submission displayed, and it is for '${student.name}' student since we applied a filter to the 'First Diff Tag' differentiation tag only.")
255+
assignmentSubmissionListPage.assertHasSubmission(1)
256+
assignmentSubmissionListPage.assertHasStudentSubmission(student)
257+
assignmentSubmissionListPage.assertCustomStatusTag("AMAZING") // The displayed submission has the custom status tag 'AMAZING'
258+
assignmentSubmissionListPage.assertStudentSubmissionNotDisplayed(student2)
259+
260+
// Check 'Second Diff Tag' differentiation tag filter option
261+
Log.d(STEP_TAG, "Click on the filter icon on the top-right corner again.")
262+
assignmentSubmissionListPage.clickFilterButton()
263+
264+
Log.d(STEP_TAG, "Unselect the 'First Diff Tag' and select the 'Second Diff Tag' differentiation tag filter and click on 'Done'.")
265+
assignmentSubmissionListPage.clickDifferentiationTagFilter("First Diff Tag")
266+
assignmentSubmissionListPage.clickDifferentiationTagFilter("Second Diff Tag")
267+
assignmentSubmissionListPage.clickFilterDialogDone()
268+
269+
Log.d(ASSERTION_TAG, "Assert that there is 1 submission displayed, and it is for '${student2.name}' student since we applied a filter to the 'Second Diff Tag' differentiation tag only.")
270+
assignmentSubmissionListPage.assertHasSubmission(1)
271+
assignmentSubmissionListPage.assertHasStudentSubmission(student2)
272+
assignmentSubmissionListPage.assertStudentSubmissionNotDisplayed(student)
273+
274+
// Check 'Students without Differentiation tags' filter option
275+
Log.d(STEP_TAG, "Click on the filter icon on the top-right corner again.")
276+
assignmentSubmissionListPage.clickFilterButton()
277+
278+
Log.d(STEP_TAG, "Unselect the 'Second Diff Tag' and select the 'Students without Differentiation tags' differentiation tag filter and click on 'Done'.")
279+
assignmentSubmissionListPage.clickDifferentiationTagFilter("Second Diff Tag")
280+
assignmentSubmissionListPage.clickDifferentiationTagFilter("Students without Differentiation tags")
281+
assignmentSubmissionListPage.clickFilterDialogDone()
282+
283+
Log.d(ASSERTION_TAG, "Assert that there is 1 submission displayed, and it is for '${studentWithoutTag.name}' student since we applied the 'Students without Differentiation tags' filter.")
284+
assignmentSubmissionListPage.assertHasSubmission(1)
285+
assignmentSubmissionListPage.assertHasStudentSubmission(studentWithoutTag)
286+
assignmentSubmissionListPage.assertStudentSubmissionNotDisplayed(student)
287+
assignmentSubmissionListPage.assertStudentSubmissionNotDisplayed(student2)
288+
289+
// Check 'AMAZING' custom status filter option
290+
Log.d(STEP_TAG, "Click on the filter icon on the top-right corner again.")
291+
assignmentSubmissionListPage.clickFilterButton()
292+
293+
Log.d(STEP_TAG, "Unselect the 'Students without Differentiation tags' differentiation tag and select 'AMAZING' custom status filter and click on 'Done'.")
294+
assignmentSubmissionListPage.clickDifferentiationTagFilter("Students without Differentiation tags")
295+
assignmentSubmissionListPage.clickFilterCustomStatus("AMAZING")
296+
assignmentSubmissionListPage.clickFilterDialogDone()
297+
298+
Log.d(ASSERTION_TAG, "Assert that there is 1 submission displayed, and it is for '${student.name}' student since we applied a filter to the 'First Diff Tag' differentiation tag only.")
299+
assignmentSubmissionListPage.assertHasSubmission(1)
300+
assignmentSubmissionListPage.assertHasStudentSubmission(student)
301+
assignmentSubmissionListPage.assertCustomStatusTag("AMAZING") // The displayed submission has the custom status tag 'AMAZING'
302+
assignmentSubmissionListPage.assertStudentSubmissionNotDisplayed(student2)
303+
304+
// Check 'Third Diff Tag' differentiation tag filter option
305+
Log.d(STEP_TAG, "Click on the filter icon on the top-right corner again.")
306+
assignmentSubmissionListPage.clickFilterButton()
307+
308+
Log.d(STEP_TAG, "Unselect the 'AMAZING' custom status filter option and select the 'Third Diff Tag' differentiation tag and click on 'Done'.")
309+
assignmentSubmissionListPage.clickFilterCustomStatus("AMAZING")
310+
assignmentSubmissionListPage.clickDifferentiationTagFilter("Third Diff Tag")
311+
assignmentSubmissionListPage.clickFilterDialogDone()
312+
313+
Log.d(ASSERTION_TAG, "Assert that there is no submission displayed since there are no students with 'Third Diff Tag' differentiation tag, so the empty view is displayed.")
314+
assignmentSubmissionListPage.assertHasNoSubmission()
315+
assignmentSubmissionListPage.assertEmptyViewDisplayed()
316+
317+
// Check 'Missing' (aka. 'Not Submitted') status filter option AND 'Second Diff Tag' differentiation tag filter option together
318+
// Important info: Filter groups behave with AND logic between them and OR logic within them.
319+
Log.d(STEP_TAG, "Click on the filter icon on the top-right corner again.")
320+
assignmentSubmissionListPage.clickFilterButton()
321+
322+
Log.d(STEP_TAG, "Select the 'Missing' status filter and 'Second Diff Tag' differentiation tag and click on 'Done'.")
323+
assignmentSubmissionListPage.clickFilterNotSubmitted()
324+
assignmentSubmissionListPage.clickDifferentiationTagFilter("Second Diff Tag")
325+
assignmentSubmissionListPage.clickFilterDialogDone()
326+
327+
Log.d(ASSERTION_TAG, "Assert that there is 1 submission displayed, one for '${student2.name}' student since we applied the 'Missing' status filter and 'Second Diff Tag' differentiation tag filter simultaneously.")
328+
assignmentSubmissionListPage.assertHasSubmission(1)
329+
assignmentSubmissionListPage.assertHasStudentSubmission(student2)
330+
assignmentSubmissionListPage.assertStudentSubmissionNotDisplayed(student)
331+
assignmentSubmissionListPage.assertStudentSubmissionNotDisplayed(studentWithoutTag)
332+
333+
// Check 'AMAZING' custom status filter option AND 'Second Diff Tag' differentiation tag filter option together to check the AND logic between filter groups
334+
Log.d(STEP_TAG, "Click on the filter icon on the top-right corner again.")
335+
assignmentSubmissionListPage.clickFilterButton()
336+
337+
Log.d(STEP_TAG, "Unselect the 'Missing' status filter select 'AMAZING' custom status filter and click on 'Done'.")
338+
assignmentSubmissionListPage.clickFilterNotSubmitted()
339+
assignmentSubmissionListPage.clickFilterCustomStatus("AMAZING")
340+
assignmentSubmissionListPage.clickFilterDialogDone()
341+
342+
Log.d(ASSERTION_TAG, "Assert that there is no submission displayed since there is no student submission which has the 'AMAZING' custom status and the 'Second Diff Tag' differentiation tag simultaneously.")
343+
assignmentSubmissionListPage.assertHasNoSubmission()
344+
assignmentSubmissionListPage.assertEmptyViewDisplayed()
345+
}
346+
134347
@After
135348
fun tearDown() {
136349
customStatusId?.let {

apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/compose/AssignmentSubmissionListPage.kt

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import androidx.compose.ui.test.swipeDown
3939
import com.instructure.canvasapi2.models.User
4040
import com.instructure.dataseeding.model.CanvasUserApiModel
4141
import com.instructure.espresso.page.BasePage
42+
import com.instructure.espresso.page.getStringFromResource
43+
import com.instructure.teacher.R
4244

4345
/**
4446
* Represents a page for managing assignment submissions.
@@ -192,6 +194,28 @@ class AssignmentSubmissionListPage(private val composeTestRule: ComposeTestRule)
192194
.performClick()
193195
}
194196

197+
/**
198+
* Select a differentiation tag filter option.
199+
* @param differentiationTagText The text of the differentiation tag to select. Defaults to "Students without differentiation tags".
200+
*/
201+
fun clickDifferentiationTagFilter(differentiationTagText: String = getStringFromResource(R.string.students_without_differentiation_tags)) {
202+
203+
val differentiationTagTestTag = if (differentiationTagText == getStringFromResource(R.string.students_without_differentiation_tags))
204+
"includeWithoutTagsCheckBox"
205+
else "differentiationTagCheckBox"
206+
207+
composeTestRule.onNode(
208+
hasTestTag(differentiationTagTestTag) and hasAnySibling(
209+
hasText(
210+
differentiationTagText
211+
)
212+
),
213+
useUnmergedTree = true
214+
).performScrollTo()
215+
.performClick()
216+
composeTestRule.waitForIdle()
217+
}
218+
195219
/**
196220
* Assert that the corresponding submission filter options are displayed.
197221
* @param filterName
@@ -203,13 +227,35 @@ class AssignmentSubmissionListPage(private val composeTestRule: ComposeTestRule)
203227
).performScrollTo().assertIsDisplayed()
204228
}
205229

230+
/**
231+
* Assert that the corresponding custom status filter options are displayed.
232+
* @param filterName Custom status filter name.
233+
*/
206234
fun assertCustomStatusFilterOption(filterName: String) {
207235
composeTestRule.onNode(
208236
hasTestTag("customStatusCheckBox") and hasAnySibling(hasText(filterName)),
209237
useUnmergedTree = true
210238
).performScrollTo().assertIsDisplayed()
211239
}
212240

241+
/**
242+
* Assert that the corresponding differentiation tag filter options are displayed.
243+
* @param differentiationTagText The text of the differentiation tag to verify. Defaults to "Students without differentiation tags".
244+
*/
245+
fun assertDifferentiationTagFilterOption(differentiationTagText: String = getStringFromResource(R.string.students_without_differentiation_tags)) {
246+
val differentiationTagTestTag = if (differentiationTagText == getStringFromResource(R.string.students_without_differentiation_tags))
247+
"includeWithoutTagsCheckBox"
248+
else "differentiationTagCheckBox"
249+
composeTestRule.onNode(
250+
hasTestTag(differentiationTagTestTag) and hasAnySibling(hasText(differentiationTagText)),
251+
useUnmergedTree = true
252+
).performScrollTo().assertIsDisplayed()
253+
}
254+
255+
/**
256+
* Assert that the corresponding precise filter options are displayed.
257+
* @param filterName Precise filter name.
258+
*/
213259
fun assertPreciseFilterOption(filterName: String) {
214260
composeTestRule.onNode(hasText(filterName), useUnmergedTree = true).performScrollTo()
215261
.assertIsDisplayed()
@@ -439,6 +485,17 @@ class AssignmentSubmissionListPage(private val composeTestRule: ComposeTestRule)
439485
.assertIsDisplayed()
440486
}
441487

488+
/**
489+
* Assert that a differentiation tag is displayed on a submission.
490+
*
491+
* @param tagName The name of the differentiation tag to verify.
492+
*/
493+
fun assertDifferentiationTag(tagName: String) {
494+
composeTestRule.onNodeWithText(tagName, useUnmergedTree = true)
495+
.performScrollTo()
496+
.assertIsDisplayed()
497+
}
498+
442499
/**
443500
* Click on a differentiation tag filter option.
444501
*
@@ -471,14 +528,4 @@ class AssignmentSubmissionListPage(private val composeTestRule: ComposeTestRule)
471528
composeTestRule.waitForIdle()
472529
}
473530

474-
/**
475-
* Assert that a differentiation tag is displayed on a submission.
476-
*
477-
* @param tagName The name of the differentiation tag to verify.
478-
*/
479-
fun assertDifferentiationTag(tagName: String) {
480-
composeTestRule.onNodeWithText(tagName, useUnmergedTree = true)
481-
.performScrollTo()
482-
.assertIsDisplayed()
483-
}
484531
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#
2+
# Copyright (C) 2026 - present Instructure, Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
mutation createGroupInSet($groupSetId: ID!, $name: String!, $nonCollaborative: Boolean) {
18+
createGroupInSet(input: {groupSetId: $groupSetId, name: $name, nonCollaborative: $nonCollaborative}) {
19+
group {
20+
_id
21+
name
22+
nonCollaborative
23+
}
24+
errors {
25+
attribute
26+
message
27+
}
28+
}
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#
2+
# Copyright (C) 2026 - present Instructure, Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
mutation createGroupSet($contextId: ID!, $contextType: GroupSetContextType!, $name: String!, $nonCollaborative: Boolean) {
18+
createGroupSet(input: {contextId: $contextId, contextType: $contextType, name: $name, nonCollaborative: $nonCollaborative}) {
19+
groupSet {
20+
_id
21+
name
22+
nonCollaborative
23+
}
24+
errors {
25+
attribute
26+
message
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)