From 61b1f8509fcb57bde78776d601103cb9e2a2f8a2 Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Fri, 27 Oct 2017 20:26:13 +0200 Subject: [PATCH] OO-2969 : selenium which use complex conditional feedbacks for QTI 2.1 question --- .../ui/editor/FeedbacksEditorController.java | 8 +- .../editor/_content/feedback_condition.html | 33 +++--- .../_content/feedback_condition_list.html | 4 + .../java/org/olat/selenium/ImsQTI21Test.java | 102 ++++++++++++++++++ .../page/qti/QTI21FeedbacksEditorPage.java | 31 ++++-- .../page/qti/QTI21HotspotEditorPage.java | 19 ++++ .../org/olat/selenium/page/qti/QTI21Page.java | 6 ++ 7 files changed, 176 insertions(+), 27 deletions(-) create mode 100644 src/main/java/org/olat/ims/qti21/ui/editor/_content/feedback_condition_list.html diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/FeedbacksEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/FeedbacksEditorController.java index a29b198d97a..e7f21d2b10c 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/FeedbacksEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/FeedbacksEditorController.java @@ -388,7 +388,9 @@ public class FeedbacksEditorController extends FormBasicController implements Sy titleEl.setEnabled(!restrictedEdit); titleEl.setElementCssClass("o_sel_assessment_item_" + feedbackType.name() + "_feedback_title"); - conditionListContainer = FormLayoutContainer.createBareBoneFormLayout("cond_list_".concat(id), getTranslator()); + String conditionListPage = velocity_root + "/feedback_condition_list.html"; + conditionListContainer = FormLayoutContainer.createCustomFormLayout("cond_list_".concat(id), + getTranslator(), conditionListPage); formLayout.add(conditionListContainer); conditionListContainer.setRootForm(mainForm); conditionListContainer.contextPut("conditions", conditions); @@ -547,6 +549,10 @@ public class FeedbacksEditorController extends FormBasicController implements Sy this.condition = condition; } + public FormLayoutContainer getRuleContainer() { + return ruleContainer; + } + public void initForm(FormItemContainer feedbackFormLayout) { String id = Integer.toString(counter.incrementAndGet()); diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/_content/feedback_condition.html b/src/main/java/org/olat/ims/qti21/ui/editor/_content/feedback_condition.html index 4713052f87c..fd6c0917206 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/_content/feedback_condition.html +++ b/src/main/java/org/olat/ims/qti21/ui/editor/_content/feedback_condition.html @@ -1,24 +1,21 @@ -<div class='form-inline o_condition'> - $r.render("var_$id") - $r.render("ope_$id") - #if($r.visible("txt_val_$id")) - $r.render("txt_val_$id") - #end - #if($r.visible("ans_$id")) - $r.render("ans_$id") - #end - #if($r.available("add_$id")) - $r.render("add_$id") - #end - #if($r.available("del_$id")) - $r.render("del_$id") - #end - +$r.render("var_$id") +$r.render("ope_$id") +#if($r.visible("txt_val_$id")) + $r.render("txt_val_$id") +#end +#if($r.visible("ans_$id")) + $r.render("ans_$id") +#end +#if($r.available("add_$id")) + $r.render("add_$id") +#end +#if($r.available("del_$id")) + $r.render("del_$id") +#end + #if($f.hasError("var_$id")) <br/>$r.render("var_${id}_ERROR") #end #if($f.hasError("txt_val_$id")) <br/>$r.render("txt_val_${id}_ERROR") #end -</div> - diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/_content/feedback_condition_list.html b/src/main/java/org/olat/ims/qti21/ui/editor/_content/feedback_condition_list.html new file mode 100644 index 00000000000..d6e9b1f7b5c --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/editor/_content/feedback_condition_list.html @@ -0,0 +1,4 @@ +#foreach($condition in $conditions) +<div class='form-inline o_condition o_condition_${foreach.count}'>$r.render($condition.ruleContainer)</div> +#end + diff --git a/src/test/java/org/olat/selenium/ImsQTI21Test.java b/src/test/java/org/olat/selenium/ImsQTI21Test.java index 4aca73268ac..ab302ad0d46 100644 --- a/src/test/java/org/olat/selenium/ImsQTI21Test.java +++ b/src/test/java/org/olat/selenium/ImsQTI21Test.java @@ -1326,6 +1326,108 @@ public class ImsQTI21Test { .assertOnAssessmentResults() .assertOnAssessmentTestScore(6);// 3 points from the first question, 3 from the second } + + /** + * Test the conditional feedback with 3 conditions based + * on attempts (and an incorrect feedback used as marker), + * on score and on response. It's done with a multiple + * choice with score per answer and a negative min. score. + * + * @param authorLoginPage + * @throws IOException + * @throws URISyntaxException + */ + @Test + @RunAsClient + public void qti21EditorMultipleChoices_complexConditionalFeedback(@InitialPage LoginPage authorLoginPage) + throws IOException, URISyntaxException { + UserVO author = new UserRestClient(deploymentUrl).createAuthor(); + 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(); + qtiEditor + .selectNode("Single choice") + .deleteNode(); + + //add a single choice: all answers score + QTI21MultipleChoiceEditorPage mcEditor = qtiEditor + .addMultipleChoice(); + mcEditor + .setAnswer(0, "Ok") + .setCorrect(0) + .addChoice(1) + .setCorrect(1) + .setAnswer(1, "Correct") + .addChoice(2) + .setAnswer(2, "Faux") + .addChoice(3) + .setAnswer(3, "Falsch") + .save(); + + //add negative scores to play with + mcEditor + .selectScores() + .selectAssessmentMode(ScoreEvaluation.perAnswer) + .setScore("Ok", "3") + .setScore("Correct", "1") + .setScore("Faux", "-1") + .setScore("Falsch", "-1") + .setMaxScore("4") + .save(); + + // set a conditional feedback + mcEditor + .selectFeedbacks() + .setIncorrectFeedback("Incorrect", "Not the right response") + // attempts = 1 && score < 0 + .addConditionalFeedback(1, "NegativeFirstAttempts", "Negative score") + .setCondition(1, 1, Variable.attempts, Operator.equals, "1") + .addCondition(1, 1) + .setCondition(1, 2, Variable.score, Operator.smaller, "0") + // response = 'Faux' + .addConditionalFeedback(2, "FauxAnswer", "You choose the 'Faux' answer") + .setCondition(2, 1, Variable.response, Operator.equals, "Faux") + // 0 < score < 3 + .addConditionalFeedback(3, "Positive", "Score between 0 and 3") + .setCondition(3, 1, Variable.score, Operator.biggerEquals, "0") + .addCondition(3, 1) + .setCondition(3, 2, Variable.score, Operator.smaller, "3") + .save(); + + qtiPage + .clickToolbarBack() + .assertOnAssessmentItem() + //1 attempt, score -2.0 + .answerMultipleChoice("Falsch", "Faux") + .saveAnswer() + .assertFeedback("Incorrect") + .assertFeedback("FauxAnswer") + .assertFeedback("NegativeFirstAttempts") + .assertNoFeedback("Positive") + //2 attempt, score 0.0 + .deselectAnswerMultipleChoice("Faux", "Falsch") + .answerMultipleChoice("Faux", "Correct") + .saveAnswer() + .assertFeedback("Incorrect") + .assertFeedback("FauxAnswer") + .assertFeedback("Positive") + .assertNoFeedback("NegativeFirstAttempts") + //3 attempt + .deselectAnswerMultipleChoice("Faux") + .answerMultipleChoice("Ok") + .saveAnswer() + .assertNoFeedback() + .endTest(); + } /** * An author make a test with 2 kprims.<br> diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21FeedbacksEditorPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21FeedbacksEditorPage.java index 5792531c157..66e3a5fcc0a 100644 --- a/src/test/java/org/olat/selenium/page/qti/QTI21FeedbacksEditorPage.java +++ b/src/test/java/org/olat/selenium/page/qti/QTI21FeedbacksEditorPage.java @@ -126,29 +126,44 @@ public class QTI21FeedbacksEditorPage { public QTI21FeedbacksEditorPage setCondition(int feedbackPosition, int conditionPosition, ModalFeedbackCondition.Variable variable, ModalFeedbackCondition.Operator operator, String value) { + + String conditionPrefix = "//fieldset[contains(@class,'o_sel_assessment_item_additional_" + feedbackPosition + "')]" + + "//div[contains(@class,'o_condition_" + conditionPosition + "')]"; - String feedbackPrefix = "//fieldset[contains(@class,'o_sel_assessment_item_additional_" + feedbackPosition + "')]"; - String conditionPrefix = "//div[contains(@class,'o_condition')][" + conditionPosition + "]"; - - By conditionBy = By.xpath(feedbackPrefix + conditionPrefix); + By conditionBy = By.xpath(conditionPrefix); OOGraphene.waitElement(conditionBy, browser); - By variableBy = By.xpath(feedbackPrefix + conditionPrefix + "//select[contains(@id,'o_fiovar_')]"); + By variableBy = By.xpath(conditionPrefix + "//select[contains(@id,'o_fiovar_')]"); WebElement variableEl = browser.findElement(variableBy); new Select(variableEl).selectByValue(variable.name()); OOGraphene.waitBusy(browser); - By operatorBy = By.xpath(feedbackPrefix + conditionPrefix + "//select[contains(@id,'o_fioope_')]"); + By operatorBy = By.xpath(conditionPrefix + "//select[contains(@id,'o_fioope_')]"); WebElement operatorEl = browser.findElement(operatorBy); new Select(operatorEl).selectByValue(operator.name()); if(variable == Variable.attempts || variable == Variable.score) { - By valueBy = By.xpath(feedbackPrefix + conditionPrefix + "//input[@type='text']"); + By valueBy = By.xpath(conditionPrefix + "//input[@type='text']"); WebElement valueEl = browser.findElement(valueBy); valueEl.clear(); valueEl.sendKeys(value); + } else if(variable == Variable.response) { + By answerBy = By.xpath(conditionPrefix + "//select[contains(@id,'o_fioans_')]"); + WebElement answerEl = browser.findElement(answerBy); + new Select(answerEl).selectByVisibleText(value); } - + return this; + } + + public QTI21FeedbacksEditorPage addCondition(int feedbackPosition, int conditionPosition) { + String conditionXpath = "//fieldset[contains(@class,'o_sel_assessment_item_additional_" + feedbackPosition + "')]" + + "//div[contains(@class,'o_condition_" + conditionPosition + "')]" + + "//a[contains(@class,'btn-default')][i[contains(@class,'o_icon_add')]]"; + + By addConditionBy = By.xpath(conditionXpath); + OOGraphene.waitElement(addConditionBy, browser); + browser.findElement(addConditionBy).click(); + OOGraphene.waitBusy(browser); return this; } diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21HotspotEditorPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21HotspotEditorPage.java index 67bf32b8e9b..faed98382a9 100644 --- a/src/test/java/org/olat/selenium/page/qti/QTI21HotspotEditorPage.java +++ b/src/test/java/org/olat/selenium/page/qti/QTI21HotspotEditorPage.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.selenium.page.qti; import java.io.File; 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 f29dcc31449..e4636d51693 100644 --- a/src/test/java/org/olat/selenium/page/qti/QTI21Page.java +++ b/src/test/java/org/olat/selenium/page/qti/QTI21Page.java @@ -285,6 +285,12 @@ public class QTI21Page { return this; } + public QTI21Page assertNoFeedback(String title) { + By feedbackBy = By.xpath("//div[contains(@class,'modalFeedback')]/h4[contains(text(),'" + title + "')]"); + OOGraphene.waitElementDisappears(feedbackBy, 5, browser); + return this; + } + /** * Check that there are no feedbacks visible. * -- GitLab