diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/classic/QuizE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/classic/QuizE2ETest.kt index 7955ef3364..18c6f89f08 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/classic/QuizE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/classic/QuizE2ETest.kt @@ -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 @@ -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() @@ -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") + } + } \ No newline at end of file diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/EditQuizDetailsPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/EditQuizDetailsPage.kt index 9db7b2eb49..3ca19651cb 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/EditQuizDetailsPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/EditQuizDetailsPage.kt @@ -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. */ @@ -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) + ) + ) + } diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/QuizDetailsPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/QuizDetailsPage.kt index 0deec368ca..4675a9ece9 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/QuizDetailsPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/QuizDetailsPage.kt @@ -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 @@ -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. @@ -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. @@ -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. */ @@ -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. * @@ -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)))) } /** diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/QuizPreviewPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/QuizPreviewPage.kt new file mode 100644 index 0000000000..9e2405ea25 --- /dev/null +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/QuizPreviewPage.kt @@ -0,0 +1,65 @@ +/* + * 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 + +/** + * Represents the quiz preview page in the Teacher app. + * + * This class extends the `BasePage` class and provides methods for verifying + * the quiz preview content, including title and description assertions within the WebView. + * + * @constructor Creates an instance of the `QuizPreviewPage` class. + */ +class QuizPreviewPage : BasePage(R.id.canvasWebView) { + + /** + * Asserts that the quiz preview is displayed with the expected title and description. + * + * This method waits for the WebView to load, then verifies that both the quiz title + * and description are present in the rendered preview content. + * + * @param quizTitle The expected quiz title to verify in the preview. + * @param description The expected quiz description to verify in the preview. + * @throws AssertionError if the preview is not loaded or if the title or description is not displayed. + */ + fun assertPreviewDisplayed(quizTitle: String, description: String) { + waitForViewWithId(R.id.canvasWebView) + assertQuizTitleDisplayed(quizTitle) + assertQuizDescriptionDisplayed(description) + } + + private fun assertQuizTitleDisplayed(quizTitle: String) { + onWebView() + .withElement(findElement(Locator.TAG_NAME, "body")) + .check(webMatches(getText(), containsString(quizTitle))) + } + + private fun assertQuizDescriptionDisplayed(description: String) { + onWebView() + .withElement(findElement(Locator.TAG_NAME, "body")) + .check(webMatches(getText(), containsString(description))) + } +} diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherTest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherTest.kt index b28c3be183..6a13073f82 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherTest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherTest.kt @@ -72,6 +72,7 @@ import com.instructure.teacher.ui.pages.classic.ProfileSettingsPage import com.instructure.teacher.ui.pages.classic.PushNotificationsPage import com.instructure.teacher.ui.pages.classic.QuizDetailsPage import com.instructure.teacher.ui.pages.classic.QuizListPage +import com.instructure.teacher.ui.pages.classic.QuizPreviewPage import com.instructure.teacher.ui.pages.classic.RemoteConfigSettingsPage import com.instructure.teacher.ui.pages.classic.SpeedGraderCommentsPage import com.instructure.teacher.ui.pages.classic.SpeedGraderQuizSubmissionPage @@ -136,6 +137,7 @@ abstract class TeacherTest : CanvasTest() { val peopleListPage = PeopleListPage(Searchable(R.id.search, R.id.search_src_text, R.id.search_close_btn)) val quizDetailsPage = QuizDetailsPage(ModuleItemInteractions(R.id.moduleName, R.id.next, R.id.previous)) val quizListPage = QuizListPage(Searchable(R.id.search, R.id.search_src_text, R.id.search_close_btn, R.id.backButton)) + val quizPreviewPage = QuizPreviewPage() val speedGraderCommentsPage = SpeedGraderCommentsPage() val speedGraderQuizSubmissionPage = SpeedGraderQuizSubmissionPage() val personContextPage = PersonContextPage() diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/QuizPreviewWebviewFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/QuizPreviewWebviewFragment.kt index 62e77813c3..f7795f1da2 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/QuizPreviewWebviewFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/QuizPreviewWebviewFragment.kt @@ -28,6 +28,7 @@ import com.instructure.pandautils.utils.enableAlgorithmicDarkening import com.instructure.pandautils.utils.setGone import com.instructure.pandautils.utils.setVisible import com.instructure.pandautils.views.CanvasWebView +import com.instructure.teacher.BuildConfig import com.instructure.teacher.router.RouteMatcher import com.instructure.teacher.utils.setupBackButton import kotlinx.coroutines.Job @@ -107,6 +108,7 @@ class QuizPreviewWebviewFragment : InternalWebViewFragment() { @JvmStatic val TITLE = "title" fun newInstance(args: Bundle) = QuizPreviewWebviewFragment().apply { + arguments = args url = args.getString(URL)!! title = args.getString(TITLE)!! } @@ -116,6 +118,12 @@ class QuizPreviewWebviewFragment : InternalWebViewFragment() { args.putString(URL, url) args.putString(TITLE, title) args.putBoolean(DARK_TOOLBAR, false) + + // Only authenticate during tests to avoid intermittent login page issues + if (BuildConfig.IS_TESTING) { + args.putBoolean(AUTHENTICATE, true) + } + return args } } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/view/AssignmentOverrideView.kt b/apps/teacher/src/main/java/com/instructure/teacher/view/AssignmentOverrideView.kt index 87c699f1e4..5ddef2196b 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/view/AssignmentOverrideView.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/view/AssignmentOverrideView.kt @@ -30,7 +30,8 @@ import com.instructure.teacher.R import com.instructure.teacher.databinding.ViewAssignmentOverrideBinding import com.instructure.teacher.models.DueDateGroup import com.instructure.teacher.utils.formatOrDoubleDash -import java.util.* +import java.util.Calendar +import java.util.Date import kotlin.properties.Delegates class AssignmentOverrideView @JvmOverloads constructor( @@ -194,7 +195,11 @@ class AssignmentOverrideView @JvmOverloads constructor( if (!showRemove) removeOverride.setGone() - if (BuildConfig.IS_TESTING) removeOverride.contentDescription = "remove_override_button_$index" + if (BuildConfig.IS_TESTING) { + removeOverride.contentDescription = "remove_override_button_$index" + binding.dueDateTextInput.contentDescription = "due_date_$index" + binding.dueTimeTextInput.contentDescription = "due_time_$index" + } removeOverride.setOnClickListener { removeOverrideClickListener(dueDateGroup)