diff --git a/src/test/java/org/olat/selenium/AssessmentTest.java b/src/test/java/org/olat/selenium/AssessmentTest.java index 8ba2b269c9e0e56bc8f23ef9771f713a31eab17f..38ffea5ff450da2f267170b6ec3f7cc03ee0be05 100644 --- a/src/test/java/org/olat/selenium/AssessmentTest.java +++ b/src/test/java/org/olat/selenium/AssessmentTest.java @@ -34,7 +34,7 @@ import org.jboss.arquillian.test.api.ArquillianResource; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; -import org.olat.course.learningpath.FullyAssessedTrigger; +import org.olat.repository.RepositoryEntryStatusEnum; import org.olat.repository.model.SingleRoleRepositoryEntrySecurity.Role; import org.olat.selenium.page.LoginPage; import org.olat.selenium.page.NavigationPage; @@ -55,6 +55,8 @@ import org.olat.selenium.page.course.MembersPage; import org.olat.selenium.page.graphene.OOGraphene; import org.olat.selenium.page.group.GroupPage; import org.olat.selenium.page.qti.QTI12Page; +import org.olat.selenium.page.qti.QTI21Page; +import org.olat.selenium.page.repository.RepositoryEditDescriptionPage; import org.olat.selenium.page.repository.ScormPage; import org.olat.selenium.page.repository.UserAccess; import org.olat.selenium.page.user.UserToolsPage; @@ -807,6 +809,154 @@ public class AssessmentTest extends Deployments { .selectStatementSegment() .assertOnCourseDetails(testNodeTitle, true); } + + /** + * This tests a course with cascading rules and expert rules + * to calculate if the course is passed and generate a + * certificate. + * + * @param loginPage + */ + @Test + @RunAsClient + public void certificatesGeneratedWithCascadingRules() + throws IOException, URISyntaxException { + + UserVO author = new UserRestClient(deploymentUrl).createRandomAuthor(); + UserVO participant1 = new UserRestClient(deploymentUrl).createRandomUser("Ryomou"); + UserVO participant2 = new UserRestClient(deploymentUrl).createRandomUser("Rei"); + + LoginPage loginPage = LoginPage.load(browser, deploymentUrl); + loginPage.loginAs(author.getLogin(), author.getPassword()); + + URL zipUrl = JunitTestHelper.class.getResource("file_resources/course_certificates_exrules.zip"); + File zipFile = new File(zipUrl.toURI()); + //go the authoring environment to import our course + String zipTitle = "Certif - " + UUID.randomUUID(); + NavigationPage navBar = NavigationPage.load(browser); + navBar + .openAuthoringEnvironment() + .uploadResource(zipTitle, zipFile); + + // publish the course + new RepositoryEditDescriptionPage(browser) + .clickToolbarBack(); + CoursePageFragment course = CoursePageFragment.getCourse(browser) + .edit() + .autoPublish(); + + // add a participant + MembersPage members = course + .members(); + members + .importMembers() + .setMembers(participant1, participant2) + .nextUsers() + .nextOverview() + .nextPermissions() + .finish(); + members + .clickToolbarBack(); + + course + .settings() + .accessConfiguration() + .setUserAccess(UserAccess.registred) + .save() + .clickToolbarBack(); + + course + .changeStatus(RepositoryEntryStatusEnum.published); + + //log out + new UserToolsPage(browser) + .logout(); + + // participant log in and go directly to the first test + LoginPage participantLoginPage = LoginPage.load(browser, deploymentUrl); + + participantLoginPage + .loginAs(participant1.getLogin(), participant1.getPassword()) + .resume(); + + //open the course + NavigationPage participantNavBar = NavigationPage.load(browser); + participantNavBar + .openMyCourses() + .select(zipTitle); + + //go to the test + CoursePageFragment certificationCourse = new CoursePageFragment(browser); + certificationCourse + .clickTree() + .assertWithTitleSelected("Test 1"); + //pass the test + QTI21Page.getQTI21Page(browser) + .passE4() + .assertOnCourseAssessmentTestScore(4); + + OOGraphene.waitingALittleLonger(); + + //open the efficiency statements + String certificateTitle = "Certificates" + zipTitle; + UserToolsPage participantUserTools = new UserToolsPage(browser); + participantUserTools + .openUserToolsMenu() + .openMyEfficiencyStatement() + .assertOnEfficiencyStatmentPage() + .assertOnCertificateAndStatements(certificateTitle) + .selectStatement(certificateTitle) + .selectStatementSegment() + .assertOnCourseDetails("CertificatesCert", true) + .assertOnCourseDetails("Struktur 1", true) + .assertOnCourseDetails("Test 1", true); + + //log out + new UserToolsPage(browser) + .logout(); + + + // participant 2 log in and go directly to the second test + LoginPage participant2LoginPage = LoginPage.load(browser, deploymentUrl); + + participant2LoginPage + .loginAs(participant2.getLogin(), participant2.getPassword()) + .resume(); + + //open the course + NavigationPage participant2NavBar = NavigationPage.load(browser); + participant2NavBar + .openMyCourses() + .select(zipTitle); + + //go to the test + CoursePageFragment certification2Course = new CoursePageFragment(browser); + certification2Course + .clickTree() + .selectWithTitle("Struktur 3") + .assertWithTitleSelected("Struktur 3") + .assertWithTitle("Test 3") + .selectWithTitle("Test 3"); + //pass the test + QTI21Page.getQTI21Page(browser) + .passE4() + .assertOnCourseAssessmentTestScore(4); + + OOGraphene.waitingALittleLonger(); + + //open the efficiency statements + UserToolsPage participant2UserTools = new UserToolsPage(browser); + participant2UserTools + .openUserToolsMenu() + .openMyEfficiencyStatement() + .assertOnEfficiencyStatmentPage() + .assertOnCertificateAndStatements(certificateTitle) + .selectStatement(certificateTitle) + .selectStatementSegment() + .assertOnCourseDetails("CertificatesCert", true) + .assertOnCourseDetails("Struktur 3", true) + .assertOnCourseDetails("Test 3", true); + } /** * An author create a course with an assessment course element with @@ -2114,107 +2264,4 @@ public class AssessmentTest extends Deployments { .openSolutions() .assertSolution("solution_1.txt"); } - - /** - * This is a degenerated form of task but the case exists. - * An author creates a course (learn path) with a task course - * element. The task is configured to only show the solution, - * and the course element to be passed if the task is done. In - * this case, the participant only need to see the solution to - * get the node done. - * - * @param participantBrowser Browser of the participant - * @throws IOException - * @throws URISyntaxException - */ - @Test - @RunAsClient - public void taskLearnPathSolutionsOnly(@Drone @User WebDriver participantBrowser) - throws IOException, URISyntaxException { - - UserVO author = new UserRestClient(deploymentUrl).createRandomAuthor(); - UserVO participant = new UserRestClient(deploymentUrl).createRandomUser("kanu"); - - LoginPage authorLoginPage = LoginPage.load(browser, deploymentUrl); - authorLoginPage.loginAs(author.getLogin(), author.getPassword()); - - //create a course - String courseTitle = "Course-with-auto-task-" + UUID.randomUUID(); - NavigationPage navBar = NavigationPage.load(browser); - navBar - .openAuthoringEnvironment() - .createCourse(courseTitle, true) - .clickToolbarBack(); - - //create a course element of type Test with the test that we create above - String gtaNodeTitle = "Solution 1"; - CourseEditorPageFragment courseEditor = CoursePageFragment.getCourse(browser) - .edit(); - courseEditor - .createNode("ita") - .nodeTitle(gtaNodeTitle); - - courseEditor - .selectTabLearnPath() - .setCompletionCriterion(FullyAssessedTrigger.statusDone) - .save(); - - GroupTaskConfigurationPage gtaConfig = new GroupTaskConfigurationPage(browser); - gtaConfig - .selectWorkflow() - .enableAssignment(false) - .enableSubmission(false) - .enableReview(false) - .enableGrading(false) - .saveWorkflow(); - - URL solutionUrl = JunitTestHelper.class.getResource("file_resources/solution_1.txt"); - File solutionFile = new File(solutionUrl.toURI()); - gtaConfig - .selectSolution() - .uploadSolution("A possible solution", solutionFile); - - courseEditor - .publish() - .quickPublish(UserAccess.membersOnly); - - - MembersPage membersPage = courseEditor - .clickToolbarBack() - .members(); - - membersPage - .importMembers() - .setMembers(participant) - .nextUsers() - .nextOverview() - .nextPermissions() - .finish(); - - //Participant log in - LoginPage participantLoginPage = LoginPage.load(participantBrowser, deploymentUrl); - participantLoginPage - .loginAs(participant) - .resume(); - - //open the course - NavigationPage participantNavBar = NavigationPage.load(participantBrowser); - participantNavBar - .openMyCourses() - .select(courseTitle); - - //go to the group task - CoursePageFragment participantCourse = new CoursePageFragment(participantBrowser); - participantCourse - .clickTree() - .selectWithTitle(gtaNodeTitle); - - GroupTaskPage participantTask = new GroupTaskPage(participantBrowser); - participantTask - .openSolutions() - .assertSolution("solution_1.txt"); - // seeing the solution got the job done - participantCourse - .assertOnLearnPathNodeDone(gtaNodeTitle); - } } diff --git a/src/test/java/org/olat/selenium/CourseLearnPathTest.java b/src/test/java/org/olat/selenium/CourseLearnPathTest.java new file mode 100644 index 0000000000000000000000000000000000000000..adcc2e7b1042927d8c173f18d010d680c443fa96 --- /dev/null +++ b/src/test/java/org/olat/selenium/CourseLearnPathTest.java @@ -0,0 +1,295 @@ +/** + * <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; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.UUID; + +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.drone.api.annotation.Drone; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.olat.course.learningpath.FullyAssessedTrigger; +import org.olat.selenium.page.LoginPage; +import org.olat.selenium.page.NavigationPage; +import org.olat.selenium.page.User; +import org.olat.selenium.page.core.MenuTreePageFragment; +import org.olat.selenium.page.course.CourseEditorPageFragment; +import org.olat.selenium.page.course.CoursePageFragment; +import org.olat.selenium.page.course.GroupTaskConfigurationPage; +import org.olat.selenium.page.course.GroupTaskPage; +import org.olat.selenium.page.course.MembersPage; +import org.olat.selenium.page.course.SinglePageConfigurationPage; +import org.olat.selenium.page.repository.UserAccess; +import org.olat.test.JunitTestHelper; +import org.olat.test.rest.UserRestClient; +import org.olat.user.restapi.UserVO; +import org.openqa.selenium.WebDriver; + +/** + * Test specifically features of the course in learn path mode. + * + * Initial date: 8 juin 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@RunWith(Arquillian.class) +public class CourseLearnPathTest extends Deployments { + + @Drone + private WebDriver browser; + @ArquillianResource + private URL deploymentUrl; + + + /** + * This is a course with learn path, three course elements, + * two to confirm, one to visit. The author creates the course + * and a user play it until 100% done. + * + * @param participantBrowser Browser of the participant + * @throws IOException + * @throws URISyntaxException + */ + @Test + @RunAsClient + public void learnPathConfirmationAndVisitedFlow(@Drone @User WebDriver participantBrowser) + throws IOException, URISyntaxException { + + UserVO author = new UserRestClient(deploymentUrl).createRandomAuthor(); + UserVO participant = new UserRestClient(deploymentUrl).createRandomUser("kanu"); + + LoginPage authorLoginPage = LoginPage.load(browser, deploymentUrl); + authorLoginPage.loginAs(author.getLogin(), author.getPassword()); + + //create a course + String courseTitle = "Course-with-auto-task-" + UUID.randomUUID(); + NavigationPage navBar = NavigationPage.load(browser); + navBar + .openAuthoringEnvironment() + .createCourse(courseTitle, true) + .clickToolbarBack(); + + //create a course element of type single page + String firstNodeTitle = "First page"; + CourseEditorPageFragment courseEditor = CoursePageFragment.getCourse(browser) + .edit(); + courseEditor + .createNode("sp") + .nodeTitle(firstNodeTitle); + + String content = "A new single page with some content"; + SinglePageConfigurationPage spConfiguration = new SinglePageConfigurationPage(browser); + spConfiguration + .selectConfiguration() + .newDefaultPage(content) + .assertOnPreview(); + + courseEditor + .selectTabLearnPath() + .setCompletionCriterion(FullyAssessedTrigger.confirmed) + .save(); + + // create a second element, a forum + String forumNodeTitle = "Forum discussion"; + courseEditor + .createNode("fo") + .nodeTitle(forumNodeTitle) + .selectTabLearnPath() + .setCompletionCriterion(FullyAssessedTrigger.confirmed) + .save(); + + // create a third element, an info message + String infosNodeTitle = "Informations"; + courseEditor + .createNode("info") + .nodeTitle(infosNodeTitle) + .selectTabLearnPath() + .setCompletionCriterion(FullyAssessedTrigger.nodeVisited) + .save(); + + courseEditor + .autoPublish() + .publish() + .members() + .importMembers() + .setMembers(participant) + .nextUsers() + .nextOverview() + .nextPermissions() + .finish(); + + //Participant log in + LoginPage participantLoginPage = LoginPage.load(participantBrowser, deploymentUrl); + participantLoginPage + .loginAs(participant) + .resume(); + + //open the course + NavigationPage participantNavBar = NavigationPage.load(participantBrowser); + participantNavBar + .openMyCourses() + .select(courseTitle); + + //go to the group task + CoursePageFragment participantCourse = new CoursePageFragment(participantBrowser); + MenuTreePageFragment menuTree = participantCourse + .clickTree() + .selectWithTitle(firstNodeTitle); + participantCourse + .assertOnLearnPathNodeReady(firstNodeTitle) + .confirmNode() + .assertOnLearnPathNodeDone(firstNodeTitle) + .assertOnLearnPathNodeInProgress(courseTitle) + .assertOnLearnPathNodeReady(forumNodeTitle) + .assertOnLearnPathNodeNotAccessible(infosNodeTitle); + + // confirm second node + menuTree + .selectWithTitle(forumNodeTitle) + .assertWithTitleSelected(forumNodeTitle); + participantCourse + .confirmNode() + .assertOnLearnPathNodeDone(forumNodeTitle) + .assertOnLearnPathNodeInProgress(courseTitle) + .assertOnLearnPathNodeReady(infosNodeTitle); + + // see the third node + menuTree + .selectWithTitle(infosNodeTitle) + .assertWithTitleSelected(infosNodeTitle); + participantCourse + .assertOnLearnPathNodeDone(infosNodeTitle) + .assertOnLearnPathNodeDone(firstNodeTitle) + .assertOnLearnPathNodeDone(forumNodeTitle) + .assertOnLearnPathNodeDone(courseTitle) + .assertOnLearnPathPercent(100); + } + + + /** + * This is a degenerated form of task but the case exists. + * An author creates a course (learn path) with a task course + * element. The task is configured to only show the solution, + * and the course element to be passed if the task is done. In + * this case, the participant only need to see the solution to + * get the node done. + * + * @param participantBrowser Browser of the participant + * @throws IOException + * @throws URISyntaxException + */ + @Test + @RunAsClient + public void taskLearnPathSolutionsOnly(@Drone @User WebDriver participantBrowser) + throws IOException, URISyntaxException { + + UserVO author = new UserRestClient(deploymentUrl).createRandomAuthor(); + UserVO participant = new UserRestClient(deploymentUrl).createRandomUser("kanu"); + + LoginPage authorLoginPage = LoginPage.load(browser, deploymentUrl); + authorLoginPage.loginAs(author.getLogin(), author.getPassword()); + + //create a course + String courseTitle = "Course-with-auto-task-" + UUID.randomUUID(); + NavigationPage navBar = NavigationPage.load(browser); + navBar + .openAuthoringEnvironment() + .createCourse(courseTitle, true) + .clickToolbarBack(); + + //create a course element of type Test with the test that we create above + String gtaNodeTitle = "Solution 1"; + CourseEditorPageFragment courseEditor = CoursePageFragment.getCourse(browser) + .edit(); + courseEditor + .createNode("ita") + .nodeTitle(gtaNodeTitle); + + courseEditor + .selectTabLearnPath() + .setCompletionCriterion(FullyAssessedTrigger.statusDone) + .save(); + + GroupTaskConfigurationPage gtaConfig = new GroupTaskConfigurationPage(browser); + gtaConfig + .selectWorkflow() + .enableAssignment(false) + .enableSubmission(false) + .enableReview(false) + .enableGrading(false) + .saveWorkflow(); + + URL solutionUrl = JunitTestHelper.class.getResource("file_resources/solution_1.txt"); + File solutionFile = new File(solutionUrl.toURI()); + gtaConfig + .selectSolution() + .uploadSolution("A possible solution", solutionFile); + + courseEditor + .publish() + .quickPublish(UserAccess.membersOnly); + + + MembersPage membersPage = courseEditor + .clickToolbarBack() + .members(); + + membersPage + .importMembers() + .setMembers(participant) + .nextUsers() + .nextOverview() + .nextPermissions() + .finish(); + + //Participant log in + LoginPage participantLoginPage = LoginPage.load(participantBrowser, deploymentUrl); + participantLoginPage + .loginAs(participant) + .resume(); + + //open the course + NavigationPage participantNavBar = NavigationPage.load(participantBrowser); + participantNavBar + .openMyCourses() + .select(courseTitle); + + //go to the group task + CoursePageFragment participantCourse = new CoursePageFragment(participantBrowser); + participantCourse + .clickTree() + .selectWithTitle(gtaNodeTitle); + + GroupTaskPage participantTask = new GroupTaskPage(participantBrowser); + participantTask + .openSolutions() + .assertSolution("solution_1.txt"); + // seeing the solution got the job done + participantCourse + .assertOnLearnPathNodeDone(gtaNodeTitle); + } + +} diff --git a/src/test/java/org/olat/selenium/CourseTest.java b/src/test/java/org/olat/selenium/CourseTest.java index d2d6404c62de57dbf4eb0365950134330bceb890..b107f1c54c669a204355ca7bc49c14bfdddad37f 100644 --- a/src/test/java/org/olat/selenium/CourseTest.java +++ b/src/test/java/org/olat/selenium/CourseTest.java @@ -1738,4 +1738,5 @@ public class CourseTest extends Deployments { .uploadResource(zipTitle, zipFile) .assertOnResourceType(); } + } diff --git a/src/test/java/org/olat/selenium/page/course/CoursePageFragment.java b/src/test/java/org/olat/selenium/page/course/CoursePageFragment.java index 5700988b382a4be8202b1b59eb77b7d0f1c2c0fb..d229a6416a04bbf223700dfcb385cc265c0fb43f 100644 --- a/src/test/java/org/olat/selenium/page/course/CoursePageFragment.java +++ b/src/test/java/org/olat/selenium/page/course/CoursePageFragment.java @@ -93,11 +93,36 @@ public class CoursePageFragment { } public CoursePageFragment assertOnLearnPathNodeDone(String nodeTitle) { - By nodeDoneBy = By.xpath("//div[contains(@class,'o_lp_tree')]//span[contains(@class,'o_tree_l1')]/a[i[contains(@class,'o_lp_done')]][span[text()[contains(.,'" + nodeTitle + "')]]]"); + return assertOnLearnPathNodeStatus(nodeTitle, "o_lp_done"); + } + + public CoursePageFragment assertOnLearnPathNodeReady(String nodeTitle) { + return assertOnLearnPathNodeStatus(nodeTitle, "o_lp_ready"); + } + + public CoursePageFragment assertOnLearnPathNodeInProgress(String nodeTitle) { + return assertOnLearnPathNodeStatus(nodeTitle, "o_lp_in_progress"); + } + + public CoursePageFragment assertOnLearnPathNodeNotAccessible(String nodeTitle) { + return assertOnLearnPathNodeStatus(nodeTitle, "o_lp_not_accessible"); + } + + private CoursePageFragment assertOnLearnPathNodeStatus(String nodeTitle, String statusCssClass) { + if(nodeTitle.length() > 20) { + nodeTitle = nodeTitle.substring(0, 20); + } + By nodeDoneBy = By.xpath("//div[contains(@class,'o_lp_tree')]//span[contains(@class,'o_tree_l')]/a[i[contains(@class,'" + statusCssClass + "')]][span[text()[contains(.,'" + nodeTitle + "')]]]"); OOGraphene.waitElement(nodeDoneBy, browser); return this; } + public CoursePageFragment assertOnLearnPathPercent(int percent) { + By percentageBy = By.xpath("//span[contains(@class,'o_progress')]//span[contains(@class,'percentage')]//span[text()[contains(.,'" + percent + "%')]]"); + OOGraphene.waitElement(percentageBy, browser); + return this; + } + /** * Assert if the password field is displayed. * @return @@ -148,6 +173,21 @@ public class CoursePageFragment { return menuTree.selectRoot(); } + /** + * + * @return + */ + public CoursePageFragment confirmNode() { + By confirmationBy = By.cssSelector("div.o_course_pagination div.o_confirm a.btn"); + OOGraphene.waitElement(confirmationBy, browser); + browser.findElement(confirmationBy).click(); + OOGraphene.waitBusy(browser); + + By confirmedBy = By.cssSelector("div.o_course_pagination div.o_confirm a.btn.o_course_pagination_status_done"); + OOGraphene.waitElement(confirmedBy, browser); + return this; + } + /** * Open the tools drop-down * @return @@ -276,6 +316,11 @@ public class CoursePageFragment { return new BookingPage(browser); } + /** + * Set the course status to published. + * + * @return Itself + */ public CoursePageFragment publish() { return changeStatus(RepositoryEntryStatusEnum.published); } 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 56be8baa524eb53632c654bdf74b45c676c4f52c..906042a87b1e7c9e5fd92ee152fb6bc94ee2bd1f 100644 --- a/src/test/java/org/olat/selenium/page/qti/QTI21Page.java +++ b/src/test/java/org/olat/selenium/page/qti/QTI21Page.java @@ -117,6 +117,21 @@ public class QTI21Page { return this; } + public QTI21Page passE4() { + start() + .answerSingleChoiceWithParagraph("Correct answer") + .saveAnswer() + .answerMultipleChoice("Correct answer", "The answer is correct") + .saveAnswer() + .answerCorrectKPrim("This answer", "Plus answer") + .answerIncorrectKPrim("Not answer", "Minus answer") + .saveAnswer() + .answerGapText("not", "qtiworks_response_oofibc4c14bfe94a41861fe19c70091182_5_1_RESPONSE_1") + .saveAnswer() + .endTest(); + return this; + } + /** * Check the answer of a single choice. * @param answer The answer @@ -807,7 +822,7 @@ public class QTI21Page { public QTI21Page closeTest() { By closeBy = By.cssSelector("a.o_sel_close_test"); - OOGraphene.waitElement(closeBy, 5, browser); + OOGraphene.waitElement(closeBy, browser); browser.findElement(closeBy).click(); OOGraphene.waitBusy(browser); confirm(); diff --git a/src/test/java/org/olat/test/file_resources/course_certificates_exrules.zip b/src/test/java/org/olat/test/file_resources/course_certificates_exrules.zip new file mode 100644 index 0000000000000000000000000000000000000000..988bde8a23373ac1b09c2bf06d3730f68769d507 Binary files /dev/null and b/src/test/java/org/olat/test/file_resources/course_certificates_exrules.zip differ