Skip to content
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

Development: Add e2e playwright tests for exam announcement #8630

Merged
merged 10 commits into from
May 25, 2024
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 });
muradium marked this conversation as resolved.
Show resolved Hide resolved
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);
}
muradium marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -62,6 +62,8 @@ import { ExamGradingPage } from './pageobjects/exam/ExamGradingPage';
import { ExamScoresPage } from './pageobjects/exam/ExamScoresPage';
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 @@ -97,11 +99,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 @@ -229,7 +233,7 @@ export const test = base.extend<ArtemisPageObjects & ArtemisCommands & ArtemisRe
use,
) => {
await use(
new ExamParticipation(
new ExamParticipationPage(
courseList,
courseOverview,
examNavigation,
Expand All @@ -242,6 +246,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 @@ -251,9 +258,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
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
Loading