From 9e174f6044fdac5b10358a69ebd7a693802bf819 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Wed, 10 May 2017 21:02:06 +0200
Subject: [PATCH] no-jira: selenium tests for QTI 2.1 editor, 2 matches with
 different settings, scoring and with feedbacks

---
 .../interactions/MatchScoreController.java    |   3 +-
 .../interactions/_content/match_choices.html  |   2 +-
 .../interactions/_content/match_score.html    |   3 +-
 .../java/org/olat/selenium/ImsQTI21Test.java  | 197 +++++++++++++++++-
 .../page/qti/QTI21MatchEditorPage.java        |  52 +++--
 .../page/qti/QTI21MatchScoreEditorPage.java   |  79 +++++++
 .../org/olat/selenium/page/qti/QTI21Page.java |   7 +
 7 files changed, 322 insertions(+), 21 deletions(-)
 create mode 100644 src/test/java/org/olat/selenium/page/qti/QTI21MatchScoreEditorPage.java

diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchScoreController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchScoreController.java
index 12020dcf15f..6191702c499 100644
--- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchScoreController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchScoreController.java
@@ -88,12 +88,13 @@ public class MatchScoreController extends AssessmentItemRefEditorController impl
 		setFormContextHelp("Test editor QTI 2.1 in detail#details_testeditor_score");
 		
 		minScoreEl = uifactory.addTextElement("min.score", "min.score", 8, "0.0", formLayout);
+		minScoreEl.setElementCssClass("o_sel_assessment_item_min_score");
 		minScoreEl.setEnabled(false);
-		minScoreEl.setEnabled(!restrictedEdit);
 		
 		ScoreBuilder maxScore = itemBuilder.getMaxScoreBuilder();
 		String maxValue = maxScore == null ? "" : (maxScore.getScore() == null ? "" : maxScore.getScore().toString());
 		maxScoreEl = uifactory.addTextElement("max.score", "max.score", 8, maxValue, formLayout);
