From fa48e0fa89ddecead9c41b5fd5eab17fb34b0397 Mon Sep 17 00:00:00 2001
From: srosse <stephane.rosse@frentix.com>
Date: Fri, 12 Jun 2020 17:10:24 +0200
Subject: [PATCH] no-jira: update selenium tests with new QTI default

---
 .../assessment/ui/tool/AssessmentForm.java    |   1 +
 ...nIdentityAssessmentItemListController.java |   2 +
 .../olat/ims/qti21/ui/report/SearchEvent.java |  19 ++
 .../grading/ui/GradersListController.java     |   1 +
 ...epositoryEntryConfigurationController.java |  20 +-
 .../grading/ui/_content/assignments_list.html |   2 +-
 .../grading/ui/_content/graders_list.html     |   4 +-
 .../org/olat/selenium/AssessmentTest.java     |   1 +
 .../java/org/olat/selenium/ImsQTI21Test.java  | 193 +++++++++++++++++-
 .../olat/selenium/page/NavigationPage.java    |   7 +
 .../selenium/page/coaching/CoachingPage.java  |  55 +++++
 .../page/core/MenuTreePageFragment.java       |   2 +
 .../page/course/AssessmentToolPage.java       |  25 +++
 .../page/qti/QTI21ConfigurationCEPage.java    |  14 ++
 .../page/qti/QTI21CorrectionPage.java         |  37 ++++
 .../page/qti/QTI21GradingGradersPage.java     |  79 +++++++
 .../selenium/page/qti/QTI21GradingPage.java   |  58 ++++++
 .../page/qti/QTI21GradingSettingsPage.java    |  87 ++++++++
 .../org/olat/selenium/page/qti/QTI21Page.java |  49 +++--
 .../qti21/simple_QTI_21_test.zip              | Bin 2477 -> 3098 bytes
 .../qti21/test_without_feedbacks.zip          | Bin 3599 -> 4215 bytes
 21 files changed, 631 insertions(+), 25 deletions(-)
 create mode 100644 src/test/java/org/olat/selenium/page/coaching/CoachingPage.java
 create mode 100644 src/test/java/org/olat/selenium/page/qti/QTI21GradingGradersPage.java
 create mode 100644 src/test/java/org/olat/selenium/page/qti/QTI21GradingPage.java
 create mode 100644 src/test/java/org/olat/selenium/page/qti/QTI21GradingSettingsPage.java

