Skip to content

Commit

Permalink
Development: Add e2e playwright tests for exam announcement (#8630)
Browse files Browse the repository at this point in the history
  • Loading branch information
muradium authored May 25, 2024
1 parent e6d058f commit 5cb587e
Show file tree
Hide file tree
Showing 11 changed files with 320 additions and 107 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<button class="btn btn-md btn-warning" [disabled]="!announcementCreationAllowed" (click)="openDialog($event)">
<button id="announcement-create-button" class="btn btn-md btn-warning" [disabled]="!announcementCreationAllowed" (click)="openDialog($event)">
<fa-icon [icon]="faBullhorn" />
<span class="d-none d-md-inline" jhiTranslate="artemisApp.examManagement.announcementCreate.button"></span>
</button>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<button class="btn btn-md btn-warning" [disabled]="!workingTimeChangeAllowed" (click)="openDialog($event)">
<button id="edit-working-time-button" class="btn btn-md btn-warning" [disabled]="!workingTimeChangeAllowed" (click)="openDialog($event)">
<fa-icon [icon]="faHourglassHalf" />
<span class="d-none d-md-inline">{{ 'artemisApp.examManagement.editWorkingTime.title' | artemisTranslate }}</span>
</button>
6 changes: 3 additions & 3 deletions src/test/playwright/e2e/exam/ExamAssessment.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -359,7 +359,7 @@ async function makeExamSubmission(
exam: Exam,
exercise: Exercise,
page: Page,
examParticipation: ExamParticipation,
examParticipation: ExamParticipationPage,
examNavigation: ExamNavigationBar,
examStartEnd: ExamStartEndPage,
) {
Expand Down
147 changes: 114 additions & 33 deletions src/test/playwright/e2e/exam/ExamParticipation.spec.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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);
});
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
}
4 changes: 2 additions & 2 deletions src/test/playwright/e2e/exam/ExamResults.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -115,7 +115,7 @@ test.describe('Test Exam - student exams', () => {
exam: Exam,
toStart: boolean,
toSubmit: boolean,
examParticipation: ExamParticipation,
examParticipation: ExamParticipationPage,
examNavigation: ExamNavigationBar,
) {
if (!toStart) {
Expand Down
17 changes: 14 additions & 3 deletions src/test/playwright/support/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -231,7 +235,7 @@ export const test = base.extend<ArtemisPageObjects & ArtemisCommands & ArtemisRe
use,
) => {
await use(
new ExamParticipation(
new ExamParticipationPage(
courseList,
courseOverview,
examNavigation,
Expand All @@ -244,6 +248,9 @@ export const test = base.extend<ArtemisPageObjects & ArtemisCommands & ArtemisRe
),
);
},
examParticipationActions: async ({ page }, use) => {
await use(new ExamParticipationActions(page));
},
examResultsPage: async ({ page }, use) => {
await use(new ExamResultsPage(page));
},
Expand All @@ -253,9 +260,13 @@ export const test = base.extend<ArtemisPageObjects & ArtemisCommands & ArtemisRe
examStartEnd: async ({ page }, use) => {
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));
},
Expand Down
49 changes: 49 additions & 0 deletions src/test/playwright/support/pageobjects/exam/ExamManagementPage.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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();
}
Expand Down
Loading

0 comments on commit 5cb587e

Please sign in to comment.