+		maxScoreEl.setElementCssClass("o_sel_assessment_item_max_score");
 		maxScoreEl.setEnabled(!restrictedEdit);
 		
 		String[] modeValues = new String[]{
diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_choices.html b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_choices.html
index 36bf895fcf7..839510d2404 100644
--- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_choices.html
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_choices.html
@@ -53,7 +53,7 @@ jQuery(function() {
 #end
 
 #if($r.available("add.match.row") || $r.available("add.match.column") || $r.available("submit"))
-<div class="o_button_group">
+<div class="o_button_group o_sel_match_save">
 	#if($r.available("submit"))
 		$r.render("submit")
 	#end
diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_score.html b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_score.html
index 4d67d571d58..03cbbdb6b0d 100644
--- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_score.html
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_score.html
@@ -11,8 +11,9 @@
 	#foreach($sourceChoice in $sourceChoices)
 		<tr>
 			<th>${sourceChoice.getSummary()}</th>
+			#set($sourceIndex = ${foreach.index})
 			#foreach($targetChoice in $targetChoices)
-				<td>
+				<td class="o_sel_match_${sourceIndex}_${foreach.index}">
 				#set($scoreWrapper = $r.get("${sourceChoice.getChoiceIdentifier().toString()}-${targetChoice.getChoiceIdentifier().toString()}"))
 				$r.render("${sourceChoice.getChoiceIdentifier().toString()}-${targetChoice.getChoiceIdentifier().toString()}")
 				#if(${scoreWrapper.isCorrect()})
diff --git a/src/test/java/org/olat/selenium/ImsQTI21Test.java b/src/test/java/org/olat/selenium/ImsQTI21Test.java
index 43d21b27336..1c955a2e428 100644
--- a/src/test/java/org/olat/selenium/ImsQTI21Test.java
+++ b/src/test/java/org/olat/selenium/ImsQTI21Test.java
@@ -46,6 +46,7 @@ import org.olat.selenium.page.course.CoursePageFragment;
 import org.olat.selenium.page.qti.QTI21ConfigurationCEPage;
 import org.olat.selenium.page.qti.QTI21EditorPage;
 import org.olat.selenium.page.qti.QTI21KprimEditorPage;
+import org.olat.selenium.page.qti.QTI21MatchEditorPage;
 import org.olat.selenium.page.qti.QTI21MultipleChoiceEditorPage;
 import org.olat.selenium.page.qti.QTI21Page;
 import org.olat.selenium.page.qti.QTI21SingleChoiceEditorPage;
@@ -953,7 +954,6 @@ public class ImsQTI21Test {
 		UserVO ryomou = new UserRestClient(deploymentUrl).createRandomUser("Ryomou");
 		authorLoginPage.loginAs(author.getLogin(), author.getPassword());
 		
-		//upload a test
 		String qtiTestTitle = "Choices QTI 2.1 " + UUID.randomUUID();
 		navBar
 			.openAuthoringEnvironment()
@@ -1097,7 +1097,6 @@ public class ImsQTI21Test {
 		UserVO eric = new UserRestClient(deploymentUrl).createRandomUser("Eric");
 		authorLoginPage.loginAs(author.getLogin(), author.getPassword());
 		
-		//upload a test
 		String qtiTestTitle = "Choices QTI 2.1 " + UUID.randomUUID();
 		navBar
 			.openAuthoringEnvironment()
@@ -1250,7 +1249,6 @@ public class ImsQTI21Test {
 			.assertOnAssessmentResults()
 			.assertOnAssessmentTestScore(6);// 3 points from the first question, 3 from the second
 	}
-	
 
 	/**
 	 * An author make a test with 2 kprims.<br>
@@ -1272,7 +1270,6 @@ public class ImsQTI21Test {
 		UserVO melissa = new UserRestClient(deploymentUrl).createRandomUser("Melissa");
 		authorLoginPage.loginAs(author.getLogin(), author.getPassword());
 		
-		//upload a test
 		String qtiTestTitle = "Choices QTI 2.1 " + UUID.randomUUID();
 		navBar
 			.openAuthoringEnvironment()
@@ -1421,4 +1418,196 @@ public class ImsQTI21Test {
 			.assertOnAssessmentResults()
 			.assertOnAssessmentTestScore(6);// 3 points from the first question, 3 from the second
 	}
+	
+	/**
+	 * An author make a test with 2 matches. A match with "multiple selection"
+	 * and score "all answers", a second with "single selection" and score
+	 * "per answers".<br>
+	 * A first user make the test, but doesn't answer all questions
+	 * correctly, log out and a second user make the perfect test.
+	 * 
+	 * @param authorLoginPage
+	 * @param participantBrowser
+	 * @throws IOException
+	 * @throws URISyntaxException
+	 */
+	@Test
+	@RunAsClient
+	public void qti21EditorMatch(@InitialPage LoginPage authorLoginPage,
+			@Drone @User WebDriver participantBrowser)
+	throws IOException, URISyntaxException {
+		UserVO author = new UserRestClient(deploymentUrl).createAuthor();
+		UserVO rei = new UserRestClient(deploymentUrl).createRandomUser("Rei");
+		UserVO melissa = new UserRestClient(deploymentUrl).createRandomUser("Melissa");
+		authorLoginPage.loginAs(author.getLogin(), author.getPassword());
+		
+		String qtiTestTitle = "Choices QTI 2.1 " + UUID.randomUUID();
+		navBar
+			.openAuthoringEnvironment()
+			.createQTI21Test(qtiTestTitle)
+			.clickToolbarBack();
+		
+		QTI21Page qtiPage = QTI21Page
+				.getQTI12Page(browser);
+		QTI21EditorPage qtiEditor = qtiPage
+				.edit();
+		//start a blank test
+		qtiEditor
+			.selectNode("Single choice")
+			.deleteNode();
+		
+		//add a match, multiple selection
+		QTI21MatchEditorPage matchEditor = qtiEditor
+			.addMatch();
+		matchEditor
+			.setSource(0, "Eclipse")
+			.setSource(1, "vim")
+			.setTarget(0, "IDE")
+			.setTarget(1, "TextProcessor")
+			.addColumn()
+			.setTarget(2, "TextEditor")
+			.setMatch(0, 0, true)
+			.setMatch(1, 2, true)
+			.save();
+		// change max score
+		matchEditor
+			.selectScores()
+			.setMaxScore("4")
+			.save();
+		// set some feedbacks
+		matchEditor
+			.selectFeedbacks()
+			.setHint("Hint", "This is only an hint")
+			.setCorrectSolution("Correct solution", "This is the correct solution")
+			.setCorrectFeedback("Correct feedback", "This is correct")
+			.setIncorrectFeedback("Incorrect", "Your answer is not correct")
+			.save();
+		
+		// second match
+		matchEditor = qtiEditor
+			.addMatch()
+			.setSingleChoices()
+			.setSource(0, "Java")
+			.setSource(1, "C")
+			.addRow()
+			.setSource(2, "PHP")
+			.setTarget(0, "CodeIgniter")
+			.setTarget(1, "VisualStudio")
+			.addColumn()
+			.setTarget(2, "Eclipse")
+			.setMatch(0, 2, true)
+			.setMatch(1, 1, true)
+			.setMatch(2, 0, true)
+			.save();
+		// select score "per answer" and set the scores
+		matchEditor
+			.selectScores()
+			.selectAssessmentMode(ScoreEvaluation.perAnswer)
+			.setMaxScore("6")
+			.setScore(0, 0, "0.0")
+			.setScore(0, 1, "0.0")
+			.setScore(0, 2, "2.0")
+			.setScore(1, 0, "0.0")
+			.setScore(1, 1, "3.0")
+			.setScore(1, 2, "0.0")
+			.setScore(2, 0, "1.0")
+			.setScore(2, 1, "0.0")
+			.setScore(2, 2, "0.0")
+			.save();
+		matchEditor
+			.selectFeedbacks()
+			.setHint("Hint", "The hint")
+			.setCorrectSolution("Correct solution", "This is the correct solution")
+			.setCorrectFeedback("Correct feedback", "This is correct")
+			.setIncorrectFeedback("Incorrect", "Your answer is not correct")
+			.save();
+		
+		qtiPage
+			.clickToolbarBack();
+		// access to all
+		qtiPage
+			.accessConfiguration()
+			.setUserAccess(UserAccess.guest)
+			.clickToolbarBack();
+		// show results
+		qtiPage
+			.options()
+			.showResults(Boolean.TRUE, QTI21AssessmentResultsOptions.allOptions())
+			.save();
+		
+		//a user search the content package
+		LoginPage reiLoginPage = LoginPage.getLoginPage(participantBrowser, deploymentUrl);
+		reiLoginPage
+			.loginAs(rei.getLogin(), rei.getPassword())
+			.resume();
+		NavigationPage reiNavBar = new NavigationPage(participantBrowser);
+		reiNavBar
+			.openMyCourses()
+			.openSearch()
+			.extendedSearch(qtiTestTitle)
+			.select(qtiTestTitle)
+			.start();
+		
+		// make the test
+		QTI21Page reiQtiPage = QTI21Page
+				.getQTI12Page(participantBrowser);
+		reiQtiPage
+			.assertOnAssessmentItem()
+			.answerMatch("Eclipse", "IDE", true)
+			.answerMatch("vim", "IDE", true)
+			.saveAnswer()
+			.assertFeedback("Incorrect")
+			.assertCorrectSolution("Correct solution")
+			.hint()
+			.assertFeedback("Hint")
+			.answerMatch("vim", "IDE", false)
+			.answerMatch("vim", "TextEditor", true)
+			.saveAnswer()
+			.assertFeedback("Correct feedback")
+			.nextAnswer()
+			.answerMatch("Java", "Eclipse", true)
+			.answerMatch("C", "CodeIgniter", true)
+			.answerMatch("PHP", "VisualStudio", true)
+			.saveAnswer()
+			.assertCorrectSolution("Correct solution")
+			.assertFeedback("Incorrect")
+			.endTest()
+			.assertOnAssessmentResults()
+			.assertOnAssessmentTestScore(6);// 4 points from the first question, 2 from the second
+		
+		//a second user search the content package
+		LoginPage melLoginPage = LoginPage.getLoginPage(participantBrowser, deploymentUrl);
+		melLoginPage
+			.loginAs(melissa.getLogin(), melissa.getPassword())
+			.resume();
+		NavigationPage melNavBar = new NavigationPage(participantBrowser);
+		melNavBar
+			.openMyCourses()
+			.openSearch()
+			.extendedSearch(qtiTestTitle)
+			.select(qtiTestTitle)
+			.start();
+		
+		// make the test
+		QTI21Page
+			.getQTI12Page(participantBrowser)
+			.assertOnAssessmentItem()
+			.answerMatch("Eclipse", "IDE", true)
+			.answerMatch("vim", "TextEditor", true)
+			.saveAnswer()
+			.assertFeedback("Correct feedback")
+			.nextAnswer()
+			.answerMatch("Java", "Eclipse", true)
+			.answerMatch("C", "CodeIgniter", true)
+			.answerMatch("PHP", "VisualStudio", true)
+			.saveAnswer()
+			.answerMatch("C", "CodeIgniter", false)
+			.answerMatch("PHP", "VisualStudio", false)
+			.answerMatch("C", "VisualStudio", true)
+			.answerMatch("PHP", "CodeIgniter", true)
+			.saveAnswer()
+			.endTest()
+			.assertOnAssessmentResults()
+			.assertOnAssessmentTestScore(10);// 4 points from the first question, 6 from the second
+	}
 }
diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21MatchEditorPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21MatchEditorPage.java
index 9057a0eeb6c..759580c71d5 100644
--- a/src/test/java/org/olat/selenium/page/qti/QTI21MatchEditorPage.java
+++ b/src/test/java/org/olat/selenium/page/qti/QTI21MatchEditorPage.java
@@ -22,6 +22,7 @@ package org.olat.selenium.page.qti;
 import org.olat.selenium.page.graphene.OOGraphene;
 import org.openqa.selenium.By;
 import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
 
 /**
  * 
@@ -35,6 +36,18 @@ public class QTI21MatchEditorPage extends QTI21AssessmentItemEditorPage {
 		super(browser);
 	}
 	
+	public QTI21MatchEditorPage setSource(int position, String text) {
+		String containerCssSelector = "th.o_sel_match_source_" + position;
+		OOGraphene.tinymce(text, containerCssSelector, browser);
+		return this;
+	}
+	
+	public QTI21MatchEditorPage setTarget(int position, String text) {
+		String containerCssSelector = "th.o_sel_match_target_" + position;
+		OOGraphene.tinymce(text, containerCssSelector, browser);
+		return this;
+	}
+	
 	/**
 	 * Set if the answer is correct or wrong.
 	 * 
@@ -42,34 +55,45 @@ public class QTI21MatchEditorPage extends QTI21AssessmentItemEditorPage {
 	 * @param correct
 	 * @return Itself
 	 */
-	public QTI21MatchEditorPage setCorrect(int position, boolean correct) {
-		By answerBy;
-		if(correct) {
-			answerBy = By.xpath("//div[contains(@class,'o_sel_choice_" + position + "')]//input[contains(@id,'oo_correct-')]");
-		} else {
-			answerBy = By.xpath("//div[contains(@class,'o_sel_choice_" + position + "')]//input[contains(@id,'oo_wrong-')]");
-		}
-		browser.findElement(answerBy).click();
+	public QTI21MatchEditorPage setMatch(int source, int target, boolean correct) {
+		By answerBy = By.xpath("//td[contains(@class,'o_sel_match_" + source + "_" + target + "')]/input[contains(@id,'oo_')]");
+		WebElement matchEl = browser.findElement(answerBy);
+		OOGraphene.check(matchEl, correct);
 		OOGraphene.waitBusy(browser);
 		return this;
 	}
 	
-	public QTI21MatchEditorPage setAnswer(int position, String answer) {
-		String containerCssSelector = "div.o_sel_choice_" + position;
-		OOGraphene.tinymce(answer, containerCssSelector, browser);
+	public QTI21MatchEditorPage addColumn() {
+		By saveBy = By.cssSelector("div.o_sel_match_save a.o_sel_match_add_column");
+		browser.findElement(saveBy).click();
+		OOGraphene.waitBusy(browser);
+		return this;
+	}
+	
+	public QTI21MatchEditorPage addRow() {
+		By saveBy = By.cssSelector("div.o_sel_match_save a.o_sel_match_add_row");
+		browser.findElement(saveBy).click();
+		OOGraphene.waitBusy(browser);
+		return this;
+	}
+	
+	public QTI21MatchEditorPage setSingleChoices() {
+		By singleBy = By.cssSelector("div.o_sel_match_single input[type='radio'][value='single']");
+		browser.findElement(singleBy).click();
+		OOGraphene.waitBusy(browser);
 		return this;
 	}
 	
 	public QTI21MatchEditorPage save() {
-		By saveBy = By.cssSelector("fieldset.o_sel_choices_save button.btn.btn-primary");
+		By saveBy = By.cssSelector("div.o_sel_match_save button.btn.btn-primary");
 		browser.findElement(saveBy).click();
 		OOGraphene.waitBusy(browser);
 		return this;
 	}
 	
-	public QTI21ChoicesScoreEditorPage selectScores() {
+	public QTI21MatchScoreEditorPage selectScores() {
 		selectTab(By.className("o_sel_assessment_item_options"));
-		return new QTI21ChoicesScoreEditorPage(browser);
+		return new QTI21MatchScoreEditorPage(browser);
 	}
 	
 	public QTI21FeedbacksEditorPage selectFeedbacks() {
diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21MatchScoreEditorPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21MatchScoreEditorPage.java
new file mode 100644
index 00000000000..20ff7120855
--- /dev/null
+++ b/src/test/java/org/olat/selenium/page/qti/QTI21MatchScoreEditorPage.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.ims.qti21.model.xml.interactions.SimpleChoiceAssessmentItemBuilder.ScoreEvaluation;
+import org.olat.selenium.page.graphene.OOGraphene;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * 
+ * Initial date: 10 mai 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class QTI21MatchScoreEditorPage {
+	
+	private static final By choiceScoreTable = By.cssSelector("table.matchInteraction.score");
+	
+	private final WebDriver browser;
+	
+	public QTI21MatchScoreEditorPage(WebDriver browser) {
+		this.browser = browser;
+	}
+	
+	public QTI21MatchScoreEditorPage selectAssessmentMode(ScoreEvaluation mode) {
+		By modeBy = By.cssSelector("#o_coassessment_mode input[value='" + mode.name() + "']");
+		browser.findElement(modeBy).click();
+		OOGraphene.waitBusy(browser);
+		if(mode == ScoreEvaluation.allCorrectAnswers) {
+			OOGraphene.waitElementDisappears(choiceScoreTable, 5, browser);
+		} else if (mode == ScoreEvaluation.perAnswer) {
+			OOGraphene.waitElement(choiceScoreTable, 5, browser);
+		}
+		return this;
+	}
+	
+	public QTI21MatchScoreEditorPage setScore(int source, int target, String score) {
+		By scoreBy = By.cssSelector("td.o_sel_match_" + source + "_" + target + " input[type='text']");
+		WebElement scoreEl = browser.findElement(scoreBy);
+		scoreEl.clear();
+		scoreEl.sendKeys(score);
+		return this;
+	}
+	
+	public QTI21MatchScoreEditorPage setMaxScore(String maxScore) {
+		By maxScoreBy = By.cssSelector("div.o_sel_assessment_item_max_score input[type='text']");
+		WebElement maxScoreEl = browser.findElement(maxScoreBy);
+		maxScoreEl.clear();
+		maxScoreEl.sendKeys(maxScore);
+		return this;
+	}
+	
+	public QTI21MatchScoreEditorPage save() {
+		By saveBy = By.cssSelector("fieldset.o_sel_assessment_item_options 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 219959bdc14..a80b149944b 100644
--- a/src/test/java/org/olat/selenium/page/qti/QTI21Page.java
+++ b/src/test/java/org/olat/selenium/page/qti/QTI21Page.java
@@ -166,6 +166,13 @@ public class QTI21Page {
 		return this;
 	}
 	
+	public QTI21Page answerMatch(String source, String target, boolean match) {
+		By matchBy = By.xpath("//div[contains(@class,'matchInteraction')]/table//tr[th/p[contains(text(),'" + source + "')]]/td[count(//div[contains(@class,'matchInteraction')]/table//tr/th[p[contains(text(),'" + target + "')]]/preceding-sibling::th)]/input");
+		WebElement matchEl = browser.findElement(matchBy);
+		OOGraphene.check(matchEl, match);
+		return this;
+	}
+	
 	public QTI21Page saveAnswer() {
 		By saveAnswerBy = By.cssSelector("button.o_sel_assessment_item_submit");
 		browser.findElement(saveAnswerBy).click();
-- 
GitLab