From 65eeeadaa482d40808a34b394c39e09a4a4ad905 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Sun, 2 Jun 2024 21:08:29 +0200 Subject: [PATCH 1/5] Convert exam results tests into isolated and parameterised tests --- ...ummary-exercise-card-header.component.html | 2 +- .../playwright/e2e/exam/ExamResults.spec.ts | 323 +++++++++--------- .../exam/ExamParticipationActions.ts | 2 +- .../pageobjects/exam/ExamResultsPage.ts | 2 - 4 files changed, 157 insertions(+), 172 deletions(-) diff --git a/src/main/webapp/app/exam/participate/summary/exercises/header/exam-result-summary-exercise-card-header.component.html b/src/main/webapp/app/exam/participate/summary/exercises/header/exam-result-summary-exercise-card-header.component.html index f7e47f016f5a..74dfce8f5638 100644 --- a/src/main/webapp/app/exam/participate/summary/exercises/header/exam-result-summary-exercise-card-header.component.html +++ b/src/main/webapp/app/exam/participate/summary/exercises/header/exam-result-summary-exercise-card-header.component.html @@ -17,7 +17,7 @@
} @if (exerciseInfo?.achievedPercentage !== undefined) { - {{ exerciseInfo!.achievedPercentage }}% + {{ exerciseInfo!.achievedPercentage }}% } diff --git a/src/test/playwright/e2e/exam/ExamResults.spec.ts b/src/test/playwright/e2e/exam/ExamResults.spec.ts index 42ebc5e7bd53..3d772d9cfb41 100644 --- a/src/test/playwright/e2e/exam/ExamResults.spec.ts +++ b/src/test/playwright/e2e/exam/ExamResults.spec.ts @@ -1,200 +1,180 @@ import { test } from '../../support/fixtures'; import { Exam } from 'app/entities/exam.model'; import { Commands } from '../../support/commands'; -import { admin, instructor, studentFour, studentOne, studentThree, studentTwo, tutor } from '../../support/users'; +import { admin, instructor, studentOne, tutor } from '../../support/users'; import { Course } from 'app/entities/course.model'; -import dayjs from 'dayjs'; +import dayjs, { Dayjs } from 'dayjs'; import { generateUUID } from '../../support/utils'; import { Exercise, ExerciseType } from '../../support/constants'; import { ExamManagementPage } from '../../support/pageobjects/exam/ExamManagementPage'; import { CourseAssessmentDashboardPage } from '../../support/pageobjects/assessment/CourseAssessmentDashboardPage'; import { ExerciseAssessmentDashboardPage } from '../../support/pageobjects/assessment/ExerciseAssessmentDashboardPage'; -import { ExamAssessmentPage } from '../../support/pageobjects/assessment/ExamAssessmentPage'; import javaPartiallySuccessfulSubmission from '../../fixtures/exercise/programming/java/partially_successful/submission.json'; -import { ExamAPIRequests } from '../../support/requests/ExamAPIRequests'; -import { ExamExerciseGroupCreationPage } from '../../support/pageobjects/exam/ExamExerciseGroupCreationPage'; -import { ExerciseAPIRequests } from '../../support/requests/ExerciseAPIRequests'; -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'; -import { ExamStartEndPage } from '../../support/pageobjects/exam/ExamStartEndPage'; -import { CoursesPage } from '../../support/pageobjects/course/CoursesPage'; -import { ModelingEditor } from '../../support/pageobjects/exercises/modeling/ModelingEditor'; -import { OnlineEditorPage } from '../../support/pageobjects/exercises/programming/OnlineEditorPage'; -import { MultipleChoiceQuiz } from '../../support/pageobjects/exercises/quiz/MultipleChoiceQuiz'; -import { TextEditorPage } from '../../support/pageobjects/exercises/text/TextEditorPage'; -import { ModelingExerciseAssessmentEditor } from '../../support/pageobjects/assessment/ModelingExerciseAssessmentEditor'; import { ProgrammingExerciseTaskStatus } from '../../support/pageobjects/exam/ExamResultsPage'; - -test.describe.configure({ mode: 'default' }); +import { Page } from '@playwright/test'; test.describe('Exam Results', () => { let course: Course; - test.beforeAll('Create course', async ({ browser }) => { + test.beforeEach('Create course', async ({ browser }) => { const page = await browser.newPage(); const courseManagementAPIRequests = new CourseManagementAPIRequests(page); await Commands.login(page, admin); course = await courseManagementAPIRequests.createCourse({ customizeGroups: true }); await courseManagementAPIRequests.addStudentToCourse(course, studentOne); - await courseManagementAPIRequests.addStudentToCourse(course, studentTwo); - await courseManagementAPIRequests.addStudentToCourse(course, studentThree); - await courseManagementAPIRequests.addStudentToCourse(course, studentFour); await courseManagementAPIRequests.addTutorToCourse(course, tutor); await courseManagementAPIRequests.addInstructorToCourse(course, instructor); }); + const testCases = [ + { exerciseType: ExerciseType.TEXT, resultScore: '70%' }, + { exerciseType: ExerciseType.PROGRAMMING, resultScore: '46.2%' }, + { exerciseType: ExerciseType.QUIZ, resultScore: '50%' }, + { exerciseType: ExerciseType.MODELING, resultScore: '40%' }, + ]; + test.describe('Check exam exercise results', () => { - let exam: Exam; - let exerciseArray: Array = []; - - test.beforeAll('Prepare exam and assess a student submission', async ({ browser }) => { - const page = await browser.newPage(); - const examAPIRequests = new ExamAPIRequests(page); - const exerciseAPIRequests = new ExerciseAPIRequests(page); - const examExerciseGroupCreation = new ExamExerciseGroupCreationPage(page, examAPIRequests, exerciseAPIRequests); - - await Commands.login(page, admin); - const endDate = dayjs().add(1, 'minutes').add(30, 'seconds'); - const examConfig = { - course, - title: 'exam' + generateUUID(), - visibleDate: dayjs().subtract(3, 'minutes'), - startDate: dayjs().subtract(2, 'minutes'), - endDate: endDate, - publishResultsDate: endDate.add(1, 'seconds'), - examMaxPoints: 40, - numberOfExercisesInExam: 4, - }; - exam = await examAPIRequests.createExam(examConfig); - const textExercise = await examExerciseGroupCreation.addGroupWithExercise(exam, ExerciseType.TEXT, { textFixture: 'loremIpsum.txt' }); - const programmingExercise = await examExerciseGroupCreation.addGroupWithExercise(exam, ExerciseType.PROGRAMMING, { submission: javaPartiallySuccessfulSubmission }); - const quizExercise = await examExerciseGroupCreation.addGroupWithExercise(exam, ExerciseType.QUIZ, { quizExerciseID: 0 }); - const modelingExercise = await examExerciseGroupCreation.addGroupWithExercise(exam, ExerciseType.MODELING); - exerciseArray = [textExercise, programmingExercise, quizExercise, modelingExercise]; - - await examAPIRequests.registerStudentForExam(exam, studentOne); - await examAPIRequests.generateMissingIndividualExams(exam); - await examAPIRequests.prepareExerciseStartForExam(exam); - const courseList = new CoursesPage(page); - const courseOverview = new CourseOverviewPage(page); - - const examParticipation = new ExamParticipationPage( - courseList, - courseOverview, - new ExamNavigationBar(page), - new ExamStartEndPage(page), - new ModelingEditor(page), - new OnlineEditorPage(page, courseList, courseOverview), - new MultipleChoiceQuiz(page), - new TextEditorPage(page), - page, - ); - - await examParticipation.startParticipation(studentOne, course, exam); - - const examNavigation = new ExamNavigationBar(page); - const examStartEnd = new ExamStartEndPage(page); - - for (let j = 0; j < exerciseArray.length; j++) { - const exercise = exerciseArray[j]; - await examNavigation.openExerciseAtIndex(j); - await examParticipation.makeSubmission(exercise.id!, exercise.type!, exercise.additionalData); - } - - await examParticipation.handInEarly(); - await examStartEnd.pressShowSummary(); - }); - - test.beforeAll('Assess student submissions', async ({ browser }) => { - const page = await browser.newPage(); - const examManagement = new ExamManagementPage(page); - const examAssessment = new ExamAssessmentPage(page); - const courseAssessment = new CourseAssessmentDashboardPage(page); - const exerciseAssessment = new ExerciseAssessmentDashboardPage(page); - const exerciseAPIRequests = new ExerciseAPIRequests(page); - - await Commands.login(page, tutor); - await startAssessing(course.id!, exam.id!, 0, 60000, examManagement, courseAssessment, exerciseAssessment); - await examAssessment.addNewFeedback(7, 'Good job'); - await examAssessment.submitTextAssessment(); - await startAssessing(course.id!, exam.id!, 1, 60000, examManagement, courseAssessment, exerciseAssessment); - - const modelingExerciseAssessment = new ModelingExerciseAssessmentEditor(page); - await modelingExerciseAssessment.addNewFeedback(5, 'Good'); - await modelingExerciseAssessment.openAssessmentForComponent(0); - await modelingExerciseAssessment.assessComponent(-1, 'Wrong'); - await modelingExerciseAssessment.clickNextAssessment(); - await modelingExerciseAssessment.assessComponent(0, 'Neutral'); - await modelingExerciseAssessment.clickNextAssessment(); - await examAssessment.submitModelingAssessment(); - await Commands.login(page, instructor); - await exerciseAPIRequests.evaluateExamQuizzes(exam); - }); - - test('Check exam result overview', async ({ page, login, examAPIRequests, examResultsPage }) => { - await login(studentOne); - await page.goto(`/courses/${course.id}/exams/${exam.id}`); - const gradeSummary = await examAPIRequests.getGradeSummary(exam); - await examResultsPage.checkGradeSummary(gradeSummary); - }); - - test('Check exam text exercise results', async ({ page, login, examParticipation, examResultsPage }) => { - await login(studentOne); - await page.goto(`/courses/${course.id}/exams/${exam.id}`); - const exercise = exerciseArray[0]; - await examParticipation.checkResultScore('70%', exercise.id!); - await examResultsPage.checkTextExerciseContent(exercise.id!, exercise.additionalData!.textFixture!); - await examResultsPage.checkAdditionalFeedback(exercise.id!, 7, 'Good job'); - }); - - test('Check exam programming exercise results', async ({ page, login, examParticipation, examResultsPage }) => { - test.fixme(); - await login(studentOne); - await page.goto(`/courses/${course.id}/exams/${exam.id}`); - const exercise = exerciseArray[1]; - await examParticipation.checkResultScore('46.2%', exercise.id!); - await examResultsPage.checkProgrammingExerciseAssessments(exercise.id!, 'Wrong', 7); - await examResultsPage.checkProgrammingExerciseAssessments(exercise.id!, 'Correct', 6); - const taskStatuses: ProgrammingExerciseTaskStatus[] = [ - ProgrammingExerciseTaskStatus.SUCCESS, - ProgrammingExerciseTaskStatus.SUCCESS, - ProgrammingExerciseTaskStatus.SUCCESS, - ProgrammingExerciseTaskStatus.FAILURE, - ProgrammingExerciseTaskStatus.FAILURE, - ProgrammingExerciseTaskStatus.FAILURE, - ProgrammingExerciseTaskStatus.FAILURE, - ]; - await examResultsPage.checkProgrammingExerciseTasks(exercise.id!, taskStatuses); - }); - - test('Check exam quiz exercise results', async ({ page, login, examParticipation, examResultsPage }) => { - await login(studentOne); - await page.goto(`/courses/${course.id}/exams/${exam.id}`); - const exercise = exerciseArray[2]; - await examParticipation.checkResultScore('50%', exercise.id!); - await examResultsPage.checkQuizExerciseScore(exercise.id!, 5, 10); - const studentAnswers = [true, false, true, false]; - const correctAnswers = [true, true, false, false]; - await examResultsPage.checkQuizExerciseAnswers(exercise.id!, studentAnswers, correctAnswers); - }); - - test('Check exam modelling exercise results', async ({ page, login, examParticipation, examResultsPage }) => { - await login(studentOne); - await page.goto(`/courses/${course.id}/exams/${exam.id}`); - const exercise = exerciseArray[3]; - await examParticipation.checkResultScore('40%', exercise.id!); - await examResultsPage.checkAdditionalFeedback(exercise.id!, 5, 'Good'); - await examResultsPage.checkModellingExerciseAssessment(exercise.id!, 'class Class', 'Wrong', -1); - await examResultsPage.checkModellingExerciseAssessment(exercise.id!, 'abstract class Abstract', 'Neutral', 0); - }); + for (const testCase of testCases) { + let exam: Exam; + let examEndDate: Dayjs; + let exercise: Exercise; + const exerciseTypeString = testCase.exerciseType.toString().toLowerCase(); + + test.describe(`Check exam results for ${exerciseTypeString} exercise`, () => { + test.beforeEach('Prepare exam', async ({ login, examAPIRequests }) => { + await login(admin); + + if (testCase.exerciseType === ExerciseType.PROGRAMMING) { + examEndDate = dayjs().add(1, 'minutes').add(30, 'seconds'); + } else { + examEndDate = dayjs().add(30, 'seconds'); + } + const examConfig = { + course, + title: 'exam' + generateUUID(), + visibleDate: dayjs().subtract(3, 'minutes'), + startDate: dayjs().subtract(2, 'minutes'), + endDate: examEndDate, + publishResultsDate: examEndDate.add(1, 'seconds'), + examMaxPoints: 10, + numberOfExercisesInExam: 1, + }; + exam = await examAPIRequests.createExam(examConfig); + }); + + test.beforeEach('Add exercise to exam', async ({ login, examAPIRequests, examExerciseGroupCreation }) => { + await login(admin); + let additionalData: any; + switch (testCase.exerciseType) { + case ExerciseType.TEXT: + additionalData = { textFixture: 'loremIpsum.txt' }; + break; + case ExerciseType.PROGRAMMING: + additionalData = { submission: javaPartiallySuccessfulSubmission }; + break; + case ExerciseType.QUIZ: + additionalData = { quizExerciseID: 0 }; + break; + } + exercise = await examExerciseGroupCreation.addGroupWithExercise(exam, testCase.exerciseType, additionalData); + await examAPIRequests.registerStudentForExam(exam, studentOne); + await examAPIRequests.generateMissingIndividualExams(exam); + await examAPIRequests.prepareExerciseStartForExam(exam); + }); + + test.beforeEach('Participate in exam', async ({ login, examParticipation, examNavigation, examStartEnd }) => { + await login(admin); + await examParticipation.startParticipation(studentOne, course, exam); + await examNavigation.openExerciseAtIndex(0); + await examParticipation.makeSubmission(exercise.id!, exercise.type!, exercise.additionalData); + await examParticipation.handInEarly(); + await examStartEnd.pressShowSummary(); + }); + + test.beforeEach( + 'Assess student submission', + async ({ page, login, examManagement, examAssessment, courseAssessment, exerciseAssessment, modelingExerciseAssessment, exerciseAPIRequests }) => { + switch (testCase.exerciseType) { + case ExerciseType.TEXT: + await login(tutor); + await startAssessing(course.id!, exam.id!, 0, 60000, examManagement, courseAssessment, exerciseAssessment); + await examAssessment.addNewFeedback(7, 'Good job'); + await examAssessment.submitTextAssessment(); + break; + case ExerciseType.MODELING: + await login(tutor); + await startAssessing(course.id!, exam.id!, 0, 60000, examManagement, courseAssessment, exerciseAssessment); + await modelingExerciseAssessment.addNewFeedback(5, 'Good'); + await modelingExerciseAssessment.openAssessmentForComponent(0); + await modelingExerciseAssessment.assessComponent(-1, 'Wrong'); + await modelingExerciseAssessment.clickNextAssessment(); + await modelingExerciseAssessment.assessComponent(0, 'Neutral'); + await modelingExerciseAssessment.clickNextAssessment(); + await examAssessment.submitModelingAssessment(); + break; + case ExerciseType.QUIZ: + await login(instructor); + await waitForExamEnd(examEndDate, page); + await exerciseAPIRequests.evaluateExamQuizzes(exam); + break; + } + }, + ); + + test(`Check exam ${exerciseTypeString} exercise results`, async ({ page, login, examParticipation, examResultsPage }) => { + await login(studentOne); + await waitForExamEnd(examEndDate, page); + await page.goto(`/courses/${course.id}/exams/${exam.id}`); + await examParticipation.checkResultScore(testCase.resultScore, exercise.id!); + + switch (testCase.exerciseType) { + case ExerciseType.TEXT: + await examResultsPage.checkTextExerciseContent(exercise.id!, exercise.additionalData!.textFixture!); + await examResultsPage.checkAdditionalFeedback(exercise.id!, 7, 'Good job'); + break; + case ExerciseType.PROGRAMMING: + await examResultsPage.checkProgrammingExerciseAssessments(exercise.id!, 'Wrong', 7); + await examResultsPage.checkProgrammingExerciseAssessments(exercise.id!, 'Correct', 6); + const taskStatuses: ProgrammingExerciseTaskStatus[] = [ + ProgrammingExerciseTaskStatus.SUCCESS, + ProgrammingExerciseTaskStatus.SUCCESS, + ProgrammingExerciseTaskStatus.SUCCESS, + ProgrammingExerciseTaskStatus.FAILURE, + ProgrammingExerciseTaskStatus.FAILURE, + ProgrammingExerciseTaskStatus.FAILURE, + ProgrammingExerciseTaskStatus.FAILURE, + ]; + await examResultsPage.checkProgrammingExerciseTasks(exercise.id!, taskStatuses); + break; + case ExerciseType.QUIZ: + await examResultsPage.checkQuizExerciseScore(exercise.id!, 5, 10); + const studentAnswers = [true, false, true, false]; + const correctAnswers = [true, true, false, false]; + await examResultsPage.checkQuizExerciseAnswers(exercise.id!, studentAnswers, correctAnswers); + break; + case ExerciseType.MODELING: + await examResultsPage.checkAdditionalFeedback(exercise.id!, 5, 'Good'); + await examResultsPage.checkModellingExerciseAssessment(exercise.id!, 'class Class', 'Wrong', -1); + await examResultsPage.checkModellingExerciseAssessment(exercise.id!, 'abstract class Abstract', 'Neutral', 0); + break; + } + }); + + if (testCase.exerciseType === ExerciseType.TEXT) { + test('Check exam result overview', async ({ page, login, examAPIRequests, examResultsPage }) => { + await login(studentOne); + await page.goto(`/courses/${course.id}/exams/${exam.id}`); + const gradeSummary = await examAPIRequests.getGradeSummary(exam); + await examResultsPage.checkGradeSummary(gradeSummary); + }); + } + }); + } }); - test.afterAll('Delete course', async ({ browser }) => { - const page = await browser.newPage(); - const courseManagementAPIRequests = new CourseManagementAPIRequests(page); + test.afterEach('Delete course', async ({ courseManagementAPIRequests }) => { await courseManagementAPIRequests.deleteCourse(course, admin); }); }); @@ -214,3 +194,10 @@ async function startAssessing( await exerciseAssessment.clickStartNewAssessment(); exerciseAssessment.getLockedMessage(); } + +async function waitForExamEnd(examEndDate: dayjs.Dayjs, page: Page) { + if (examEndDate > dayjs()) { + const timeToWait = examEndDate.diff(dayjs()); + await page.waitForTimeout(timeToWait); + } +} diff --git a/src/test/playwright/support/pageobjects/exam/ExamParticipationActions.ts b/src/test/playwright/support/pageobjects/exam/ExamParticipationActions.ts index ab7616f92a5b..3b959704a3e5 100644 --- a/src/test/playwright/support/pageobjects/exam/ExamParticipationActions.ts +++ b/src/test/playwright/support/pageobjects/exam/ExamParticipationActions.ts @@ -30,7 +30,7 @@ export class ExamParticipationActions { async getResultScore(exerciseID?: number) { const parentComponent = exerciseID ? getExercise(this.page, exerciseID) : this.page; - const resultScoreLocator = parentComponent.locator('#exercise-result-score'); + const resultScoreLocator = parentComponent.getByTestId('achieved-percentage'); await Commands.reloadUntilFound(this.page, resultScoreLocator); return resultScoreLocator; } diff --git a/src/test/playwright/support/pageobjects/exam/ExamResultsPage.ts b/src/test/playwright/support/pageobjects/exam/ExamResultsPage.ts index 77a78e007fb6..71b2bc814a33 100644 --- a/src/test/playwright/support/pageobjects/exam/ExamResultsPage.ts +++ b/src/test/playwright/support/pageobjects/exam/ExamResultsPage.ts @@ -19,12 +19,10 @@ export class ExamResultsPage { const achievedPoints = Math.floor(exerciseResult.achievedPoints).toString(); const achievablePoints = Math.floor(exerciseResult.maxScore).toString(); const achievedPercentage = exerciseResult.achievedScore.toString(); - const bonusPoints = Math.floor(exercise.bonusPoints).toString(); await expect(exerciseRow.locator('td').nth(1).getByText(achievedPoints)).toBeVisible(); await expect(exerciseRow.locator('td').nth(2).getByText(achievablePoints)).toBeVisible(); await expect(exerciseRow.locator('td').nth(3).getByText(`${achievedPercentage} %`)).toBeVisible(); - await expect(exerciseRow.locator('td').nth(4).getByText(bonusPoints)).toBeVisible(); } } From e0e1cbc5f208c942c61db5f842b259ce41cb4308 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Sun, 2 Jun 2024 22:49:36 +0200 Subject: [PATCH 2/5] Increase timeout of quiz exercise score assertion, as it can take longer than expected --- .../support/pageobjects/exercises/ExerciseResultPage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/playwright/support/pageobjects/exercises/ExerciseResultPage.ts b/src/test/playwright/support/pageobjects/exercises/ExerciseResultPage.ts index dc7ba5102921..b662a1adb57f 100644 --- a/src/test/playwright/support/pageobjects/exercises/ExerciseResultPage.ts +++ b/src/test/playwright/support/pageobjects/exercises/ExerciseResultPage.ts @@ -26,7 +26,7 @@ export class ExerciseResultPage { } async shouldShowScore(percentage: number) { - await Commands.reloadUntilFound(this.page, this.page.locator('jhi-course-exercise-details #submission-result-graded')); + await Commands.reloadUntilFound(this.page, this.page.locator('jhi-course-exercise-details #submission-result-graded'), 4000, 60000); await expect(this.page.locator('.tab-bar-exercise-details').getByText(`${percentage}%`)).toBeVisible(); } From bb1a94a2287b66bf5b02735278fde6cb7c43ea5e Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Sun, 2 Jun 2024 23:03:35 +0200 Subject: [PATCH 3/5] Reduce exercise submissions from 3 to 2 for programming exercise team participations to reduce load and testing time --- .../programming/ProgrammingExerciseParticipation.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index ea6c63748e8e..d028013b4367 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -15,7 +15,7 @@ import { Fixtures } from '../../../fixtures/fixtures'; import { createFileWithContent } from '../../../support/utils'; import { ProgrammingExerciseSubmission } from '../../../support/pageobjects/exercises/programming/OnlineEditorPage'; import cAllSuccessful from '../../../fixtures/exercise/programming/c/all_successful/submission.json'; -import { UserCredentials, admin, instructor, studentFour, studentOne, studentThree, studentTwo, tutor } from '../../../support/users'; +import { UserCredentials, admin, instructor, studentFour, studentOne, studentTwo, tutor } from '../../../support/users'; import { Team } from 'app/entities/team.model'; import { ProgrammingExerciseOverviewPage } from '../../../support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage'; import { Participation } from 'app/entities/participation/participation.model'; @@ -27,6 +27,7 @@ test.describe('Programming exercise participation', () => { await login(admin, '/'); course = await courseManagementAPIRequests.createCourse({ customizeGroups: true }); await courseManagementAPIRequests.addStudentToCourse(course, studentOne); + await courseManagementAPIRequests.addStudentToCourse(course, studentTwo); }); const testCases = [ @@ -90,7 +91,6 @@ test.describe('Programming exercise participation', () => { const submissions = [ { student: studentOne, submission: javaBuildErrorSubmission, commitMessage: 'Initial commit' }, { student: studentTwo, submission: javaPartiallySuccessfulSubmission, commitMessage: 'Initial implementation' }, - { student: studentThree, submission: javaAllSuccessfulSubmission, commitMessage: 'Implemented all tasks' }, ]; test.beforeEach('Create team programming exercise', async ({ login, exerciseAPIRequests }) => { @@ -107,7 +107,7 @@ test.describe('Programming exercise participation', () => { test.beforeEach('Create an exercise team', async ({ login, userManagementAPIRequests, exerciseAPIRequests }) => { await login(admin); const students = await Promise.all( - [studentOne, studentTwo, studentThree].map(async (student) => { + [studentOne, studentTwo].map(async (student) => { const response = await userManagementAPIRequests.getUser(student.username); return response.json(); }), From 2e8a9c43c0b294336a1ab6bc3dd26628dfe1a14f Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Tue, 4 Jun 2024 14:25:58 +0200 Subject: [PATCH 4/5] Wait for a build to finish before starting a new one to reduce server load in team participation test --- .../ProgrammingExerciseParticipation.spec.ts | 5 ++- src/test/playwright/support/commands.ts | 31 +++++++++++++++++++ src/test/playwright/support/fixtures.ts | 6 ++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index d028013b4367..e35048615d49 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -182,7 +182,7 @@ test.describe('Programming exercise participation', () => { }); test.describe('Check team participation', () => { - test.beforeEach('Each team member makes a submission', async ({ login, exerciseAPIRequests }) => { + test.beforeEach('Each team member makes a submission', async ({ login, waitForExerciseBuildToFinish, exerciseAPIRequests }) => { for (const { student, submission } of submissions) { await login(student); const response = await exerciseAPIRequests.startExerciseParticipation(exercise.id!); @@ -192,6 +192,7 @@ test.describe('Programming exercise participation', () => { await exerciseAPIRequests.createProgrammingExerciseFile(participation.id!, filename); } await exerciseAPIRequests.makeProgrammingExerciseSubmission(participation.id!, submission); + await waitForExerciseBuildToFinish(exercise.id!); } }); @@ -203,8 +204,6 @@ test.describe('Programming exercise participation', () => { programmingExerciseRepository, programmingExerciseParticipations, }) => { - // Marked test as slow as there are 3 builds being awaited - test.slow(); await login(instructor); await navigationBar.openCourseManagement(); await courseManagement.openExercisesOfCourse(course.id!); diff --git a/src/test/playwright/support/commands.ts b/src/test/playwright/support/commands.ts index 9b42d25c8060..cb65a5992985 100644 --- a/src/test/playwright/support/commands.ts +++ b/src/test/playwright/support/commands.ts @@ -1,6 +1,8 @@ import { UserCredentials } from './users'; import { BASE_API } from './constants'; import { Locator, Page, expect } from '@playwright/test'; +import { StudentParticipation } from 'app/entities/participation/student-participation.model'; +import { ExerciseAPIRequests } from './requests/ExerciseAPIRequests'; /** * A class that encapsulates static helper command methods. @@ -62,4 +64,33 @@ export class Commands { throw new Error(`Timed out finding an element matching the "${locator}" locator`); }; + + /** + * Waits for the build of an exercise to finish. + * Throws an error if the build does not finish within the timeout. + * @param page - Playwright page object. + * @param exerciseAPIRequests - ExerciseAPIRequests object. + * @param exerciseId - ID of the exercise to wait for. + * @param interval - Interval in milliseconds between checks for the build to finish. + * @param timeout - Timeout in milliseconds to wait for the build to finish. + */ + static waitForExerciseBuildToFinish = async (page: Page, exerciseAPIRequests: ExerciseAPIRequests, exerciseId: number, interval: number = 2000, timeout: number = 60000) => { + let exerciseParticipation: StudentParticipation; + const startTime = Date.now(); + + const numberOfBuildResults = (await exerciseAPIRequests.getExerciseParticipation(exerciseId)).results?.length; + console.log('Waiting for build of an exercise to finish...'); + + while (Date.now() - startTime < timeout) { + exerciseParticipation = await exerciseAPIRequests.getExerciseParticipation(exerciseId); + + if (exerciseParticipation.results && exerciseParticipation.results.length > (numberOfBuildResults ?? 0)) { + return exerciseParticipation; + } + + await new Promise((resolve) => setTimeout(resolve, interval)); + } + + throw new Error('Timed out while waiting for build to finish.'); + }; } diff --git a/src/test/playwright/support/fixtures.ts b/src/test/playwright/support/fixtures.ts index f277be1e2ca2..1bba5e74b0f3 100644 --- a/src/test/playwright/support/fixtures.ts +++ b/src/test/playwright/support/fixtures.ts @@ -71,6 +71,7 @@ import { ExamParticipationActions } from './pageobjects/exam/ExamParticipationAc */ export type ArtemisCommands = { login: (credentials: UserCredentials, url?: string) => Promise; + waitForExerciseBuildToFinish: (exerciseId: number, interval?: number, timeout?: number) => Promise; }; export type ArtemisPageObjects = { @@ -155,6 +156,11 @@ export const test = base.extend { + await use(async (exerciseId: number, interval?, timeout?) => { + await Commands.waitForExerciseBuildToFinish(page, exerciseAPIRequests, exerciseId, interval, timeout); + }); + }, navigationBar: async ({ page }, use) => { await use(new NavigationBar(page)); }, From 53d666cdb62f9edc2d3f71ce268fde7ecfc70f7e Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Tue, 4 Jun 2024 15:17:35 +0200 Subject: [PATCH 5/5] Increase timeout of waiting for an exercise result score --- .../support/pageobjects/exam/ExamParticipationActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/playwright/support/pageobjects/exam/ExamParticipationActions.ts b/src/test/playwright/support/pageobjects/exam/ExamParticipationActions.ts index 3b959704a3e5..a7be0510b65a 100644 --- a/src/test/playwright/support/pageobjects/exam/ExamParticipationActions.ts +++ b/src/test/playwright/support/pageobjects/exam/ExamParticipationActions.ts @@ -31,7 +31,7 @@ export class ExamParticipationActions { async getResultScore(exerciseID?: number) { const parentComponent = exerciseID ? getExercise(this.page, exerciseID) : this.page; const resultScoreLocator = parentComponent.getByTestId('achieved-percentage'); - await Commands.reloadUntilFound(this.page, resultScoreLocator); + await Commands.reloadUntilFound(this.page, resultScoreLocator, 4000, 60000); return resultScoreLocator; }