From a63fd3a6a871e2a1d71b0c5f261f2055511ceea3 Mon Sep 17 00:00:00 2001 From: David Monllao Date: Thu, 6 Feb 2014 18:06:36 +0800 Subject: [PATCH 1/5] MDL-42404 behat: Adding yui-dialog to dialogue selector --- lib/behat/classes/behat_selectors.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/behat/classes/behat_selectors.php b/lib/behat/classes/behat_selectors.php index b0153d924cb..48688b077f5 100644 --- a/lib/behat/classes/behat_selectors.php +++ b/lib/behat/classes/behat_selectors.php @@ -83,10 +83,10 @@ class behat_selectors { */ protected static $moodleselectors = array( 'dialogue' => << << << Date: Thu, 6 Feb 2014 18:07:37 +0800 Subject: [PATCH 2/5] MDL-42404 behat: Adding edit rubrics feature Automates the following MDLQA-1185, MDLQA-1188 and MDLQA-1427. All of them related with the expected outcomes after editing rubrics definitions that were already used to grade students. --- .../rubric/tests/behat/edit_rubric.feature | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 grade/grading/form/rubric/tests/behat/edit_rubric.feature diff --git a/grade/grading/form/rubric/tests/behat/edit_rubric.feature b/grade/grading/form/rubric/tests/behat/edit_rubric.feature new file mode 100644 index 00000000000..d883d9f3686 --- /dev/null +++ b/grade/grading/form/rubric/tests/behat/edit_rubric.feature @@ -0,0 +1,136 @@ +@gradingform @gradingform_rubric +Feature: Rubrics can be created and edited + In order to use and refine rubrics to grade students + As a teacher + I need to edit previously used rubrics + + Background: + Given the following "users" exists: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@asd.com | + | student1 | Student | 1 | student1@asd.com | + And the following "courses" exists: + | fullname | shortname | format | + | Course 1 | C1 | topics | + And the following "course enrolments" exists: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + And I log in as "teacher1" + And I follow "Course 1" + And I turn editing mode on + And I add a "Assignment" to section "1" and I fill the form with: + | Assignment name | Test assignment 1 name | + | Description | Test assignment description | + | Grading method | Rubric | + When I go to "Test assignment 1 name" advanced grading definition page + # Defining a rubric. + And I fill the moodle form with: + | Name | Assignment 1 rubric | + | Description | Rubric test description | + And I define the following rubric: + | TMP Criterion 1 | TMP Level 11 | 11 | TMP Level 12 | 12 | + | TMP Criterion 2 | TMP Level 21 | 21 | TMP Level 22 | 22 | + | TMP Criterion 3 | TMP Level 31 | 31 | TMP Level 32 | 32 | + | TMP Criterion 4 | TMP Level 41 | 41 | TMP Level 42 | 42 | + # Checking that only the last ones are saved. + And I define the following rubric: + | Criterion 1 | Level 11 | 1 | Level 12 | 20 | Level 13 | 40 | Level 14 | 50 | + | Criterion 2 | Level 21 | 10 | Level 22 | 20 | Level 23 | 30 | + | Criterion 3 | Level 31 | 5 | Level 32 | 20 | + And I press "Save as draft" + And I go to "Test assignment 1 name" advanced grading definition page + And I click on "Move down" "button" in the "Criterion 1" "table_row" + And I press "Save rubric and make it ready" + Then I should see "Ready for use" + # Grading two students. + And I go to "Student 1" "Test assignment 1 name" activity advanced grading page + And I grade by filling the rubric with: + | Criterion 1 | 50 | Very good | + And I press "Save changes" + # Checking that it complains if you don't select a level for each criterion. + And I should see "Please choose something for each criterion" + And I grade by filling the rubric with: + | Criterion 1 | 50 | Very good | + | Criterion 2 | 10 | Mmmm, you can do it better | + | Criterion 3 | 5 | Not good | + And I complete the advanced grading form with these values: + | Feedback comments | In general... work harder... | + # Checking that the user grade is correct. + And I should see "58.33" in the "Student 1" "table_row" + # Updating the user grade. + And I go to "Student 1" "Test assignment 1 name" activity advanced grading page + And I grade by filling the rubric with: + | Criterion 1 | 20 | Bad, I changed my mind | + | Criterion 2 | 10 | Mmmm, you can do it better | + | Criterion 3 | 5 | Not good | + #And the level with "50" points was previously selected for the rubric criterion "Criterion 1" + #And the level with "20" points is selected for the rubric criterion "Criterion 1" + And I save the advanced grading form + And I should see "22.62" in the "Student 1" "table_row" + And I log out + # Viewing it as a student. + And I log in as "student1" + And I follow "Course 1" + And I follow "Test assignment 1 name" + And I should see "22.62" in the ".feedback" "css_element" + And I should see "In general... work harder..." + And the level with "10" points is selected for the rubric criterion "Criterion 2" + And the level with "20" points is selected for the rubric criterion "Criterion 1" + And the level with "5" points is selected for the rubric criterion "Criterion 3" + And I log out + And I log in as "teacher1" + And I follow "Course 1" + # Editing a rubric definition without regrading students. + And I go to "Test assignment 1 name" advanced grading definition page + And "Save as draft" "button" should not exists + And I click on "Move up" "button" in the "Criterion 1" "table_row" + And I replace "Level 11" rubric level with "Level 11 edited" in "Criterion 1" criterion + And I press "Save" + And I should see "You are about to save changes to a rubric that has already been used for grading." + And I select "Do not mark for regrade" from "menurubricregrade" + And I press "Continue" + And I log out + # Check that the student still sees the grade. + And I log in as "student1" + And I follow "Course 1" + And I follow "Test assignment 1 name" + And I should see "22.62" in the ".feedback" "css_element" + And the level with "20" points is selected for the rubric criterion "Criterion 1" + And I log out + # Editing a rubric with significant changes. + And I log in as "teacher1" + And I follow "Course 1" + And I go to "Test assignment 1 name" advanced grading definition page + And I click on "Move down" "button" in the "Criterion 2" "table_row" + And I replace "1" rubric level with "11" in "Criterion 1" criterion + And I press "Save" + And I should see "You are about to save significant changes to a rubric that has already been used for grading. The gradebook value will be unchanged, but the rubric will be hidden from students until their item is regraded." + And I press "Continue" + And I log out + # Check that the student doesn't see the grade. + And I log in as "student1" + And I follow "Course 1" + And I follow "Test assignment 1 name" + And I should see "22.62" in the ".feedback" "css_element" + And the level with "20" points is not selected for the rubric criterion "Criterion 1" + And I log out + # Regrade student. + And I log in as "teacher1" + And I follow "Course 1" + And I follow "Test assignment 1 name" + And I go to "Student 1" "Test assignment 1 name" activity advanced grading page + And I should see "The rubric definition was changed after this student had been graded. The student can not see this rubric until you check the rubric and update the grade." + And I save the advanced grading form + And I log out + # Check that the student sees the grade again. + And I log in as "student1" + And I follow "Course 1" + And I follow "Test assignment 1 name" + And I should see "12.16" in the ".feedback" "css_element" + And the level with "20" points is not selected for the rubric criterion "Criterion 1" + + @javascript + Scenario: I can use rubrics to grade and edit them later updating students grades with Javascript enabled + + Scenario: I can use rubrics to grade and edit them later updating students grades with Javascript disabled From 577efa89cdaf90fd5b7e42f3c0fc9c825a1948cb Mon Sep 17 00:00:00 2001 From: David Monllao Date: Thu, 6 Feb 2014 18:08:05 +0800 Subject: [PATCH 3/5] MDL-42404 behat: Adding rubrics step definitions --- .../tests/behat/behat_gradingform_rubric.php | 490 ++++++++++++++++++ grade/grading/tests/behat/behat_grading.php | 181 +++++++ 2 files changed, 671 insertions(+) create mode 100644 grade/grading/form/rubric/tests/behat/behat_gradingform_rubric.php create mode 100644 grade/grading/tests/behat/behat_grading.php diff --git a/grade/grading/form/rubric/tests/behat/behat_gradingform_rubric.php b/grade/grading/form/rubric/tests/behat/behat_gradingform_rubric.php new file mode 100644 index 00000000000..bef83f91ba6 --- /dev/null +++ b/grade/grading/form/rubric/tests/behat/behat_gradingform_rubric.php @@ -0,0 +1,490 @@ +. + +/** + * Steps definitions for rubrics. + * + * @package gradingform_rubric + * @category test + * @copyright 2013 David Monllaó + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. + +require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php'); + +use Behat\Gherkin\Node\TableNode as TableNode, + Behat\Behat\Context\Step\Given as Given, + Behat\Behat\Context\Step\When as When, + Behat\Behat\Context\Step\Then as Then, + Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException, + Behat\Mink\Exception\ExpectationException as ExpectationException; + +/** + * Steps definitions to help with rubrics. + * + * @package gradingform_rubric + * @category test + * @copyright 2013 David Monllaó + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class behat_gradingform_rubric extends behat_base { + + /** + * @var The number of levels added by default when a rubric is created. + */ + const DEFAULT_RUBRIC_LEVELS = 3; + + /** + * Defines the rubric with the provided data, following rubric's definition grid cells. + * + * This method fills the rubric of the rubric definition + * form; the provided TableNode should contain one row for + * each criterion and each cell of the row should contain: + * # Criterion description + * # Criterion level 1 name + * # Criterion level 1 points + * # Criterion level 2 name + * # Criterion level 2 points + * # Criterion level 3 ..... + * + * Works with both JS and non-JS. + * + * @When /^I define the following rubric:$/ + * @throws ExpectationException + * @param TableNode $rubric + */ + public function i_define_the_following_rubric(TableNode $rubric) { + + // Being a smart method is nothing good when we talk about step definitions, in + // this case we didn't have any other options as there are no labels no elements + // id we can point to without having to "calculate" them. + + $steptableinfo = '| criterion description | level1 name | level1 points | level2 name | level2 points | ...'; + + $criteria = $rubric->getRows(); + + $addcriterionbutton = $this->find_button(get_string('addcriterion', 'gradingform_rubric')); + + // Cleaning the current ones. + $deletebuttons = $this->find_all('css', "input[title='" . get_string('criteriondelete', 'gradingform_rubric') . "']"); + if ($deletebuttons) { + + // We should reverse the deletebuttons because otherwise once we delete + // the first one the DOM will change and the [X] one will not exist anymore. + $deletebuttons = array_reverse($deletebuttons, true); + foreach ($deletebuttons as $button) { + $this->click_and_confirm($button); + } + } + + // The level number (NEWID$N) is not reset after each criterion. + $levelnumber = 1; + + // The next criterion is created with the same number of levels than the last criterion. + $defaultnumberoflevels = self::DEFAULT_RUBRIC_LEVELS; + + if ($criteria) { + foreach ($criteria as $criterionit => $criterion) { + + // Checking the number of cells. + if (count($criterion) % 2 === 0) { + throw new ExpectationException( + 'The criterion levels should contain both definition and points, follow this format:' . $steptableinfo, + $this->getSession() + ); + } + + // Minimum 2 levels per criterion. + // description + definition1 + score1 + definition2 + score2 = 5. + if (count($criterion) < 5) { + throw new ExpectationException( + get_string('err_mintwolevels', 'gradingform_rubric'), + $this->getSession() + ); + + } + + // Add new criterion. + $addcriterionbutton->click(); + + $criterionroot = 'rubric[criteria][NEWID' . ($criterionit + 1) . ']'; + + // Getting the criterion description, this one is visible by default. + $this->set_rubric_field_value($criterionroot . '[description]', array_shift($criterion), true); + + // When JS is disabled each criterion's levels name numbers starts from 0. + if (!$this->running_javascript()) { + $levelnumber = 0; + } + + // Setting the correct number of levels. + $nlevels = count($criterion) / 2; + if ($nlevels < $defaultnumberoflevels) { + + // Removing levels if there are too much levels. + // When we add a new level the NEWID$N is increased from the last criterion. + $lastcriteriondefaultlevel = $defaultnumberoflevels + $levelnumber - 1; + $lastcriterionlevel = $nlevels + $levelnumber - 1; + for ($i = $lastcriteriondefaultlevel; $i > $lastcriterionlevel; $i--) { + + // If JS is disabled seems that new levels are not added. + if ($this->running_javascript()) { + $deletelevel = $this->find_button($criterionroot . '[levels][NEWID' . $i . '][delete]'); + $this->click_and_confirm($deletelevel); + + } else { + // Only if the level exists. + $buttonname = $criterionroot . '[levels][NEWID' . $i . '][delete]'; + if ($deletelevel = $this->getSession()->getPage()->findButton($buttonname)) { + $deletelevel->click(); + } + } + } + } else if ($nlevels > $defaultnumberoflevels) { + // Adding levels if we don't have enough. + $addlevel = $this->find_button($criterionroot . '[levels][addlevel]'); + for ($i = ($defaultnumberoflevels + 1); $i <= $nlevels; $i++) { + $addlevel->click(); + } + } + + // Updating it. + if ($nlevels > self::DEFAULT_RUBRIC_LEVELS) { + $defaultnumberoflevels = $nlevels; + } else { + // If it is less than the default value it sets it to + // the default value. + $defaultnumberoflevels = self::DEFAULT_RUBRIC_LEVELS; + } + + foreach ($criterion as $i => $value) { + + $levelroot = $criterionroot . '[levels][NEWID' . $levelnumber . ']'; + + if ($i % 2 === 0) { + // Pairs are the definitions. + $fieldname = $levelroot . '[definition]'; + $this->set_rubric_field_value($fieldname, $value); + + } else { + // Odds are the points. + + // Checking it now, we would need to remove it if we are testing the form validations... + if (!is_numeric($value)) { + throw new ExpectationException( + 'The points cells should contain numeric values, follow this format: ' . $steptableinfo, + $this->getSession() + ); + } + + $fieldname = $levelroot . '[score]'; + $this->set_rubric_field_value($fieldname, $value, true); + + // Increase the level by one every 2 cells. + $levelnumber++; + } + + } + } + } + } + + /** + * Replaces a value from the specified criterion. You can use it when editing rubrics, to set both name or points. + * + * @When /^I replace "(?P(?:[^"]|\\")*)" rubric level with "(?P(?:[^"]|\\")*)" in "(?P(?:[^"]|\\")*)" criterion$/ + * @throws ElementNotFoundException + * @param string $currentvalue + * @param string $value + * @param string $criterionname + * @return Given[] + */ + public function i_replace_rubric_level_with($currentvalue, $value, $criterionname) { + + $currentvalueliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($currentvalue); + $criterionliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($criterionname); + + $criterionxpath = "//div[@id='rubric-rubric']" . + "/descendant::td[contains(concat(' ', normalize-space(@class), ' '), ' description ')]"; + // It differs between JS on/off. + if ($this->running_javascript()) { + $criterionxpath .= "/descendant::span[@class='textvalue'][text()=$criterionliteral]" . + "/ancestor::tr[contains(concat(' ', normalize-space(@class), ' '), ' criterion ')]"; + } else { + $criterionxpath .= "/descendant::textarea[text()=$criterionliteral]" . + "/ancestor::tr[contains(concat(' ', normalize-space(@class), ' '), ' criterion ')]"; + } + + $inputxpath = $criterionxpath . + "/descendant::input[@type='text'][@value=$currentvalueliteral]"; + $textareaxpath = $criterionxpath . + "/descendant::textarea[text()=$currentvalueliteral]"; + + if ($this->running_javascript()) { + + $spansufix = "/ancestor::div[@class='level-wrapper']" . + "/descendant::div[@class='definition']" . + "/descendant::span[@class='textvalue']"; + + // Expanding the level input boxes. + $spannode = $this->find('xpath', $inputxpath . $spansufix . '|' . $textareaxpath . $spansufix); + $spannode->click(); + + $inputfield = $this->find('xpath', $inputxpath . '|' . $textareaxpath); + $inputfield->setValue($value); + + } else { + $fieldnode = $this->find('xpath', $inputxpath . '|' . $textareaxpath); + $this->set_rubric_field_value($fieldnode->getAttribute('name'), $value); + } + + } + + /** + * Grades filling the current page rubric. Set one line per criterion and for each criterion set "| Criterion name | Points | Remark |". + * + * @When /^I grade by filling the rubric with:$/ + * + * @throws ExpectationException + * @param TableNode $rubric + * @return void + */ + public function i_grade_by_filling_the_rubric_with(TableNode $rubric) { + + $criteria = $rubric->getRowsHash(); + + $stepusage = '"I grade by filling the rubric with:" step needs you to provide a table where each row is a criterion' . + ' and each criterion has 3 different values: | Criterion name | Number of points | Remark text |'; + + // To fill with the steps to execute. + $steps = array(); + + // First element -> name, second -> points, third -> Remark. + foreach ($criteria as $name => $criterion) { + + // We only expect the points and the remark, as the criterion name is $name. + if (count($criterion) !== 2) { + throw new ExpectationException($stepusage, $this->getSession()); + } + + // Numeric value here. + $points = $criterion[0]; + if (!is_numeric($points)) { + throw new ExpectationException($stepusage, $this->getSession()); + } + + // Selecting a value. + // When JS is disabled there are radio options, with JS enabled divs. + $selectedlevelxpath = $this->get_level_xpath($points); + if ($this->running_javascript()) { + + // Only clicking on the selected level if it was not already selected. + $levelnode = $this->find('xpath', $selectedlevelxpath); + + // Using in_array() as there are only a few elements. + if (!in_array('checked', explode(' ', $levelnode->getAttribute('class')))) { + $steps[] = new Given('I click on "' . $selectedlevelxpath . '" "xpath_element" in the "' . + $this->escape($name) . '" "table_row"'); + } + + } else { + + // Getting the name of the field. + $radioxpath = $this->get_criterion_xpath($name) . + $selectedlevelxpath . "/descendant::input[@type='radio']"; + $radionode = $this->find('xpath', $radioxpath); + // TODO MDL-43738: Change setValue() to use the generic set_value() + // which will delegate the process to the field type. + $radionode->setValue($radionode->getAttribute('value')); + } + + // Setting the remark. + + // First we need to get the textarea name, then we can set the value. + $textarea = $this->get_node_in_container('css_element', 'textarea', 'table_row', $name); + $steps[] = new Given('I fill in "' . $textarea->getAttribute('name') . '" with "' . $criterion[1] . '"'); + } + + return $steps; + } + + /** + * Checks that the level was previously selected and the user changed to another level. + * + * @Then /^the level with "(?P\d+)" points was previously selected for the rubric criterion "(?P(?:[^"]|\\")*)"$/ + * @throws ExpectationException + * @param string $criterionname + * @param int $points + * @return void + */ + public function the_level_with_points_was_previously_selected_for_the_rubric_criterion($points, $criterionname) { + + $levelxpath = $this->get_criterion_xpath($criterionname) . + $this->get_level_xpath($points) . + "[contains(concat(' ', normalize-space(@class), ' '), ' currentchecked ')]"; + + // Works both for JS and non-JS. + // - JS: Class -> checked is there when is marked as green. + // - Non-JS: When editing a rubric definition, there are radio inputs and when viewing a + // grade @class contains checked. + $levelxpath .= "[not(contains(concat(' ', normalize-space(@class), ' '), ' checked '))]" . + "[not(/descendant::input[@type='radio'][@checked!='checked'])]"; + + try { + $this->find('xpath', $levelxpath); + } catch (ElementNotFoundException $e) { + throw new ExpectationException('"' . $points . '" points level was not previously selected', $this->getSession()); + } + } + + /** + * Checks that the level is currently selected. Works both when grading rubrics and viewing graded rubrics. + * + * @Then /^the level with "(?P\d+)" points is selected for the rubric criterion "(?P(?:[^"]|\\")*)"$/ + * @throws ExpectationException + * @param string $criterionname + * @param int $points + * @return void + */ + public function the_level_with_points_is_selected_for_the_rubric_criterion($points, $criterionname) { + + $levelxpath = $this->get_criterion_xpath($criterionname) . + $this->get_level_xpath($points); + + // Works both for JS and non-JS. + // - JS: Class -> checked is there when is marked as green. + // - Non-JS: When editing a rubric definition, there are radio inputs and when viewing a + // grade @class contains checked. + $levelxpath .= "[" . + "contains(concat(' ', normalize-space(@class), ' '), ' checked ')" . + " or " . + "/descendant::input[@type='radio'][@checked='checked']" . + "]"; + + try { + $this->find('xpath', $levelxpath); + } catch (ElementNotFoundException $e) { + throw new ExpectationException('"' . $points . '" points level is not selected', $this->getSession()); + } + } + + /** + * Checks that the level is not currently selected. Works both when grading rubrics and viewing graded rubrics. + * + * @Then /^the level with "(?P\d+)" points is not selected for the rubric criterion "(?P(?:[^"]|\\")*)"$/ + * @throws ExpectationException + * @param string $criterionname + * @param int $points + * @return void + */ + public function the_level_with_points_is_not_selected_for_the_rubric_criterion($points, $criterionname) { + + $levelxpath = $this->get_criterion_xpath($criterionname) . + $this->get_level_xpath($points); + + // Works both for JS and non-JS. + // - JS: Class -> checked is there when is marked as green. + // - Non-JS: When editing a rubric definition, there are radio inputs and when viewing a + // grade @class contains checked. + $levelxpath .= "[not(contains(concat(' ', normalize-space(@class), ' '), ' checked '))]" . + "[./descendant::input[@type='radio'][@checked!='checked'] or not(./descendant::input[@type='radio'])]"; + + try { + $this->find('xpath', $levelxpath); + } catch (ElementNotFoundException $e) { + throw new ExpectationException('"' . $points . '" points level is selected', $this->getSession()); + } + } + + + /** + * Makes a hidden rubric field visible (if necessary) and sets a value on it. + * + * @param string $name The name of the field + * @param string $value The value to set + * @param bool $visible + * @return void + */ + protected function set_rubric_field_value($name, $value, $visible = false) { + + // Fields are hidden by default. + if ($this->running_javascript() == true && $visible === false) { + $xpath = "//*[@name='$name']/following-sibling::*[contains(concat(' ', normalize-space(@class), ' '), ' plainvalue ')]"; + $textnode = $this->find('xpath', $xpath); + $textnode->click(); + } + + // Set the value now. + $description = $this->find_field($name); + $description->setValue($value); + } + + /** + * Performs click confirming the action. + * + * @param NodeElement $node + * @return void + */ + protected function click_and_confirm($node) { + + // Clicks to perform the action. + $node->click(); + + // Confirms the delete. + if ($this->running_javascript()) { + $confirmbutton = $this->get_node_in_container( + 'button', + get_string('yes'), + 'dialogue', + get_string('confirmation', 'admin') + ); + $confirmbutton->click(); + } + } + + /** + * Returns the xpath representing a selected level. + * + * It is not including the path to the criterion. + * + * It is the xpath when grading a rubric or viewing a rubric, + * it is not the same xpath when editing a rubric. + * + * @param int $points + * @return string + */ + protected function get_level_xpath($points) { + return "//td[contains(concat(' ', normalize-space(@class), ' '), ' level ')]" . + "[./descendant::span[@class='scorevalue'][text()='$points']]"; + } + + /** + * Returns the xpath representing the selected criterion. + * + * It is the xpath when grading a rubric or viewing a rubric, + * it is not the same xpath when editing a rubric. + * + * @param string $criterionname Literal including the criterion name. + * @return string + */ + protected function get_criterion_xpath($criterionname) { + $literal = $this->getSession()->getSelectorsHandler()->xpathLiteral($criterionname); + return "//tr[contains(concat(' ', normalize-space(@class), ' '), ' criterion ')]" . + "[./descendant::td[@class='description'][text()=$literal]]"; + } +} diff --git a/grade/grading/tests/behat/behat_grading.php b/grade/grading/tests/behat/behat_grading.php new file mode 100644 index 00000000000..3c60cd6c060 --- /dev/null +++ b/grade/grading/tests/behat/behat_grading.php @@ -0,0 +1,181 @@ +. + +/** + * Grading methods steps definitions. + * + * @package core_grading + * @category test + * @copyright 2013 David Monllaó + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. + +require_once(__DIR__ . '/../../../../lib/behat/behat_base.php'); + +use Behat\Gherkin\Node\TableNode as TableNode, + Behat\Behat\Context\Step\Given as Given, + Behat\Behat\Context\Step\When as When; + +/** + * Generic grading methods step definitions. + * + * @package core_grading + * @category test + * @copyright 2013 David Monllaó + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class behat_grading extends behat_base { + + /** + * Goes to the selected advanced grading page. You should be in the course page when this step begins. + * + * @Given /^I go to "(?P(?:[^"]|\\")*)" advanced grading page$/ + * @param string $activityname + * @return Given[] + */ + public function i_go_to_advanced_grading_page($activityname) { + return array( + new Given('I follow "' . $this->escape($activityname) . '"'), + new Given('I follow "' . get_string('gradingmanagement', 'grading') . '"'), + ); + } + + /** + * Goes to the selected advanced grading definition page. You should be in the course page when this step begins. + * + * @Given /^I go to "(?P(?:[^"]|\\")*)" advanced grading definition page$/ + * @param string $activityname + * @return Given[] + */ + public function i_go_to_advanced_grading_definition_page($activityname) { + + // Transforming to literals, probably not necessary, just in case. + $newactionliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral(get_string("manageactionnew", "grading")); + $editactionliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral(get_string("manageactionedit", "grading")); + + // Working both when adding and editing. + $definitionxpath = "//a[@class='action']" . + "[./descendant::*[contains(., $newactionliteral) or contains(., $editactionliteral)]]"; + + return array( + new Given('I go to "' . $this->escape($activityname) . '" advanced grading page'), + new Given('I click on "' . $this->escape($definitionxpath) . '" "xpath_element"'), + ); + } + /** + * Goes to the student's advanced grading page. + * + * @Given /^I go to "(?P(?:[^"]|\\")*)" "(?P(?:[^"]|\\")*)" activity advanced grading page$/ + * @param string $userfullname The user full name including firstname and lastname. + * @param string $activityname The activity name + * @return Given[] + */ + public function i_go_to_activity_advanced_grading_page($userfullname, $activityname) { + + // Step to access the user grade page from the grading page. + $usergradetext = get_string('gradeuser', 'assign', $userfullname); + $gradeuserstep = new Given('I follow "' . $this->escape($usergradetext) . '"'); + + // Shortcut in case we already are in the grading page. + $usergradetextliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($usergradetext); + if ($this->getSession()->getPage()->find('named', array('link', $usergradetextliteral))) { + return $gradeuserstep; + } + + return array( + new Given('I follow "' . $this->escape($activityname) . '"'), + new Given('I follow "' . $this->escape(get_string('viewgrading', 'assign')) . '"'), + $gradeuserstep + ); + } + + /** + * Publishes current activity grading defined form as a public template. + * + * @Given /^I publish "(?P(?:[^"]|\\")*)" grading form definition as a public template$/ + * @param string $activityname + * @return Given[] + */ + public function i_publish_grading_form_definition_as_a_public_template($activityname) { + + return array( + new Given('I go to "' . $this->escape($activityname) . '" advanced grading page'), + new Given('I click on "' . $this->escape(get_string("manageactionshare", "grading")) . '" "link"'), + new Given('I press "' . get_string('continue') . '"') + ); + } + + /** + * Sets a previously created grading form as the activity grading form. + * + * @Given /^I set "(?P(?:[^"]|\\")*)" activity to use "(?P(?:[^"]|\\")*)" grading form$/ + * @param string $activityname + * @param string $templatename + * @return Given[] + */ + public function i_set_activity_to_use_grading_form($activityname, $templatename) { + + $templateliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($templatename); + + $templatexpath = "//h2[@class='template-name'][contains(., $templateliteral)]/" . + "following-sibling::div[contains(concat(' ', normalize-space(@class), ' '), ' template-actions ')]"; + + // Should work with both templates and own forms. + $literaltemplate = $this->getSession()->getSelectorsHandler()->xpathLiteral(get_string('templatepick', 'grading')); + $literalownform = $this->getSession()->getSelectorsHandler()->xpathLiteral(get_string('templatepickownform', 'grading')); + $usetemplatexpath = "//a[./descendant::div[text()=$literaltemplate]]|" . + "//a[./descendant::div[text()=$literalownform]]"; + + return array( + new Given('I go to "' . $this->escape($activityname) . '" advanced grading page'), + new Given('I follow "' . $this->escape(get_string('manageactionclone', 'grading')) . '"'), + new Given('I check "' . get_string('searchownforms', 'grading') . '"'), + new Given('I click on "' . get_string('search') . '" "button" in the "region-main" "region"'), + new Given('I click on "' . $this->escape($usetemplatexpath) . '" "xpath_element" ' . + 'in the "' . $this->escape($templatexpath) . '" "xpath_element"'), + new Given('I press "' . get_string('continue') . '"') + ); + } + + /** + * Saves the current page advanced grading form. + * + * @When /^I save the advanced grading form$/ + * @return When[] + */ + public function i_save_the_advanced_grading_form() { + return array( + new When('I press "' . get_string('savechanges') . '"'), + new When('I press "' . get_string('continue') . '"') + ); + } + + /** + * Grades an activity using advanced grading. Note the grade is set by other steps, depending on the grading method. + * + * @Given /^I complete the advanced grading form with these values:$/ + * @param TableNode $data + * @return Given[] + */ + public function i_complete_the_advanced_grading_form_with_these_values(TableNode $data) { + return array( + new Given('I fill the moodle form with:', $data), + new Given('I save the advanced grading form') + ); + } +} From 782e32e3e4f62cfd0716b64ec318bfd0a014a97e Mon Sep 17 00:00:00 2001 From: David Monllao Date: Thu, 6 Feb 2014 18:47:37 +0800 Subject: [PATCH 4/5] MDL-42404 behat: Adding feature to test reusing user's rubrics Automates MDLQA-1187, teachers can reuse their rubrics in other activity. --- .../rubric/tests/behat/reuse_rubrics.feature | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 grade/grading/form/rubric/tests/behat/reuse_rubrics.feature diff --git a/grade/grading/form/rubric/tests/behat/reuse_rubrics.feature b/grade/grading/form/rubric/tests/behat/reuse_rubrics.feature new file mode 100644 index 00000000000..a6998ab61bf --- /dev/null +++ b/grade/grading/form/rubric/tests/behat/reuse_rubrics.feature @@ -0,0 +1,52 @@ +@gradingform @gradingform_rubric +Feature: Reuse rubrics in other activities + In order to save time creating duplicated grading forms + As a teacher + I need to reuse rubrics that I created previously + + Background: + Given the following "users" exists: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@asd.com | + And the following "courses" exists: + | fullname | shortname | format | + | Course 1 | C1 | topics | + And the following "course enrolments" exists: + | user | course | role | + | teacher1 | C1 | editingteacher | + And I log in as "teacher1" + And I follow "Course 1" + And I turn editing mode on + And I add a "Assignment" to section "1" and I fill the form with: + | Assignment name | Test assignment 1 name | + | Description | Test assignment 1 description | + | Grading method | Rubric | + And I go to "Test assignment 1 name" advanced grading definition page + And I fill the moodle form with: + | Name | Assignment 1 rubric | + | Description | Assignment 1 description | + And I define the following rubric: + | Criterion 1 | Level 11 | 11 | Level 12 | 12 | Level 3 | 13 | + | Criterion 2 | Level 21 | 21 | Level 22 | 22 | Level 3 | 23 | + | Criterion 3 | Level 31 | 31 | Level 32 | 32 | + And I press "Save rubric and make it ready" + And I follow "Course 1" + When I add a "Assignment" to section "1" and I fill the form with: + | Assignment name | Test assignment 2 name | + | Description | Test assignment 2 description | + | Grading method | Rubric | + And I set "Test assignment 2 name" activity to use "Assignment 1 rubric" grading form + Then I should see "Ready for use" + And I should see "Criterion 1" + And I should see "Criterion 2" + And I should see "Criterion 3" + And I go to "Test assignment 1 name" advanced grading definition page + And I should see "Criterion 1" + And I should see "Criterion 2" + And I should see "Criterion 3" + And I press "Cancel" + + @javascript + Scenario: A teacher can reuse one of his/her previously created rubrics, with Javascript enabled + + Scenario: A teacher can reuse one of his/her previously created rubrics, with Javascript disabled From 0a1c3118ba42b3c15241bb7553d8e67c41449b3f Mon Sep 17 00:00:00 2001 From: David Monllao Date: Thu, 6 Feb 2014 19:07:50 +0800 Subject: [PATCH 5/5] MDL-42404 behat: Covering MDL-39710 with behat features I've contributed to a large an ugly scenario in favour of performance as separating it in another scenario or feature would add 3 or 4 minutes to the suite. --- .../rubric/tests/behat/edit_rubric.feature | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/grade/grading/form/rubric/tests/behat/edit_rubric.feature b/grade/grading/form/rubric/tests/behat/edit_rubric.feature index d883d9f3686..2ff3a9ae3f4 100644 --- a/grade/grading/form/rubric/tests/behat/edit_rubric.feature +++ b/grade/grading/form/rubric/tests/behat/edit_rubric.feature @@ -74,6 +74,7 @@ Feature: Rubrics can be created and edited And I follow "Course 1" And I follow "Test assignment 1 name" And I should see "22.62" in the ".feedback" "css_element" + And I should see "Rubric test description" in the ".feedback" "css_element" And I should see "In general... work harder..." And the level with "10" points is selected for the rubric criterion "Criterion 2" And the level with "20" points is selected for the rubric criterion "Criterion 1" @@ -129,6 +130,28 @@ Feature: Rubrics can be created and edited And I follow "Test assignment 1 name" And I should see "12.16" in the ".feedback" "css_element" And the level with "20" points is not selected for the rubric criterion "Criterion 1" + # Hide all rubric info for students + And I log out + And I log in as "teacher1" + And I follow "Course 1" + And I go to "Test assignment 1 name" advanced grading definition page + And I uncheck "Allow users to preview rubric used in the module (otherwise rubric will only become visible after grading)" + And I uncheck "Display rubric description during evaluation" + And I uncheck "Display rubric description to those being graded" + And I uncheck "Display points for each level during evaluation" + And I uncheck "Display points for each level to those being graded" + And I press "Save" + And I select "Do not mark for regrade" from "menurubricregrade" + And I press "Continue" + And I log out + # Students should not see anything. + And I log in as "student1" + And I follow "Course 1" + And I follow "Test assignment 1 name" + And I should not see "Criterion 1" in the ".submissionstatustable" "css_element" + And I should not see "Criterion 2" in the ".submissionstatustable" "css_element" + And I should not see "Criterion 3" in the ".submissionstatustable" "css_element" + And I should not see "Rubric test description" in the ".feedback" "css_element" @javascript Scenario: I can use rubrics to grade and edit them later updating students grades with Javascript enabled