diff --git a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentForm.java b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentForm.java
index 1b83fbe861d..937829f1286 100644
--- a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentForm.java
+++ b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentForm.java
@@ -605,6 +605,7 @@ public class AssessmentForm extends FormBasicController {
 		
 		String[] userVisibilityValues = new String[]{ translate("user.visibility.visible"), translate("user.visibility.hidden") };
 		userVisibility = uifactory.addRadiosHorizontal("user.visibility", "user.visibility", formLayout, userVisibilityKeys, userVisibilityValues);
+		userVisibility.setElementCssClass("o_sel_assessment_form_visibility");
 		if(scoreEval.getUserVisible() == null || scoreEval.getUserVisible().booleanValue()) {
 			userVisibility.select(userVisibilityKeys[0], true);
 		} else {
diff --git a/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionIdentityAssessmentItemListController.java b/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionIdentityAssessmentItemListController.java
index cc3bf1aac44..8c77cc83717 100644
--- a/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionIdentityAssessmentItemListController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionIdentityAssessmentItemListController.java
@@ -204,10 +204,12 @@ public class CorrectionIdentityAssessmentItemListController extends FormBasicCon
 		tableEl = uifactory.addTableElement(getWindowControl(), "table", tableModel, getTranslator(), formLayout);
 		tableEl.setExportEnabled(true);
 		tableEl.setAndLoadPersistedPreferences(ureq, "corr-identity-assessment-item-list");
+		tableEl.setElementCssClass("o_sel_correction_assessment_items_list");
 		
 		backLink = uifactory.addFormLink("back", formLayout, Link.LINK_BACK);
 		if(saveEnabled && !readOnly) {
 			saveButton = uifactory.addFormLink("save.tests", formLayout, Link.BUTTON);
+			saveButton.setElementCssClass("o_sel_correction_save_test");
 		} else {
 			backOverviewButton = uifactory.addFormLink("back.overview", formLayout, Link.BUTTON);
 		}
diff --git a/src/main/java/org/olat/ims/qti21/ui/report/SearchEvent.java b/src/main/java/org/olat/ims/qti21/ui/report/SearchEvent.java
index 3aec5474a63..e85ee0f11ce 100644
--- a/src/main/java/org/olat/ims/qti21/ui/report/SearchEvent.java
+++ b/src/main/java/org/olat/ims/qti21/ui/report/SearchEvent.java
@@ -1,3 +1,22 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
 package org.olat.ims.qti21.ui.report;
 
 import org.olat.core.gui.control.Event;
diff --git a/src/main/java/org/olat/modules/grading/ui/GradersListController.java b/src/main/java/org/olat/modules/grading/ui/GradersListController.java
index ba2c767ee44..e8acace7a2f 100644
--- a/src/main/java/org/olat/modules/grading/ui/GradersListController.java
+++ b/src/main/java/org/olat/modules/grading/ui/GradersListController.java
@@ -142,6 +142,7 @@ public class GradersListController extends FormBasicController {
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
 		addGraderButton = uifactory.addFormLink("add.grader", formLayout, Link.BUTTON);
 		addGraderButton.setIconLeftCSS("o_icon o_icon_add_item");
+		addGraderButton.setElementCssClass("o_sel_repo_grading_add_graders");
 		
 		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
 		if(isAdministrativeUser) {
diff --git a/src/main/java/org/olat/modules/grading/ui/GradingRepositoryEntryConfigurationController.java b/src/main/java/org/olat/modules/grading/ui/GradingRepositoryEntryConfigurationController.java
index 65d32a4d831..5b4090d6c4e 100644
--- a/src/main/java/org/olat/modules/grading/ui/GradingRepositoryEntryConfigurationController.java
+++ b/src/main/java/org/olat/modules/grading/ui/GradingRepositoryEntryConfigurationController.java
@@ -115,6 +115,7 @@ public class GradingRepositoryEntryConfigurationController extends FormBasicCont
 
 	@Override
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		formLayout.setElementCssClass("o_sel_repo_grading_settings_form");
 
 		String[] onValues = new String[] { translate("on") };
 		enableEl = uifactory.addCheckboxesHorizontal("grading.repo.enabled", formLayout, onKeys, onValues);
@@ -129,6 +130,7 @@ public class GradingRepositoryEntryConfigurationController extends FormBasicCont
 			};
 		identityVisibilityEl = uifactory.addRadiosHorizontal("anonymous", "configuration.assessed.identity.visibility", formLayout,
 				visibilityKeys, visibilityValues);
+		identityVisibilityEl.setElementCssClass("o_sel_repo_grading_visibility");
 		identityVisibilityEl.select(configuration.getIdentityVisibilityEnum().name(), true);
 		
 		String[] notificationTypeValues = new String[] {
@@ -136,20 +138,23 @@ public class GradingRepositoryEntryConfigurationController extends FormBasicCont
 			};
 		notificationTypeEl = uifactory.addRadiosHorizontal("notificationType", "configuration.notification.type", formLayout,
 				notificationTypeKeys, notificationTypeValues);
+		notificationTypeEl.setElementCssClass("o_sel_repo_grading_notification_type");
 		notificationTypeEl.select(configuration.getNotificationTypeEnum().name(), true);
 		
 		String period = configuration.getGradingPeriod() == null ? null : configuration.getGradingPeriod().toString();
 		gradingPeriodEl = uifactory.addTextElement("configuration.grading.period", 6, period, formLayout);
-		initWorkingDays(gradingPeriodEl);
+		initWorkingDays(gradingPeriodEl, "o_sel_repo_grading_period");
 		
 		notificationSubjectEl = uifactory.addTextElement("configuration.notification.subject", 255, configuration.getNotificationSubject(), formLayout);
+		notificationSubjectEl.setElementCssClass("o_sel_repo_grading_notification_subject");
 		notificationBodyEl = uifactory.addTextAreaElement("configuration.notification.body", 4, 60, configuration.getNotificationBody(), formLayout);
-
+		notificationBodyEl.setElementCssClass("o_sel_repo_grading_notification_body");
+		
 		spacerNotificationsEl = uifactory.addSpacerElement("spacer-notification", formLayout, false);
 		
 		String firstReminder = configuration.getFirstReminder() == null ? null : configuration.getFirstReminder().toString();
 		firstReminderPeriodEl = uifactory.addTextElement("configuration.first.reminder.period", 6, firstReminder, formLayout);
-		initWorkingDays(firstReminderPeriodEl);
+		initWorkingDays(firstReminderPeriodEl, "o_sel_repo_grading_first_reminder_period");
 		firstReminderSubjectEl = uifactory.addTextElement("configuration.first.reminder.subject", 255, configuration.getFirstReminderSubject(), formLayout);
 		firstReminderBodyEl = uifactory.addTextAreaElement("configuration.first.reminder.body", 4, 60, configuration.getFirstReminderBody(), formLayout);
 
@@ -157,7 +162,9 @@ public class GradingRepositoryEntryConfigurationController extends FormBasicCont
 		
 		String secondReminder = configuration.getSecondReminder() == null ? null : configuration.getSecondReminder().toString();
 		secondReminderPeriodEl = uifactory.addTextElement("configuration.second.reminder.period", 6, secondReminder, formLayout);
-		initWorkingDays(secondReminderPeriodEl);
+		secondReminderPeriodEl.setElementCssClass("");
+		
+		initWorkingDays(secondReminderPeriodEl, "o_sel_repo_grading_second_reminder_period");
 		secondReminderSubjectEl = uifactory.addTextElement("configuration.second.reminder.subject", 255, configuration.getSecondReminderSubject(), formLayout);
 		secondReminderBodyEl = uifactory.addTextAreaElement("configuration.second.reminder.body", 4, 60, configuration.getSecondReminderBody(), formLayout);
 
@@ -167,6 +174,7 @@ public class GradingRepositoryEntryConfigurationController extends FormBasicCont
 		uifactory.addFormSubmitButton("save", buttonsCont);
 		
 		templatesDropdownEl = uifactory.addDropdownMenu("choose.template.language", null, buttonsCont, getTranslator());
+		templatesDropdownEl.setElementCssClass("o_sel_repo_grading_templates");
 		templatesDropdownEl.setOrientation(DropdownOrientation.right);
 		templatesDropdownEl.setEmbbeded(true);
 		
@@ -186,10 +194,10 @@ public class GradingRepositoryEntryConfigurationController extends FormBasicCont
 		}
 	}
 	
-	private void initWorkingDays(TextElement textEl) {
+	private void initWorkingDays(TextElement textEl, String elementCss) {
 		textEl.setDisplaySize(6);
 		textEl.setMaxLength(6);
-		textEl.setElementCssClass("form-inline");
+		textEl.setElementCssClass("form-inline ".concat(elementCss));
 		textEl.setTextAddOn("working.days");
 	}
 
diff --git a/src/main/java/org/olat/modules/grading/ui/_content/assignments_list.html b/src/main/java/org/olat/modules/grading/ui/_content/assignments_list.html
index 11892e3c989..39c00bbbcae 100644
--- a/src/main/java/org/olat/modules/grading/ui/_content/assignments_list.html
+++ b/src/main/java/org/olat/modules/grading/ui/_content/assignments_list.html
@@ -1,4 +1,4 @@
-<fieldset>
+<fieldset class="o_sel_grading_assignments">
 	#if($r.isTrue($myTitle))<legend>$r.translate("grading.my.assignments.title")</legend>#end
 
 	<div class="clearfix">$r.render("search")</div>
diff --git a/src/main/java/org/olat/modules/grading/ui/_content/graders_list.html b/src/main/java/org/olat/modules/grading/ui/_content/graders_list.html
index 4a0685930a1..c2b9c84eb45 100644
--- a/src/main/java/org/olat/modules/grading/ui/_content/graders_list.html
+++ b/src/main/java/org/olat/modules/grading/ui/_content/graders_list.html
@@ -1,7 +1,9 @@
+<div class="o_sel_graders_list">
 #if($r.available("search"))
 	<div class="clearfix">$r.render("search")</div>
 #end
 <div class="o_button_group o_button_group_right">
 	$r.render("add.grader")
 </div>
-$r.render("graders")
\ No newline at end of file
+$r.render("graders")
+</div>
\ No newline at end of file
diff --git a/src/test/java/org/olat/selenium/AssessmentTest.java b/src/test/java/org/olat/selenium/AssessmentTest.java
index 4a1f956d0b4..d9d33af5214 100644
--- a/src/test/java/org/olat/selenium/AssessmentTest.java
+++ b/src/test/java/org/olat/selenium/AssessmentTest.java
@@ -701,6 +701,7 @@ public class AssessmentTest extends Deployments {
 			.assertOnEfficiencyStatmentPage()
 			.assertOnCertificate(courseTitle);
 	}
+	
 	/**
 	 * An author create a course, set up the root node to make efficiency statement,
 	 * add a test, publish it and add a participant. It set the certificate.<br>
diff --git a/src/test/java/org/olat/selenium/ImsQTI21Test.java b/src/test/java/org/olat/selenium/ImsQTI21Test.java
index aac8c62a4cc..836520906f2 100644
--- a/src/test/java/org/olat/selenium/ImsQTI21Test.java
+++ b/src/test/java/org/olat/selenium/ImsQTI21Test.java
@@ -30,12 +30,14 @@ import org.jboss.arquillian.drone.api.annotation.Drone;
 import org.jboss.arquillian.junit.Arquillian;
 import org.jboss.arquillian.test.api.ArquillianResource;
 import org.junit.Assert;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.olat.ims.qti21.QTI21AssessmentResultsOptions;
 import org.olat.repository.model.SingleRoleRepositoryEntrySecurity.Role;
 import org.olat.selenium.page.LoginPage;
 import org.olat.selenium.page.NavigationPage;
+import org.olat.selenium.page.Participant;
 import org.olat.selenium.page.User;
 import org.olat.selenium.page.course.AssessmentToolPage;
 import org.olat.selenium.page.course.CourseEditorPageFragment;
@@ -44,6 +46,7 @@ import org.olat.selenium.page.course.MembersPage;
 import org.olat.selenium.page.graphene.OOGraphene;
 import org.olat.selenium.page.qti.QTI21ConfigurationCEPage;
 import org.olat.selenium.page.qti.QTI21CorrectionPage;
+import org.olat.selenium.page.qti.QTI21GradingPage;
 import org.olat.selenium.page.qti.QTI21Page;
 import org.olat.selenium.page.repository.UserAccess;
 import org.olat.selenium.page.user.UserToolsPage;
@@ -648,7 +651,7 @@ public class ImsQTI21Test extends Deployments {
 	public void qti21Course_lmsHidden_results()
 	throws IOException, URISyntaxException {
 		
-		UserVO author = new UserRestClient(deploymentUrl).createAuthor();
+		UserVO author = new UserRestClient(deploymentUrl).createRandomAuthor();
 		LoginPage authorLoginPage = LoginPage.load(browser, deploymentUrl);
 		authorLoginPage.loginAs(author.getLogin(), author.getPassword());
 		
@@ -738,6 +741,7 @@ public class ImsQTI21Test extends Deployments {
 			.closeAssessmentResults()
 			.assertOnCourseAttempts(1)
 			.assertOnCourseAssessmentTestScore(1)
+			.assertOnCourseAssessmentTestPassed()
 			.assertOnAssessmentResults();
 	}
 
@@ -888,6 +892,7 @@ public class ImsQTI21Test extends Deployments {
 			.assertOnCourseAttempts(1);
 	}
 	
+
 	/**
 	 * An author create a course with a test to overview
 	 * the progress of a participant doing a test.
@@ -902,7 +907,7 @@ public class ImsQTI21Test extends Deployments {
 	public void qti21CourseTestCockpitProgress(@Drone @User WebDriver participantBrowser)
 	throws IOException, URISyntaxException {
 		
-		UserVO author = new UserRestClient(deploymentUrl).createAuthor();
+		UserVO author = new UserRestClient(deploymentUrl).createRandomAuthor();
 		UserVO participant = new UserRestClient(deploymentUrl).createRandomUser("Ryomou");
 
 		LoginPage loginPage = LoginPage.load(browser, deploymentUrl);
@@ -1007,7 +1012,8 @@ public class ImsQTI21Test extends Deployments {
 		// participant ends the test
 		qtiPage
 			.endTest()//auto close because 1 part, no feedbacks
-			.assertOnCourseAssessmentTestScore(2);
+			.assertOnCourseAssessmentTestScore(2)
+			.assertOnCourseAssessmentTestPassed();
 		
 		// author wait the status changes
 		assessmentTool
@@ -1150,6 +1156,187 @@ public class ImsQTI21Test extends Deployments {
 			.assertOnCourseAssessmentTestScore(2);
 	}
 	
+
+	/**
+	 * An author create a test with essay and single choice, configures it
+	 * with grading, add a grader then makes a course. A user play the test,
+	 * the grader correct it, the author set the test as passed and free the
+	 * results. The user reloads the results to see passed!
+	 * 
+	 * @param participantBrowser The participant browser
+	 * @param graderBrowser The grader browser
+	 * @throws IOException
+	 * @throws URISyntaxException
+	 */
+	@Test
+	@Ignore
+	@RunAsClient
+	public void qti21CourseTestGradingWorkflow(@Drone @Participant WebDriver participantBrowser, @Drone @User WebDriver graderBrowser)
+	throws IOException, URISyntaxException {
+		
+		UserVO author = new UserRestClient(deploymentUrl).createRandomAuthor();
+		UserVO participant = new UserRestClient(deploymentUrl).createRandomUser("Hakufu");
+		UserVO grader = new UserRestClient(deploymentUrl).createRandomUser("Hakufu");
+		
+		LoginPage loginPage = LoginPage.load(browser, deploymentUrl);
+		loginPage.loginAs(author.getLogin(), author.getPassword());
+		
+		//upload a test and prepare the grading configuration
+		String qtiTestTitle = "Correction 2.1 " + UUID.randomUUID();
+		URL qtiTestUrl = JunitTestHelper.class.getResource("file_resources/qti21/test_sc_essay_mc.zip");
+		File qtiTestFile = new File(qtiTestUrl.toURI());
+		NavigationPage navBar = NavigationPage.load(browser);
+		navBar
+			.openAuthoringEnvironment()
+			.uploadResource(qtiTestTitle, qtiTestFile)
+			.clickToolbarRootCrumb();
+			
+		QTI21Page qtiPage = QTI21Page
+				.getQTI21Page(browser);
+		QTI21GradingPage gradingPage = qtiPage
+			.grading();
+		gradingPage
+			.settings()
+			.enable()
+			.setPeriods(9, 5, 7)
+			.selectMailTemplate()
+			.save();
+		
+		gradingPage
+			.graders()
+			.addGrader(grader)
+			.assertGrader(grader);
+
+		qtiPage
+			.clickToolbarBack();
+		
+		//create a course
+		String courseTitle = "Grading QTI 2.1 " + UUID.randomUUID();
+		navBar
+			.openAuthoringEnvironment()
+			.createCourse(courseTitle)
+			.clickToolbarBack();
+		
+		String testNodeTitle = "QTI21Grading-1";
+		
+		//create a course element of type CP with the CP that we create above
+		CourseEditorPageFragment courseEditor = CoursePageFragment.getCourse(browser)
+			.edit();
+		courseEditor
+			.createNode("iqtest")
+			.nodeTitle(testNodeTitle)
+			.selectTabLearnContent()
+			.chooseTest(qtiTestTitle);
+		OOGraphene.closeWarningBox(browser);//close the warning
+		
+		QTI21ConfigurationCEPage configPage = new QTI21ConfigurationCEPage(browser);
+		configPage
+			.selectConfiguration()
+			.setCorrectionMode("grading")
+			.saveConfiguration();
+		
+		courseEditor
+			.autoPublish()
+			.settings()
+			.accessConfiguration()
+			.setUserAccess(UserAccess.membersOnly)
+			.save();
+		
+		//add a participant
+		CoursePageFragment courseRuntime = courseEditor
+			.clickToolbarBack();
+		courseRuntime
+			.publish()
+			.members()
+			.quickAdd(participant);
+		
+		//a user search the course and make the test
+		LoginPage userLoginPage = LoginPage.load(participantBrowser, deploymentUrl);
+		userLoginPage
+			.loginAs(participant.getLogin(), participant.getPassword())
+			.resume();
+		NavigationPage userNavBar = NavigationPage.load(participantBrowser);
+		userNavBar
+			.openMyCourses()
+			.openSearch()
+			.extendedSearch(courseTitle)
+			.select(courseTitle);
+		
+		// open the course and see the test
+		CoursePageFragment course = CoursePageFragment.getCourse(participantBrowser);		
+		course
+			.clickTree()
+			.selectWithTitle(testNodeTitle);
+		QTI21Page participantQtiPage = QTI21Page
+				.getQTI21Page(participantBrowser);
+		participantQtiPage
+			.assertOnStart()
+			.start()
+			.assertOnAssessmentItem()
+			.answerSingleChoiceWithParagraph("Correct answer")
+			.saveAnswer()
+			.assertOnAssessmentItem("Essay")
+			.answerEssay("Bla bla bla")
+			.saveAnswer()
+			.answerMultipleChoice("Good choice", "Bad choice")
+			.saveAnswer()
+			.endTest()
+			.assertOnCourseAssessmentTestWaitingCorrection();
+		
+		// grader assignment
+		LoginPage graderLoginPage = LoginPage.load(graderBrowser, deploymentUrl);
+		graderLoginPage
+			.loginAs(grader.getLogin(), grader.getPassword())
+			.resume();
+		
+		NavigationPage graderNavBar = NavigationPage.load(graderBrowser);
+		graderNavBar
+			.openCoaching()
+			.assertOnGrading()
+			.startGrading(testNodeTitle)
+			.assertOnAssessmentItemNotCorrected("Essay", 0)
+			.selectAssessmentItem("Essay")
+			.setScore("1.0")
+			.save()
+			.assertOnStatusOk()
+			.back()
+			.publish()
+			.confirmDialog();
+		
+		// author check the assignment
+		navBar
+			.openAuthoringEnvironment()
+			.selectResource(qtiTestTitle);
+		qtiPage
+			.grading()
+			.graders()
+			.assertGraderAssignmentsDone(grader, 1);
+		// go in assessment tool to free the results
+		navBar
+			.openAuthoringEnvironment()
+			.selectResource(courseTitle);
+		
+		courseRuntime
+			.assessmentTool()
+			.users()
+			.assertOnUsers(participant)
+			.selectUser(participant)
+			.selectUsersCourseNode(testNodeTitle)
+			.reopenAssessment()
+			.setAssessmentPassed(Boolean.TRUE)
+			.setAssessmentVisibility(true)
+			.closeAssessment()
+			.assertUserPassedCourseNode(testNodeTitle);
+		
+		// participant checks its result
+		course
+			.clickTree()
+			.selectWithTitle(testNodeTitle);
+		participantQtiPage
+			.assertOnCourseAssessmentTestScore(2)
+			.assertOnCourseAssessmentTestPassed();
+	}
+	
 	/**
 	 * An author create a course with a course element
 	 * of type self test. It add a participant. The
diff --git a/src/test/java/org/olat/selenium/page/NavigationPage.java b/src/test/java/org/olat/selenium/page/NavigationPage.java
index cb609f68cd3..d8fdaca19d8 100644
--- a/src/test/java/org/olat/selenium/page/NavigationPage.java
+++ b/src/test/java/org/olat/selenium/page/NavigationPage.java
@@ -22,6 +22,7 @@ package org.olat.selenium.page;
 import java.util.List;
 
 import org.junit.Assert;
+import org.olat.selenium.page.coaching.CoachingPage;
 import org.olat.selenium.page.core.AdministrationPage;
 import org.olat.selenium.page.course.MyCoursesPage;
 import org.olat.selenium.page.graphene.OOGraphene;
@@ -48,6 +49,7 @@ public class NavigationPage {
 	private static final By navigationSitesBy = By.cssSelector("ul.o_navbar_sites");
 	private static final By authoringEnvTabBy = By.cssSelector("li.o_site_author_env > a");
 	private static final By questionPoolTabBy = By.cssSelector("li.o_site_qpool > a");
+	private static final By coachingTabBy = By.cssSelector("li.o_site_coaching > a");
 	private static final By portalBy = By.cssSelector("li.o_site_portal > a");
 	private static final By myCoursesBy = By.cssSelector("li.o_site_repository > a");
 	private static final By userManagementBy = By.cssSelector("li.o_site_useradmin > a");
@@ -89,6 +91,11 @@ public class NavigationPage {
 				.assertOnQuestionPool();
 	}
 	
+	public CoachingPage openCoaching() {
+		navigate(coachingTabBy);
+		return new CoachingPage(browser);
+	}
+	
 	public PortalPage openPortal() {
 		navigate(portalBy);
 		return new PortalPage(browser);
diff --git a/src/test/java/org/olat/selenium/page/coaching/CoachingPage.java b/src/test/java/org/olat/selenium/page/coaching/CoachingPage.java
new file mode 100644
index 00000000000..b43843676e6
--- /dev/null
+++ b/src/test/java/org/olat/selenium/page/coaching/CoachingPage.java
@@ -0,0 +1,55 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.selenium.page.coaching;
+
+import org.olat.selenium.page.graphene.OOGraphene;
+import org.olat.selenium.page.qti.QTI21CorrectionPage;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * 
+ * Initial date: 11 juin 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CoachingPage {
+	
+	private final WebDriver browser;
+	
+	public CoachingPage(WebDriver browser) {
+		this.browser = browser;
+	}
+	
+	public CoachingPage assertOnGrading() {
+		By gradingBy = By.cssSelector("fieldset.o_sel_grading_assignments");
+		OOGraphene.waitElement(gradingBy, browser);
+		return this;
+	}
+	
+	public QTI21CorrectionPage startGrading(String nodeTitle) {
+		By gradingBy = By.xpath("//fieldset[@class='o_sel_grading_assignments']//table//tr[td/a[text()[contains(.,'" + nodeTitle + "')]]]/td/a[contains(@onclick,'grade')]");
+		OOGraphene.waitElement(gradingBy, browser);
+		browser.findElement(gradingBy).click();
+		OOGraphene.waitBusy(browser);
+		return new QTI21CorrectionPage(browser);
+	}
+
+}
diff --git a/src/test/java/org/olat/selenium/page/core/MenuTreePageFragment.java b/src/test/java/org/olat/selenium/page/core/MenuTreePageFragment.java
index b92fbfa1de7..71f7d03830f 100644
--- a/src/test/java/org/olat/selenium/page/core/MenuTreePageFragment.java
+++ b/src/test/java/org/olat/selenium/page/core/MenuTreePageFragment.java
@@ -55,6 +55,8 @@ public class MenuTreePageFragment {
 		OOGraphene.waitElement(rootNodeBy, browser);
 		browser.findElement(rootNodeBy).click();
 		OOGraphene.waitBusy(browser);
+		By rootNodeActiveBy = By.xpath("//div[contains(@class,'o_tree')]//span[contains(@class,'o_tree_link')][contains(@class,'o_tree_l0')][contains(@class,'active')]/a");
+		OOGraphene.waitElement(rootNodeActiveBy, browser);
 		return this;
 	}
 	
diff --git a/src/test/java/org/olat/selenium/page/course/AssessmentToolPage.java b/src/test/java/org/olat/selenium/page/course/AssessmentToolPage.java
index ca7b57f3555..fd51110be0d 100644
--- a/src/test/java/org/olat/selenium/page/course/AssessmentToolPage.java
+++ b/src/test/java/org/olat/selenium/page/course/AssessmentToolPage.java
@@ -141,12 +141,37 @@ public class AssessmentToolPage {
 		By scoreBy = By.cssSelector(".o_sel_assessment_form_score input[type='text']");
 		browser.findElement(scoreBy).sendKeys(Float.toString(score));
 		
+		return closeAssessment();
+	}
+	
+	public AssessmentToolPage setAssessmentPassed(Boolean passed) {
+		String val = passed == null ? "undefined" : passed.toString();
+		By passedBy = By.cssSelector(".o_sel_assessment_form_passed input[type='radio'][value='" + val + "']");
+		browser.findElement(passedBy).click();
+		return this;
+	}
+	
+	public AssessmentToolPage setAssessmentVisibility(boolean visible) {
+		String val = visible ? "visible" : "hidden";
+		By visibleBy = By.cssSelector(".o_sel_assessment_form_visibility input[type='radio'][value='" + val + "']");
+		browser.findElement(visibleBy).click();
+		return this;
+	}
+	
+	public AssessmentToolPage closeAssessment() {
 		By saveBy = By.cssSelector("button.btn.o_sel_assessment_form_save_and_close");
 		browser.findElement(saveBy).click();
 		OOGraphene.waitBusy(browser);
 		return this;
 	}
 	
+	public AssessmentToolPage reopenAssessment() {
+		By saveBy = By.cssSelector("a.o_sel_assessment_form_reopen");
+		browser.findElement(saveBy).click();
+		OOGraphene.waitBusy(browser);
+		return this;
+	}
+	
 	public AssessmentToolPage assertPassed(UserVO user) {
 		By userInfosBy = By.xpath("//div[@class='o_user_infos']/div[@class='o_user_infos_inner']//tr[contains(@class,'o_userDisplayName')]/td[text()[contains(.,'" + user.getFirstName() + "')]]");
 		OOGraphene.waitElement(userInfosBy, browser);
diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21ConfigurationCEPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21ConfigurationCEPage.java
index 137e91d8fc3..190aa4557e4 100644
--- a/src/test/java/org/olat/selenium/page/qti/QTI21ConfigurationCEPage.java
+++ b/src/test/java/org/olat/selenium/page/qti/QTI21ConfigurationCEPage.java
@@ -84,6 +84,20 @@ public class QTI21ConfigurationCEPage {
 		return this;
 	}
 	
+	/**
+	 * Set the correction mode.
+	 * 
+	 * @param mode The mode defined by IQEditController.CORRECTION_AUTO,
+	 * 		IQEditController.CORRECTION_MANUAL or IQEditController.CORRECTION_GRADING
+	 * @return Itself
+	 */
+	public QTI21ConfigurationCEPage setCorrectionMode(String mode) {
+		By correctionBy = By.xpath("//fieldset[contains(@class,'o_qti_21_correction')]//div[@id='o_cocorrection_mode']//input[@value='" + mode + "'][@name='correction.mode'][@type='radio']");
+		OOGraphene.waitElement(correctionBy, browser);
+		browser.findElement(correctionBy).click();
+		return this;
+	}
+	
 	public QTI21ConfigurationCEPage saveConfiguration() {
 		By saveBy = By.cssSelector(".o_qti_21_configuration button");
 		browser.findElement(saveBy).click();
diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21CorrectionPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21CorrectionPage.java
index 871239bad98..71641e4c3bd 100644
--- a/src/test/java/org/olat/selenium/page/qti/QTI21CorrectionPage.java
+++ b/src/test/java/org/olat/selenium/page/qti/QTI21CorrectionPage.java
@@ -58,12 +58,32 @@ public class QTI21CorrectionPage {
 		return this;
 	}
 	
+	/**
+	 * The assessment items list after selecting an identity in the list.
+	 * 
+	 * @param questionTitle The question title
+	 * @param numberOfErrors The number of errors
+	 * @return
+	 */
 	public QTI21CorrectionPage assertOnAssessmentItemError(String questionTitle, int numberOfErrors) {
 		By questionBy = By.xpath("//div[contains(@class,'o_sel_correction_assessment_items_list')]//tr[td/a[text()[contains(.,'" + questionTitle + "')]]]/td/a[text()[contains(.,'" + numberOfErrors + "')]]/i[contains(@class,'o_icon_error')]");
 		OOGraphene.waitElement(questionBy, browser);
 		return this;
 	}
 	
+	/**
+	 * Assert an assessment item which is not corrected.
+	 * 
+	 * @param questionTitle The question title
+	 * @param numberOfErrors
+	 * @return
+	 */
+	public QTI21CorrectionPage assertOnAssessmentItemNotCorrected(String questionTitle, int numberOfPoints) {
+		By questionBy = By.xpath("//div[contains(@class,'o_sel_correction_assessment_items_list')]//tr[td/a[text()[contains(.,'" + questionTitle + "')]]][td[text()[contains(.,'" + numberOfPoints + "')]]]/td/a/i[contains(@class,'o_icon_error')]");
+		OOGraphene.waitElement(questionBy, browser);
+		return this;
+	}
+	
 	public QTI21CorrectionPage setScore(String score) {
 		By scoreBy = By.cssSelector("div.o_assessmentitem_wrapper .o_sel_assessment_item_score input[type='text']");
 		OOGraphene.waitElement(scoreBy, browser);
@@ -91,6 +111,10 @@ public class QTI21CorrectionPage {
 		return this;
 	}
 	
+	/**
+	 * 
+	 * @return Itself
+	 */
 	public QTI21CorrectionPage publishAll() {
 		By saveBy = By.cssSelector("a.o_sel_correction_save_tests");
 		OOGraphene.waitElement(saveBy, browser);
@@ -99,6 +123,19 @@ public class QTI21CorrectionPage {
 		return this;
 	}
 	
+	/**
+	 * Publish the results of a single test (grader).
+	 * 
+	 * @return Itself
+	 */
+	public QTI21CorrectionPage publish() {
+		By saveBy = By.cssSelector("a.o_sel_correction_save_test");
+		OOGraphene.waitElement(saveBy, browser);
+		browser.findElement(saveBy).click();
+		OOGraphene.waitBusy(browser);
+		return this;
+	}
+	
 	public QTI21CorrectionPage confirmDialog() {
 		By confirmButtonBy = By.cssSelector("div.modal-dialog div.modal-body button.btn-primary");
 		OOGraphene.waitElement(confirmButtonBy, browser);
diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21GradingGradersPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21GradingGradersPage.java
new file mode 100644
index 00000000000..958e035a49b
--- /dev/null
+++ b/src/test/java/org/olat/selenium/page/qti/QTI21GradingGradersPage.java
@@ -0,0 +1,79 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.selenium.page.qti;
+
+import org.olat.selenium.page.graphene.OOGraphene;
+import org.olat.selenium.page.group.MembersWizardPage;
+import org.olat.user.restapi.UserVO;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * 
+ * Initial date: 11 juin 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class QTI21GradingGradersPage {
+	
+	private final WebDriver browser;
+	
+	public QTI21GradingGradersPage(WebDriver browser) {
+		this.browser = browser;
+	}
+	
+	public QTI21GradingGradersPage addGrader(UserVO user) {
+		By addGraderBy = By.cssSelector("div.o_button_group a.o_sel_repo_grading_add_graders");
+		OOGraphene.waitElement(addGraderBy, browser);
+		browser.findElement(addGraderBy).click();
+		
+		MembersWizardPage wizard = new MembersWizardPage(browser)
+			.searchMember(user, true);
+		
+		OOGraphene.nextStep(browser);
+		OOGraphene.waitElement(By.cssSelector("fieldset.o_sel_grader_import_overview"), browser);
+
+		OOGraphene.nextStep(browser);
+		wizard.finish();
+		return this;
+	}
+	
+	public QTI21GradingGradersPage assertGrader(UserVO user) {
+		By graderBy = By.xpath("//div[@class='o_sel_graders_list']//table//tr/td/a[text()[contains(.,'" + user.getFirstName() + "')]]");
+		OOGraphene.waitElement(graderBy, browser);
+		return this;
+	}
+	
+	/**
+	 * The method is not precise.
+	 * 
+	 * @param user The grader
+	 * @param assignments A number
+	 * 
+	 * @return Itself
+	 */
+	public QTI21GradingGradersPage assertGraderAssignmentsDone(UserVO user, int assignments) {
+		By graderBy = By.xpath("//div[@class='o_sel_graders_list']//table//tr[td/a[contains(@onclick,'done')][text()[contains(.,'" + assignments + "')]]]/td/a[text()[contains(.,'" + user.getFirstName() + "')]]");
+		OOGraphene.waitElement(graderBy, browser);
+		return this;
+	}
+	
+
+}
diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21GradingPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21GradingPage.java
new file mode 100644
index 00000000000..034b712d112
--- /dev/null
+++ b/src/test/java/org/olat/selenium/page/qti/QTI21GradingPage.java
@@ -0,0 +1,58 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.selenium.page.qti;
+
+import org.olat.selenium.page.graphene.OOGraphene;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * 
+ * Initial date: 10 juin 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class QTI21GradingPage {
+	
+	private final WebDriver browser;
+	
+	public QTI21GradingPage(WebDriver browser) {
+		this.browser = browser;
+	}
+	
+	public QTI21GradingSettingsPage settings() {
+		selectSegment("repository.configuration");
+		return new QTI21GradingSettingsPage(browser);
+	}
+	
+	public QTI21GradingGradersPage graders() {
+		selectSegment("repository.graders");
+		return new QTI21GradingGradersPage(browser);
+	}
+	
+	private void selectSegment(String action) {
+		By segmentBy = By.xpath("//div[contains(@class,'o_segments')]/a[contains(@onclick,'" + action + "')]");
+		OOGraphene.waitElement(segmentBy, browser);
+		browser.findElement(segmentBy).click();
+		OOGraphene.waitBusy(browser);
+		By segmentSelectedBy = By.xpath("//div[contains(@class,'o_segments')]/a[contains(@class,'btn-primary')][contains(@onclick,'" + action + "')]");
+		OOGraphene.waitElement(segmentSelectedBy, browser);
+	}
+}
diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21GradingSettingsPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21GradingSettingsPage.java
new file mode 100644
index 00000000000..414be7a9eb5
--- /dev/null
+++ b/src/test/java/org/olat/selenium/page/qti/QTI21GradingSettingsPage.java
@@ -0,0 +1,87 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.selenium.page.qti;
+
+import org.olat.selenium.page.graphene.OOGraphene;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * 
+ * Initial date: 10 juin 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class QTI21GradingSettingsPage {
+	
+	private final WebDriver browser;
+	
+	public QTI21GradingSettingsPage(WebDriver browser) {
+		this.browser = browser;
+	}
+	
+	public QTI21GradingSettingsPage enable() {
+		By enableBy = By.xpath("//div[contains(@class,'o_sel_repo_grading_enable')]//input[@name='grading.repo.enabled'][@type='checkbox']");
+		OOGraphene.waitElement(enableBy, browser);
+		browser.findElement(enableBy).click();
+		OOGraphene.waitBusy(browser);
+		
+		By reminderBy = By.className("o_sel_repo_grading_notification_type");
+		OOGraphene.waitElement(reminderBy, browser);
+		return this;
+	}
+	
+	public QTI21GradingSettingsPage setPeriods(int gradingPeriod, int firstReminder, int secondReminder) {
+		By gradingPeriodBy = By.cssSelector("input.o_sel_repo_grading_period[type='text']");
+		browser.findElement(gradingPeriodBy).sendKeys(Integer.toString(gradingPeriod));
+		
+		By firstReminderBy = By.cssSelector("input.o_sel_repo_grading_first_reminder_period[type='text']");
+		browser.findElement(firstReminderBy).sendKeys(Integer.toString(firstReminder));
+		
+		By secondReminderBy = By.cssSelector("input.o_sel_repo_grading_second_reminder_period[type='text']");
+		browser.findElement(secondReminderBy).sendKeys(Integer.toString(secondReminder));
+		return this;
+	}
+	
+	public QTI21GradingSettingsPage selectMailTemplate() {
+		By templatesMenuCaret = By.cssSelector("button.o_sel_repo_grading_templates");
+		browser.findElement(templatesMenuCaret).click();
+		
+		By templatesMenu = By.cssSelector("ul.o_sel_repo_grading_templates");
+		OOGraphene.waitElement(templatesMenu, browser);
+		
+		By firstTemplateBy = By.xpath("//ul[contains(@class,'o_sel_repo_grading_templates')]/li[1]/a");
+		OOGraphene.waitElement(firstTemplateBy, browser);
+		browser.findElement(firstTemplateBy).click();
+		OOGraphene.waitBusy(browser);
+		
+		By notificationSubjectBy = By.xpath("//div[contains(@class,'o_sel_repo_grading_notification_subject')]//input[@value[string()]]");
+		OOGraphene.waitElement(notificationSubjectBy, browser);
+		return this;
+	}
+	
+	public QTI21GradingSettingsPage save() {
+		By saveBy = By.cssSelector("fieldset.o_sel_repo_grading_settings_form button.btn.btn-primary");
+		browser.findElement(saveBy).click();
+		OOGraphene.waitBusy(browser);
+		return this;
+	}
+
+}
diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21Page.java b/src/test/java/org/olat/selenium/page/qti/QTI21Page.java
index 906042a87b1..46a4052e551 100644
--- a/src/test/java/org/olat/selenium/page/qti/QTI21Page.java
+++ b/src/test/java/org/olat/selenium/page/qti/QTI21Page.java
@@ -895,49 +895,55 @@ public class QTI21Page {
 	
 	public QTI21Page assertOnCourseAssessmentTestScore(int score) {
 		By resultsBy = By.xpath("//div[contains(@class,'o_personal')]//tr[contains(@class,'o_score')]/td[contains(text(),'" + score + "')]");
-		OOGraphene.waitElement(resultsBy, 5, browser);
+		OOGraphene.waitElement(resultsBy, browser);
 		return this;
 	}
 	
 	public QTI21Page assertOnCourseAssessmentTestWaitingCorrection() {
 		By resultsBy = By.xpath("//div[contains(@class,'o_personal')]//tr[contains(@class,'o_score')]/td/span[@id='o_score_in_review']");
-		OOGraphene.waitElement(resultsBy, 5, browser);
+		OOGraphene.waitElement(resultsBy, browser);
+		return this;
+	}
+	
+	public QTI21Page assertOnCourseAssessmentTestPassed() {
+		By passedBy = By.xpath("//div[contains(@class,'o_personal')]//tr[contains(@class,'o_state')][contains(@class,'o_passed')]/td/i[contains(@class,'o_icon_passed')]");
+		OOGraphene.waitElement(passedBy, browser);
 		return this;
 	}
 	
 	public QTI21Page assertOnAssessmentTestScore(int score) {
 		By resultsBy = By.xpath("//div[contains(@class,'o_sel_results_details')]//tr[contains(@class,'o_sel_assessmenttest_scores')]/td/div/span[contains(@class,'o_sel_assessmenttest_score')][contains(text(),'" + score + "')]");
-		OOGraphene.waitElement(resultsBy, 5, browser);
+		OOGraphene.waitElement(resultsBy, browser);
 		return this;
 	}
 	
 	public QTI21Page assertOnAssessmentItemScore(String title, int score) {
 		By resultsBy = By.xpath("//div[contains(@class,'o_qti_item')][div/h4[text()[contains(.,'" + title + "')]]]//tr[contains(@class,'o_sel_assessmentitem_score')]//span[@class='o_sel_assessmentitem_score'][contains(text(),'" + score + "')]");
-		OOGraphene.waitElement(resultsBy, 5, browser);
+		OOGraphene.waitElement(resultsBy, browser);
 		return this;
 	}
 	
 	public QTI21Page assertOnAssessmentTestScore(String score) {
 		By resultsBy = By.xpath("//div[contains(@class,'o_sel_results_details')]//tr[contains(@class,'o_sel_assessmenttest_scores')]/td/div/span[contains(@class,'o_sel_assessmenttest_score')][contains(text(),'" + score + "')]");
-		OOGraphene.waitElement(resultsBy, 5, browser);
+		OOGraphene.waitElement(resultsBy, browser);
 		return this;
 	}
 	
 	public QTI21Page assertOnAssessmentTestPassed() {
-		By notPassedBy = By.cssSelector("div.o_sel_results_details tr.o_qti_stateinfo.o_passed");
-		OOGraphene.waitElement(notPassedBy, 5, browser);
+		By passedBy = By.cssSelector("div.o_sel_results_details tr.o_qti_stateinfo.o_passed");
+		OOGraphene.waitElement(passedBy, browser);
 		return this;
 	}
 	
 	public QTI21Page assertOnAssessmentTestNotPassed() {
 		By notPassedBy = By.cssSelector("div.o_sel_results_details tr.o_qti_stateinfo.o_failed");
-		OOGraphene.waitElement(notPassedBy, 5, browser);
+		OOGraphene.waitElement(notPassedBy, browser);
 		return this;
 	}
 	
 	public QTI21Page assertOnAssessmentTestMaxScore(int score) {
 		By resultsBy = By.xpath("//div[contains(@class,'o_sel_results_details')]//tr[contains(@class,'o_sel_assessmenttest_scores')]/td/div/span[contains(@class,'o_sel_assessmenttest_maxscore')][contains(text(),'" + score + "')]");
-		OOGraphene.waitElement(resultsBy, 5, browser);
+		OOGraphene.waitElement(resultsBy, browser);
 		return this;
 	}
 	
@@ -1021,11 +1027,7 @@ public class QTI21Page {
 	}
 	
 	public QTI21SettingsPage settings() {
-		By toolsLinkBy = By.cssSelector("ul.o_tools li.o_tool_dropdown a.o_sel_repository_tools");
-		OOGraphene.waitElement(toolsLinkBy, browser);
-		if(!browser.findElement(toolsMenu).isDisplayed()) {
-			openToolsMenu();
-		}
+		openAdministrationMenu();
 		
 		By optionsBy = By.cssSelector("ul.o_sel_repository_tools a.o_sel_repo_settings");
 		OOGraphene.waitElement(optionsBy, browser);
@@ -1034,6 +1036,25 @@ public class QTI21Page {
 		return new QTI21SettingsPage(browser);
 	}
 	
+	public QTI21GradingPage grading() {
+		openAdministrationMenu();
+		
+		By optionsBy = By.cssSelector("ul.o_sel_repository_tools a.o_sel_grading");
+		OOGraphene.waitElement(optionsBy, browser);
+		browser.findElement(optionsBy).click();
+		OOGraphene.waitBusy(browser);
+		return new QTI21GradingPage(browser);
+	}
+	
+	private QTI21Page openAdministrationMenu() {
+		By toolsLinkBy = By.cssSelector("ul.o_tools li.o_tool_dropdown a.o_sel_repository_tools");
+		OOGraphene.waitElement(toolsLinkBy, browser);
+		if(!browser.findElement(toolsMenu).isDisplayed()) {
+			openToolsMenu();
+		}
+		return this;
+	}
+	
 	/**
 	 * Click the editor link in the tools drop-down
 	 * @return
diff --git a/src/test/java/org/olat/test/file_resources/qti21/simple_QTI_21_test.zip b/src/test/java/org/olat/test/file_resources/qti21/simple_QTI_21_test.zip
index c324d4f7f3cccab7774bd0d0511d9e2654341e6f..d11a0339c38d43e3a25494f2c2b45f08f96eb138 100644
GIT binary patch
literal 3098
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

delta 2244
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
VzJfMp0H2TPzvtTbTEleS{Rg@24z~aR

diff --git a/src/test/java/org/olat/test/file_resources/qti21/test_without_feedbacks.zip b/src/test/java/org/olat/test/file_resources/qti21/test_without_feedbacks.zip
index eb9cd0d4e5b287eff47d9e39fa93593005ac571b..91866206c88daade176bde7316acaa9200833d90 100644
GIT binary patch
delta 1543
zcmZ{kc{tR09LJ||&6qK)wW>*0vqDU++~<m9jAI;OY1}fdAx+Lazud{9j3Y<xa~RPG
zvDST+atq10T51Zp#jtFDWcPXY^Zn=j`8=P`^L)S0`}O%I2y6)<a99>rJ`e-~0SQO<
z;>ZP1HGpb?_kODaXG%oTPXa)J9+>n_5|@LIEfU-G)54ge%xiovD);O&393E#6w-&K
zB{ROnS><RH)VSHYx~6qPJ^%Jp&ET`f32XU{)YsICFnikg?+QKLa<M{)HnHs>Kyd+E
zJ{?Y>hNtIiPO8-BiZ}E}g)T`EsgUl~1Tz45GIwo0Sf3{+ku#%o8KU^gNias(C=@G}
z){Dhhd7^20+gWBtbHmF}Vv<ezXdthN^z@UI$<9S7oJo5dCnLInvCy&^7I&>E>#K>J
z{#`5U+b+Q${iDJe_~pV+jfy@x2h8yZF<-M8(RpOLAVY7B`8jpxRojp&eXbvJ4G6bb
zdOKW8u_aJMSz`O>76p3lEb*(*q<;4;aDB(P^SgtHd>>wT7sf|`Cv(>BFA{U3Pr2hj
zs+fL`?|ScMfO@u_L1)P=o{x&_n0|f$>+ohOp}%e}%g#luH&0FqmG;87*9z&F8T0gB
zu0iEWBXZPzptw#kj4)C(O3@_%clv6|O-BW<Q~2GfI<Ry-iRcl5;N`edyi>Jh%jU$x
z!1n}Q?fNFUj3&Q1?G6Utjv&EH!MM(%PI;`p`T4+SX>m>VsYeu>)4~OATQxgn*3^<r
z)nC-Rni^c<_vYo%<Bt5vza=fWLC;Cd3bftKTGbKhsasbstr6Qi)o{KNcsU>J;qns$
z-ME{%QgIF$%5@WUftrM0&k4J3_!8Nn>E347s3p%KUI5zqamz-3FJkqcYKgNze4AVx
zZ;A-&3=IV(%W$uu3<hyur=DtIkbz0ArDAE#(#dG23-(zKywRvw$Fw-YEnD3(r5mzX
zST`9RS9;~XUU!MuDW@JlDY^1$17Gb`n2}(V=dOmj!GM!ffMs8hOokPSJN%jV#-&U8
zGG6keU6>r_qFZ_G)MKJ?t13S;a=Lh!BOpj8H$J7EnrT?g5V_eM6m?}KYOB&)byBh`
zm=*t;)T+K6>b_F+U~P~eap3Vo!)N(LrSU=LWsU6*t)Mm74l_kZEqkr<OhcNg(e6vr
zPx0Aq>-z%1wPAEN4WX>Sm}vttM;HAvZ5fB<fMnNdP=r`OAOR={^zTKYX|g-8upX@x
zZ60j-O;sTV!N5@obS(G}wTU@KRtD$b<l*4zqU}w*;pQqC;N=;aWI}9}hDXu;If>1P
zyW&+C0=aX{qS}85c~6fl>Brv4OiZaZ#Ev`dAJFDh{7x$Ce~B%cNJ3ZV%{wUA<b*wL
zcg3B4;mn=rBa={#(7%{mvib<%%<J;iDvS_+Ro%j!&&?hK4jE#wg;)#Qjp$5vFGW_&
zm==e)pm`N)V($z5taUZSHvg$*xjMVIgcWLIvTT~k%jL$lioaCzz9X+I9@SNgnuiom
zm-DQhbuL!WLI-(@-uAOzI&J7qD}QspYUT@A66;5|ls%si8+fqj^}rO!PSr1?Kmxy-
z4t0jxKPXiav-ha<VE)>~_C%|@^zH<VisyZv2s7V{bUr_%OO4<q!3VZ-iE!0UO$M1^
zqxMj7uhiZjx(a)=XB&m6&kz|2-d8l_GAzUx=lzkIyUD<?7B9S|v<uVrxCV`_jmzmF
z!#%_g%wZvI1bpVtO<-~?BCXiWhR>d|8R_^;3#iCYwvYB@vGHvyEA-NTo>w(?m*^4K
z;ME?#UI#(i($Ot<>dz3}Ie-q$BgcDULj&VafIy;bM~<HW^MSs9p2ulGAG7$IB)Qxo
zIV+Isp&~s09VFwqkl-K|vLU+&Ihzaq4>n}jVU5mC7Uh!tmcNE0!77~OkMOfbM+_am
d$Pq)|gNN(?h{9pn*pK?y4i9knh*`u@_BSV!wL|~_

delta 925
zcmeya&@aOm;LXe;!ob17!4TMy;kDU=>k<<SnDv$|oEgIEU`~XvCi2<TPYpetciTW<
z&v$K>yV-kJgeORQTo&V)VB#plvQyN%C{uW$OZbwU@9(`&f6^`!2v_fv;Y)AZeMO+h
zF<EQPTVtu$YqdgWubw(lxjyafA1#h)r=KRw<S8-cePqznbp7{k&e@5q@u|5wS9t#F
z-`l-B*)nOu)R|hoj-@WzhndYk9j`xizq>+iu8_w&7Ei^El0UD#REj*KH17z@jE1JJ
zH3ELzx~@_mFJBFdYE|BEyk$z+vgp^GE`E)BXBro*y{f<BPFK6-HU9GJ=bszu-;h}O
zdZN5*Gt0RhW}i-#T)dOXwyil%s<B+N;K9DX8*f*?h%t<PSf0W)D{uGW8}HV~tDE!t
z*W1_hJ04Eu^}4lR`TmKjRTsW%NYwnZE??GqDZ$#y{ZydC>Nngg#E$Ph{BY6lF7}58
z5pUGn4px6Ku((>%dFB5_$&!}WvnpDS$%&t`NllcRsMF6P=<2D-Y3Nru@pypBjNfs*
z+?H>@?Z18}@Y=4hO>P-|{Fk?EnRM(;!lq|Mij~~;S<;iYMEGuKzVqUiSyZLynssZX
z=S7GAT=;(G%(#DVy3cj8r_PZM@Y0`Opjfz5I+|HMYU}K#n>_QbT>JKtBd=s@Uc~bq
znf8bM?mek_x9QD6$NYr*--J`t*^}->_$*fH-P9u@aLMMJfsXMZp@}M~538NlP53_N
zcHQflb|PCQxLv9@-4MNX^}5SC&3EVIS(y3l<z6Y8w6|>XdAW;QwoH68`_h}Uw~w4U
zAS=#Z-Er&o-qPK+3oSNr8)+UkQc>~J@T`csWWzamQfs7~nu>_L`<t^;B6|8_shSU2
zpGpWh2757V7MX1EBvYij<bmFi?yDg@!4oIRY*JC(xcaWmBc)TtUiFzlLX7iQ{p@)V
zaMr+LWs9$RzV(|8_43kZud#>v|31q0_Hf#&-OT4qzMk8hx%#7D#r6IA7d#5SChT$j
zUsk=TW`5)b<%0FcZ_CBnwoCD!U0cugxIVy}o#XAj?V{(H7#QBMF)##pGct)VATq^&
zuIGzq0_B{z7#Jr1;pYP~C-Vr%f-}eDdHhmL*O(^%W>cH|fS(5<<^vQnWSgA9Z=?Xq
iO2}sGFd&0zoRj(Z4MbRhmNGD~0HHM_1H(QZ5Dx&{e~&Ey

-- 
GitLab