Skip to content

Commit dbd6745

Browse files
adamNagy56claude
andauthored
[MBL-17351][Student] Extend AssignmentDetails interaction test with more submission types (#3365)
* Extend AssignmentDetailsInteractionTest to cover multiple submission types. refs: MBL-17351 affects: Student release note: * Extend AssignmentDetailsInteractionTest to include additional media recording submission scenarios. refs: MBL-17351 affects: Student release note: * Fix flaky audio submission test, also renames several media submission tests for better clarity. refs: MBL-17351 affects: Student release note: * Add test asset files for submission tests Adds test.txt, test_audio.mp3, and test_video.mp4 to androidTest assets directory. These files are required by AssignmentDetailsInteractionTest for testing file upload, audio, and video submission functionality. Without these files in the repository, the tests fail in CI with FileNotFoundException. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * fix PR refs: MBL-17351 affects: Student release note: * fix assets files refs: MBL-17351 affects: Student release note: * Fix PR findings refs: MBL-17351 affects: Student release note: --------- Co-authored-by: Claude <[email protected]>
1 parent 0e92d24 commit dbd6745

File tree

5 files changed

+268
-4
lines changed

5 files changed

+268
-4
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This is a test file for assignment submission.
40.2 KB
Binary file not shown.
5.25 MB
Binary file not shown.

apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentDetailsInteractionTest.kt

Lines changed: 266 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,21 @@
1515
*/
1616
package com.instructure.student.ui.interaction
1717

18+
import android.Manifest
19+
import android.app.Activity
20+
import android.app.Instrumentation
21+
import android.content.Intent
22+
import android.net.Uri
23+
import android.provider.MediaStore
1824
import androidx.compose.ui.platform.ComposeView
25+
import androidx.test.espresso.Espresso.onView
26+
import androidx.test.espresso.action.ViewActions.click
27+
import androidx.test.espresso.intent.Intents
28+
import androidx.test.espresso.intent.matcher.IntentMatchers
1929
import androidx.test.espresso.matcher.ViewMatchers
30+
import androidx.test.espresso.matcher.ViewMatchers.withId
31+
import androidx.test.platform.app.InstrumentationRegistry
32+
import androidx.test.uiautomator.UiSelector
2033
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils
2134
import com.google.android.apps.common.testing.accessibility.framework.checks.SpeakableTextPresentCheck
2235
import com.instructure.canvas.espresso.FeatureCategory
@@ -31,6 +44,7 @@ import com.instructure.canvas.espresso.mockcanvas.addAssignmentsToGroups
3144
import com.instructure.canvas.espresso.mockcanvas.addSubmissionForAssignment
3245
import com.instructure.canvas.espresso.mockcanvas.fakes.FakeCustomGradeStatusesManager
3346
import com.instructure.canvas.espresso.mockcanvas.init
47+
import com.instructure.canvas.espresso.refresh
3448
import com.instructure.canvasapi2.di.graphql.CustomGradeStatusModule
3549
import com.instructure.canvasapi2.managers.graphql.CustomGradeStatusesManager
3650
import com.instructure.canvasapi2.models.Assignment
@@ -49,6 +63,7 @@ import dagger.hilt.android.testing.UninstallModules
4963
import org.hamcrest.Matchers
5064
import org.junit.Assert.assertNotNull
5165
import org.junit.Test
66+
import java.io.File
5267
import java.util.Calendar
5368

5469
@HiltAndroidTest
@@ -63,9 +78,7 @@ class AssignmentDetailsInteractionTest : StudentComposeTest() {
6378

6479
@Test
6580
@TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, SecondaryFeatureCategory.SUBMISSIONS_ONLINE_URL)
66-
fun testSubmission_submitAssignment() {
67-
// TODO - Test submitting for each submission type
68-
// For now, I'm going to just test one submission type
81+
fun testSubmission_submitOnlineURL() {
6982
val data = MockCanvas.init(
7083
studentCount = 1,
7184
courseCount = 1
@@ -89,6 +102,246 @@ class AssignmentDetailsInteractionTest : StudentComposeTest() {
89102
assignmentDetailsPage.assertStatusSubmitted()
90103
}
91104

105+
@Test
106+
@TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, SecondaryFeatureCategory.SUBMISSIONS_TEXT_ENTRY)
107+
fun testSubmission_submitTextEntry() {
108+
val data = MockCanvas.init(
109+
studentCount = 1,
110+
courseCount = 1
111+
)
112+
113+
val course = data.courses.values.first()
114+
val student = data.students[0]
115+
val token = data.tokenFor(student)!!
116+
val assignment = data.addAssignment(courseId = course.id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY))
117+
data.addSubmissionForAssignment(
118+
assignmentId = assignment.id,
119+
userId = data.users.values.first().id,
120+
type = Assignment.SubmissionType.ONLINE_TEXT_ENTRY.apiString
121+
)
122+
tokenLogin(data.domain, token, student)
123+
routeTo("courses/${course.id}/assignments", data.domain)
124+
125+
assignmentListPage.clickAssignment(assignment)
126+
assignmentDetailsPage.clickSubmit()
127+
textSubmissionUploadPage.typeText("This is my test submission text.")
128+
textSubmissionUploadPage.clickOnSubmitButton()
129+
assignmentDetailsPage.assertStatusSubmitted()
130+
}
131+
132+
@Test
133+
@TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, SecondaryFeatureCategory.SUBMISSIONS_FILE_UPLOAD)
134+
fun testSubmission_submitFileUpload() {
135+
val data = MockCanvas.init(
136+
studentCount = 1,
137+
courseCount = 1
138+
)
139+
140+
val course = data.courses.values.first()
141+
val student = data.students[0]
142+
val token = data.tokenFor(student)!!
143+
val assignment = data.addAssignment(courseId = course.id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_UPLOAD))
144+
data.addSubmissionForAssignment(
145+
assignmentId = assignment.id,
146+
userId = data.users.values.first().id,
147+
type = Assignment.SubmissionType.ONLINE_UPLOAD.apiString
148+
)
149+
tokenLogin(data.domain, token, student)
150+
routeTo("courses/${course.id}/assignments", data.domain)
151+
152+
val fileName = "test.txt"
153+
Intents.init()
154+
try {
155+
stubFilePickerIntent(fileName)
156+
setupFileOnDevice(fileName)
157+
158+
assignmentListPage.clickAssignment(assignment)
159+
assignmentDetailsPage.clickSubmit()
160+
pickerSubmissionUploadPage.chooseDevice()
161+
pickerSubmissionUploadPage.waitForSubmitButtonToAppear()
162+
pickerSubmissionUploadPage.assertFileDisplayed(fileName)
163+
pickerSubmissionUploadPage.submit()
164+
assignmentDetailsPage.assertStatusSubmitted()
165+
} finally {
166+
Intents.release()
167+
}
168+
}
169+
170+
@Test
171+
@TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, SecondaryFeatureCategory.SUBMISSIONS_MEDIA_RECORDING)
172+
fun testSubmission_submitMediaRecordingChooseMediaFile() {
173+
val data = MockCanvas.init(
174+
studentCount = 1,
175+
courseCount = 1
176+
)
177+
178+
val course = data.courses.values.first()
179+
val student = data.students[0]
180+
val token = data.tokenFor(student)!!
181+
val assignment = data.addAssignment(courseId = course.id, submissionTypeList = listOf(Assignment.SubmissionType.MEDIA_RECORDING))
182+
data.addSubmissionForAssignment(
183+
assignmentId = assignment.id,
184+
userId = data.users.values.first().id,
185+
type = Assignment.SubmissionType.MEDIA_RECORDING.apiString
186+
)
187+
tokenLogin(data.domain, token, student)
188+
routeTo("courses/${course.id}/assignments", data.domain)
189+
190+
val activity = activityRule.activity
191+
grantPermissions(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
192+
193+
val fileName = "test_video.mp4"
194+
copyAssetFileToExternalCache(activity, fileName)
195+
196+
val resultData = Intent()
197+
val dir = activity.externalCacheDir
198+
val file = File(dir?.path, fileName)
199+
val uri = Uri.fromFile(file)
200+
resultData.data = uri
201+
val activityResult = Instrumentation.ActivityResult(Activity.RESULT_OK, resultData)
202+
203+
Intents.init()
204+
try {
205+
Intents.intending(
206+
Matchers.anyOf(
207+
IntentMatchers.hasAction(Intent.ACTION_GET_CONTENT),
208+
IntentMatchers.hasAction(Intent.ACTION_PICK),
209+
IntentMatchers.hasAction(Intent.ACTION_OPEN_DOCUMENT)
210+
)
211+
).respondWith(activityResult)
212+
213+
assignmentListPage.clickAssignment(assignment)
214+
assignmentDetailsPage.clickSubmit()
215+
216+
onView(withId(R.id.submissionEntryMediaFile)).perform(click())
217+
218+
pickerSubmissionUploadPage.waitForSubmitButtonToAppear()
219+
pickerSubmissionUploadPage.assertFileDisplayed(fileName)
220+
pickerSubmissionUploadPage.submit()
221+
assignmentDetailsPage.assertStatusSubmitted()
222+
} finally {
223+
Intents.release()
224+
}
225+
}
226+
227+
@Test
228+
@TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, SecondaryFeatureCategory.SUBMISSIONS_MEDIA_RECORDING)
229+
fun testSubmission_submitMediaRecordingRecordVideo() {
230+
val data = MockCanvas.init(
231+
studentCount = 1,
232+
courseCount = 1
233+
)
234+
235+
val course = data.courses.values.first()
236+
val student = data.students[0]
237+
val token = data.tokenFor(student)!!
238+
val assignment = data.addAssignment(courseId = course.id, submissionTypeList = listOf(Assignment.SubmissionType.MEDIA_RECORDING))
239+
data.addSubmissionForAssignment(
240+
assignmentId = assignment.id,
241+
userId = data.users.values.first().id,
242+
type = Assignment.SubmissionType.MEDIA_RECORDING.apiString
243+
)
244+
tokenLogin(data.domain, token, student)
245+
routeTo("courses/${course.id}/assignments", data.domain)
246+
247+
val activity = activityRule.activity
248+
grantPermissions(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
249+
250+
val testVideoFile = "test_video.mp4"
251+
copyAssetFileToExternalCache(activity, testVideoFile)
252+
253+
var capturedVideoUri: Uri? = null
254+
255+
Intents.init()
256+
Intents.intending(
257+
Matchers.allOf(
258+
IntentMatchers.hasAction(MediaStore.ACTION_VIDEO_CAPTURE),
259+
IntentMatchers.hasExtraWithKey(MediaStore.EXTRA_OUTPUT)
260+
)
261+
).respondWithFunction { intent ->
262+
val outputUri = intent.extras?.get(MediaStore.EXTRA_OUTPUT) as? Uri
263+
capturedVideoUri = outputUri
264+
if (outputUri != null) {
265+
val context = InstrumentationRegistry.getInstrumentation().targetContext
266+
val dir = context.externalCacheDir
267+
val sampleFile = File(dir, testVideoFile)
268+
if (outputUri.scheme == "file") {
269+
val destFile = File(outputUri.path!!)
270+
destFile.parentFile?.mkdirs()
271+
sampleFile.copyTo(destFile, overwrite = true)
272+
} else if (outputUri.scheme == "content") {
273+
context.contentResolver.openOutputStream(outputUri)?.use { outputStream ->
274+
sampleFile.inputStream().use { inputStream ->
275+
inputStream.copyTo(outputStream)
276+
}
277+
}
278+
}
279+
}
280+
Instrumentation.ActivityResult(Activity.RESULT_OK, Intent())
281+
}
282+
283+
assignmentListPage.clickAssignment(assignment)
284+
assignmentDetailsPage.clickSubmit()
285+
onView(withId(R.id.submissionEntryVideo)).perform(click())
286+
287+
Intents.release()
288+
289+
pickerSubmissionUploadPage.waitForSubmitButtonToAppear()
290+
291+
val fileName = File(capturedVideoUri!!.path!!).name
292+
pickerSubmissionUploadPage.assertFileDisplayed(fileName)
293+
pickerSubmissionUploadPage.submit()
294+
295+
assignmentDetailsPage.assertStatusSubmitted()
296+
}
297+
298+
@Test
299+
@TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, SecondaryFeatureCategory.SUBMISSIONS_MEDIA_RECORDING)
300+
fun testSubmission_submitMediaRecordingRecordAudio() {
301+
val data = MockCanvas.init(
302+
studentCount = 1,
303+
courseCount = 1
304+
)
305+
306+
val course = data.courses.values.first()
307+
val student = data.students[0]
308+
val token = data.tokenFor(student)!!
309+
val assignment = data.addAssignment(courseId = course.id, submissionTypeList = listOf(Assignment.SubmissionType.MEDIA_RECORDING))
310+
data.addSubmissionForAssignment(
311+
assignmentId = assignment.id,
312+
userId = data.users.values.first().id,
313+
type = Assignment.SubmissionType.MEDIA_RECORDING.apiString
314+
)
315+
tokenLogin(data.domain, token, student)
316+
routeTo("courses/${course.id}/assignments", data.domain)
317+
318+
val activity = activityRule.activity
319+
grantPermissions(Manifest.permission.RECORD_AUDIO)
320+
321+
val testAudioFileName = "test_audio.mp3"
322+
copyAssetFileToExternalCache(activity, testAudioFileName)
323+
324+
val context = InstrumentationRegistry.getInstrumentation().targetContext
325+
val recordingFile = File(context.externalCacheDir, "audio.amr")
326+
val testAudioFile = File(context.externalCacheDir, testAudioFileName)
327+
testAudioFile.copyTo(recordingFile, overwrite = true)
328+
329+
assignmentListPage.clickAssignment(assignment)
330+
assignmentDetailsPage.clickSubmit()
331+
onView(withId(R.id.submissionEntryAudio)).perform(click())
332+
333+
device.findObject(UiSelector().resourceIdMatches(".*recordAudioButton")).click()
334+
335+
testAudioFile.copyTo(recordingFile, overwrite = true)
336+
337+
device.findObject(UiSelector().resourceIdMatches(".*stopButton")).click()
338+
339+
device.findObject(UiSelector().resourceIdMatches(".*sendAudioButton")).click()
340+
341+
refresh()
342+
assignmentDetailsPage.assertStatusSubmitted()
343+
}
344+
92345
@Test
93346
@TestMetaData(Priority.MANDATORY, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION)
94347
fun testSubmissionStatus_NotSubmitted() {
@@ -694,6 +947,16 @@ class AssignmentDetailsInteractionTest : StudentComposeTest() {
694947
return assignment
695948
}
696949

950+
private fun grantPermissions(vararg permissions: String) {
951+
val activity = activityRule.activity
952+
permissions.forEach { permission ->
953+
InstrumentationRegistry.getInstrumentation().uiAutomation.grantRuntimePermission(
954+
activity.packageName,
955+
permission
956+
)
957+
}
958+
}
959+
697960
override fun enableAndConfigureAccessibilityChecks() {
698961
extraAccessibilitySupressions = Matchers.allOf(
699962
AccessibilityCheckResultUtils.matchesCheck(

automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/TestMetaData.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ enum class FeatureCategory {
3939

4040
enum class SecondaryFeatureCategory {
4141
NONE, LOGIN_K5,
42-
SUBMISSIONS_TEXT_ENTRY, SUBMISSIONS_ANNOTATIONS, SUBMISSIONS_ONLINE_URL, SUBMISSIONS_MULTIPLE_TYPE,
42+
SUBMISSIONS_TEXT_ENTRY, SUBMISSIONS_ANNOTATIONS, SUBMISSIONS_ONLINE_URL, SUBMISSIONS_MULTIPLE_TYPE, SUBMISSIONS_FILE_UPLOAD, SUBMISSIONS_MEDIA_RECORDING,
4343
ASSIGNMENT_COMMENTS, ASSIGNMENT_QUIZZES, ASSIGNMENT_DISCUSSIONS, HOMEROOM, K5_GRADES, IMPORTANT_DATES, RESOURCES, SCHEDULE,
4444
GROUPS_DASHBOARD, GROUPS_FILES, GROUPS_ANNOUNCEMENTS, GROUPS_DISCUSSIONS, GROUPS_PAGES, GROUPS_PEOPLE,
4545
EVENTS_DISCUSSIONS, EVENTS_QUIZZES, EVENTS_ASSIGNMENTS, EVENTS_NOTIFICATIONS, SETTINGS_EMAIL_NOTIFICATIONS,

0 commit comments

Comments
 (0)