diff --git a/src/main/java/org/olat/course/condition/_content/defaultaccessedit.html b/src/main/java/org/olat/course/condition/_content/defaultaccessedit.html index 939f4590fff03cd5b9785b7083d2ed1a02d222b3..1981979ba8c8da69134f603de9ab751825f75d33 100644 --- a/src/main/java/org/olat/course/condition/_content/defaultaccessedit.html +++ b/src/main/java/org/olat/course/condition/_content/defaultaccessedit.html @@ -4,7 +4,7 @@ $r.render("defaultAccessConditionView") </fieldset> #if($renderPW) - <fieldset> + <fieldset class="o_sel_course_node_password_config"> <legend>$r.contextHelpWithWrapper("org.olat.course.condition.additionalconditions","ced-password.html","help.hover.password") $r.translate("password.field")</legend> $r.render("pwcond") diff --git a/src/main/java/org/olat/course/condition/additionalconditions/PasswordConditionEditController.java b/src/main/java/org/olat/course/condition/additionalconditions/PasswordConditionEditController.java index 7e67970675139f1523a817753991d5d93b21cfaa..f1f9c36a2463ec5552587a8a1c3a00a9d77e345d 100644 --- a/src/main/java/org/olat/course/condition/additionalconditions/PasswordConditionEditController.java +++ b/src/main/java/org/olat/course/condition/additionalconditions/PasswordConditionEditController.java @@ -58,9 +58,11 @@ public class PasswordConditionEditController extends FormBasicController { protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { passwordSwitch = uifactory.addCheckboxesHorizontal("password.field", "password.field", formLayout, new String[]{"ison"}, new String[]{ "" }); passwordSwitch.addActionListener(FormEvent.ONCHANGE); + passwordSwitch.setElementCssClass("o_sel_course_password_condition_switch"); passwordField = uifactory.addTextElement("passwordField", null, 30, "", formLayout); passwordField.setExampleKey("password.example", null); + passwordField.setElementCssClass("o_sel_course_password_condition_value"); passwordField.showError(false); if (condition != null && StringHelper.containsNonWhitespace(condition.getPassword())) { passwordSwitch.select("ison", true); diff --git a/src/main/java/org/olat/course/condition/additionalconditions/PasswordVerificationController.java b/src/main/java/org/olat/course/condition/additionalconditions/PasswordVerificationController.java index 0ce78f44d06199bc02bea8cbfe740369abe58744..2964b84eb6bfe50ab26fc32b20daa6450b4ad7eb 100644 --- a/src/main/java/org/olat/course/condition/additionalconditions/PasswordVerificationController.java +++ b/src/main/java/org/olat/course/condition/additionalconditions/PasswordVerificationController.java @@ -58,8 +58,10 @@ public class PasswordVerificationController extends FormBasicController { protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { setFormTitle("password.title"); setFormWarning("password.inputorder"); + formLayout.setElementCssClass("o_sel_course_password_form"); pwElement = uifactory.addPasswordElement("password.field", "password.field", 255, "", formLayout); + pwElement.setElementCssClass("o_sel_course_password"); pwElement.setMandatory(true); pwElement.setDisplaySize(30); diff --git a/src/main/java/org/olat/course/editor/EditorMainController.java b/src/main/java/org/olat/course/editor/EditorMainController.java index 848e133b72b3dd5510905da0d932cf22a2265330..cce7aced7b589f59271b3ca2ba78c21ea6997e04 100644 --- a/src/main/java/org/olat/course/editor/EditorMainController.java +++ b/src/main/java/org/olat/course/editor/EditorMainController.java @@ -299,10 +299,12 @@ public class EditorMainController extends MainLayoutBasicController implements G nodeTools = new Dropdown("insertNodes", NLS_COMMAND_DELETENODE_HEADER, false, getTranslator()); nodeTools.setIconCSS("o_icon o_icon_customize"); + nodeTools.setElementCssClass("o_sel_course_editor_change_node"); deleteNodeLink = LinkFactory.createToolLink(CMD_DELNODE, translate(NLS_COMMAND_DELETENODE), this, "o_icon_delete_item"); nodeTools.addComponent(deleteNodeLink); moveNodeLink = LinkFactory.createToolLink(CMD_MOVENODE, translate(NLS_COMMAND_MOVENODE), this, "o_icon_move"); + moveNodeLink.setElementCssClass("o_sel_course_editor_move_node"); nodeTools.addComponent(moveNodeLink); copyNodeLink = LinkFactory.createToolLink(CMD_COPYNODE, translate(NLS_COMMAND_COPYNODE), this, "o_icon_copy"); nodeTools.addComponent(copyNodeLink); diff --git a/src/test/java/org/olat/selenium/CourseTest.java b/src/test/java/org/olat/selenium/CourseTest.java index 1ec30ac526332c93a19c1e5a507ea8b1c5dd9414..3f8d27191887c6ec9c0890213020d4afbf095661 100644 --- a/src/test/java/org/olat/selenium/CourseTest.java +++ b/src/test/java/org/olat/selenium/CourseTest.java @@ -45,6 +45,7 @@ import org.olat.selenium.page.NavigationPage; import org.olat.selenium.page.Participant; import org.olat.selenium.page.User; import org.olat.selenium.page.core.BookingPage; +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.CourseWizardPage; @@ -1006,4 +1007,125 @@ public class CourseTest { .assertLogList(kanu, reminderTitle, true) .assertLogList(author, reminderTitle, false); } + + /** + * An author creates a course with a structure element. The structure + * element is password protected. Under it, there is an info node. The + * course is published and a first user search the course, go to the + * structure element, give the password and see the info node. A second + * user grabs the rest url of the structure node, use it, give the password + * and go to the info node. + * + * @param loginPage + * @throws IOException + * @throws URISyntaxException + */ + @Test + @RunAsClient + public void coursePassword(@InitialPage LoginPage loginPage, + @Drone @Participant WebDriver kanuBrowser, + @Drone @User WebDriver ryomouBrowser) + throws IOException, URISyntaxException { + UserVO author = new UserRestClient(deploymentUrl).createAuthor(); + UserVO kanu = new UserRestClient(deploymentUrl).createRandomUser("Kanu"); + UserVO ryomou = new UserRestClient(deploymentUrl).createRandomUser("Ryomou"); + loginPage.loginAs(author.getLogin(), author.getPassword()); + + //go to authoring + AuthoringEnvPage authoringEnv = navBar + .assertOnNavigationPage() + .openAuthoringEnvironment(); + + String title = "Password-me-" + UUID.randomUUID(); + //create course + authoringEnv + .openCreateDropDown() + .clickCreate(ResourceType.course) + .fillCreateForm(title) + .assertOnGeneralTab() + .save(); + + String infoTitle = "Info - " + UUID.randomUUID(); + String structureTitle = "St - " + UUID.randomUUID(); + + //open course editor, create a structure node + CoursePageFragment course = new CoursePageFragment(browser); + CourseEditorPageFragment editor = course + .openToolsMenu() + .edit(); + editor + .createNode("st") + .nodeTitle(structureTitle); + String courseInfoUrl = editor.getRestUrl(); + editor + //create an info node and move it under the structure node + .createNode("info") + .nodeTitle(infoTitle) + .moveUnder(structureTitle) + .selectNode(structureTitle) + //select and set password on structure node + .selectTabPassword() + .setPassword("super secret") + //publish + .autoPublish() + .accessConfiguration() + .setUserAccess(UserAccess.registred) + .clickToolbarBack(); + + MenuTreePageFragment courseTree = course + .clickTree() + .selectWithTitle(structureTitle.substring(0, 20)); + course + .assertOnPassword() + .enterPassword("super secret"); + courseTree + .selectWithTitle(infoTitle.substring(0, 20)); + course + .assertOnTitle(infoTitle); + + //First user go to the course + LoginPage kanuLoginPage = LoginPage.getLoginPage(kanuBrowser, deploymentUrl); + kanuLoginPage + .loginAs(kanu.getLogin(), kanu.getPassword()) + .resume(); + + NavigationPage kanuNavBar = new NavigationPage(kanuBrowser); + kanuNavBar + .openMyCourses() + .openSearch() + .extendedSearch(title) + .select(title) + .start(); + + //go to the structure, give the password + CoursePageFragment kanuCourse = new CoursePageFragment(kanuBrowser); + MenuTreePageFragment kanuTree = kanuCourse + .clickTree() + .selectWithTitle(structureTitle.substring(0, 20)); + kanuCourse + .assertOnPassword() + .enterPassword("super secret"); + kanuTree + .selectWithTitle(infoTitle.substring(0, 20)); + kanuCourse + .assertOnTitle(infoTitle); + + //Second user use the rest url + LoginPage ryomouLoginPage = LoginPage.getLoginPage(ryomouBrowser, new URL(courseInfoUrl)); + ryomouLoginPage + .loginAs(ryomou.getLogin(), ryomou.getPassword()) + .resume(); + + CoursePageFragment ryomouCourse = new CoursePageFragment(ryomouBrowser); + ryomouCourse + .assertOnPassword() + .enterPassword("super secret"); + //find the secret info course element + ryomouCourse + .clickTree() + .selectWithTitle(structureTitle.substring(0, 20)) + .selectWithTitle(infoTitle.substring(0, 20)); + ryomouCourse + .assertOnTitle(infoTitle); + } } diff --git a/src/test/java/org/olat/selenium/page/course/CourseEditorPageFragment.java b/src/test/java/org/olat/selenium/page/course/CourseEditorPageFragment.java index 82ab18128ba14935eeb633430a2a1e1803a5efa2..cdd8e6f71e56f138ad15067eec4d1f6ced54feda 100644 --- a/src/test/java/org/olat/selenium/page/course/CourseEditorPageFragment.java +++ b/src/test/java/org/olat/selenium/page/course/CourseEditorPageFragment.java @@ -58,6 +58,9 @@ public class CourseEditorPageFragment { public static final By chooseScormButton = By.className("o_sel_scorm_choose_repofile"); public static final By choosePortfolioButton = By.className("o_sel_map_choose_repofile"); + public static final By changeNodeToolsMenu = By.cssSelector("ul.o_sel_course_editor_change_node"); + public static final By changeNodeToolsMenuCaret = By.cssSelector("a.o_sel_course_editor_change_node"); + public static final List<By> chooseRepoEntriesButtonList = new ArrayList<>(); static { @@ -96,6 +99,29 @@ public class CourseEditorPageFragment { return this; } + /** + * Select the tab where the password setting are + * @return + */ + public CourseEditorPageFragment selectTabPassword() { + By passwordTabBy = By.cssSelector("fieldset.o_sel_course_node_password_config"); + return selectTab(passwordTabBy); + } + + public CourseEditorPageFragment setPassword(String password) { + By switchBy = By.cssSelector(".o_sel_course_password_condition_switch input[type='checkbox']"); + browser.findElement(switchBy).click(); + OOGraphene.waitBusy(browser); + + By passwordBy = By.cssSelector(".o_sel_course_password_condition_value input[type='text']"); + browser.findElement(passwordBy).sendKeys(password); + + By saveBy = By.cssSelector("fieldset.o_sel_course_node_password_config button.btn-primary"); + browser.findElement(saveBy).click(); + OOGraphene.waitBusy(browser); + return this; + } + /** * Select the tab score in a structure node. * @@ -209,6 +235,66 @@ public class CourseEditorPageFragment { return this; } + public String getRestUrl() { + By openerBy = By.cssSelector("a.o_opener"); + browser.findElement(openerBy).click(); + + By urlBy = By.cssSelector("div.o_copy_code"); + OOGraphene.waitElement(urlBy, browser); + + String url = null; + List<WebElement> urlEls = browser.findElements(urlBy); + for(WebElement urlEl:urlEls) { + String text = urlEl.getText(); + if(text.contains("http")) { + url = text.trim(); + break; + } + } + Assert.assertNotNull(url); + return url; + } + + public CourseEditorPageFragment moveUnder(String targetNodeTitle) { + if(!browser.findElement(changeNodeToolsMenu).isDisplayed()) { + openChangeNodeToolsMenu(); + } + By changeNodeLinkBy = By.cssSelector("a.o_sel_course_editor_move_node"); + browser.findElement(changeNodeLinkBy).click(); + OOGraphene.waitBusy(browser); + + By targetNodeBy = By.xpath("//div[contains(@class,'o_tree_insert_tool')]//a[contains(@title,'" + targetNodeTitle + "')]"); + browser.findElement(targetNodeBy).click(); + OOGraphene.waitBusy(browser); + + By underBy = By.xpath("//div[contains(@class,'o_tree_insert_tool')]//a[i[contains(@class,'o_icon_node_under')]]"); + browser.findElement(underBy).click(); + OOGraphene.waitBusy(browser); + + By saveBy = By.cssSelector("div.modal-content div.o_button_group a.btn-primary"); + browser.findElement(saveBy).click(); + OOGraphene.waitBusy(browser); + OOGraphene.waitAndCloseBlueMessageWindow(browser); + return this; + } + + public CourseEditorPageFragment selectNode(String nodeTitle) { + By targetNodeBy = By.xpath("//div[contains(@class,'o_editor_menu')]//a[contains(@title,'" + nodeTitle + "')]"); + browser.findElement(targetNodeBy).click(); + OOGraphene.waitBusy(browser); + return this; + } + + /** + * Open the tools drop-down + * @return + */ + public CourseEditorPageFragment openChangeNodeToolsMenu() { + browser.findElement(changeNodeToolsMenuCaret).click(); + OOGraphene.waitElement(changeNodeToolsMenu, browser); + return this; + } + /** * Loop the tabs of the course element configuration to find * the one with a button to select a repository entry. 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 46a993cbc66fc852af491f7b2ac66b1ed3461255..27837161f916a9d080d75122c852c145c2ba4815 100644 --- a/src/test/java/org/olat/selenium/page/course/CoursePageFragment.java +++ b/src/test/java/org/olat/selenium/page/course/CoursePageFragment.java @@ -98,6 +98,27 @@ public class CoursePageFragment { return this; } + /** + * Assert if the password field is displayed. + * @return + */ + public CoursePageFragment assertOnPassword() { + By passwordBy = By.cssSelector(".o_sel_course_password_form input[type='password']"); + List<WebElement> passwordEls = browser.findElements(passwordBy); + Assert.assertEquals(1, passwordEls.size()); + return this; + } + + public CoursePageFragment enterPassword(String password) { + By passwordBy = By.cssSelector(".o_sel_course_password_form .o_sel_course_password input[type='password']"); + browser.findElement(passwordBy).sendKeys(password); + + By enterBy = By.cssSelector(".o_sel_course_password_form button.btn-primary"); + browser.findElement(enterBy).click(); + OOGraphene.waitBusy(browser); + return this; + } + /** * Click the first element of the menu tree * @return