Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -89,8 +90,9 @@ class QuizE2ETest: TeacherTest() {
quizDetailsPage.openEditPage()
editQuizDetailsPage.editQuizTitle(newQuizTitle)

Log.d(ASSERTION_TAG, "Assert that the quiz name has been changed to: '$newQuizTitle'.")
quizDetailsPage.assertQuizNameChanged(newQuizTitle)
Log.d(ASSERTION_TAG, "Assert that the quiz title has been changed from: '${firstQuiz.title}' to: '$newQuizTitle'.")
quizDetailsPage.assertQuizTitleNotDisplayed(firstQuiz.title)
quizDetailsPage.assertQuizTitleDisplayed(newQuizTitle)

Log.d(STEP_TAG, "Open 'Edit' page and switch on the 'Published' checkbox, so publish the '$newQuizTitle' quiz. Click on 'Save'.")
quizDetailsPage.openEditPage()
Expand Down Expand Up @@ -143,4 +145,116 @@ 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(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)
val quiz = testQuizList.quizList[0]
val quizTitle = quiz.title
val quizDescription = quiz.description

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(STEP_TAG, "Click on the quiz: '$quizTitle'.")
quizListPage.clickQuiz(quizTitle)

val newQuizTitle = "My Custom Quiz Title"
val newQuizDescription = "This is my custom quiz description"
Log.d(STEP_TAG, "Open 'Edit' page and edit the quiz description to: '$newQuizDescription' and title to: '$newQuizTitle'.")
quizDetailsPage.openEditPage()
editQuizDetailsPage.editQuizDescription(newQuizDescription)
editQuizDetailsPage.editQuizTitle(newQuizTitle)

Log.d(ASSERTION_TAG, "Assert that the quiz title and description have been changed FROM: '$quizTitle' and '$quizDescription' TO: '$newQuizTitle' and '$newQuizDescription'.")
quizDetailsPage.assertQuizTitleNotDisplayed(quizTitle)
quizDetailsPage.assertQuizDescriptionNotDisplayed(quizDescription)
quizDetailsPage.assertQuizTitleDisplayed(newQuizTitle)
quizDetailsPage.assertQuizDescriptionDisplayed(newQuizDescription)

Log.d(STEP_TAG, "Open preview page.")
quizDetailsPage.openPreviewPage()

Log.d(ASSERTION_TAG, "Assert that the preview loaded and displays the edited quiz title: '$newQuizTitle' and the edited quiz description: '$newQuizDescription'.")
quizPreviewPage.assertPreviewDisplayed(newQuizTitle, newQuizDescription)

Log.d(STEP_TAG, "Go back to Quiz Details page and open Due Dates section.")
Espresso.pressBack()
quizDetailsPage.openAllDatesPage()

Log.d(STEP_TAG, "Click the pencil/edit icon to open the edit page and set due date to 'May 15, 2025 at 10:30 AM' for the first due date.")
assignmentDueDatesPage.openEditPage()
editQuizDetailsPage.clickEditDueDate()
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 to 'Jun 20, 2025 at 2:45 PM' for the second override.")
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 and refresh the page.")
editQuizDetailsPage.saveQuiz()
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 and remove the second due date ('Jun 20, 2025 at 2:45 PM').")
assignmentDueDatesPage.openEditPage()
editQuizDetailsPage.removeSecondOverride()

Log.d(STEP_TAG, "Save the quiz after removing the second due date and refresh the page.")
editQuizDetailsPage.saveQuiz()
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
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.
*/
Expand Down Expand Up @@ -250,20 +261,68 @@ class EditQuizDetailsPage : BasePage() {
.check(ViewAssertions.matches(hasTextInputLayoutErrorText(R.string.assignee_blank_error)))
}

private fun addOverrideButton() = waitForView(
allOf(
withId(R.id.addOverride),
withEffectiveVisibility(Visibility.VISIBLE)
)
)

/**
* Opens the assignees editor to modify who the quiz is assigned to.
*/
fun editAssignees() = waitScrollClick(R.id.assignTo)
fun clickEditDueDate() = waitScrollClick(R.id.dueDate)
fun clickEditDueTime() = waitScrollClick(R.id.dueTime)

/**
* Clicks on the due date field to edit the date for a specific override.
*
* @param overrideIndex The index of the override whose due date to edit (default: 0 for the first override).
*/
fun clickEditDueDate(overrideIndex: Int = 0) {
addOverrideButton().scrollTo()
Thread.sleep(1000) //wait for the UI to be settled
onViewWithContentDescription("due_date_$overrideIndex").scrollTo().click()
}

/**
* Clicks on the due time field to edit the time for a specific override.
*
* @param overrideIndex The index of the override whose due time to edit (default: 0 for the first override).
*/
fun clickEditDueTime(overrideIndex: Int = 0) {
addOverrideButton().scrollTo()
Thread.sleep(1000) //wait for the UI to be settled
onViewWithContentDescription("due_time_$overrideIndex").scrollTo().click()
}

/**
* Clicks on the unlock date field to edit when the quiz becomes available.
*/
fun clickEditUnlockDate() = waitScrollClick(R.id.fromDate)

/**
* Clicks on the unlock time field to edit when the quiz becomes available.
*/
fun clickEditUnlockTime() = waitScrollClick(R.id.fromTime)

/**
* Clicks on the lock date field to edit when the quiz is no longer accessible.
*/
fun clickEditLockDate() = waitScrollClick(R.id.toDate)

/**
* Clicks on the lock time field to edit when the quiz is no longer accessible.
*/
fun clickEditLockTime() = waitScrollClick(R.id.toTime)

/**
* Clicks the "Add Override" button to create a new due date override for specific assignees.
*/
fun clickAddOverride() = addOverrideButton().scrollTo().click()

/**
* Toggles the publish switch to change the quiz's published state.
*/
fun switchPublish() = waitScrollClick(R.id.publishSwitch)

private fun addOverrideButton() = waitForView(
allOf(
withId(R.id.addOverride),
withEffectiveVisibility(Visibility.VISIBLE)
)
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
package com.instructure.teacher.ui.pages.classic

import androidx.test.InstrumentationRegistry
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers
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
Expand All @@ -33,11 +40,15 @@ 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
import org.hamcrest.Matchers.containsString

/**
* Represents the Quiz Details page.
Expand Down Expand Up @@ -71,6 +82,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.
Expand All @@ -91,9 +103,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.
*/
Expand All @@ -109,6 +130,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.
*
Expand Down Expand Up @@ -169,12 +198,48 @@ class QuizDetailsPage(val moduleItemInteractions: ModuleItemInteractions) : Base
}

/**
* Asserts that the quiz name has changed to the specified new quiz name.
* Asserts that the quiz title is displayed with the specified text.
*
* @param quizTitle The quiz title to assert.
*/
fun assertQuizTitleDisplayed(quizTitle: String) {
quizTitleTextView.assertHasText(quizTitle)
}

/**
* Asserts that the quiz description is displayed with the specified text.
*
* @param newQuizName The new quiz name to assert.
* @param description The description to assert.
*/
fun assertQuizNameChanged(newQuizName: String) {
quizTitleTextView.assertHasText(newQuizName)
fun assertQuizDescriptionDisplayed(description: String) {
scrollTo(R.id.contentWebView)
instructionsWebView.assertVisible()
onWebView()
.withElement(findElement(Locator.TAG_NAME, "body"))
.check(webMatches(getText(), containsString(description)))
}

/**
* Asserts that the quiz title is NOT displayed with the specified text.
*
* @param quizTitle The quiz title that should not be displayed.
*/
fun assertQuizTitleNotDisplayed(quizTitle: String) {
onView(withId(R.id.quizTitleTextView))
.check(ViewAssertions.matches(Matchers.not(ViewMatchers.withText(quizTitle))))
}

/**
* Asserts that the quiz description is NOT displayed with the specified text.
*
* @param description The description that should not be displayed.
*/
fun assertQuizDescriptionNotDisplayed(description: String?) {
scrollTo(R.id.contentWebView)
instructionsWebView.assertVisible()
onWebView()
.withElement(findElement(Locator.TAG_NAME, "body"))
.check(webMatches(getText(), Matchers.not(containsString(description))))
}

/**
Expand Down
Loading
Loading