diff --git a/src/main/webapp/app/exam/manage/exams/exam-checklist-component/exam-announcement-dialog/exam-live-announcement-create-button.component.html b/src/main/webapp/app/exam/manage/exams/exam-checklist-component/exam-announcement-dialog/exam-live-announcement-create-button.component.html index 99a6bf8f458b..245662fb970c 100644 --- a/src/main/webapp/app/exam/manage/exams/exam-checklist-component/exam-announcement-dialog/exam-live-announcement-create-button.component.html +++ b/src/main/webapp/app/exam/manage/exams/exam-checklist-component/exam-announcement-dialog/exam-live-announcement-create-button.component.html @@ -1,4 +1,4 @@ - diff --git a/src/main/webapp/app/exam/manage/exams/exam-checklist-component/exam-edit-workingtime-dialog/exam-edit-working-time.component.html b/src/main/webapp/app/exam/manage/exams/exam-checklist-component/exam-edit-workingtime-dialog/exam-edit-working-time.component.html index d50ec3b2b423..0145161f28b6 100644 --- a/src/main/webapp/app/exam/manage/exams/exam-checklist-component/exam-edit-workingtime-dialog/exam-edit-working-time.component.html +++ b/src/main/webapp/app/exam/manage/exams/exam-checklist-component/exam-edit-workingtime-dialog/exam-edit-working-time.component.html @@ -1,4 +1,4 @@ - diff --git a/src/test/playwright/e2e/exam/ExamAssessment.spec.ts b/src/test/playwright/e2e/exam/ExamAssessment.spec.ts index c273cd5440f9..56161de7baaf 100644 --- a/src/test/playwright/e2e/exam/ExamAssessment.spec.ts +++ b/src/test/playwright/e2e/exam/ExamAssessment.spec.ts @@ -10,7 +10,7 @@ import { Exam } from 'app/entities/exam.model'; import { Commands } from '../../support/commands'; import { ExamAPIRequests } from '../../support/requests/ExamAPIRequests'; import { ExamExerciseGroupCreationPage } from '../../support/pageobjects/exam/ExamExerciseGroupCreationPage'; -import { ExamParticipation } from '../../support/pageobjects/exam/ExamParticipation'; +import { ExamParticipationPage } from '../../support/pageobjects/exam/ExamParticipationPage'; import { ExamNavigationBar } from '../../support/pageobjects/exam/ExamNavigationBar'; import { ExamStartEndPage } from '../../support/pageobjects/exam/ExamStartEndPage'; import { ExamManagementPage } from '../../support/pageobjects/exam/ExamManagementPage'; @@ -307,7 +307,7 @@ export async function prepareExam(course: Course, end: dayjs.Dayjs, exerciseType const textExerciseEditor = new TextEditorPage(page); const examNavigation = new ExamNavigationBar(page); const examStartEnd = new ExamStartEndPage(page); - const examParticipation = new ExamParticipation( + const examParticipation = new ExamParticipationPage( courseList, courseOverview, examNavigation, @@ -359,7 +359,7 @@ async function makeExamSubmission( exam: Exam, exercise: Exercise, page: Page, - examParticipation: ExamParticipation, + examParticipation: ExamParticipationPage, examNavigation: ExamNavigationBar, examStartEnd: ExamStartEndPage, ) { diff --git a/src/test/playwright/e2e/exam/ExamParticipation.spec.ts b/src/test/playwright/e2e/exam/ExamParticipation.spec.ts index 6cc539f65011..46c8fe0e6e01 100644 --- a/src/test/playwright/e2e/exam/ExamParticipation.spec.ts +++ b/src/test/playwright/e2e/exam/ExamParticipation.spec.ts @@ -1,12 +1,17 @@ import { test } from '../../support/fixtures'; import { Course } from 'app/entities/course.model'; import { Exercise, ExerciseType } from '../../support/constants'; -import { admin, instructor, studentFour, studentThree, studentTwo, tutor, users } from '../../support/users'; +import { admin, instructor, studentFour, studentOne, studentThree, studentTwo, tutor, users } from '../../support/users'; import { generateUUID } from '../../support/utils'; import javaAllSuccessfulSubmission from '../../fixtures/exercise/programming/java/all_successful/submission.json'; import dayjs from 'dayjs'; import { Exam } from 'app/entities/exam.model'; import { expect } from '@playwright/test'; +import { ExamStartEndPage } from '../../support/pageobjects/exam/ExamStartEndPage'; +import { Commands } from '../../support/commands'; +import { ExamAPIRequests } from '../../support/requests/ExamAPIRequests'; +import { ModalDialogBox } from '../../support/pageobjects/exam/ModalDialogBox'; +import { ExamParticipationActions } from '../../support/pageobjects/exam/ExamParticipationActions'; // Common primitives const textFixture = 'loremIpsum.txt'; @@ -44,16 +49,7 @@ test.describe('Exam participation', () => { test.beforeEach('Create exam', async ({ login, examAPIRequests, examExerciseGroupCreation }) => { await login(admin); - const examConfig = { - course, - title: examTitle, - visibleDate: dayjs().subtract(3, 'minutes'), - startDate: dayjs().subtract(2, 'minutes'), - endDate: dayjs().add(1, 'hour'), - examMaxPoints: 40, - numberOfExercisesInExam: 4, - }; - exam = await examAPIRequests.createExam(examConfig); + exam = await createExam(course, examAPIRequests, { title: examTitle, examMaxPoints: 40, numberOfExercisesInExam: 4 }); const textExercise = await examExerciseGroupCreation.addGroupWithExercise(exam, ExerciseType.TEXT, { textFixture }); const programmingExercise = await examExerciseGroupCreation.addGroupWithExercise(exam, ExerciseType.PROGRAMMING, { submission: javaAllSuccessfulSubmission }); const quizExercise = await examExerciseGroupCreation.addGroupWithExercise(exam, ExerciseType.QUIZ, { quizExerciseID: 0 }); @@ -136,17 +132,7 @@ test.describe('Exam participation', () => { exerciseArray = []; await login(admin); - - const examConfig = { - course, - title: examTitle, - visibleDate: dayjs().subtract(3, 'minutes'), - startDate: dayjs().subtract(2, 'minutes'), - endDate: dayjs().add(1, 'hour'), - examMaxPoints: 10, - numberOfExercisesInExam: 1, - }; - exam = await examAPIRequests.createExam(examConfig); + exam = await createExam(course, examAPIRequests, { title: examTitle }); await examExerciseGroupCreation.addGroupWithExercise(exam, ExerciseType.TEXT, { textFixture }).then((response) => { exerciseArray.push(response); }); @@ -249,17 +235,7 @@ test.describe('Exam participation', () => { exerciseArray = []; await login(admin); - - const examConfig = { - course, - title: examTitle, - visibleDate: dayjs().subtract(3, 'minutes'), - startDate: dayjs().subtract(2, 'minutes'), - endDate: dayjs().add(30, 'seconds'), - examMaxPoints: 10, - numberOfExercisesInExam: 1, - }; - exam = await examAPIRequests.createExam(examConfig); + exam = await createExam(course, examAPIRequests, { title: examTitle, endDate: dayjs().add(30, 'seconds') }); const exercise = await examExerciseGroupCreation.addGroupWithExercise(exam, ExerciseType.TEXT, { textFixture }); exerciseArray.push(exercise); @@ -288,7 +264,112 @@ test.describe('Exam participation', () => { }); }); + test.describe('Exam announcements', () => { + let exam: Exam; + const students = [studentOne, studentTwo]; + + test.beforeEach('Create exam', async ({ login, examAPIRequests, examExerciseGroupCreation }) => { + await login(admin); + exam = await createExam(course, examAPIRequests); + const exercise = await examExerciseGroupCreation.addGroupWithExercise(exam, ExerciseType.TEXT, { textFixture }); + exerciseArray.push(exercise); + for (const student of students) { + await examAPIRequests.registerStudentForExam(exam, student); + } + + await examAPIRequests.generateMissingIndividualExams(exam); + await examAPIRequests.prepareExerciseStartForExam(exam); + }); + + test('Instructor sends an announcement message and all participants receive it', async ({ browser, login, navigationBar, courseManagement, examManagement }) => { + await login(instructor); + await navigationBar.openCourseManagement(); + await courseManagement.openExamsOfCourse(course.id!); + await examManagement.openExam(exam.id!); + + const studentPages = []; + + for (const student of [studentOne, studentTwo]) { + const studentContext = await browser.newContext(); + const studentPage = await studentContext.newPage(); + studentPages.push(studentPage); + + await Commands.login(studentPage, student); + await studentPage.goto(`/courses/${course.id!}/exams/${exam.id!}`); + const examStartEnd = new ExamStartEndPage(studentPage); + await examStartEnd.startExam(false); + } + + const announcement = 'Important announcement!'; + await examManagement.openAnnouncementDialog(); + const announcementTypingTime = dayjs(); + await examManagement.typeAnnouncementMessage(announcement); + await examManagement.verifyAnnouncementContent(announcementTypingTime, announcement, instructor.username); + await examManagement.sendAnnouncement(); + + for (const studentPage of studentPages) { + const modalDialog = new ModalDialogBox(studentPage); + await modalDialog.checkDialogTime(announcementTypingTime); + await modalDialog.checkDialogMessage(announcement); + await modalDialog.checkDialogAuthor(instructor.username); + await modalDialog.closeDialog(); + } + }); + + test('Instructor changes working time and all participants are informed', async ({ browser, login, navigationBar, courseManagement, examManagement }) => { + await login(instructor); + await navigationBar.openCourseManagement(); + await courseManagement.openExamsOfCourse(course.id!); + await examManagement.openExam(exam.id!); + + const studentPages = []; + + for (const student of students) { + const studentContext = await browser.newContext(); + const studentPage = await studentContext.newPage(); + studentPages.push(studentPage); + + await Commands.login(studentPage, student); + await studentPage.goto(`/courses/${course.id!}/exams/${exam.id!}`); + const examStartEnd = new ExamStartEndPage(studentPage); + await examStartEnd.startExam(false); + } + + await examManagement.openEditWorkingTimeDialog(); + await examManagement.changeExamWorkingTime({ minutes: -30 }); + await examManagement.verifyExamWorkingTimeChange('1h 2min', '32min'); + const workingTimeChangeTime = dayjs(); + await examManagement.confirmWorkingTimeChange(exam.title!); + + for (const studentPage of studentPages) { + const examParticipationActions = new ExamParticipationActions(studentPage); + const modalDialog = new ModalDialogBox(studentPage); + const timeChangeMessage = 'The working time of the exam has been changed.'; + await modalDialog.checkExamTimeChangeDialog('1h 2min', '32min'); + await modalDialog.checkDialogTime(workingTimeChangeTime); + await modalDialog.checkDialogMessage(timeChangeMessage); + await modalDialog.checkDialogAuthor(instructor.username); + await modalDialog.closeDialog(); + await examParticipationActions.checkExamTimeLeft('29min'); + } + }); + }); + test.afterEach('Delete course', async ({ courseManagementAPIRequests }) => { await courseManagementAPIRequests.deleteCourse(course, admin); }); }); + +async function createExam(course: Course, examAPIRequests: ExamAPIRequests, customExamConfig?: any) { + const defaultExamConfig = { + course, + title: 'exam' + generateUUID(), + visibleDate: dayjs().subtract(3, 'minutes'), + startDate: dayjs().subtract(2, 'minutes'), + endDate: dayjs().add(1, 'hour'), + examMaxPoints: 10, + numberOfExercisesInExam: 1, + }; + const examConfig = { ...defaultExamConfig, ...customExamConfig }; + return await examAPIRequests.createExam(examConfig); +} diff --git a/src/test/playwright/e2e/exam/ExamResults.spec.ts b/src/test/playwright/e2e/exam/ExamResults.spec.ts index 9823f62846c1..42ebc5e7bd53 100644 --- a/src/test/playwright/e2e/exam/ExamResults.spec.ts +++ b/src/test/playwright/e2e/exam/ExamResults.spec.ts @@ -14,7 +14,7 @@ import javaPartiallySuccessfulSubmission from '../../fixtures/exercise/programmi import { ExamAPIRequests } from '../../support/requests/ExamAPIRequests'; import { ExamExerciseGroupCreationPage } from '../../support/pageobjects/exam/ExamExerciseGroupCreationPage'; import { ExerciseAPIRequests } from '../../support/requests/ExerciseAPIRequests'; -import { ExamParticipation } from '../../support/pageobjects/exam/ExamParticipation'; +import { ExamParticipationPage } from '../../support/pageobjects/exam/ExamParticipationPage'; import { CourseManagementAPIRequests } from '../../support/requests/CourseManagementAPIRequests'; import { ExamNavigationBar } from '../../support/pageobjects/exam/ExamNavigationBar'; import { CourseOverviewPage } from '../../support/pageobjects/course/CourseOverviewPage'; @@ -81,7 +81,7 @@ test.describe('Exam Results', () => { const courseList = new CoursesPage(page); const courseOverview = new CourseOverviewPage(page); - const examParticipation = new ExamParticipation( + const examParticipation = new ExamParticipationPage( courseList, courseOverview, new ExamNavigationBar(page), diff --git a/src/test/playwright/e2e/exam/test-exam/TestExamStudentExams.spec.ts b/src/test/playwright/e2e/exam/test-exam/TestExamStudentExams.spec.ts index b27b11567615..c7a872dff51e 100644 --- a/src/test/playwright/e2e/exam/test-exam/TestExamStudentExams.spec.ts +++ b/src/test/playwright/e2e/exam/test-exam/TestExamStudentExams.spec.ts @@ -6,7 +6,7 @@ import { Exercise, ExerciseType } from '../../../support/constants'; import dayjs from 'dayjs'; import { test } from '../../../support/fixtures'; import { expect } from '@playwright/test'; -import { ExamParticipation } from '../../../support/pageobjects/exam/ExamParticipation'; +import { ExamParticipationPage } from '../../../support/pageobjects/exam/ExamParticipationPage'; import { ExamNavigationBar } from '../../../support/pageobjects/exam/ExamNavigationBar'; // Common primitives @@ -115,7 +115,7 @@ test.describe('Test Exam - student exams', () => { exam: Exam, toStart: boolean, toSubmit: boolean, - examParticipation: ExamParticipation, + examParticipation: ExamParticipationPage, examNavigation: ExamNavigationBar, ) { if (!toStart) { diff --git a/src/test/playwright/support/fixtures.ts b/src/test/playwright/support/fixtures.ts index f653ebbd50cf..f277be1e2ca2 100644 --- a/src/test/playwright/support/fixtures.ts +++ b/src/test/playwright/support/fixtures.ts @@ -20,7 +20,7 @@ import { ExamDetailsPage } from './pageobjects/exam/ExamDetailsPage'; import { ExamManagementPage } from './pageobjects/exam/ExamManagementPage'; import { ExamExerciseGroupCreationPage } from './pageobjects/exam/ExamExerciseGroupCreationPage'; import { ExamNavigationBar } from './pageobjects/exam/ExamNavigationBar'; -import { ExamParticipation } from './pageobjects/exam/ExamParticipation'; +import { ExamParticipationPage } from './pageobjects/exam/ExamParticipationPage'; import { ExamStartEndPage } from './pageobjects/exam/ExamStartEndPage'; import { CoursesPage } from './pageobjects/course/CoursesPage'; import { ExamAssessmentPage } from './pageobjects/assessment/ExamAssessmentPage'; @@ -63,6 +63,8 @@ import { ExamScoresPage } from './pageobjects/exam/ExamScoresPage'; import { ProgrammingExerciseParticipationsPage } from './pageobjects/exercises/programming/ProgrammingExerciseParticipationsPage'; import { ExamResultsPage } from './pageobjects/exam/ExamResultsPage'; import { ExerciseTeamsPage } from './pageobjects/exercises/ExerciseTeamsPage'; +import { ModalDialogBox } from './pageobjects/exam/ModalDialogBox'; +import { ExamParticipationActions } from './pageobjects/exam/ExamParticipationActions'; /* * Define custom types for fixtures @@ -98,11 +100,13 @@ export type ArtemisPageObjects = { examGrading: ExamGradingPage; examNavigation: ExamNavigationBar; examManagement: ExamManagementPage; - examParticipation: ExamParticipation; + examParticipation: ExamParticipationPage; + examParticipationActions: ExamParticipationActions; examResultsPage: ExamResultsPage; examScores: ExamScoresPage; examStartEnd: ExamStartEndPage; examTestRun: ExamTestRunPage; + modalDialog: ModalDialogBox; studentExamManagement: StudentExamManagementPage; fileUploadExerciseCreation: FileUploadExerciseCreationPage; fileUploadExerciseEditor: FileUploadEditorPage; @@ -231,7 +235,7 @@ export const test = base.extend { await use( - new ExamParticipation( + new ExamParticipationPage( courseList, courseOverview, examNavigation, @@ -244,6 +248,9 @@ export const test = base.extend { + await use(new ExamParticipationActions(page)); + }, examResultsPage: async ({ page }, use) => { await use(new ExamResultsPage(page)); }, @@ -253,9 +260,13 @@ export const test = base.extend { await use(new ExamStartEndPage(page)); }, + examTestRun: async ({ page, examStartEnd }, use) => { await use(new ExamTestRunPage(page, examStartEnd)); }, + modalDialog: async ({ page }, use) => { + await use(new ModalDialogBox(page)); + }, studentExamManagement: async ({ page }, use) => { await use(new StudentExamManagementPage(page)); }, diff --git a/src/test/playwright/support/pageobjects/exam/ExamManagementPage.ts b/src/test/playwright/support/pageobjects/exam/ExamManagementPage.ts index f1988e8114cb..ad7dae5a7225 100644 --- a/src/test/playwright/support/pageobjects/exam/ExamManagementPage.ts +++ b/src/test/playwright/support/pageobjects/exam/ExamManagementPage.ts @@ -1,4 +1,5 @@ import { Page, expect } from '@playwright/test'; +import { Dayjs } from 'dayjs'; /** * A class which encapsulates UI selectors and actions for the exam management page. @@ -108,6 +109,54 @@ export class ExamManagementPage { await expect(this.page.locator('#exercise-result-score')).toHaveText(score, { useInnerText: true }); } + async openAnnouncementDialog() { + await this.page.locator('#announcement-create-button').click(); + } + + async typeAnnouncementMessage(message: string) { + await this.page.locator('.ace_text-input').fill(message); + } + + async verifyAnnouncementContent(announcementTime: Dayjs, message: string, authorUsername: string) { + const announcementDialog = this.page.locator('.modal-content'); + const timeFormat = 'MMM D, YYYY HH:mm'; + const announcementTimeFormatted = announcementTime.format(timeFormat); + const announcementTimeAfterMinute = announcementTime.add(1, 'minute').format(timeFormat); + await expect(announcementDialog.locator('.date').getByText(new RegExp(`(${announcementTimeFormatted}|${announcementTimeAfterMinute})`))).toBeVisible(); + await expect(announcementDialog.locator('.content').getByText(message)).toBeVisible(); + await expect(announcementDialog.locator('.author').getByText(authorUsername)).toBeVisible(); + } + + async sendAnnouncement() { + await this.page.locator('button', { hasText: 'Send Announcement' }).click(); + } + + async openEditWorkingTimeDialog() { + await this.page.locator('#edit-working-time-button').click(); + } + + async changeExamWorkingTime(newWorkingTime: any) { + if (newWorkingTime.hours) { + await this.page.locator('#workingTimeHours').fill(newWorkingTime.hours.toString()); + } + if (newWorkingTime.minutes) { + await this.page.locator('#workingTimeMinutes').fill(newWorkingTime.minutes.toString()); + } + if (newWorkingTime.seconds) { + await this.page.locator('#workingTimeSeconds').fill(newWorkingTime.seconds.toString()); + } + } + + async verifyExamWorkingTimeChange(previousWorkingTime: any, newWorkingTime: any) { + await expect(this.page.locator('[data-testid="old-time"]').getByText(previousWorkingTime)).toBeVisible(); + await expect(this.page.locator('[data-testid="new-time"]').getByText(newWorkingTime)).toBeVisible(); + } + + async confirmWorkingTimeChange(examTitle: string) { + await this.page.locator('#confirm-entity-name').fill(examTitle); + await this.page.locator('#confirm').click(); + } + async clickEdit() { await this.page.locator('#editButton').click(); } diff --git a/src/test/playwright/support/pageobjects/exam/ExamParticipationActions.ts b/src/test/playwright/support/pageobjects/exam/ExamParticipationActions.ts new file mode 100644 index 000000000000..ab7616f92a5b --- /dev/null +++ b/src/test/playwright/support/pageobjects/exam/ExamParticipationActions.ts @@ -0,0 +1,91 @@ +import { Page, expect } from '@playwright/test'; +import { Fixtures } from '../../../fixtures/fixtures'; +import { Commands } from '../../commands'; +import { getExercise } from '../../utils'; +import { Dayjs } from 'dayjs'; + +export class ExamParticipationActions { + protected readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + async selectExerciseOnOverview(index: number) { + await this.page.locator(`.exercise-table tr:nth-child(${index}) a`).click(); + } + + async clickSaveAndContinue() { + await this.page.click('#save'); + } + + async checkExerciseTitle(exerciseID: number, title: string) { + const exercise = getExercise(this.page, exerciseID); + await expect(exercise.locator('.exercise-title')).toContainText(title); + } + + async checkExamTitle(title: string) { + await expect(this.page.locator('#exam-title')).toContainText(title); + } + + async getResultScore(exerciseID?: number) { + const parentComponent = exerciseID ? getExercise(this.page, exerciseID) : this.page; + const resultScoreLocator = parentComponent.locator('#exercise-result-score'); + await Commands.reloadUntilFound(this.page, resultScoreLocator); + return resultScoreLocator; + } + + async checkResultScore(scoreText: string, exerciseID?: number) { + const scoreElement = await this.getResultScore(exerciseID); + await expect(scoreElement.getByText(new RegExp(scoreText))).toBeVisible(); + } + + async checkExamFinishedTitle(title: string) { + await expect(this.page.locator('#exam-finished-title')).toContainText(title, { timeout: 40000 }); + } + + async checkExamFullnameInputExists() { + await expect(this.page.locator('#fullname')).toBeVisible({ timeout: 30000 }); + } + + async checkYourFullname(name: string) { + await expect(this.page.locator('#your-name')).toContainText(name, { timeout: 30000 }); + } + + async checkExamTimeLeft(timeLeft: string) { + await expect(this.page.locator('#displayTime').getByText(timeLeft)).toBeVisible(); + } + + async checkExamTimeChangeDialog(previousWorkingTime: string, newWorkingTime: string, announcementTime: Dayjs, authorUsername: string, message: string) { + const timeChangeDialog = this.page.locator('.modal-content'); + await expect(timeChangeDialog.getByTestId('old-time').getByText(previousWorkingTime)).toBeVisible(); + await expect(timeChangeDialog.getByTestId('new-time').getByText(newWorkingTime)).toBeVisible(); + const timeFormat = 'MMM D, YYYY HH:mm'; + const announcementTimeFormatted = announcementTime.format(timeFormat); + const announcementTimeAfterMinute = announcementTime.add(1, 'minute').format(timeFormat); + await expect(timeChangeDialog.locator('.date').getByText(new RegExp(`(${announcementTimeFormatted}|${announcementTimeAfterMinute})`))).toBeVisible(); + await expect(timeChangeDialog.locator('.content').getByText(message)).toBeVisible(); + await expect(timeChangeDialog.locator('.author').getByText(authorUsername)).toBeVisible(); + } + + async closeDialog() { + await this.page.locator('button', { hasText: 'Acknowledge' }).nth(0).click({ force: true, timeout: 5000 }); + } + + async verifyExerciseTitleOnFinalPage(exerciseID: number, exerciseTitle: string): Promise { + const exercise = getExercise(this.page, exerciseID); + await expect(exercise.locator(`#exercise-group-title-${exerciseID}`).getByText(exerciseTitle)).toBeVisible(); + } + + async verifyTextExerciseOnFinalPage(exerciseID: number, textFixture: string): Promise { + const exercise = getExercise(this.page, exerciseID); + const submissionText = await Fixtures.get(textFixture); + await expect(exercise.locator('#text-editor')).toHaveValue(submissionText!); + } + + async verifyGradingKeyOnFinalPage(gradeName: string) { + const gradingKeyCard = this.page.locator('jhi-collapsible-card').filter({ hasText: 'Grading Key Grade Interval' }); + await gradingKeyCard.locator('button.rotate-icon').click(); + await expect(gradingKeyCard.locator('tr.highlighted').locator('td', { hasText: gradeName })).toBeVisible(); + } +} diff --git a/src/test/playwright/support/pageobjects/exam/ExamParticipation.ts b/src/test/playwright/support/pageobjects/exam/ExamParticipationPage.ts similarity index 67% rename from src/test/playwright/support/pageobjects/exam/ExamParticipation.ts rename to src/test/playwright/support/pageobjects/exam/ExamParticipationPage.ts index 9ae85065182c..4b31b8059af4 100644 --- a/src/test/playwright/support/pageobjects/exam/ExamParticipation.ts +++ b/src/test/playwright/support/pageobjects/exam/ExamParticipationPage.ts @@ -3,7 +3,6 @@ import { Course } from 'app/entities/course.model'; import { Exam } from 'app/entities/exam.model'; import { AdditionalData, ExerciseType } from '../../constants'; import { UserCredentials } from '../../users'; -import { getExercise } from '../../utils'; import { OnlineEditorPage, ProgrammingExerciseSubmission } from '../exercises/programming/OnlineEditorPage'; import { CoursesPage } from '../course/CoursesPage'; import { CourseOverviewPage } from '../course/CourseOverviewPage'; @@ -14,8 +13,9 @@ import { MultipleChoiceQuiz } from '../exercises/quiz/MultipleChoiceQuiz'; import { TextEditorPage } from '../exercises/text/TextEditorPage'; import { Commands } from '../../commands'; import { Fixtures } from '../../../fixtures/fixtures'; +import { ExamParticipationActions } from './ExamParticipationActions'; -export class ExamParticipation { +export class ExamParticipationPage extends ExamParticipationActions { private readonly courseList: CoursesPage; private readonly courseOverview: CourseOverviewPage; private readonly examNavigation: ExamNavigationBar; @@ -24,7 +24,6 @@ export class ExamParticipation { private readonly programmingExerciseEditor: OnlineEditorPage; private readonly quizExerciseMultipleChoice: MultipleChoiceQuiz; private readonly textExerciseEditor: TextEditorPage; - private readonly page: Page; constructor( courseList: CoursesPage, @@ -37,6 +36,7 @@ export class ExamParticipation { textExerciseEditor: TextEditorPage, page: Page, ) { + super(page); this.courseList = courseList; this.courseOverview = courseOverview; this.examNavigation = examNavigation; @@ -45,7 +45,6 @@ export class ExamParticipation { this.programmingExerciseEditor = programmingExerciseEditor; this.quizExerciseMultipleChoice = quizExerciseMultipleChoice; this.textExerciseEditor = textExerciseEditor; - this.page = page; } async makeSubmission(exerciseID: number, exerciseType: ExerciseType, additionalData?: AdditionalData) { @@ -110,67 +109,9 @@ export class ExamParticipation { await this.examStartEnd.startExam(true); } - async selectExerciseOnOverview(index: number) { - await this.page.locator(`.exercise-table tr:nth-child(${index}) a`).click(); - } - - async clickSaveAndContinue() { - await this.page.click('#save'); - } - - async checkExerciseTitle(exerciseID: number, title: string) { - const exercise = getExercise(this.page, exerciseID); - await expect(exercise.locator('.exercise-title')).toContainText(title); - } - - async checkExamTitle(title: string) { - await expect(this.page.locator('#exam-title')).toContainText(title); - } - - async getResultScore(exerciseID?: number) { - const parentComponent = exerciseID ? getExercise(this.page, exerciseID) : this.page; - const resultScoreLocator = parentComponent.locator('#exercise-result-score'); - await Commands.reloadUntilFound(this.page, resultScoreLocator); - return resultScoreLocator; - } - - async checkResultScore(scoreText: string, exerciseID?: number) { - const scoreElement = await this.getResultScore(exerciseID); - await expect(scoreElement.getByText(new RegExp(scoreText))).toBeVisible(); - } - - async checkExamFinishedTitle(title: string) { - await expect(this.page.locator('#exam-finished-title')).toContainText(title, { timeout: 40000 }); - } - - async checkExamFullnameInputExists() { - await expect(this.page.locator('#fullname')).toBeVisible({ timeout: 30000 }); - } - - async checkYourFullname(name: string) { - await expect(this.page.locator('#your-name')).toContainText(name, { timeout: 30000 }); - } - async handInEarly() { await this.examNavigation.handInEarly(); const response = await this.examStartEnd.finishExam(); expect(response.status()).toBe(200); } - - async verifyExerciseTitleOnFinalPage(exerciseID: number, exerciseTitle: string): Promise { - const exercise = getExercise(this.page, exerciseID); - await expect(exercise.locator(`#exercise-group-title-${exerciseID}`).getByText(exerciseTitle)).toBeVisible(); - } - - async verifyTextExerciseOnFinalPage(exerciseID: number, textFixture: string): Promise { - const exercise = getExercise(this.page, exerciseID); - const submissionText = await Fixtures.get(textFixture); - await expect(exercise.locator('#text-editor')).toHaveValue(submissionText!); - } - - async verifyGradingKeyOnFinalPage(gradeName: string) { - const gradingKeyCard = this.page.locator('jhi-collapsible-card').filter({ hasText: 'Grading Key Grade Interval' }); - await gradingKeyCard.locator('button.rotate-icon').click(); - await expect(gradingKeyCard.locator('tr.highlighted').locator('td', { hasText: gradeName })).toBeVisible(); - } } diff --git a/src/test/playwright/support/pageobjects/exam/ModalDialogBox.ts b/src/test/playwright/support/pageobjects/exam/ModalDialogBox.ts new file mode 100644 index 000000000000..77d7a06f2168 --- /dev/null +++ b/src/test/playwright/support/pageobjects/exam/ModalDialogBox.ts @@ -0,0 +1,40 @@ +import { Page, expect } from '@playwright/test'; +import { Dayjs } from 'dayjs'; + +export class ModalDialogBox { + private readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + getModalDialogContent() { + return this.page.locator('.modal-content'); + } + + async checkDialogTime(dialogTime: Dayjs) { + const modalDialog = this.getModalDialogContent(); + const timeFormat = 'MMM D, YYYY HH:mm'; + const dialogTimeFormatted = dialogTime.format(timeFormat); + const dialogTimeAfterMinuteFormatted = dialogTime.add(1, 'minute').format(timeFormat); + await expect(modalDialog.locator('.date').getByText(new RegExp(`(${dialogTimeFormatted}|${dialogTimeAfterMinuteFormatted})`))).toBeVisible(); + } + + async checkDialogMessage(message: string) { + await expect(this.getModalDialogContent().locator('.content').getByText(message)).toBeVisible(); + } + + async checkDialogAuthor(authorUsername: string) { + await expect(this.getModalDialogContent().locator('.author').getByText(authorUsername)).toBeVisible(); + } + + async checkExamTimeChangeDialog(previousWorkingTime: string, newWorkingTime: string) { + const timeChangeDialog = this.getModalDialogContent(); + await expect(timeChangeDialog.getByTestId('old-time').getByText(previousWorkingTime)).toBeVisible(); + await expect(timeChangeDialog.getByTestId('new-time').getByText(newWorkingTime)).toBeVisible(); + } + + async closeDialog() { + await this.getModalDialogContent().locator('button').click({ force: true }); + } +}