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