Skip to content

Commit

Permalink
Development: Adds playwright e2e quiz exercise tests for different ba…
Browse files Browse the repository at this point in the history
…tch modes (#8645)
  • Loading branch information
muradium authored Jun 2, 2024
1 parent 1348a86 commit 89fb93f
Show file tree
Hide file tree
Showing 13 changed files with 287 additions and 29 deletions.
6 changes: 3 additions & 3 deletions src/test/playwright/e2e/course/CourseExercise.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ test.describe('Course exercise', () => {
let exercise3: QuizExercise;

test.beforeEach('Create Exercises', async ({ exerciseAPIRequests }) => {
exercise1 = await exerciseAPIRequests.createQuizExercise({ course }, [multipleChoiceQuizTemplate], 'Course Exercise Quiz 1');
exercise2 = await exerciseAPIRequests.createQuizExercise({ course }, [multipleChoiceQuizTemplate], 'Course Exercise Quiz 2');
exercise3 = await exerciseAPIRequests.createQuizExercise({ course }, [multipleChoiceQuizTemplate], 'Course Exercise 3');
exercise1 = await exerciseAPIRequests.createQuizExercise({ body: { course }, quizQuestions: [multipleChoiceQuizTemplate], title: 'Course Exercise Quiz 1' });
exercise2 = await exerciseAPIRequests.createQuizExercise({ body: { course }, quizQuestions: [multipleChoiceQuizTemplate], title: 'Course Exercise Quiz 2' });
exercise3 = await exerciseAPIRequests.createQuizExercise({ body: { course }, quizQuestions: [multipleChoiceQuizTemplate], title: 'Course Exercise 3' });
});

test('Filters exercises based on title', async ({ page, courseOverview }) => {
Expand Down
2 changes: 1 addition & 1 deletion src/test/playwright/e2e/exercise/ExerciseImport.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ test.describe('Import exercises', () => {
course = await courseManagementAPIRequests.createCourse({ customizeGroups: true });
await courseManagementAPIRequests.addInstructorToCourse(course, instructor);
textExercise = await exerciseAPIRequests.createTextExercise({ course });
quizExercise = await exerciseAPIRequests.createQuizExercise({ course }, [multipleChoiceQuizTemplate]);
quizExercise = await exerciseAPIRequests.createQuizExercise({ body: { course }, quizQuestions: [multipleChoiceQuizTemplate] });
modelingExercise = await exerciseAPIRequests.createModelingExercise({ course });
programmingExercise = await exerciseAPIRequests.createProgrammingExercise({ course });
secondCourse = await courseManagementAPIRequests.createCourse({ customizeGroups: true });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ test.describe('Quiz Exercise Assessment', () => {
test.describe.configure({ retries: 2 });
test('Assesses a mc quiz submission automatically', async ({ login, page, exerciseAPIRequests, exerciseResult }) => {
await login(admin);
const quizExercise = await exerciseAPIRequests.createQuizExercise({ course }, [multipleChoiceQuizTemplate], undefined, undefined, 10);
const quizExercise = await exerciseAPIRequests.createQuizExercise({ body: { course }, quizQuestions: [multipleChoiceQuizTemplate], duration: 10 });
await exerciseAPIRequests.setQuizVisible(quizExercise.id!);
await exerciseAPIRequests.startQuizNow(quizExercise.id!);
await login(studentOne);
Expand All @@ -33,7 +33,7 @@ test.describe('Quiz Exercise Assessment', () => {
test.describe.configure({ retries: 2 });
test('Assesses a sa quiz submission automatically', async ({ login, page, exerciseAPIRequests, exerciseResult }) => {
await login(admin);
const quizExercise = await exerciseAPIRequests.createQuizExercise({ course }, [shortAnswerQuizTemplate], undefined, undefined, 10);
const quizExercise = await exerciseAPIRequests.createQuizExercise({ body: { course }, quizQuestions: [shortAnswerQuizTemplate], duration: 10 });
await exerciseAPIRequests.setQuizVisible(quizExercise.id!);
await exerciseAPIRequests.startQuizNow(quizExercise.id!);
await login(studentOne);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ test.describe('Quiz Exercise Management', () => {

test.beforeEach('Create quiz exercise', async ({ login, exerciseAPIRequests }) => {
await login(admin);
quizExercise = await exerciseAPIRequests.createQuizExercise({ course }, [multipleChoiceTemplate]);
quizExercise = await exerciseAPIRequests.createQuizExercise({ body: { course }, quizQuestions: [multipleChoiceTemplate] });
});

test('Deletes a quiz exercise', async ({ login, navigationBar, courseManagement, courseManagementExercises }) => {
Expand All @@ -73,7 +73,7 @@ test.describe('Quiz Exercise Management', () => {

test.beforeEach('Create quiz exercise', async ({ login, exerciseAPIRequests }) => {
await login(admin);
quizExercise = await exerciseAPIRequests.createQuizExercise({ course }, [multipleChoiceTemplate]);
quizExercise = await exerciseAPIRequests.createQuizExercise({ body: { course }, quizQuestions: [multipleChoiceTemplate] });
});

test('Export quiz exercise questions', async ({ page, login, navigationBar, courseManagement, courseManagementExercises }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { Course } from 'app/entities/course.model';
import { QuizExercise } from 'app/entities/quiz/quiz-exercise.model';
import multipleChoiceQuizTemplate from '../../../fixtures/exercise/quiz/multiple_choice/template.json';
import shortAnswerQuizTemplate from '../../../fixtures/exercise/quiz/short_answer/template.json';
import { admin, studentOne } from '../../../support/users';
import { admin, instructor, studentOne } from '../../../support/users';
import { test } from '../../../support/fixtures';
import { expect } from '@playwright/test';
import dayjs from 'dayjs';
import { QuizMode } from '../../../support/constants';

test.describe('Quiz Exercise Participation', () => {
let course: Course;
let quizExercise: QuizExercise;

test.beforeEach('Create course', async ({ login, courseManagementAPIRequests }) => {
await login(admin);
Expand All @@ -17,9 +18,11 @@ test.describe('Quiz Exercise Participation', () => {
});

test.describe('Quiz exercise participation', () => {
let quizExercise: QuizExercise;

test.beforeEach('Create quiz exercise', async ({ login, exerciseAPIRequests }) => {
await login(admin);
quizExercise = await exerciseAPIRequests.createQuizExercise({ course }, [multipleChoiceQuizTemplate]);
quizExercise = await exerciseAPIRequests.createQuizExercise({ body: { course }, quizQuestions: [multipleChoiceQuizTemplate] });
});

test('Student cannot see hidden quiz', async ({ login, courseOverview }) => {
Expand All @@ -46,10 +49,129 @@ test.describe('Quiz Exercise Participation', () => {
});
});

test.describe('Quiz exercise scheduled participation', () => {
let quizExercise: QuizExercise;
const timeUntilQuizStartInSeconds = 10;

test.beforeEach('Create quiz exercise', async ({ login, exerciseAPIRequests }) => {
await login(admin);
const releaseDate = dayjs();
const startOfWorkingTime = releaseDate.add(timeUntilQuizStartInSeconds, 'seconds');
quizExercise = await exerciseAPIRequests.createQuizExercise({ body: { course }, quizQuestions: [multipleChoiceQuizTemplate], releaseDate, startOfWorkingTime });
});

test('Student cannot participate in scheduled quiz before start of working time', async ({ login, courseOverview, quizExerciseParticipation }) => {
await login(studentOne, '/courses/' + course.id);
await courseOverview.openRunningExercise(quizExercise.id!);
await expect(quizExerciseParticipation.getWaitingForStartAlert()).toBeVisible();
});

test('Student can participate in scheduled quiz when working time arrives', async ({ page, login, courseOverview, quizExerciseParticipation }) => {
await login(studentOne, `/courses/${course.id}`);
await courseOverview.openRunningExercise(quizExercise.id!);
await page.waitForTimeout(timeUntilQuizStartInSeconds * 1000);
await expect(quizExerciseParticipation.getWaitingForStartAlert()).not.toBeVisible();
await expect(quizExerciseParticipation.getQuizQuestion(0)).toBeVisible();
});
});

test.describe('Quiz exercise batched participation', () => {
let quizExercise: QuizExercise;
const exerciseDuration = 60;

test.beforeEach('Create quiz exercise', async ({ login, exerciseAPIRequests, courseManagementAPIRequests }) => {
await login(admin);
quizExercise = await exerciseAPIRequests.createQuizExercise({
body: { course },
quizQuestions: [multipleChoiceQuizTemplate],
releaseDate: dayjs(),
duration: exerciseDuration,
quizMode: QuizMode.BATCHED,
});
await courseManagementAPIRequests.addInstructorToCourse(course, instructor);
});

test('Instructor creates a quiz batch and student joins it', async ({
login,
navigationBar,
courseManagement,
quizExerciseOverview,
courseOverview,
quizExerciseParticipation,
}) => {
await login(instructor);
await navigationBar.openCourseManagement();
await courseManagement.openExercisesOfCourse(course.id!);
const quizBatch = await quizExerciseOverview.addQuizBatch(quizExercise.id!);
await quizExerciseOverview.startQuizBatch(quizExercise.id!, quizBatch.id!);
await login(studentOne, '/courses/' + course.id);
await courseOverview.openRunningExercise(quizExercise.id!);
await quizExerciseParticipation.joinQuizBatch(quizBatch.password!);
await expect(quizExerciseParticipation.getQuizQuestion(0)).toBeVisible();
});

test('Instructor ends the quiz batch and student cannot participate anymore', async ({
login,
navigationBar,
courseManagement,
courseManagementExercises,
courseOverview,
}) => {
await login(instructor, '/');
await navigationBar.openCourseManagement();
await courseManagement.openExercisesOfCourse(course.id!);
await courseManagementExercises.endQuiz(quizExercise);
await login(studentOne, '/courses/' + course.id);
await expect(courseOverview.getOpenRunningExerciseButton(quizExercise.id!)).not.toBeVisible();
});

test('Instructor release ended exercise for practice and student practices', async ({
login,
navigationBar,
courseManagement,
courseManagementExercises,
courseOverview,
quizExerciseParticipation,
}) => {
await login(instructor, '/');
await navigationBar.openCourseManagement();
await courseManagement.openExercisesOfCourse(course.id!);
await courseManagementExercises.endQuiz(quizExercise);
await courseManagementExercises.getExercise(quizExercise.id!).locator('button', { hasText: 'Release For Practice' }).click();
await login(studentOne, `/courses/${course.id}`);
await courseOverview.practiceExercise();
await expect(quizExerciseParticipation.getQuizQuestion(0)).toBeVisible();
});
});

test.describe('Quiz exercise individual participation', () => {
let quizExercise: QuizExercise;

test.beforeEach('Create quiz exercise', async ({ login, exerciseAPIRequests, courseManagementAPIRequests }) => {
await login(admin);
quizExercise = await exerciseAPIRequests.createQuizExercise({
body: { course },
quizQuestions: [multipleChoiceQuizTemplate],
releaseDate: dayjs().subtract(1, 'weeks'),
quizMode: QuizMode.INDIVIDUAL,
});
await courseManagementAPIRequests.addInstructorToCourse(course, instructor);
});

test('Student can start a batch in an individual quiz', async ({ login, courseOverview, quizExerciseParticipation }) => {
await login(studentOne, '/courses/' + course.id);
await courseOverview.openRunningExercise(quizExercise.id!);
await quizExerciseParticipation.startQuizBatch();
await expect(quizExerciseParticipation.getQuizQuestion(0)).toBeVisible();
});
});

test.describe('SA quiz participation', () => {
let quizExercise: QuizExercise;

test.beforeEach('Create SA quiz', async ({ login, exerciseAPIRequests }) => {
await login(admin);
quizExercise = await exerciseAPIRequests.createQuizExercise({ course }, [shortAnswerQuizTemplate]);
quizExercise = await exerciseAPIRequests.createQuizExercise({ body: { course }, quizQuestions: [shortAnswerQuizTemplate] });
await exerciseAPIRequests.setQuizVisible(quizExercise.id!);
await exerciseAPIRequests.startQuizNow(quizExercise.id!);
});
Expand All @@ -69,6 +191,8 @@ test.describe('Quiz Exercise Participation', () => {
});

test.describe('DnD Quiz participation', () => {
let quizExercise: QuizExercise;

test.beforeEach('Create DND quiz', async ({ login, courseManagementExercises, exerciseAPIRequests, quizExerciseCreation }) => {
await login(admin, '/course-management/' + course.id + '/exercises');
await courseManagementExercises.createQuizExercise();
Expand Down
7 changes: 7 additions & 0 deletions src/test/playwright/support/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ export enum ExerciseMode {
TEAM = 'TEAM',
}

// Copied from src\main\webapp\app\entities\quiz\quiz-exercise.model.ts
export enum QuizMode {
SYNCHRONIZED = 'SYNCHRONIZED',
BATCHED = 'BATCHED',
INDIVIDUAL = 'INDIVIDUAL',
}

// Exercise commit entity displayed in commit history
export type ExerciseCommit = {
message: string;
Expand Down
10 changes: 10 additions & 0 deletions src/test/playwright/support/fixtures.ts
Original file line number Diff line number Diff line change
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 { QuizExerciseOverviewPage } from './pageobjects/exercises/quiz/QuizExerciseOverviewPage';
import { QuizExerciseParticipationPage } from './pageobjects/exercises/quiz/QuizExerciseParticipationPage';
import { ModalDialogBox } from './pageobjects/exam/ModalDialogBox';
import { ExamParticipationActions } from './pageobjects/exam/ExamParticipationActions';

Expand Down Expand Up @@ -125,6 +127,8 @@ export type ArtemisPageObjects = {
quizExerciseCreation: QuizExerciseCreationPage;
quizExerciseDragAndDropQuiz: DragAndDropQuiz;
quizExerciseMultipleChoice: MultipleChoiceQuiz;
quizExerciseOverview: QuizExerciseOverviewPage;
quizExerciseParticipation: QuizExerciseParticipationPage;
quizExerciseShortAnswerQuiz: ShortAnswerQuiz;
textExerciseCreation: TextExerciseCreationPage;
textExerciseEditor: TextEditorPage;
Expand Down Expand Up @@ -321,6 +325,12 @@ export const test = base.extend<ArtemisPageObjects & ArtemisCommands & ArtemisRe
quizExerciseMultipleChoice: async ({ page }, use) => {
await use(new MultipleChoiceQuiz(page));
},
quizExerciseOverview: async ({ page }, use) => {
await use(new QuizExerciseOverviewPage(page));
},
quizExerciseParticipation: async ({ page }, use) => {
await use(new QuizExerciseParticipationPage(page));
},
quizExerciseShortAnswerQuiz: async ({ page }, use) => {
await use(new ShortAnswerQuiz(page));
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Page } from 'playwright';
import { Exercise } from 'app/entities/exercise.model';
import { MODELING_EXERCISE_BASE, PROGRAMMING_EXERCISE_BASE, QUIZ_EXERCISE_BASE, TEXT_EXERCISE_BASE, UPLOAD_EXERCISE_BASE } from '../../constants';
import { expect } from '@playwright/test';
import { QuizExercise } from 'app/entities/quiz/quiz-exercise.model';

/**
* A class which encapsulates UI selectors and actions for the course management exercises page.
Expand Down Expand Up @@ -123,6 +124,12 @@ export class CourseManagementExercisesPage {
await this.page.locator(`#instructor-quiz-start-${quizID}`).click();
}

async endQuiz(quizExercise: QuizExercise) {
await this.page.locator(`#quiz-set-end-${quizExercise.id}`).click();
await this.page.locator('#confirm-entity-name').fill(quizExercise.title!);
await this.page.locator('#delete').click();
}

async shouldContainExerciseWithName(exerciseID: number) {
const exerciseElement = this.getExercise(exerciseID);
await exerciseElement.scrollIntoViewIfNeeded();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,14 @@ export class CourseOverviewPage {
* @param exerciseId The ID of the exercise to open.
*/
async openRunningExercise(exerciseId: number) {
await this.page.locator('#open-exercise-' + exerciseId).click();
await this.getOpenRunningExerciseButton(exerciseId).click();
}

/**
* Initiates the practice of an exercise.
*/
async practiceExercise() {
await this.page.locator('button', { hasText: 'Practice' }).click();
}

/**
Expand All @@ -53,13 +60,21 @@ export class CourseOverviewPage {
return this.page.locator('#test-sidebar-card-medium');
}

/**
* Retrieves the Locator for the button opening running exercise with the given ID.
* @param exerciseId The ID of the exercise.
* @returns The Locator for the button opening running exercise.
*/
getOpenRunningExerciseButton(exerciseId: number) {
return this.page.locator(`#open-exercise-${exerciseId}`);
}
/**
* Retrieves the Locator for the start exercise button by its ID.
* @param exerciseId The ID of the exercise.
* @returns The Locator for the start exercise button.
*/
getStartExerciseButton(exerciseId: number) {
return this.page.locator('#start-exercise-' + exerciseId);
return this.page.locator(`#start-exercise-${exerciseId}`);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export class ExamExerciseGroupCreationPage {
case ExerciseType.MODELING:
return await this.exerciseAPIRequests.createModelingExercise({ exerciseGroup }, title);
case ExerciseType.QUIZ:
return await this.exerciseAPIRequests.createQuizExercise({ exerciseGroup }, [multipleChoiceTemplate], title);
return await this.exerciseAPIRequests.createQuizExercise({ body: { exerciseGroup }, quizQuestions: [multipleChoiceTemplate], title });
case ExerciseType.PROGRAMMING:
return await this.exerciseAPIRequests.createProgrammingExercise({ exerciseGroup, title, assessmentType: additionalData.progExerciseAssessmentType });
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Page } from '@playwright/test';
import { BASE_API } from '../../../constants';
import { QuizBatch } from 'app/entities/quiz/quiz-exercise.model';

export class QuizExerciseOverviewPage {
private readonly page: Page;

constructor(page: Page) {
this.page = page;
}

/**
* Adds a quiz batch to the quiz exercise with the given ID.
* @param exerciseId The ID of the quiz exercise to add a batch to.
* @returns The created quiz batch.
*/
async addQuizBatch(exerciseId: number): Promise<QuizBatch> {
const responsePromise = this.page.waitForResponse(`${BASE_API}/quiz-exercises/${exerciseId}/add-batch`);
await this.page.locator(`#instructor-quiz-add-${exerciseId}`).click();
const response = await responsePromise;
return await response.json();
}

/**
* Starts the quiz batch with the given batch ID for the given exercise.
* @param exerciseId The ID of the quiz exercise the batch belongs to.
* @param batchId The ID of the batch to start.
*/
async startQuizBatch(exerciseId: number, batchId: number) {
await this.page.locator(`#instructor-quiz-start-${exerciseId}-${batchId}`).click();
}
}
Loading

0 comments on commit 89fb93f

Please sign in to comment.