From fa48e0fa89ddecead9c41b5fd5eab17fb34b0397 Mon Sep 17 00:00:00 2001 From: srosse <stephane.rosse@frentix.com> Date: Fri, 12 Jun 2020 17:10:24 +0200 Subject: [PATCH] no-jira: update selenium tests with new QTI default --- .../assessment/ui/tool/AssessmentForm.java | 1 + ...nIdentityAssessmentItemListController.java | 2 + .../olat/ims/qti21/ui/report/SearchEvent.java | 19 ++ .../grading/ui/GradersListController.java | 1 + ...epositoryEntryConfigurationController.java | 20 +- .../grading/ui/_content/assignments_list.html | 2 +- .../grading/ui/_content/graders_list.html | 4 +- .../org/olat/selenium/AssessmentTest.java | 1 + .../java/org/olat/selenium/ImsQTI21Test.java | 193 +++++++++++++++++- .../olat/selenium/page/NavigationPage.java | 7 + .../selenium/page/coaching/CoachingPage.java | 55 +++++ .../page/core/MenuTreePageFragment.java | 2 + .../page/course/AssessmentToolPage.java | 25 +++ .../page/qti/QTI21ConfigurationCEPage.java | 14 ++ .../page/qti/QTI21CorrectionPage.java | 37 ++++ .../page/qti/QTI21GradingGradersPage.java | 79 +++++++ .../selenium/page/qti/QTI21GradingPage.java | 58 ++++++ .../page/qti/QTI21GradingSettingsPage.java | 87 ++++++++ .../org/olat/selenium/page/qti/QTI21Page.java | 49 +++-- .../qti21/simple_QTI_21_test.zip | Bin 2477 -> 3098 bytes .../qti21/test_without_feedbacks.zip | Bin 3599 -> 4215 bytes 21 files changed, 631 insertions(+), 25 deletions(-) create mode 100644 src/test/java/org/olat/selenium/page/coaching/CoachingPage.java create mode 100644 src/test/java/org/olat/selenium/page/qti/QTI21GradingGradersPage.java create mode 100644 src/test/java/org/olat/selenium/page/qti/QTI21GradingPage.java create mode 100644 src/test/java/org/olat/selenium/page/qti/QTI21GradingSettingsPage.java diff --git a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentForm.java b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentForm.java index 1b83fbe861d..937829f1286 100644 --- a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentForm.java +++ b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentForm.java @@ -605,6 +605,7 @@ public class AssessmentForm extends FormBasicController { String[] userVisibilityValues = new String[]{ translate("user.visibility.visible"), translate("user.visibility.hidden") }; userVisibility = uifactory.addRadiosHorizontal("user.visibility", "user.visibility", formLayout, userVisibilityKeys, userVisibilityValues); + userVisibility.setElementCssClass("o_sel_assessment_form_visibility"); if(scoreEval.getUserVisible() == null || scoreEval.getUserVisible().booleanValue()) { userVisibility.select(userVisibilityKeys[0], true); } else { diff --git a/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionIdentityAssessmentItemListController.java b/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionIdentityAssessmentItemListController.java index cc3bf1aac44..8c77cc83717 100644 --- a/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionIdentityAssessmentItemListController.java +++ b/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionIdentityAssessmentItemListController.java @@ -204,10 +204,12 @@ public class CorrectionIdentityAssessmentItemListController extends FormBasicCon tableEl = uifactory.addTableElement(getWindowControl(), "table", tableModel, getTranslator(), formLayout); tableEl.setExportEnabled(true); tableEl.setAndLoadPersistedPreferences(ureq, "corr-identity-assessment-item-list"); + tableEl.setElementCssClass("o_sel_correction_assessment_items_list"); backLink = uifactory.addFormLink("back", formLayout, Link.LINK_BACK); if(saveEnabled && !readOnly) { saveButton = uifactory.addFormLink("save.tests", formLayout, Link.BUTTON); + saveButton.setElementCssClass("o_sel_correction_save_test"); } else { backOverviewButton = uifactory.addFormLink("back.overview", formLayout, Link.BUTTON); } diff --git a/src/main/java/org/olat/ims/qti21/ui/report/SearchEvent.java b/src/main/java/org/olat/ims/qti21/ui/report/SearchEvent.java index 3aec5474a63..e85ee0f11ce 100644 --- a/src/main/java/org/olat/ims/qti21/ui/report/SearchEvent.java +++ b/src/main/java/org/olat/ims/qti21/ui/report/SearchEvent.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.ims.qti21.ui.report; import org.olat.core.gui.control.Event; diff --git a/src/main/java/org/olat/modules/grading/ui/GradersListController.java b/src/main/java/org/olat/modules/grading/ui/GradersListController.java index ba2c767ee44..e8acace7a2f 100644 --- a/src/main/java/org/olat/modules/grading/ui/GradersListController.java +++ b/src/main/java/org/olat/modules/grading/ui/GradersListController.java @@ -142,6 +142,7 @@ public class GradersListController extends FormBasicController { protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { addGraderButton = uifactory.addFormLink("add.grader", formLayout, Link.BUTTON); addGraderButton.setIconLeftCSS("o_icon o_icon_add_item"); + addGraderButton.setElementCssClass("o_sel_repo_grading_add_graders"); FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); if(isAdministrativeUser) { diff --git a/src/main/java/org/olat/modules/grading/ui/GradingRepositoryEntryConfigurationController.java b/src/main/java/org/olat/modules/grading/ui/GradingRepositoryEntryConfigurationController.java index 65d32a4d831..5b4090d6c4e 100644 --- a/src/main/java/org/olat/modules/grading/ui/GradingRepositoryEntryConfigurationController.java +++ b/src/main/java/org/olat/modules/grading/ui/GradingRepositoryEntryConfigurationController.java @@ -115,6 +115,7 @@ public class GradingRepositoryEntryConfigurationController extends FormBasicCont @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + formLayout.setElementCssClass("o_sel_repo_grading_settings_form"); String[] onValues = new String[] { translate("on") }; enableEl = uifactory.addCheckboxesHorizontal("grading.repo.enabled", formLayout, onKeys, onValues); @@ -129,6 +130,7 @@ public class GradingRepositoryEntryConfigurationController extends FormBasicCont }; identityVisibilityEl = uifactory.addRadiosHorizontal("anonymous", "configuration.assessed.identity.visibility", formLayout, visibilityKeys, visibilityValues); + identityVisibilityEl.setElementCssClass("o_sel_repo_grading_visibility"); identityVisibilityEl.select(configuration.getIdentityVisibilityEnum().name(), true); String[] notificationTypeValues = new String[] { @@ -136,20 +138,23 @@ public class GradingRepositoryEntryConfigurationController extends FormBasicCont }; notificationTypeEl = uifactory.addRadiosHorizontal("notificationType", "configuration.notification.type", formLayout, notificationTypeKeys, notificationTypeValues); + notificationTypeEl.setElementCssClass("o_sel_repo_grading_notification_type"); notificationTypeEl.select(configuration.getNotificationTypeEnum().name(), true); String period = configuration.getGradingPeriod() == null ? null : configuration.getGradingPeriod().toString(); gradingPeriodEl = uifactory.addTextElement("configuration.grading.period", 6, period, formLayout); - initWorkingDays(gradingPeriodEl); + initWorkingDays(gradingPeriodEl, "o_sel_repo_grading_period"); notificationSubjectEl = uifactory.addTextElement("configuration.notification.subject", 255, configuration.getNotificationSubject(), formLayout); + notificationSubjectEl.setElementCssClass("o_sel_repo_grading_notification_subject"); notificationBodyEl = uifactory.addTextAreaElement("configuration.notification.body", 4, 60, configuration.getNotificationBody(), formLayout); - + notificationBodyEl.setElementCssClass("o_sel_repo_grading_notification_body"); + spacerNotificationsEl = uifactory.addSpacerElement("spacer-notification", formLayout, false); String firstReminder = configuration.getFirstReminder() == null ? null : configuration.getFirstReminder().toString(); firstReminderPeriodEl = uifactory.addTextElement("configuration.first.reminder.period", 6, firstReminder, formLayout); - initWorkingDays(firstReminderPeriodEl); + initWorkingDays(firstReminderPeriodEl, "o_sel_repo_grading_first_reminder_period"); firstReminderSubjectEl = uifactory.addTextElement("configuration.first.reminder.subject", 255, configuration.getFirstReminderSubject(), formLayout); firstReminderBodyEl = uifactory.addTextAreaElement("configuration.first.reminder.body", 4, 60, configuration.getFirstReminderBody(), formLayout); @@ -157,7 +162,9 @@ public class GradingRepositoryEntryConfigurationController extends FormBasicCont String secondReminder = configuration.getSecondReminder() == null ? null : configuration.getSecondReminder().toString(); secondReminderPeriodEl = uifactory.addTextElement("configuration.second.reminder.period", 6, secondReminder, formLayout); - initWorkingDays(secondReminderPeriodEl); + secondReminderPeriodEl.setElementCssClass(""); + + initWorkingDays(secondReminderPeriodEl, "o_sel_repo_grading_second_reminder_period"); secondReminderSubjectEl = uifactory.addTextElement("configuration.second.reminder.subject", 255, configuration.getSecondReminderSubject(), formLayout); secondReminderBodyEl = uifactory.addTextAreaElement("configuration.second.reminder.body", 4, 60, configuration.getSecondReminderBody(), formLayout); @@ -167,6 +174,7 @@ public class GradingRepositoryEntryConfigurationController extends FormBasicCont uifactory.addFormSubmitButton("save", buttonsCont); templatesDropdownEl = uifactory.addDropdownMenu("choose.template.language", null, buttonsCont, getTranslator()); + templatesDropdownEl.setElementCssClass("o_sel_repo_grading_templates"); templatesDropdownEl.setOrientation(DropdownOrientation.right); templatesDropdownEl.setEmbbeded(true); @@ -186,10 +194,10 @@ public class GradingRepositoryEntryConfigurationController extends FormBasicCont } } - private void initWorkingDays(TextElement textEl) { + private void initWorkingDays(TextElement textEl, String elementCss) { textEl.setDisplaySize(6); textEl.setMaxLength(6); - textEl.setElementCssClass("form-inline"); + textEl.setElementCssClass("form-inline ".concat(elementCss)); textEl.setTextAddOn("working.days"); } diff --git a/src/main/java/org/olat/modules/grading/ui/_content/assignments_list.html b/src/main/java/org/olat/modules/grading/ui/_content/assignments_list.html index 11892e3c989..39c00bbbcae 100644 --- a/src/main/java/org/olat/modules/grading/ui/_content/assignments_list.html +++ b/src/main/java/org/olat/modules/grading/ui/_content/assignments_list.html @@ -1,4 +1,4 @@ -<fieldset> +<fieldset class="o_sel_grading_assignments"> #if($r.isTrue($myTitle))<legend>$r.translate("grading.my.assignments.title")</legend>#end <div class="clearfix">$r.render("search")</div> diff --git a/src/main/java/org/olat/modules/grading/ui/_content/graders_list.html b/src/main/java/org/olat/modules/grading/ui/_content/graders_list.html index 4a0685930a1..c2b9c84eb45 100644 --- a/src/main/java/org/olat/modules/grading/ui/_content/graders_list.html +++ b/src/main/java/org/olat/modules/grading/ui/_content/graders_list.html @@ -1,7 +1,9 @@ +<div class="o_sel_graders_list"> #if($r.available("search")) <div class="clearfix">$r.render("search")</div> #end <div class="o_button_group o_button_group_right"> $r.render("add.grader") </div> -$r.render("graders") \ No newline at end of file +$r.render("graders") +</div> \ No newline at end of file diff --git a/src/test/java/org/olat/selenium/AssessmentTest.java b/src/test/java/org/olat/selenium/AssessmentTest.java index 4a1f956d0b4..d9d33af5214 100644 --- a/src/test/java/org/olat/selenium/AssessmentTest.java +++ b/src/test/java/org/olat/selenium/AssessmentTest.java @@ -701,6 +701,7 @@ public class AssessmentTest extends Deployments { .assertOnEfficiencyStatmentPage() .assertOnCertificate(courseTitle); } + /** * An author create a course, set up the root node to make efficiency statement, * add a test, publish it and add a participant. It set the certificate.<br> diff --git a/src/test/java/org/olat/selenium/ImsQTI21Test.java b/src/test/java/org/olat/selenium/ImsQTI21Test.java index aac8c62a4cc..836520906f2 100644 --- a/src/test/java/org/olat/selenium/ImsQTI21Test.java +++ b/src/test/java/org/olat/selenium/ImsQTI21Test.java @@ -30,12 +30,14 @@ import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.arquillian.junit.Arquillian; import org.jboss.arquillian.test.api.ArquillianResource; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.olat.ims.qti21.QTI21AssessmentResultsOptions; import org.olat.repository.model.SingleRoleRepositoryEntrySecurity.Role; import org.olat.selenium.page.LoginPage; import org.olat.selenium.page.NavigationPage; +import org.olat.selenium.page.Participant; import org.olat.selenium.page.User; import org.olat.selenium.page.course.AssessmentToolPage; import org.olat.selenium.page.course.CourseEditorPageFragment; @@ -44,6 +46,7 @@ import org.olat.selenium.page.course.MembersPage; import org.olat.selenium.page.graphene.OOGraphene; import org.olat.selenium.page.qti.QTI21ConfigurationCEPage; import org.olat.selenium.page.qti.QTI21CorrectionPage; +import org.olat.selenium.page.qti.QTI21GradingPage; import org.olat.selenium.page.qti.QTI21Page; import org.olat.selenium.page.repository.UserAccess; import org.olat.selenium.page.user.UserToolsPage; @@ -648,7 +651,7 @@ public class ImsQTI21Test extends Deployments { public void qti21Course_lmsHidden_results() throws IOException, URISyntaxException { - UserVO author = new UserRestClient(deploymentUrl).createAuthor(); + UserVO author = new UserRestClient(deploymentUrl).createRandomAuthor(); LoginPage authorLoginPage = LoginPage.load(browser, deploymentUrl); authorLoginPage.loginAs(author.getLogin(), author.getPassword()); @@ -738,6 +741,7 @@ public class ImsQTI21Test extends Deployments { .closeAssessmentResults() .assertOnCourseAttempts(1) .assertOnCourseAssessmentTestScore(1) + .assertOnCourseAssessmentTestPassed() .assertOnAssessmentResults(); } @@ -888,6 +892,7 @@ public class ImsQTI21Test extends Deployments { .assertOnCourseAttempts(1); } + /** * An author create a course with a test to overview * the progress of a participant doing a test. @@ -902,7 +907,7 @@ public class ImsQTI21Test extends Deployments { public void qti21CourseTestCockpitProgress(@Drone @User WebDriver participantBrowser) throws IOException, URISyntaxException { - UserVO author = new UserRestClient(deploymentUrl).createAuthor(); + UserVO author = new UserRestClient(deploymentUrl).createRandomAuthor(); UserVO participant = new UserRestClient(deploymentUrl).createRandomUser("Ryomou"); LoginPage loginPage = LoginPage.load(browser, deploymentUrl); @@ -1007,7 +1012,8 @@ public class ImsQTI21Test extends Deployments { // participant ends the test qtiPage .endTest()//auto close because 1 part, no feedbacks - .assertOnCourseAssessmentTestScore(2); + .assertOnCourseAssessmentTestScore(2) + .assertOnCourseAssessmentTestPassed(); // author wait the status changes assessmentTool @@ -1150,6 +1156,187 @@ public class ImsQTI21Test extends Deployments { .assertOnCourseAssessmentTestScore(2); } + + /** + * An author create a test with essay and single choice, configures it + * with grading, add a grader then makes a course. A user play the test, + * the grader correct it, the author set the test as passed and free the + * results. The user reloads the results to see passed! + * + * @param participantBrowser The participant browser + * @param graderBrowser The grader browser + * @throws IOException + * @throws URISyntaxException + */ + @Test + @Ignore + @RunAsClient + public void qti21CourseTestGradingWorkflow(@Drone @Participant WebDriver participantBrowser, @Drone @User WebDriver graderBrowser) + throws IOException, URISyntaxException { + + UserVO author = new UserRestClient(deploymentUrl).createRandomAuthor(); + UserVO participant = new UserRestClient(deploymentUrl).createRandomUser("Hakufu"); + UserVO grader = new UserRestClient(deploymentUrl).createRandomUser("Hakufu"); + + LoginPage loginPage = LoginPage.load(browser, deploymentUrl); + loginPage.loginAs(author.getLogin(), author.getPassword()); + + //upload a test and prepare the grading configuration + String qtiTestTitle = "Correction 2.1 " + UUID.randomUUID(); + URL qtiTestUrl = JunitTestHelper.class.getResource("file_resources/qti21/test_sc_essay_mc.zip"); + File qtiTestFile = new File(qtiTestUrl.toURI()); + NavigationPage navBar = NavigationPage.load(browser); + navBar + .openAuthoringEnvironment() + .uploadResource(qtiTestTitle, qtiTestFile) + .clickToolbarRootCrumb(); + + QTI21Page qtiPage = QTI21Page + .getQTI21Page(browser); + QTI21GradingPage gradingPage = qtiPage + .grading(); + gradingPage + .settings() + .enable() + .setPeriods(9, 5, 7) + .selectMailTemplate() + .save(); + + gradingPage + .graders() + .addGrader(grader) + .assertGrader(grader); + + qtiPage + .clickToolbarBack(); + + //create a course + String courseTitle = "Grading QTI 2.1 " + UUID.randomUUID(); + navBar + .openAuthoringEnvironment() + .createCourse(courseTitle) + .clickToolbarBack(); + + String testNodeTitle = "QTI21Grading-1"; + + //create a course element of type CP with the CP that we create above + CourseEditorPageFragment courseEditor = CoursePageFragment.getCourse(browser) + .edit(); + courseEditor + .createNode("iqtest") + .nodeTitle(testNodeTitle) + .selectTabLearnContent() + .chooseTest(qtiTestTitle); + OOGraphene.closeWarningBox(browser);//close the warning + + QTI21ConfigurationCEPage configPage = new QTI21ConfigurationCEPage(browser); + configPage + .selectConfiguration() + .setCorrectionMode("grading") + .saveConfiguration(); + + courseEditor + .autoPublish() + .settings() + .accessConfiguration() + .setUserAccess(UserAccess.membersOnly) + .save(); + + //add a participant + CoursePageFragment courseRuntime = courseEditor + .clickToolbarBack(); + courseRuntime + .publish() + .members() + .quickAdd(participant); + + //a user search the course and make the test + LoginPage userLoginPage = LoginPage.load(participantBrowser, deploymentUrl); + userLoginPage + .loginAs(participant.getLogin(), participant.getPassword()) + .resume(); + NavigationPage userNavBar = NavigationPage.load(participantBrowser); + userNavBar + .openMyCourses() + .openSearch() + .extendedSearch(courseTitle) + .select(courseTitle); + + // open the course and see the test + CoursePageFragment course = CoursePageFragment.getCourse(participantBrowser); + course + .clickTree() + .selectWithTitle(testNodeTitle); + QTI21Page participantQtiPage = QTI21Page + .getQTI21Page(participantBrowser); + participantQtiPage + .assertOnStart() + .start() + .assertOnAssessmentItem() + .answerSingleChoiceWithParagraph("Correct answer") + .saveAnswer() + .assertOnAssessmentItem("Essay") + .answerEssay("Bla bla bla") + .saveAnswer() + .answerMultipleChoice("Good choice", "Bad choice") + .saveAnswer() + .endTest() + .assertOnCourseAssessmentTestWaitingCorrection(); + + // grader assignment + LoginPage graderLoginPage = LoginPage.load(graderBrowser, deploymentUrl); + graderLoginPage + .loginAs(grader.getLogin(), grader.getPassword()) + .resume(); + + NavigationPage graderNavBar = NavigationPage.load(graderBrowser); + graderNavBar + .openCoaching() + .assertOnGrading() + .startGrading(testNodeTitle) + .assertOnAssessmentItemNotCorrected("Essay", 0) + .selectAssessmentItem("Essay") + .setScore("1.0") + .save() + .assertOnStatusOk() + .back() + .publish() + .confirmDialog(); + + // author check the assignment + navBar + .openAuthoringEnvironment() + .selectResource(qtiTestTitle); + qtiPage + .grading() + .graders() + .assertGraderAssignmentsDone(grader, 1); + // go in assessment tool to free the results + navBar + .openAuthoringEnvironment() + .selectResource(courseTitle); + + courseRuntime + .assessmentTool() + .users() + .assertOnUsers(participant) + .selectUser(participant) + .selectUsersCourseNode(testNodeTitle) + .reopenAssessment() + .setAssessmentPassed(Boolean.TRUE) + .setAssessmentVisibility(true) + .closeAssessment() + .assertUserPassedCourseNode(testNodeTitle); + + // participant checks its result + course + .clickTree() + .selectWithTitle(testNodeTitle); + participantQtiPage + .assertOnCourseAssessmentTestScore(2) + .assertOnCourseAssessmentTestPassed(); + } + /** * An author create a course with a course element * of type self test. It add a participant. The diff --git a/src/test/java/org/olat/selenium/page/NavigationPage.java b/src/test/java/org/olat/selenium/page/NavigationPage.java index cb609f68cd3..d8fdaca19d8 100644 --- a/src/test/java/org/olat/selenium/page/NavigationPage.java +++ b/src/test/java/org/olat/selenium/page/NavigationPage.java @@ -22,6 +22,7 @@ package org.olat.selenium.page; import java.util.List; import org.junit.Assert; +import org.olat.selenium.page.coaching.CoachingPage; import org.olat.selenium.page.core.AdministrationPage; import org.olat.selenium.page.course.MyCoursesPage; import org.olat.selenium.page.graphene.OOGraphene; @@ -48,6 +49,7 @@ public class NavigationPage { private static final By navigationSitesBy = By.cssSelector("ul.o_navbar_sites"); private static final By authoringEnvTabBy = By.cssSelector("li.o_site_author_env > a"); private static final By questionPoolTabBy = By.cssSelector("li.o_site_qpool > a"); + private static final By coachingTabBy = By.cssSelector("li.o_site_coaching > a"); private static final By portalBy = By.cssSelector("li.o_site_portal > a"); private static final By myCoursesBy = By.cssSelector("li.o_site_repository > a"); private static final By userManagementBy = By.cssSelector("li.o_site_useradmin > a"); @@ -89,6 +91,11 @@ public class NavigationPage { .assertOnQuestionPool(); } + public CoachingPage openCoaching() { + navigate(coachingTabBy); + return new CoachingPage(browser); + } + public PortalPage openPortal() { navigate(portalBy); return new PortalPage(browser); diff --git a/src/test/java/org/olat/selenium/page/coaching/CoachingPage.java b/src/test/java/org/olat/selenium/page/coaching/CoachingPage.java new file mode 100644 index 00000000000..b43843676e6 --- /dev/null +++ b/src/test/java/org/olat/selenium/page/coaching/CoachingPage.java @@ -0,0 +1,55 @@ +/** + * <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.coaching; + +import org.olat.selenium.page.graphene.OOGraphene; +import org.olat.selenium.page.qti.QTI21CorrectionPage; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; + +/** + * + * Initial date: 11 juin 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CoachingPage { + + private final WebDriver browser; + + public CoachingPage(WebDriver browser) { + this.browser = browser; + } + + public CoachingPage assertOnGrading() { + By gradingBy = By.cssSelector("fieldset.o_sel_grading_assignments"); + OOGraphene.waitElement(gradingBy, browser); + return this; + } + + public QTI21CorrectionPage startGrading(String nodeTitle) { + By gradingBy = By.xpath("//fieldset[@class='o_sel_grading_assignments']//table//tr[td/a[text()[contains(.,'" + nodeTitle + "')]]]/td/a[contains(@onclick,'grade')]"); + OOGraphene.waitElement(gradingBy, browser); + browser.findElement(gradingBy).click(); + OOGraphene.waitBusy(browser); + return new QTI21CorrectionPage(browser); + } + +} diff --git a/src/test/java/org/olat/selenium/page/core/MenuTreePageFragment.java b/src/test/java/org/olat/selenium/page/core/MenuTreePageFragment.java index b92fbfa1de7..71f7d03830f 100644 --- a/src/test/java/org/olat/selenium/page/core/MenuTreePageFragment.java +++ b/src/test/java/org/olat/selenium/page/core/MenuTreePageFragment.java @@ -55,6 +55,8 @@ public class MenuTreePageFragment { OOGraphene.waitElement(rootNodeBy, browser); browser.findElement(rootNodeBy).click(); OOGraphene.waitBusy(browser); + By rootNodeActiveBy = By.xpath("//div[contains(@class,'o_tree')]//span[contains(@class,'o_tree_link')][contains(@class,'o_tree_l0')][contains(@class,'active')]/a"); + OOGraphene.waitElement(rootNodeActiveBy, browser); return this; } diff --git a/src/test/java/org/olat/selenium/page/course/AssessmentToolPage.java b/src/test/java/org/olat/selenium/page/course/AssessmentToolPage.java index ca7b57f3555..fd51110be0d 100644 --- a/src/test/java/org/olat/selenium/page/course/AssessmentToolPage.java +++ b/src/test/java/org/olat/selenium/page/course/AssessmentToolPage.java @@ -141,12 +141,37 @@ public class AssessmentToolPage { By scoreBy = By.cssSelector(".o_sel_assessment_form_score input[type='text']"); browser.findElement(scoreBy).sendKeys(Float.toString(score)); + return closeAssessment(); + } + + public AssessmentToolPage setAssessmentPassed(Boolean passed) { + String val = passed == null ? "undefined" : passed.toString(); + By passedBy = By.cssSelector(".o_sel_assessment_form_passed input[type='radio'][value='" + val + "']"); + browser.findElement(passedBy).click(); + return this; + } + + public AssessmentToolPage setAssessmentVisibility(boolean visible) { + String val = visible ? "visible" : "hidden"; + By visibleBy = By.cssSelector(".o_sel_assessment_form_visibility input[type='radio'][value='" + val + "']"); + browser.findElement(visibleBy).click(); + return this; + } + + public AssessmentToolPage closeAssessment() { By saveBy = By.cssSelector("button.btn.o_sel_assessment_form_save_and_close"); browser.findElement(saveBy).click(); OOGraphene.waitBusy(browser); return this; } + public AssessmentToolPage reopenAssessment() { + By saveBy = By.cssSelector("a.o_sel_assessment_form_reopen"); + browser.findElement(saveBy).click(); + OOGraphene.waitBusy(browser); + return this; + } + public AssessmentToolPage assertPassed(UserVO user) { By userInfosBy = By.xpath("//div[@class='o_user_infos']/div[@class='o_user_infos_inner']//tr[contains(@class,'o_userDisplayName')]/td[text()[contains(.,'" + user.getFirstName() + "')]]"); OOGraphene.waitElement(userInfosBy, browser); diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21ConfigurationCEPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21ConfigurationCEPage.java index 137e91d8fc3..190aa4557e4 100644 --- a/src/test/java/org/olat/selenium/page/qti/QTI21ConfigurationCEPage.java +++ b/src/test/java/org/olat/selenium/page/qti/QTI21ConfigurationCEPage.java @@ -84,6 +84,20 @@ public class QTI21ConfigurationCEPage { return this; } + /** + * Set the correction mode. + * + * @param mode The mode defined by IQEditController.CORRECTION_AUTO, + * IQEditController.CORRECTION_MANUAL or IQEditController.CORRECTION_GRADING + * @return Itself + */ + public QTI21ConfigurationCEPage setCorrectionMode(String mode) { + By correctionBy = By.xpath("//fieldset[contains(@class,'o_qti_21_correction')]//div[@id='o_cocorrection_mode']//input[@value='" + mode + "'][@name='correction.mode'][@type='radio']"); + OOGraphene.waitElement(correctionBy, browser); + browser.findElement(correctionBy).click(); + return this; + } + public QTI21ConfigurationCEPage saveConfiguration() { By saveBy = By.cssSelector(".o_qti_21_configuration button"); browser.findElement(saveBy).click(); diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21CorrectionPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21CorrectionPage.java index 871239bad98..71641e4c3bd 100644 --- a/src/test/java/org/olat/selenium/page/qti/QTI21CorrectionPage.java +++ b/src/test/java/org/olat/selenium/page/qti/QTI21CorrectionPage.java @@ -58,12 +58,32 @@ public class QTI21CorrectionPage { return this; } + /** + * The assessment items list after selecting an identity in the list. + * + * @param questionTitle The question title + * @param numberOfErrors The number of errors + * @return + */ public QTI21CorrectionPage assertOnAssessmentItemError(String questionTitle, int numberOfErrors) { By questionBy = By.xpath("//div[contains(@class,'o_sel_correction_assessment_items_list')]//tr[td/a[text()[contains(.,'" + questionTitle + "')]]]/td/a[text()[contains(.,'" + numberOfErrors + "')]]/i[contains(@class,'o_icon_error')]"); OOGraphene.waitElement(questionBy, browser); return this; } + /** + * Assert an assessment item which is not corrected. + * + * @param questionTitle The question title + * @param numberOfErrors + * @return + */ + public QTI21CorrectionPage assertOnAssessmentItemNotCorrected(String questionTitle, int numberOfPoints) { + By questionBy = By.xpath("//div[contains(@class,'o_sel_correction_assessment_items_list')]//tr[td/a[text()[contains(.,'" + questionTitle + "')]]][td[text()[contains(.,'" + numberOfPoints + "')]]]/td/a/i[contains(@class,'o_icon_error')]"); + OOGraphene.waitElement(questionBy, browser); + return this; + } + public QTI21CorrectionPage setScore(String score) { By scoreBy = By.cssSelector("div.o_assessmentitem_wrapper .o_sel_assessment_item_score input[type='text']"); OOGraphene.waitElement(scoreBy, browser); @@ -91,6 +111,10 @@ public class QTI21CorrectionPage { return this; } + /** + * + * @return Itself + */ public QTI21CorrectionPage publishAll() { By saveBy = By.cssSelector("a.o_sel_correction_save_tests"); OOGraphene.waitElement(saveBy, browser); @@ -99,6 +123,19 @@ public class QTI21CorrectionPage { return this; } + /** + * Publish the results of a single test (grader). + * + * @return Itself + */ + public QTI21CorrectionPage publish() { + By saveBy = By.cssSelector("a.o_sel_correction_save_test"); + OOGraphene.waitElement(saveBy, browser); + browser.findElement(saveBy).click(); + OOGraphene.waitBusy(browser); + return this; + } + public QTI21CorrectionPage confirmDialog() { By confirmButtonBy = By.cssSelector("div.modal-dialog div.modal-body button.btn-primary"); OOGraphene.waitElement(confirmButtonBy, browser); diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21GradingGradersPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21GradingGradersPage.java new file mode 100644 index 00000000000..958e035a49b --- /dev/null +++ b/src/test/java/org/olat/selenium/page/qti/QTI21GradingGradersPage.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.selenium.page.graphene.OOGraphene; +import org.olat.selenium.page.group.MembersWizardPage; +import org.olat.user.restapi.UserVO; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; + +/** + * + * Initial date: 11 juin 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTI21GradingGradersPage { + + private final WebDriver browser; + + public QTI21GradingGradersPage(WebDriver browser) { + this.browser = browser; + } + + public QTI21GradingGradersPage addGrader(UserVO user) { + By addGraderBy = By.cssSelector("div.o_button_group a.o_sel_repo_grading_add_graders"); + OOGraphene.waitElement(addGraderBy, browser); + browser.findElement(addGraderBy).click(); + + MembersWizardPage wizard = new MembersWizardPage(browser) + .searchMember(user, true); + + OOGraphene.nextStep(browser); + OOGraphene.waitElement(By.cssSelector("fieldset.o_sel_grader_import_overview"), browser); + + OOGraphene.nextStep(browser); + wizard.finish(); + return this; + } + + public QTI21GradingGradersPage assertGrader(UserVO user) { + By graderBy = By.xpath("//div[@class='o_sel_graders_list']//table//tr/td/a[text()[contains(.,'" + user.getFirstName() + "')]]"); + OOGraphene.waitElement(graderBy, browser); + return this; + } + + /** + * The method is not precise. + * + * @param user The grader + * @param assignments A number + * + * @return Itself + */ + public QTI21GradingGradersPage assertGraderAssignmentsDone(UserVO user, int assignments) { + By graderBy = By.xpath("//div[@class='o_sel_graders_list']//table//tr[td/a[contains(@onclick,'done')][text()[contains(.,'" + assignments + "')]]]/td/a[text()[contains(.,'" + user.getFirstName() + "')]]"); + OOGraphene.waitElement(graderBy, browser); + return this; + } + + +} diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21GradingPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21GradingPage.java new file mode 100644 index 00000000000..034b712d112 --- /dev/null +++ b/src/test/java/org/olat/selenium/page/qti/QTI21GradingPage.java @@ -0,0 +1,58 @@ +/** + * <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.selenium.page.graphene.OOGraphene; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; + +/** + * + * Initial date: 10 juin 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTI21GradingPage { + + private final WebDriver browser; + + public QTI21GradingPage(WebDriver browser) { + this.browser = browser; + } + + public QTI21GradingSettingsPage settings() { + selectSegment("repository.configuration"); + return new QTI21GradingSettingsPage(browser); + } + + public QTI21GradingGradersPage graders() { + selectSegment("repository.graders"); + return new QTI21GradingGradersPage(browser); + } + + private void selectSegment(String action) { + By segmentBy = By.xpath("//div[contains(@class,'o_segments')]/a[contains(@onclick,'" + action + "')]"); + OOGraphene.waitElement(segmentBy, browser); + browser.findElement(segmentBy).click(); + OOGraphene.waitBusy(browser); + By segmentSelectedBy = By.xpath("//div[contains(@class,'o_segments')]/a[contains(@class,'btn-primary')][contains(@onclick,'" + action + "')]"); + OOGraphene.waitElement(segmentSelectedBy, browser); + } +} diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21GradingSettingsPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21GradingSettingsPage.java new file mode 100644 index 00000000000..414be7a9eb5 --- /dev/null +++ b/src/test/java/org/olat/selenium/page/qti/QTI21GradingSettingsPage.java @@ -0,0 +1,87 @@ +/** + * <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.selenium.page.graphene.OOGraphene; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; + +/** + * + * Initial date: 10 juin 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTI21GradingSettingsPage { + + private final WebDriver browser; + + public QTI21GradingSettingsPage(WebDriver browser) { + this.browser = browser; + } + + public QTI21GradingSettingsPage enable() { + By enableBy = By.xpath("//div[contains(@class,'o_sel_repo_grading_enable')]//input[@name='grading.repo.enabled'][@type='checkbox']"); + OOGraphene.waitElement(enableBy, browser); + browser.findElement(enableBy).click(); + OOGraphene.waitBusy(browser); + + By reminderBy = By.className("o_sel_repo_grading_notification_type"); + OOGraphene.waitElement(reminderBy, browser); + return this; + } + + public QTI21GradingSettingsPage setPeriods(int gradingPeriod, int firstReminder, int secondReminder) { + By gradingPeriodBy = By.cssSelector("input.o_sel_repo_grading_period[type='text']"); + browser.findElement(gradingPeriodBy).sendKeys(Integer.toString(gradingPeriod)); + + By firstReminderBy = By.cssSelector("input.o_sel_repo_grading_first_reminder_period[type='text']"); + browser.findElement(firstReminderBy).sendKeys(Integer.toString(firstReminder)); + + By secondReminderBy = By.cssSelector("input.o_sel_repo_grading_second_reminder_period[type='text']"); + browser.findElement(secondReminderBy).sendKeys(Integer.toString(secondReminder)); + return this; + } + + public QTI21GradingSettingsPage selectMailTemplate() { + By templatesMenuCaret = By.cssSelector("button.o_sel_repo_grading_templates"); + browser.findElement(templatesMenuCaret).click(); + + By templatesMenu = By.cssSelector("ul.o_sel_repo_grading_templates"); + OOGraphene.waitElement(templatesMenu, browser); + + By firstTemplateBy = By.xpath("//ul[contains(@class,'o_sel_repo_grading_templates')]/li[1]/a"); + OOGraphene.waitElement(firstTemplateBy, browser); + browser.findElement(firstTemplateBy).click(); + OOGraphene.waitBusy(browser); + + By notificationSubjectBy = By.xpath("//div[contains(@class,'o_sel_repo_grading_notification_subject')]//input[@value[string()]]"); + OOGraphene.waitElement(notificationSubjectBy, browser); + return this; + } + + public QTI21GradingSettingsPage save() { + By saveBy = By.cssSelector("fieldset.o_sel_repo_grading_settings_form 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 906042a87b1..46a4052e551 100644 --- a/src/test/java/org/olat/selenium/page/qti/QTI21Page.java +++ b/src/test/java/org/olat/selenium/page/qti/QTI21Page.java @@ -895,49 +895,55 @@ public class QTI21Page { public QTI21Page assertOnCourseAssessmentTestScore(int score) { By resultsBy = By.xpath("//div[contains(@class,'o_personal')]//tr[contains(@class,'o_score')]/td[contains(text(),'" + score + "')]"); - OOGraphene.waitElement(resultsBy, 5, browser); + OOGraphene.waitElement(resultsBy, browser); return this; } public QTI21Page assertOnCourseAssessmentTestWaitingCorrection() { By resultsBy = By.xpath("//div[contains(@class,'o_personal')]//tr[contains(@class,'o_score')]/td/span[@id='o_score_in_review']"); - OOGraphene.waitElement(resultsBy, 5, browser); + OOGraphene.waitElement(resultsBy, browser); + return this; + } + + public QTI21Page assertOnCourseAssessmentTestPassed() { + By passedBy = By.xpath("//div[contains(@class,'o_personal')]//tr[contains(@class,'o_state')][contains(@class,'o_passed')]/td/i[contains(@class,'o_icon_passed')]"); + OOGraphene.waitElement(passedBy, browser); return this; } public QTI21Page assertOnAssessmentTestScore(int score) { By resultsBy = By.xpath("//div[contains(@class,'o_sel_results_details')]//tr[contains(@class,'o_sel_assessmenttest_scores')]/td/div/span[contains(@class,'o_sel_assessmenttest_score')][contains(text(),'" + score + "')]"); - OOGraphene.waitElement(resultsBy, 5, browser); + OOGraphene.waitElement(resultsBy, browser); return this; } public QTI21Page assertOnAssessmentItemScore(String title, int score) { By resultsBy = By.xpath("//div[contains(@class,'o_qti_item')][div/h4[text()[contains(.,'" + title + "')]]]//tr[contains(@class,'o_sel_assessmentitem_score')]//span[@class='o_sel_assessmentitem_score'][contains(text(),'" + score + "')]"); - OOGraphene.waitElement(resultsBy, 5, browser); + OOGraphene.waitElement(resultsBy, browser); return this; } public QTI21Page assertOnAssessmentTestScore(String score) { By resultsBy = By.xpath("//div[contains(@class,'o_sel_results_details')]//tr[contains(@class,'o_sel_assessmenttest_scores')]/td/div/span[contains(@class,'o_sel_assessmenttest_score')][contains(text(),'" + score + "')]"); - OOGraphene.waitElement(resultsBy, 5, browser); + OOGraphene.waitElement(resultsBy, browser); return this; } public QTI21Page assertOnAssessmentTestPassed() { - By notPassedBy = By.cssSelector("div.o_sel_results_details tr.o_qti_stateinfo.o_passed"); - OOGraphene.waitElement(notPassedBy, 5, browser); + By passedBy = By.cssSelector("div.o_sel_results_details tr.o_qti_stateinfo.o_passed"); + OOGraphene.waitElement(passedBy, browser); return this; } public QTI21Page assertOnAssessmentTestNotPassed() { By notPassedBy = By.cssSelector("div.o_sel_results_details tr.o_qti_stateinfo.o_failed"); - OOGraphene.waitElement(notPassedBy, 5, browser); + OOGraphene.waitElement(notPassedBy, browser); return this; } public QTI21Page assertOnAssessmentTestMaxScore(int score) { By resultsBy = By.xpath("//div[contains(@class,'o_sel_results_details')]//tr[contains(@class,'o_sel_assessmenttest_scores')]/td/div/span[contains(@class,'o_sel_assessmenttest_maxscore')][contains(text(),'" + score + "')]"); - OOGraphene.waitElement(resultsBy, 5, browser); + OOGraphene.waitElement(resultsBy, browser); return this; } @@ -1021,11 +1027,7 @@ public class QTI21Page { } public QTI21SettingsPage settings() { - By toolsLinkBy = By.cssSelector("ul.o_tools li.o_tool_dropdown a.o_sel_repository_tools"); - OOGraphene.waitElement(toolsLinkBy, browser); - if(!browser.findElement(toolsMenu).isDisplayed()) { - openToolsMenu(); - } + openAdministrationMenu(); By optionsBy = By.cssSelector("ul.o_sel_repository_tools a.o_sel_repo_settings"); OOGraphene.waitElement(optionsBy, browser); @@ -1034,6 +1036,25 @@ public class QTI21Page { return new QTI21SettingsPage(browser); } + public QTI21GradingPage grading() { + openAdministrationMenu(); + + By optionsBy = By.cssSelector("ul.o_sel_repository_tools a.o_sel_grading"); + OOGraphene.waitElement(optionsBy, browser); + browser.findElement(optionsBy).click(); + OOGraphene.waitBusy(browser); + return new QTI21GradingPage(browser); + } + + private QTI21Page openAdministrationMenu() { + By toolsLinkBy = By.cssSelector("ul.o_tools li.o_tool_dropdown a.o_sel_repository_tools"); + OOGraphene.waitElement(toolsLinkBy, browser); + if(!browser.findElement(toolsMenu).isDisplayed()) { + openToolsMenu(); + } + return this; + } + /** * Click the editor link in the tools drop-down * @return diff --git a/src/test/java/org/olat/test/file_resources/qti21/simple_QTI_21_test.zip b/src/test/java/org/olat/test/file_resources/qti21/simple_QTI_21_test.zip index c324d4f7f3cccab7774bd0d0511d9e2654341e6f..d11a0339c38d43e3a25494f2c2b45f08f96eb138 100644 GIT binary patch literal 3098 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 delta 2244 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 VzJfMp0H2TPzvtTbTEleS{Rg@24z~aR diff --git a/src/test/java/org/olat/test/file_resources/qti21/test_without_feedbacks.zip b/src/test/java/org/olat/test/file_resources/qti21/test_without_feedbacks.zip index eb9cd0d4e5b287eff47d9e39fa93593005ac571b..91866206c88daade176bde7316acaa9200833d90 100644 GIT binary patch delta 1543 zcmZ{kc{tR09LJ||&6qK)wW>*0vqDU++~<m9jAI;OY1}fdAx+Lazud{9j3Y<xa~RPG zvDST+atq10T51Zp#jtFDWcPXY^Zn=j`8=P`^L)S0`}O%I2y6)<a99>rJ`e-~0SQO< z;>ZP1HGpb?_kODaXG%oTPXa)J9+>n_5|@LIEfU-G)54ge%xiovD);O&393E#6w-&K zB{ROnS><RH)VSHYx~6qPJ^%Jp&ET`f32XU{)YsICFnikg?+QKLa<M{)HnHs>Kyd+E zJ{?Y>hNtIiPO8-BiZ}E}g)T`EsgUl~1Tz45GIwo0Sf3{+ku#%o8KU^gNias(C=@G} z){Dhhd7^20+gWBtbHmF}Vv<ezXdthN^z@UI$<9S7oJo5dCnLInvCy&^7I&>E>#K>J z{#`5U+b+Q${iDJe_~pV+jfy@x2h8yZF<-M8(RpOLAVY7B`8jpxRojp&eXbvJ4G6bb zdOKW8u_aJMSz`O>76p3lEb*(*q<;4;aDB(P^SgtHd>>wT7sf|`Cv(>BFA{U3Pr2hj zs+fL`?|ScMfO@u_L1)P=o{x&_n0|f$>+ohOp}%e}%g#luH&0FqmG;87*9z&F8T0gB zu0iEWBXZPzptw#kj4)C(O3@_%clv6|O-BW<Q~2GfI<Ry-iRcl5;N`edyi>Jh%jU$x z!1n}Q?fNFUj3&Q1?G6Utjv&EH!MM(%PI;`p`T4+SX>m>VsYeu>)4~OATQxgn*3^<r z)nC-Rni^c<_vYo%<Bt5vza=fWLC;Cd3bftKTGbKhsasbstr6Qi)o{KNcsU>J;qns$ z-ME{%QgIF$%5@WUftrM0&k4J3_!8Nn>E347s3p%KUI5zqamz-3FJkqcYKgNze4AVx zZ;A-&3=IV(%W$uu3<hyur=DtIkbz0ArDAE#(#dG23-(zKywRvw$Fw-YEnD3(r5mzX zST`9RS9;~XUU!MuDW@JlDY^1$17Gb`n2}(V=dOmj!GM!ffMs8hOokPSJN%jV#-&U8 zGG6keU6>r_qFZ_G)MKJ?t13S;a=Lh!BOpj8H$J7EnrT?g5V_eM6m?}KYOB&)byBh` zm=*t;)T+K6>b_F+U~P~eap3Vo!)N(LrSU=LWsU6*t)Mm74l_kZEqkr<OhcNg(e6vr zPx0Aq>-z%1wPAEN4WX>Sm}vttM;HAvZ5fB<fMnNdP=r`OAOR={^zTKYX|g-8upX@x zZ60j-O;sTV!N5@obS(G}wTU@KRtD$b<l*4zqU}w*;pQqC;N=;aWI}9}hDXu;If>1P zyW&+C0=aX{qS}85c~6fl>Brv4OiZaZ#Ev`dAJFDh{7x$Ce~B%cNJ3ZV%{wUA<b*wL zcg3B4;mn=rBa={#(7%{mvib<%%<J;iDvS_+Ro%j!&&?hK4jE#wg;)#Qjp$5vFGW_& zm==e)pm`N)V($z5taUZSHvg$*xjMVIgcWLIvTT~k%jL$lioaCzz9X+I9@SNgnuiom zm-DQhbuL!WLI-(@-uAOzI&J7qD}QspYUT@A66;5|ls%si8+fqj^}rO!PSr1?Kmxy- z4t0jxKPXiav-ha<VE)>~_C%|@^zH<VisyZv2s7V{bUr_%OO4<q!3VZ-iE!0UO$M1^ zqxMj7uhiZjx(a)=XB&m6&kz|2-d8l_GAzUx=lzkIyUD<?7B9S|v<uVrxCV`_jmzmF z!#%_g%wZvI1bpVtO<-~?BCXiWhR>d|8R_^;3#iCYwvYB@vGHvyEA-NTo>w(?m*^4K z;ME?#UI#(i($Ot<>dz3}Ie-q$BgcDULj&VafIy;bM~<HW^MSs9p2ulGAG7$IB)Qxo zIV+Isp&~s09VFwqkl-K|vLU+&Ihzaq4>n}jVU5mC7Uh!tmcNE0!77~OkMOfbM+_am d$Pq)|gNN(?h{9pn*pK?y4i9knh*`u@_BSV!wL|~_ delta 925 zcmeya&@aOm;LXe;!ob17!4TMy;kDU=>k<<SnDv$|oEgIEU`~XvCi2<TPYpetciTW< z&v$K>yV-kJgeORQTo&V)VB#plvQyN%C{uW$OZbwU@9(`&f6^`!2v_fv;Y)AZeMO+h zF<EQPTVtu$YqdgWubw(lxjyafA1#h)r=KRw<S8-cePqznbp7{k&e@5q@u|5wS9t#F z-`l-B*)nOu)R|hoj-@WzhndYk9j`xizq>+iu8_w&7Ei^El0UD#REj*KH17z@jE1JJ zH3ELzx~@_mFJBFdYE|BEyk$z+vgp^GE`E)BXBro*y{f<BPFK6-HU9GJ=bszu-;h}O zdZN5*Gt0RhW}i-#T)dOXwyil%s<B+N;K9DX8*f*?h%t<PSf0W)D{uGW8}HV~tDE!t z*W1_hJ04Eu^}4lR`TmKjRTsW%NYwnZE??GqDZ$#y{ZydC>Nngg#E$Ph{BY6lF7}58 z5pUGn4px6Ku((>%dFB5_$&!}WvnpDS$%&t`NllcRsMF6P=<2D-Y3Nru@pypBjNfs* z+?H>@?Z18}@Y=4hO>P-|{Fk?EnRM(;!lq|Mij~~;S<;iYMEGuKzVqUiSyZLynssZX z=S7GAT=;(G%(#DVy3cj8r_PZM@Y0`Opjfz5I+|HMYU}K#n>_QbT>JKtBd=s@Uc~bq znf8bM?mek_x9QD6$NYr*--J`t*^}->_$*fH-P9u@aLMMJfsXMZp@}M~538NlP53_N zcHQflb|PCQxLv9@-4MNX^}5SC&3EVIS(y3l<z6Y8w6|>XdAW;QwoH68`_h}Uw~w4U zAS=#Z-Er&o-qPK+3oSNr8)+UkQc>~J@T`csWWzamQfs7~nu>_L`<t^;B6|8_shSU2 zpGpWh2757V7MX1EBvYij<bmFi?yDg@!4oIRY*JC(xcaWmBc)TtUiFzlLX7iQ{p@)V zaMr+LWs9$RzV(|8_43kZud#>v|31q0_Hf#&-OT4qzMk8hx%#7D#r6IA7d#5SChT$j zUsk=TW`5)b<%0FcZ_CBnwoCD!U0cugxIVy}o#XAj?V{(H7#QBMF)##pGct)VATq^& zuIGzq0_B{z7#Jr1;pYP~C-Vr%f-}eDdHhmL*O(^%W>cH|fS(5<<^vQnWSgA9Z=?Xq iO2}sGFd&0zoRj(Z4MbRhmNGD~0HHM_1H(QZ5Dx&{e~&Ey -- GitLab