-
Notifications
You must be signed in to change notification settings - Fork 107
[MBL-16328][Teacher] Implement E2E test for quiz edit and preview #3405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 4 commits
35c0d71
c5f9c2a
73d44d8
d4f3639
cfac4eb
106c03e
522acb5
4c9fbe0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ import com.instructure.canvas.espresso.Priority | |
| import com.instructure.canvas.espresso.TestCategory | ||
| import com.instructure.canvas.espresso.TestMetaData | ||
| import com.instructure.canvas.espresso.annotations.E2E | ||
| import com.instructure.canvas.espresso.refresh | ||
| import com.instructure.dataseeding.util.days | ||
| import com.instructure.dataseeding.util.fromNow | ||
| import com.instructure.dataseeding.util.iso8601 | ||
|
|
@@ -143,4 +144,127 @@ class QuizE2ETest: TeacherTest() { | |
| quizListPage.assertHasQuiz(secondQuiz.title) | ||
| } | ||
|
|
||
| @E2E | ||
| @Test | ||
| @TestMetaData(Priority.MANDATORY, FeatureCategory.QUIZZES, TestCategory.E2E) | ||
| fun testQuizEditAndPreviewE2E() { | ||
|
|
||
| Log.d(PREPARATION_TAG, "Seeding data.") | ||
| val data = seedData(students = 1, teachers = 1, courses = 1) | ||
| val student = data.studentsList[0] | ||
| val teacher = data.teachersList[0] | ||
| val course = data.coursesList[0] | ||
|
|
||
| Log.d(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 Quizzes Page.") | ||
| dashboardPage.openCourse(course.name) | ||
| courseBrowserPage.openQuizzesTab() | ||
|
|
||
| Log.d(PREPARATION_TAG, "Seed a quiz for the '${course.name}' course.") | ||
| val testQuizList = seedQuizzes(courseId = course.id, quizzes = 1, withDescription = true, teacherToken = teacher.token, published = true) | ||
|
|
||
| Log.d(STEP_TAG, "Refresh the page.") | ||
| quizListPage.refresh() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you put the seeding before the login, you don't even need this refresh. |
||
|
|
||
| val quiz = testQuizList.quizList[0] | ||
| Log.d(STEP_TAG, "Click on the quiz: '${quiz.title}'.") | ||
| quizListPage.clickQuiz(quiz.title) | ||
|
|
||
| val quizTitle = "My Custom Quiz Title" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd call these 'newQuizTitle' and 'newQuizDescription' as these will be the values that the old values will be changed to. |
||
| val quizDescription = "This is my custom quiz description" | ||
| Log.d(STEP_TAG, "Open 'Edit' page and edit the quiz description to: '$quizDescription' and title to: '$quizTitle'.") | ||
| quizDetailsPage.openEditPage() | ||
| editQuizDetailsPage.editQuizDescription(quizDescription) | ||
| editQuizDetailsPage.editQuizTitle(quizTitle) | ||
|
|
||
| Log.d(ASSERTION_TAG, "Assert that the quiz name and description have been changed to: '$quizTitle' and '$quizDescription'.") | ||
| quizDetailsPage.assertQuizNameChanged(quizTitle) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I commented on the methods' impementations, these methods aren'T testing the "change" process, just validating the current value. Please refactor logs accordingly. Also, you should get the 'old' values from the seeded data so you will be able to write logs like: Log.d(ASSERTION_TAG, "Assert that the quiz name and description have been changed FROM: '$originalQuizTitle' and '$originalQuizDescription' TO: '$newQuizTitle' and '$newQuizDescription'.") |
||
| quizDetailsPage.assertQuizDescriptionChanged(quizDescription) | ||
|
|
||
| Log.d(STEP_TAG, "Open preview page.") | ||
| quizDetailsPage.openPreviewPage() | ||
|
|
||
| Log.d(ASSERTION_TAG, "Assert that the preview loaded and displays the edited quiz title: '$quizTitle' and the edited quiz description: '$quizDescription'.") | ||
| quizPreviewPage.assertPreviewLoaded() | ||
| quizPreviewPage.assertQuizTitleDisplayed(quizTitle) | ||
| quizPreviewPage.assertQuizDescriptionDisplayed(quizDescription) | ||
|
|
||
| Log.d(STEP_TAG, "Go back to Quiz Details page.") | ||
| Espresso.pressBack() | ||
|
|
||
| Log.d(STEP_TAG, "Open Due Dates section.") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can merge the logs of these 2 steps. |
||
| quizDetailsPage.openAllDatesPage() | ||
|
|
||
| Log.d(STEP_TAG, "Click the pencil/edit icon to open the edit page.") | ||
| assignmentDueDatesPage.openEditPage() | ||
|
|
||
| Log.d(STEP_TAG, "Set due date and time for the first due date.") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice to put the values to the log as well to see what date and time will be set. |
||
| editQuizDetailsPage.clickEditDueDate() | ||
adamNagy56 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| editQuizDetailsPage.editDate(2025, 5, 15) | ||
| editQuizDetailsPage.clickEditDueTime() | ||
| editQuizDetailsPage.editTime(10, 30) | ||
|
|
||
| Log.d(STEP_TAG, "Click 'Add Override' to add a second due date and assign it to '${student.name}'.") | ||
| editQuizDetailsPage.clickAddOverride() | ||
| assigneeListPage.toggleAssignees(listOf(student.name)) | ||
| assigneeListPage.saveAndClose() | ||
|
|
||
| Log.d(ASSERTION_TAG, "Assert that another new due date override has been created.") | ||
| editQuizDetailsPage.assertNewOverrideCreated() | ||
|
|
||
| Log.d(STEP_TAG, "Set due date and time for the second override.") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above. |
||
| editQuizDetailsPage.clickEditDueDate(1) | ||
| editQuizDetailsPage.editDate(2025, 6, 20) | ||
| editQuizDetailsPage.clickEditDueTime(1) | ||
| editQuizDetailsPage.editTime(14, 45) | ||
|
|
||
| Log.d(STEP_TAG, "Save the quiz after creating 2 due dates.") | ||
| editQuizDetailsPage.saveQuiz() | ||
|
|
||
| Log.d(STEP_TAG, "Refresh the Due Dates page.") | ||
| refresh() | ||
|
|
||
| Log.d(ASSERTION_TAG, "Assert that 2 due dates are visible on the Due Dates page.") | ||
| assignmentDueDatesPage.assertDueDatesCount(2) | ||
|
|
||
| Log.d(ASSERTION_TAG, "Assert first due date is for 'Everyone else' with date 'May 15, 2025 at 10:30 AM'.") | ||
| assignmentDueDatesPage.assertDueFor("Everyone else") | ||
| assignmentDueDatesPage.assertDueDateTime("May 15, 2025 at 10:30 AM") | ||
|
|
||
| Log.d(ASSERTION_TAG, "Assert second due date is for '${student.name}' with date 'Jun 20, 2025 at 2:45 PM'.") | ||
| assignmentDueDatesPage.assertDueFor(student.name) | ||
| assignmentDueDatesPage.assertDueDateTime("Jun 20, 2025 at 2:45 PM") | ||
|
|
||
| Log.d(STEP_TAG, "Press back to return to Quiz Details page.") | ||
| Espresso.pressBack() | ||
|
|
||
| Log.d(ASSERTION_TAG, "Assert that the due dates section shows 'Multiple Due Dates'.") | ||
| quizDetailsPage.assertMultipleDueDatesTextDisplayed() | ||
|
|
||
| Log.d(STEP_TAG, "Open Due Dates section again.") | ||
| quizDetailsPage.openAllDatesPage() | ||
|
|
||
| Log.d(STEP_TAG, "Click the pencil/edit icon to open the edit page.") | ||
| assignmentDueDatesPage.openEditPage() | ||
|
|
||
| Log.d(STEP_TAG, "Remove the second due date.") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Value should be in the log to be clear what date will be removed. |
||
| editQuizDetailsPage.removeSecondOverride() | ||
|
|
||
| Log.d(STEP_TAG, "Save the quiz after removing the second due date.") | ||
| editQuizDetailsPage.saveQuiz() | ||
|
|
||
| Log.d(STEP_TAG, "Refresh the Due Dates page.") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can merge this and the previous steps logs. |
||
| refresh() | ||
|
|
||
| Log.d(ASSERTION_TAG, "Assert that only 1 due date is visible on the Due Dates page.") | ||
| assignmentDueDatesPage.assertDisplaysSingleDueDate() | ||
|
|
||
| Log.d(ASSERTION_TAG, "Assert remaining due date is for 'Everyone' with date 'May 15, 2025 at 10:30 AM'.") | ||
| assignmentDueDatesPage.assertDueFor("Everyone") | ||
| assignmentDueDatesPage.assertDueDateTime("May 15, 2025 at 10:30 AM") | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,6 +50,7 @@ import com.instructure.espresso.randomString | |
| import com.instructure.espresso.replaceText | ||
| import com.instructure.espresso.scrollTo | ||
| import com.instructure.teacher.R | ||
| import com.instructure.teacher.ui.utils.TypeInRCETextEditor | ||
| import com.instructure.teacher.view.AssignmentOverrideView | ||
| import org.hamcrest.CoreMatchers.allOf | ||
| import org.hamcrest.Matchers | ||
|
|
@@ -69,6 +70,7 @@ class EditQuizDetailsPage : BasePage() { | |
| private val accessCodeEditText by WaitForViewWithId(R.id.editAccessCode) | ||
| private val saveButton by OnViewWithId(R.id.menuSave) | ||
| private val descriptionWebView by OnViewWithId(R.id.descriptionWebView, autoAssert = false) | ||
| private val contentRceView by WaitForViewWithId(R.id.rce_webView, autoAssert = false) | ||
| private val noDescriptionTextView by OnViewWithId( | ||
| R.id.noDescriptionTextView, | ||
| autoAssert = false | ||
|
|
@@ -91,6 +93,15 @@ class EditQuizDetailsPage : BasePage() { | |
| saveQuiz() | ||
| } | ||
|
|
||
| /** | ||
| * Edits the quiz description with the specified new description. | ||
| * | ||
| * @param newDescription The new description to be set as the quiz description. | ||
| */ | ||
| fun editQuizDescription(newDescription: String) { | ||
| contentRceView.perform(TypeInRCETextEditor(newDescription)) | ||
| } | ||
|
|
||
| /** | ||
| * Clicks on the access code switch to toggle its state. | ||
| */ | ||
|
|
@@ -258,8 +269,18 @@ class EditQuizDetailsPage : BasePage() { | |
| ) | ||
|
|
||
| fun editAssignees() = waitScrollClick(R.id.assignTo) | ||
| fun clickEditDueDate() = waitScrollClick(R.id.dueDate) | ||
| fun clickEditDueTime() = waitScrollClick(R.id.dueTime) | ||
| fun clickEditDueDate(overrideIndex: Int = 0) { | ||
| addOverrideButton().scrollTo() | ||
| Thread.sleep(1000) //wait for the UI to be settled | ||
| onViewWithContentDescription("due_date_$overrideIndex").scrollTo() | ||
adamNagy56 marked this conversation as resolved.
Show resolved
Hide resolved
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need to get the element twice, you can just do: onViewWithContentDescription("due_date_$overrideIndex").scrollTo().click() |
||
| onViewWithContentDescription("due_date_$overrideIndex").click() | ||
| } | ||
| fun clickEditDueTime(overrideIndex: Int = 0) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing empty line between the functions |
||
| addOverrideButton().scrollTo() | ||
| Thread.sleep(1000) //wait for the UI to be settled | ||
| onViewWithContentDescription("due_time_$overrideIndex").scrollTo() | ||
| onViewWithContentDescription("due_time_$overrideIndex").click() | ||
| } | ||
| fun clickEditUnlockDate() = waitScrollClick(R.id.fromDate) | ||
| fun clickEditUnlockTime() = waitScrollClick(R.id.fromTime) | ||
| fun clickEditLockDate() = waitScrollClick(R.id.toDate) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,11 @@ | |
| package com.instructure.teacher.ui.pages.classic | ||
|
|
||
| import androidx.test.InstrumentationRegistry | ||
| import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches | ||
| import androidx.test.espresso.web.sugar.Web.onWebView | ||
| import androidx.test.espresso.web.webdriver.DriverAtoms.findElement | ||
| import androidx.test.espresso.web.webdriver.DriverAtoms.getText | ||
| import androidx.test.espresso.web.webdriver.Locator | ||
| import com.instructure.canvasapi2.models.Quiz | ||
| import com.instructure.dataseeding.model.QuizApiModel | ||
| import com.instructure.espresso.ModuleItemInteractions | ||
|
|
@@ -33,11 +38,14 @@ import com.instructure.espresso.assertVisible | |
| import com.instructure.espresso.click | ||
| import com.instructure.espresso.page.BasePage | ||
| import com.instructure.espresso.page.onView | ||
| import com.instructure.espresso.page.plus | ||
| import com.instructure.espresso.page.scrollTo | ||
| import com.instructure.espresso.page.waitForView | ||
| import com.instructure.espresso.page.withId | ||
| import com.instructure.espresso.page.withText | ||
| import com.instructure.espresso.swipeDown | ||
| import com.instructure.teacher.R | ||
| import org.hamcrest.Matchers.containsString | ||
|
|
||
| /** | ||
| * Represents the Quiz Details page. | ||
|
|
@@ -71,6 +79,7 @@ class QuizDetailsPage(val moduleItemInteractions: ModuleItemInteractions) : Base | |
| private val gradedDonut by OnViewWithId(R.id.gradedWrapper) | ||
| private val ungradedDonut by OnViewWithId(R.id.ungradedWrapper) | ||
| private val notSubmittedDonut by OnViewWithId(R.id.notSubmittedWrapper) | ||
| private val quizPreviewButton by OnViewWithId(R.id.quizPreviewButton) | ||
|
|
||
| /** | ||
| * Asserts that the instructions for the quiz are displayed. | ||
|
|
@@ -91,9 +100,18 @@ class QuizDetailsPage(val moduleItemInteractions: ModuleItemInteractions) : Base | |
| * Opens the All Dates page for the quiz. | ||
| */ | ||
| fun openAllDatesPage() { | ||
| scrollTo(R.id.dueLayout) | ||
| dueDatesLayout.click() | ||
| } | ||
|
|
||
| /** | ||
| * Asserts that the due dates section displays "Multiple Due Dates". | ||
| */ | ||
| fun assertMultipleDueDatesTextDisplayed() { | ||
| dueSectionLabel.assertDisplayed() | ||
| onView(withId(R.id.otherDueDateTextView) + withText(R.string.multiple_due_dates)).assertDisplayed() | ||
| } | ||
|
|
||
| /** | ||
| * Opens the Edit page for the quiz. | ||
| */ | ||
|
|
@@ -109,6 +127,14 @@ class QuizDetailsPage(val moduleItemInteractions: ModuleItemInteractions) : Base | |
| viewAllSubmissions.click() | ||
| } | ||
|
|
||
| /** | ||
| * Opens the Preview page for the quiz. | ||
| */ | ||
| fun openPreviewPage() { | ||
| scrollTo(R.id.quizPreviewButton) | ||
| quizPreviewButton.click() | ||
| } | ||
|
|
||
| /** | ||
| * Asserts the quiz details such as title and publish status. | ||
| * | ||
|
|
@@ -177,6 +203,19 @@ class QuizDetailsPage(val moduleItemInteractions: ModuleItemInteractions) : Base | |
| quizTitleTextView.assertHasText(newQuizName) | ||
| } | ||
|
|
||
| /** | ||
| * Asserts that the quiz description has changed to the specified new description. | ||
| * | ||
| * @param newDescription The new description to assert. | ||
| */ | ||
| fun assertQuizDescriptionChanged(newDescription: String) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please call this method 'assertQuizDescriptionDisplayed' because it is actually just checking the description, not validating change. If you use this to validate a change process in your test, then write the logs accordingly, but this method itself does not test the change, just the current value.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, I know 'assertQuizNameChanged' is a legacy method but please refactor that one as well. |
||
| scrollTo(R.id.contentWebView) | ||
| instructionsWebView.assertVisible() | ||
| onWebView() | ||
| .withElement(findElement(Locator.TAG_NAME, "body")) | ||
| .check(webMatches(getText(), containsString(newDescription))) | ||
| } | ||
|
|
||
| /** | ||
| * Asserts that the quiz points have changed to the specified new quiz points. | ||
| * | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| /* | ||
| * Copyright (C) 2025 - present Instructure, Inc. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package com.instructure.teacher.ui.pages.classic | ||
|
|
||
| import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches | ||
| import androidx.test.espresso.web.sugar.Web.onWebView | ||
| import androidx.test.espresso.web.webdriver.DriverAtoms.findElement | ||
| import androidx.test.espresso.web.webdriver.DriverAtoms.getText | ||
| import androidx.test.espresso.web.webdriver.Locator | ||
| import com.instructure.espresso.page.BasePage | ||
| import com.instructure.espresso.page.waitForViewWithId | ||
| import com.instructure.teacher.R | ||
| import org.hamcrest.Matchers.containsString | ||
|
|
||
| class QuizPreviewPage : BasePage(R.id.canvasWebView) { | ||
|
|
||
| fun assertPreviewLoaded() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are using the KDoc format in the teacher app, please comment up these functions like the existing ones. |
||
| waitForViewWithId(R.id.canvasWebView) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wouldn't be sure if the canvasWebView container is loaded that implicates that the preview is loaded successfully. I'd put some other assertions below it, like calling the assertQuizTitleDisplayed and assertQuizDescriptionDisplayed methods. (And you can use just the assertPreviewDisplayed method in the test, so the other 2 methods may could be private if you don't use them elsewhere). |
||
| } | ||
|
|
||
| fun assertQuizTitleDisplayed(quizTitle: String) { | ||
| onWebView() | ||
| .withElement(findElement(Locator.TAG_NAME, "body")) | ||
| .check(webMatches(getText(), containsString(quizTitle))) | ||
| } | ||
|
|
||
| fun assertQuizDescriptionDisplayed(description: String) { | ||
| onWebView() | ||
| .withElement(findElement(Locator.TAG_NAME, "body")) | ||
| .check(webMatches(getText(), containsString(description))) | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can put this seeding before the login as we usually do in our tests. (In this case there will be more time for the API call to propagate).