diff --git a/tests/behat/behat_mod_coursework.php b/tests/behat/behat_mod_coursework.php index 8d3a9edd..4759c005 100644 --- a/tests/behat/behat_mod_coursework.php +++ b/tests/behat/behat_mod_coursework.php @@ -901,6 +901,13 @@ public function the_coursework_start_date_is_disabled() { $this->coursework->update_attribute('startdate', 0); } + /** + * @Given /^the coursework start date is now$/ + */ + public function the_coursework_start_date_is_now() { + $this->coursework->update_attribute('startdate', time()); + } + /** * @Given /^the coursework start date is in the future$/ */ @@ -2651,15 +2658,28 @@ public function i_have_a_submission() { /** * Named student has a submission. - * @Given /^the student called "([\w]+)" has a( finalised)? submission*$/ + * @Given /^the student called "(?P(?:[^"]|\\")*)" has a( finalised)? submission*$/ */ - public function named_student_has_a_submission(string $firstname, bool $finalised = false) { + public function named_student_has_a_submission(string $fullname, bool $finalised = false) { global $DB; $generator = testing_util::get_data_generator()->get_plugin_generator('mod_coursework'); - $userid = $DB->get_field_sql( - "SELECT id FROM {user} WHERE firstname = ? AND lastname LIKE 'student%'", - [$firstname] - ); + + // Check if the name consists of first- and last name. + $nameparts = explode(' ', $fullname, 2); + $firstname = $nameparts[0]; + $lastname = $nameparts[1] ?? ''; + + if (empty($lastname)) { + $userid = $DB->get_field_sql( + "SELECT id FROM {user} WHERE firstname = ? AND lastname LIKE 'student%'", + [$firstname] + ); + } else { + $userid = $DB->get_field_sql( + "SELECT id FROM {user} WHERE firstname = ? AND lastname = ?", + [$firstname, $lastname] + ); + } if ($userid) { $submission = new stdClass(); $submission->allocatableid = $userid; @@ -3360,6 +3380,25 @@ public function i_should_see_submitted_date($date) { } } + /** + * For matching a late submitted date ignoring the time part + * + * Example: I should see late submitted date 4 July 2025 + * + * @Given /^I should see late submitted date "(?P(?:[^"]|\\")*)"$/ + */ + public function i_should_see_late_submitted_date($date) { + $page = $this->getsession()->getpage(); + $match = $page->find('xpath', "//li[starts-with(normalize-space(string()), 'Submitted Late $date')]"); + + if (!$match) { + throw new ExpectationException( + "Should have seen expected submitted late date $date, but it was not there", + $this->getsession() + ); + } + } + /** * @Given /^sample marking includes student for stage (\d)$/ */ @@ -3621,4 +3660,281 @@ public function i_set_the_field_to_replacing_line_breaks($field, $value) { $value = str_replace('\n', chr(10), $value); $this->execute([behat_forms::class, 'i_set_the_field_to'], [$field, $value]); } + + /** + * Sets an extension deadline for a student in a coursework. + * + * Example: And the coursework extension for "Student 1" in "Coursework 1" is "1 January 2027 08:00" + * Example: And the coursework extension for "Student 1" in "Coursework 1" is "## + 1 month ##" + * + * @Given /^the coursework extension for "(?P(?:[^"]|\\")*)" in "(?P(?:[^"]|\\")*)" is "(?P(?:[^"]|\\")*)"$/ + */ + public function set_extension_for_user($fullname, $cwname, $datestr) { + global $DB; + + // Check date string. + if (is_int($datestr) || (is_string($datestr) && ctype_digit($datestr))) { + $extendeddeadline = (int)$datestr; + } else { + $extendeddeadline = strtotime($datestr); + if ($extendeddeadline === false) { + throw new \InvalidArgumentException('Invalid user extension date string: ' . $datestr); + } + } + + // Find the coursework by name. + $cw = $DB->get_record('coursework', ['name' => $cwname], '*', MUST_EXIST); + + $user = $this->get_user_from_fullname($fullname); + + // See if an extension already exists. + $existing = $DB->get_record('coursework_extensions', [ + 'courseworkid' => $cw->id, + 'allocatableid' => $user->id, + 'allocatabletype' => 'user', + ]); + + $record = new stdClass(); + $record->courseworkid = $cw->id; + $record->allocatableid = $user->id; + $record->allocatabletype = 'user'; + $record->extended_deadline = $extendeddeadline; + $record->createdbyid = 2; // Admin ID. + + if ($existing) { + $record->id = $existing->id; + $DB->update_record('coursework_extensions', $record); + } else { + $DB->insert_record('coursework_extensions', $record); + } + } + + /** + * @Given /^the following markers are allocated:$/ + * + * @param TableNode $allocations Students and their markers. + */ + public function the_following_markers_are_allocated(TableNode $allocations) { + global $DB; + + $datahash = $allocations->getHash(); + + foreach ($datahash as $allocate) { + $stages = ['assessor_1']; + if (isset($allocate['moderator'])) { + $stages[] = 'moderator'; + } else if (isset($allocate['assessor_2'])) { + $stages[] = 'assessor_2'; + } + + $student = $this->get_user_from_fullname($allocate['student']); + foreach ($stages as $stage) { + $marker = $this->get_user_from_fullname($allocate[$stage]); + + $record = $DB->get_record('coursework_allocation_pairs', [ + 'courseworkid' => $this->coursework->id, + 'allocatableid' => $student->id, + 'allocatableuser' => $student->id, + 'stageidentifier' => $stage, + 'allocatabletype' => 'user', + ]); + if ($record) { + $record->assessorid = $marker->id; + $record->ismanual = 1; + $DB->update_record('coursework_allocation_pairs', $record); + } else { + $record = new stdClass(); + $record->courseworkid = $this->coursework->id; + $record->allocatableid = $student->id; + $record->allocatableuser = $student->id; + $record->assessorid = $marker->id; + $record->stageidentifier = $stage; + $record->allocatabletype = 'user'; + $record->ismanual = 1; + $DB->insert_record('coursework_allocation_pairs', $record); + } + } + } + allocation::remove_cache($this->coursework->id); + } + + /** + * Return a user record from a given full name ("firstname lastname") + * + * @param string $fullname + * @return stdClass The user record + * @throws coding_exception When the full name is invalid or the user cannot be found + */ + private function get_user_from_fullname(string $fullname) { + global $DB; + + // Check if the name consists of first- and last name. + $nameparts = explode(' ', $fullname, 2); + $firstname = $nameparts[0]; + $lastname = $nameparts[1] ?? ''; + + if (empty($lastname)) { + throw new coding_exception("Full name '{$fullname}' must contain at least one space between first and last name."); + } + + // Find user by full name (firstname + lastname). + $user = $DB->get_record('user', [ + 'firstname' => $firstname, + 'lastname' => $lastname, + ]); + + if (!$user) { + throw new coding_exception("Could not find user with name '{$fullname}'."); + } + return $user; + } + + /** + * Inserts a grade directly into coursework_feedbacks table. + * + * @When /^the submission from "(?P[^"]*)" is marked by "(?P[^"]*)" with:$/ + */ + public function mark_coursework_submission_directly( + string $studentfullname, + string $markerfullname, + TableNode $table + ) { + global $DB; + + $student = $this->get_user_from_fullname($studentfullname); + $marker = $this->get_user_from_fullname($markerfullname); + + // Resolve submission for this student. + $submission = $DB->get_record('coursework_submissions', [ + 'courseworkid' => $this->coursework->id, + 'allocatableid' => $student->id, + ]); + if (!$submission) { + throw new ExpectationException("Submission for '$studentfullname' not found", $this->getSession()); + } + + // Resolve marker allocation. + $allocation = $DB->get_record('coursework_allocation_pairs', [ + 'courseworkid' => $this->coursework->id, + 'assessorid' => $marker->id, + 'allocatableid' => $student->id, + ]); + if (!$allocation) { + throw new ExpectationException("Marker '$markerfullname' for '$studentfullname' not found", $this->getSession()); + } + + // Extract the provided table values. + $data = $table->getRowsHash(); + + $mark = isset($data['Mark']) ? floatval($data['Mark']) : null; + $comment = $data['Comment'] ?? ''; + $finalised = $data['Finalised'] ?? ''; + + if ($mark === null) { + throw new ExpectationException("Missing 'Mark' value in table", $this->getSession()); + } + + // Check if there is already a feedback record. + $existing = $DB->get_record('coursework_feedbacks', [ + 'submissionid' => $submission->id, + 'assessorid' => $marker->id, + 'stageidentifier' => $allocation->stageidentifier, + ]); + + // Insert/update feedback record. + $feedback = new stdClass(); + $feedback->submissionid = $submission->id; + $feedback->assessorid = $marker->id; + $feedback->stageidentifier = $allocation->stageidentifier; + $feedback->grade = $mark; + $feedback->feedbackcomment = $comment; + $feedback->lasteditedbyuser = $marker->id; + $feedback->finalised = $finalised; + $feedback->timecreated = time(); + $feedback->timemodified = time(); + + if ($existing) { + $feedback->id = $existing->id; + $DB->update_record('coursework_feedbacks', $feedback); + } else { + $DB->insert_record('coursework_feedbacks', $feedback); + } + } + + /** + * Inserts a moderation directly into coursework_mod_agreements table. + * + * @When /^the submission from "(?P[^"]*)" is moderated by "(?P[^"]*)" with:$/ + */ + public function moderate_submission_directly( + string $studentfullname, + string $moderatorfullname, + TableNode $table + ) { + global $DB; + + $student = $this->get_user_from_fullname($studentfullname); + $moderator = $this->get_user_from_fullname($moderatorfullname); + + // Resolve submission for this student. + $submission = $DB->get_record('coursework_submissions', [ + 'courseworkid' => $this->coursework->id, + 'allocatableid' => $student->id, + ]); + if (!$submission) { + throw new ExpectationException("Submission for '$studentfullname' not found", $this->getSession()); + } + + // Resolve moderator allocation. + $allocation = $DB->get_record('coursework_allocation_pairs', [ + 'courseworkid' => $this->coursework->id, + 'assessorid' => $moderator->id, + 'allocatableid' => $student->id, + 'stageidentifier' => 'moderator', + ]); + if (!$allocation) { + throw new ExpectationException("Moderator '$moderatorfullname' for '$studentfullname' not found", $this->getSession()); + } + + // Resolve feedback. + $params = ['submissionid' => $submission->id]; + $sql = "SELECT * + FROM {coursework_feedbacks} + WHERE submissionid = :submissionid + AND stageidentifier LIKE 'assessor_%'"; + $feedback = $DB->get_record_sql($sql, $params); + + if (!$feedback) { + throw new ExpectationException("Feedback for '$studentfullname' not found", $this->getSession()); + } + + // Extract the provided table values. + $data = $table->getRowsHash(); + $agreementtext = $data['Agreement'] ?? ''; + $comment = $data['Comment'] ?? ''; + + // Check if there is already an agreement record. + $existing = $DB->get_record('coursework_mod_agreements', [ + 'feedbackid' => $feedback->id, + 'moderatorid' => $moderator->id, + ]); + + // Insert/update agreement record. + $agreement = new stdClass(); + $agreement->feedbackid = $feedback->id; + $agreement->moderatorid = $moderator->id; + $agreement->agreement = $agreementtext; + $agreement->modcomment = $comment; + $agreement->modcommentformat = FORMAT_HTML; + $agreement->lasteditedby = $moderator->id; + $agreement->timecreated = time(); + $agreement->timemodified = time(); + + if ($existing) { + $agreement->id = $existing->id; + $DB->update_record('coursework_mod_agreements', $agreement); + } else { + $DB->insert_record('coursework_mod_agreements', $agreement); + } + } } diff --git a/tests/behat/double_marking_blind.feature b/tests/behat/double_marking_blind.feature new file mode 100644 index 00000000..da9e57fe --- /dev/null +++ b/tests/behat/double_marking_blind.feature @@ -0,0 +1,349 @@ +@mod @mod_coursework @double-marking-blind +Feature: Double marking - blind + In order to ensure double marking works correctly + As an admin + I want to perform the full coursework workflow with blind marking. + + Background: + And the following "roles" exist: + | shortname | name | archetype | + | courseworkexamoffice | courseworkexamoffice | | + | courseworkdbm | courseworkdbm | | + | courseworkmoderator | courseworkmoderator | | + | norole | norole | | + + And the following "role capability" exists: + | role | courseworkexamoffice | + | mod/coursework:addinstance | allow | + | moodle/role:assign | allow | + | mod/coursework:addagreedgrade | allow | + | mod/coursework:addallocatedagreedgrade | allow | + | mod/coursework:addgeneralfeedback | allow | + | mod/coursework:addinitialgrade | allow | + | mod/coursework:addplagiarismflag | allow | + | mod/coursework:administergrades | allow | + | mod/coursework:allocate | allow | + | mod/coursework:canexportfinalgrades | allow | + | mod/coursework:editagreedgrade | allow | + | mod/coursework:editallocatedagreedgrade | allow | + | mod/coursework:editinitialgrade | allow | + | mod/coursework:editpersonaldeadline | allow | + | mod/coursework:grade | allow | + | mod/coursework:grantextensions | allow | + | mod/coursework:moderate | allow | + | mod/coursework:publish | allow | + | mod/coursework:receivesubmissionnotifications | allow | + | mod/coursework:revertfinalised | allow | + | mod/coursework:submitonbehalfof | allow | + | mod/coursework:updateplagiarismflag | allow | + | mod/coursework:view | allow | + | mod/coursework:viewallgradesatalltimes | allow | + | mod/coursework:viewallstudents | allow | + | mod/coursework:viewanonymous | allow | + | mod/coursework:viewextensions | allow | + | moodle/course:manageactivities | allow | + | moodle/calendar:manageentries | allow | + + And the following "role capability" exists: + | role | courseworkdbm | + | mod/coursework:addallocatedagreedgrade | allow | + | mod/coursework:addgeneralfeedback | allow | + | mod/coursework:addinitialgrade | allow | + | mod/coursework:addplagiarismflag | allow | + | mod/coursework:editagreedgrade | allow | + | mod/coursework:editallocatedagreedgrade | allow | + | mod/coursework:editinitialgrade | allow | + | mod/coursework:grade | allow | + | mod/coursework:receivesubmissionnotifications | allow | + | mod/coursework:updateplagiarismflag | allow | + | mod/coursework:view | allow | + | mod/coursework:viewextensions | allow | + + And the following "role capability" exists: + | role | courseworkmoderator | + | mod/coursework:addplagiarismflag | allow | + | mod/coursework:grade | allow | + | mod/coursework:moderate | allow | + | mod/coursework:updateplagiarismflag | allow | + | mod/coursework:view | allow | + | mod/coursework:viewallgradesatalltimes | allow | + | mod/coursework:viewextensions | allow | + + And the following "users" exist: + | username | firstname | lastname | email | + | manager | Assessment | Manager | manager@example.com | + | marker1 | Marker | 1 | marker1@example.com | + | marker2 | Marker | 2 | marker2@example.com | + | marker3 | Marker | 3 | marker3@example.com | + | moderator1 | Moderator | 1 | moderator1@example.com | + | student1 | Student | 1 | student1@example.com | + | student2 | Student | 2 | student2@example.com | + | student3 | Student | 3 | student3@example.com | + + Given there is a course + And the following "course enrolments" exist: + | user | course | role | + | manager | C1 | courseworkexamoffice | + | moderator1 | C1 | courseworkmoderator | + | moderator1 | C1 | teacher | + | marker1 | C1 | courseworkdbm | + | marker2 | C1 | courseworkdbm | + | marker3 | C1 | courseworkdbm | + | student1 | C1 | student | + | student2 | C1 | student | + | student3 | C1 | student | + + Given there is a coursework + And the coursework start date is now + And the coursework "numberofmarkers" setting is "2" in the database + And the coursework "blindmarking" setting is "1" in the database + And the coursework "allocationenabled" setting is "1" in the database + And the coursework "extensionsenabled" setting is "1" in the database + And the coursework "allowlatesubmissions" setting is "1" in the database + And the coursework "moderationagreementenabled" setting is "0" in the database + And the coursework "filetypes" setting is "pdf" in the database + And the coursework "assessorallocationstrategy" setting is "none" in the database + + Scenario: Create coursework assignment with double blind marking + Given I am on the "Course 1" "course" page logged in as "manager" + And I add a coursework activity to course "Course 1" section "2" and I fill the form with: + | Coursework title | Coursework – Double marking blind | + | Description | Test coursework description | + | Display description on course page | Yes | + | Start date | ##now## | + | Deadline for submissions: | ##now + 15 minutes## | + | Use marking deadline | Yes | + | Types of file that students are allowed to submit | pdf | + | Enable plagiarism flagging | Yes | + | Number of times each submission should initially be marked. | 2 | + | Marker allocation enabled | Yes | + | Marker allocation strategy | Manual | + | Automatic agreement of marks | Percentage distance | + | Automatic agreement range | 10 | + | View initial markers' grades | No | + | Auto-populate agreed feedback comment | Yes | + | Blind marking | Yes | + | Marker anonymity | Yes | + + Then I should see "Coursework – Double marking blind" + + Scenario: Allocate markers + Given I am on the "Course 1" "course" page logged in as "manager" + And I follow "Coursework 1" + And I follow "Allocate markers" + And I set the field "Allocation strategy" to "Manual" + And I press "Apply" + Then I should see "Please make sure markers are allocated" + + When I set the field with xpath "//tr[contains(.,'Student 1')]//td[@class='assessor_1']//select" to "Marker 1" + And I set the field with xpath "//tr[contains(.,'Student 1')]//td[@class='assessor_2']//select" to "Marker 2" + And I set the field with xpath "//tr[contains(.,'Student 2')]//td[@class='assessor_1']//select" to "Marker 1" + And I set the field with xpath "//tr[contains(.,'Student 2')]//td[@class='assessor_2']//select" to "Marker 2" + And I set the field with xpath "//tr[contains(.,'Student 3')]//td[@class='assessor_1']//select" to "Marker 1" + And I set the field with xpath "//tr[contains(.,'Student 3')]//td[@class='assessor_2']//select" to "Marker 2" + And I press "Save" + + Then I should see "Marker 1" in the "Student 1" "table_row" + And I should see "Marker 2" in the "Student 1" "table_row" + And I should not see "Marker 3" in the "Student 1" "table_row" + And I should see "Marker 1" in the "Student 2" "table_row" + And I should see "Marker 2" in the "Student 2" "table_row" + And I should not see "Marker 3" in the "Student 2" "table_row" + And I should see "Marker 1" in the "Student 3" "table_row" + And I should see "Marker 2" in the "Student 3" "table_row" + And I should not see "Marker 3" in the "Student 3" "table_row" + + Scenario: Check anonymity + Given the following markers are allocated: + | student | assessor_1 | assessor_2 | + | Student 1 | Marker 1 | Marker 2 | + | Student 2 | Marker 1 | Marker 2 | + | Student 3 | Marker 1 | Marker 2 | + + And I am on the "Course 1" "course" page logged in as "marker1" + And I follow "Coursework 1" + Then I should see "Submissions" + And I should see "Hidden" + And I should not see "Student 1" + And I should not see "Student 2" + And I should not see "Student 3" + + @javascript + Scenario: Add extension to a student + Given the following markers are allocated: + | student | assessor_1 | assessor_2 | + | Student 1 | Marker 1 | Marker 2 | + | Student 2 | Marker 1 | Marker 2 | + | Student 3 | Marker 1 | Marker 2 | + + And I am on the "Course 1" "course" page logged in as "manager" + + And I follow "Coursework 1" + And I press "Actions" + And I wait until the page is ready + And I click on "Submission extension" "link" + And I wait until the page is ready + And I set the following fields to these values: + | extended_deadline[day] | ##+ 1 month##%d## | + | extended_deadline[month] | ##+ 1 month##%B## | + | extended_deadline[year] | ##+ 1 month##%Y## | + | extended_deadline[hour] | 08 | + | extended_deadline[minute] | 00 | + And I click on "Save" "button" in the "Extended deadline" "dialogue" + And I should see "##+ 1 month##%d %B %Y, 8:00 AM##" in the "Student 1" "table_row" + Then I visit the coursework page + And I should see "##+ 1 month##%d %B %Y, 8:00 AM##" in the "Student 1" "table_row" + + @javascript @_file_upload + Scenario: Student can submit a PDF file + When I am on the "Course 1" "course" page logged in as "student1" + + And I follow "Coursework 1" + When I visit the coursework page + And I click on "Upload your submission" "link" + And I upload "mod/coursework/tests/files_for_uploading/Test_document.pdf" file to "Upload a file" filemanager + And I save the submission + Then I should be on the coursework page + And I should see the file on the page + And I should see the edit submission button + And I should see submission status "Submitted" + And I should see submitted date "##today##%d %B %Y##" + + @javascript @_file_upload + Scenario: Student with extension can submit after deadline w/o being late + # The coursework deadline has passed 5 minutes ago + Given the coursework deadline date is "##-5 minutes##" + + # Student has extension so late submission is in time + And the coursework extension for "Student 1" in "Coursework 1" is "## + 1 month ##" + When I am on the "Course 1" "course" page logged in as "student1" + And I follow "Coursework 1" + When I visit the coursework page + And I click on "Upload your submission" "link" + And I upload "mod/coursework/tests/files_for_uploading/Test_document.pdf" file to "Upload a file" filemanager + And I save the submission + Then I should be on the coursework page + And I should see the file on the page + And I should see the edit submission button + And I should see submission status "Submitted" + And I should see submitted date "##today##%d %B %Y##" + + @javascript @_file_upload + Scenario: Student has no extension so submission is late + # The coursework deadline has passed 5 minutes ago + Given the coursework deadline date is "##-5 minutes##" + + When I am on the "Course 1" "course" page logged in as "student1" + And I follow "Coursework 1" + And I click on "Upload your submission" "link" + And I upload "mod/coursework/tests/files_for_uploading/Test_document.pdf" file to "Upload a file" filemanager + And I save the submission + Then I should be on the coursework page + And I should see the file on the page + And I should see submission status "Submitted" + And I should see "late" + And I should see late submitted date "##today##%d %B %Y##" + + @javascript @_file_upload + Scenario: Manager can submit on behalf of students. + Given the student called "Student 1" has a finalised submission + + When I am on the "Course 1" "course" page logged in as "manager" + And I follow "Coursework 1" + Then I should see "Add mark" + + # Unfinalise a submission. + And I press "Actions" + And I wait until the page is ready + And I click on "Unfinalise submission" "link" + And I wait until the page is ready + Then I should see "Are you sure you want to unfinalise the submission" + And I press "Yes" + Then I should not see "Agree marking" + + # Submit on behalf. + When I press "Actions" + And I wait until the page is ready + And I click on "Edit submission on behalf of this student" "link" + And I wait until the page is ready + Then I should see "Edit your submission" + And I should see "myfile.txt" + And I follow "myfile.txt" + And I wait until "Delete" "button" exists + # Delete previous file + # And I click on "Delete" "button" - does not work + And I click on "//div[contains(@class,'fp-select')]//button[contains(@class,'fp-file-delete')]" "xpath" + Then I should see "Are you sure you want to delete this file?" + And I press "Yes" + # Now upload a new file + And I upload "mod/coursework/tests/files_for_uploading/TestPDF1.pdf" file to "Upload a file" filemanager + And I save the submission + Then I should be on the coursework page + And I should not see "myfile.txt" + + @javascript @_file_upload + Scenario: Mark the assignments + Given the following markers are allocated: + | student | assessor_1 | assessor_2 | + | Student 1 | Marker 1 | Marker 2 | + | Student 2 | Marker 1 | Marker 2 | + | Student 3 | Marker 1 | Marker 2 | + + And the student called "Student 1" has a finalised submission + And the student called "Student 2" has a finalised submission + And the student called "Student 3" has a finalised submission + + And I am on the "Course 1" "course" page logged in as "marker1" + And I follow "Coursework 1" + + And I click on "Add mark" "link" in the "(//tr[contains(@class,'mod-coursework-submissions-row')])[1]" "xpath_element" + + Then I should see "Marking for Hidden" + When I set the following fields to these values: + | Mark | 71 | + | Comment | Test comment 1 | + And I upload "mod/coursework/tests/files_for_uploading/Test_document.pdf" file to "Upload a file" filemanager + And I press "Save as draft" + + And I click on "Add mark" "link" in the "(//tr[contains(@class,'mod-coursework-submissions-row')])[2]" "xpath_element" + + Then I should see "Marking for Hidden" + When I set the following fields to these values: + | Mark | 72 | + | Comment | Test comment 2 | + And I press "Save as draft" + + And I click on "Add mark" "link" in the "(//tr[contains(@class,'mod-coursework-submissions-row')])[3]" "xpath_element" + Then I should see "Marking for Hidden" + When I set the following fields to these values: + | Mark | 73 | + | Comment | Test comment 3 | + And I press "Save as draft" + + Then I should see "Submissions" + And "71" "link" should exist + And "72" "link" should exist + And "73" "link" should exist + + @javascript + Scenario: Verify assignment shows in marking + Given the following markers are allocated: + | student | assessor_1 | assessor_2 | + | Student 1 | Marker 1 | Marker 2 | + + And the student called "Student 1" has a finalised submission + + And the submission from "Student 1" is marked by "Marker 1" with: + | Mark | 70 | + | Comment | Excellent work! | + + And the submission from "Student 1" is marked by "Marker 2" with: + | Mark | 65 | + | Comment | Nice! | + + And I am on the "Course 1" "course" page logged in as "student1" + And I follow "Coursework 1" + And I wait until the page is ready + Then I should see "Submission" + And I should see "In marking" + And I should not see "Edit your submission" diff --git a/tests/behat/moderation_marking.feature b/tests/behat/moderation_marking.feature new file mode 100644 index 00000000..a759b6a0 --- /dev/null +++ b/tests/behat/moderation_marking.feature @@ -0,0 +1,401 @@ +@mod @mod_coursework @moderation-marking +Feature: Moderated marking - blind + In order to ensure double marking works correctly + As an admin + I want to perform the full coursework workflow with moderated blind marking. + + Background: + And the following "roles" exist: + | shortname | name | archetype | + | courseworkexamoffice | courseworkexamoffice | | + | courseworkdbm | courseworkdbm | | + | courseworkmoderator | courseworkmoderator | | + | norole | norole | | + + And the following "role capability" exists: + | role | courseworkexamoffice | + | mod/coursework:addinstance | allow | + | moodle/role:assign | allow | + | mod/coursework:addagreedgrade | allow | + | mod/coursework:addallocatedagreedgrade | allow | + | mod/coursework:addgeneralfeedback | allow | + | mod/coursework:addinitialgrade | allow | + | mod/coursework:addplagiarismflag | allow | + | mod/coursework:administergrades | allow | + | mod/coursework:allocate | allow | + | mod/coursework:canexportfinalgrades | allow | + | mod/coursework:editagreedgrade | allow | + | mod/coursework:editallocatedagreedgrade | allow | + | mod/coursework:editinitialgrade | allow | + | mod/coursework:editpersonaldeadline | allow | + | mod/coursework:grade | allow | + | mod/coursework:grantextensions | allow | + | mod/coursework:moderate | allow | + | mod/coursework:publish | allow | + | mod/coursework:receivesubmissionnotifications | allow | + | mod/coursework:revertfinalised | allow | + | mod/coursework:submitonbehalfof | allow | + | mod/coursework:updateplagiarismflag | allow | + | mod/coursework:view | allow | + | mod/coursework:viewallgradesatalltimes | allow | + | mod/coursework:viewallstudents | allow | + | mod/coursework:viewanonymous | allow | + | mod/coursework:viewextensions | allow | + | moodle/course:manageactivities | allow | + | moodle/calendar:manageentries | allow | + + And the following "role capability" exists: + | role | courseworkdbm | + | mod/coursework:addallocatedagreedgrade | allow | + | mod/coursework:addgeneralfeedback | allow | + | mod/coursework:addinitialgrade | allow | + | mod/coursework:addplagiarismflag | allow | + | mod/coursework:editagreedgrade | allow | + | mod/coursework:editallocatedagreedgrade | allow | + | mod/coursework:editinitialgrade | allow | + | mod/coursework:grade | allow | + | mod/coursework:receivesubmissionnotifications | allow | + | mod/coursework:updateplagiarismflag | allow | + | mod/coursework:view | allow | + | mod/coursework:viewextensions | allow | + + And the following "role capability" exists: + | role | courseworkmoderator | + | mod/coursework:addplagiarismflag | allow | + | mod/coursework:grade | allow | + | mod/coursework:moderate | allow | + | mod/coursework:updateplagiarismflag | allow | + | mod/coursework:view | allow | + | mod/coursework:viewallgradesatalltimes | allow | + | mod/coursework:viewextensions | allow | + + And the following "users" exist: + | username | firstname | lastname | email | + | manager | Assessment | Manager | manager@example.com | + | marker1 | Marker | 1 | marker1@example.com | + | marker2 | Marker | 2 | marker2@example.com | + | marker3 | Marker | 3 | marker3@example.com | + | moderator1 | Moderator | 1 | moderator1@example.com | + | student1 | Student | 1 | student1@example.com | + | student2 | Student | 2 | student2@example.com | + | student3 | Student | 3 | student3@example.com | + + Given there is a course + And the following "course enrolments" exist: + | user | course | role | + | manager | C1 | courseworkexamoffice | + | moderator1 | C1 | courseworkmoderator | + | moderator1 | C1 | teacher | + | marker1 | C1 | courseworkdbm | + | marker2 | C1 | courseworkdbm | + | marker3 | C1 | courseworkdbm | + | student1 | C1 | student | + | student2 | C1 | student | + | student3 | C1 | student | + + Given there is a coursework + And the coursework start date is now + And the coursework "numberofmarkers" setting is "1" in the database + And the coursework "blindmarking" setting is "1" in the database + And the coursework "allocationenabled" setting is "1" in the database + And the coursework "extensionsenabled" setting is "1" in the database + And the coursework "allowlatesubmissions" setting is "1" in the database + And the coursework "moderationagreementenabled" setting is "1" in the database + And the coursework "filetypes" setting is "pdf" in the database + And the coursework "assessorallocationstrategy" setting is "none" in the database + + Scenario: Moderate the assessment + Given the following markers are allocated: + | student | assessor_1 | moderator | + | Student 1 | Marker 1 | Moderator 1 | + | Student 2 | Marker 1 | Moderator 1 | + | Student 3 | Marker 2 | Moderator 1 | + + And the student called "Student 1" has a finalised submission + And the student called "Student 2" has a finalised submission + And the student called "Student 3" has a finalised submission + + And the submission from "Student 1" is marked by "Marker 1" with: + | Mark | 70 | + | Comment | Excellent work! | + | Finalised | 1 | + + And the submission from "Student 2" is marked by "Marker 1" with: + | Mark | 75 | + | Comment | Superb work! | + | Finalised | 1 | + + And the submission from "Student 3" is marked by "Marker 2" with: + | Mark | 50 | + | Comment | I've seen worse... | + | Finalised | 1 | + + And I am on the "Course 1" "course" page logged in as "moderator1" + And I follow "Coursework 1" + And I click on "Agree marking" "link" in the ".mod-coursework-submissions-row:nth-child(1)" "css_element" + Then I should see "Moderation for" + And I set the field "Moderation agreement" to "Agreed" + And I press "Save changes" + And I click on "Agree marking" "link" in the ".mod-coursework-submissions-row:nth-child(2)" "css_element" + Then I should see "Moderation for" + And I set the field "Moderation agreement" to "Disagreed" + And I set the field "Comment" to "I don't like it!" + And I press "Save changes" + Then I should see "Agreed" in the "70" "table_row" + Then I should see "Disagreed" in the "75" "table_row" + When I follow "Disagreed" + And I wait until the page is ready + Then I should see "I don't like it!" + + Scenario: Check moderation + Given the following markers are allocated: + | student | assessor_1 | moderator | + | Student 1 | Marker 1 | Moderator 1 | + | Student 2 | Marker 1 | Moderator 1 | + | Student 3 | Marker 2 | Moderator 1 | + + And the student called "Student 1" has a finalised submission + And the student called "Student 2" has a finalised submission + And the student called "Student 3" has a finalised submission + + And the submission from "Student 1" is marked by "Marker 1" with: + | Mark | 70 | + | Comment | Excellent work! | + | Finalised | 1 | + + And the submission from "Student 2" is marked by "Marker 1" with: + | Mark | 75 | + | Comment | Superb work! | + | Finalised | 1 | + + And the submission from "Student 3" is marked by "Marker 2" with: + | Mark | 50 | + | Comment | I've seen worse... | + | Finalised | 1 | + + And the submission from "Student 1" is moderated by "Moderator 1" with: + | Agreement | agreed | + + And the submission from "Student 2" is moderated by "Moderator 1" with: + | Agreement | disagreed | + | Comment | I don't like it at all! | + + And I am on the "Course 1" "course" page logged in as "marker1" + And I follow "Coursework 1" + # See the moderation + Then I should see "Moderation" in the "70" "table_row" + Then I should see "Moderator 1" in the "70" "table_row" + Then I should see "Agreed" in the "70" "table_row" + Then I should see "Moderation" in the "75" "table_row" + Then I should see "Moderator 1" in the "75" "table_row" + Then I should see "Disagreed" in the "75" "table_row" + + # Update marking + And I click on "75" "link" in the ".mod-coursework-submissions-row:nth-child(2)" "css_element" + When I set the following fields to these values: + | Mark | 65 | + | Comment | Updated mark | + And I press "Save as draft" + Then I should see "Disagreed" in the "65" "table_row" + + @javascript + Scenario: Release the grades + Given the following markers are allocated: + | student | assessor_1 | moderator | + | Student 1 | Marker 1 | Moderator 1 | + | Student 2 | Marker 1 | Moderator 1 | + | Student 3 | Marker 2 | Moderator 1 | + + And the student called "Student 1" has a finalised submission + And the student called "Student 2" has a finalised submission + And the student called "Student 3" has a finalised submission + + And the submission from "Student 1" is marked by "Marker 1" with: + | Mark | 70 | + | Comment | Excellent work! | + | Finalised | 1 | + + And the submission from "Student 2" is marked by "Marker 1" with: + | Mark | 75 | + | Comment | Superb work! | + | Finalised | 1 | + + And the submission from "Student 3" is marked by "Marker 2" with: + | Mark | 50 | + | Comment | I've seen worse... | + | Finalised | 1 | + + And the submission from "Student 1" is moderated by "Moderator 1" with: + | Agreement | agreed | + + And the submission from "Student 2" is moderated by "Moderator 1" with: + | Agreement | disagreed | + | Comment | I don't like it at all! | + + And I am on the "Course 1" "course" page logged in as "manager" + And I follow "Coursework 1" + Then I should see "Agreed" in the "70" "table_row" + Then I should see "Disagreed" in the "75" "table_row" + + When I follow "Release the marks" + Then I should see "Are you sure you want to release all marks?" + And I press "Confirm" + Then I should see "Marks released" + Then I should see "Released" in the "70" "table_row" + Then I should see "Released" in the "75" "table_row" + Then I should see "Released" in the "50" "table_row" + + @javascript + Scenario: Student 1 sees the released grades + Given the following markers are allocated: + | student | assessor_1 | moderator | + | Student 1 | Marker 1 | Moderator 1 | + | Student 2 | Marker 1 | Moderator 1 | + | Student 3 | Marker 2 | Moderator 1 | + + And the student called "Student 1" has a finalised submission + And the student called "Student 2" has a finalised submission + And the student called "Student 3" has a finalised submission + + And the submission from "Student 1" is marked by "Marker 1" with: + | Mark | 70 | + | Comment | Excellent work! | + | Finalised | 1 | + + And the submission from "Student 2" is marked by "Marker 1" with: + | Mark | 75 | + | Comment | Superb work! | + | Finalised | 1 | + + And the submission from "Student 3" is marked by "Marker 2" with: + | Mark | 50 | + | Comment | I've seen worse... | + | Finalised | 1 | + + And the submission from "Student 1" is moderated by "Moderator 1" with: + | Agreement | agreed | + + And the submission from "Student 2" is moderated by "Moderator 1" with: + | Agreement | disagreed | + | Comment | I don't like it at all! | + + And I am on the "Course 1" "course" page logged in as "manager" + And I follow "Coursework 1" + And I press the release marks button + + And I log out + + And I am on the "Course 1" "course" page logged in as "student1" + And I follow "Coursework 1" + Then I should see "Agreed feedback for Student 1" + And I should see "Marker 1" + And I should see "Excellent work!" + And I should see "70" + And I should see "Released" + + @javascript + Scenario: Student 2 sees disagreed released grades + Given the following markers are allocated: + | student | assessor_1 | moderator | + | Student 1 | Marker 1 | Moderator 1 | + | Student 2 | Marker 1 | Moderator 1 | + | Student 3 | Marker 2 | Moderator 1 | + + And the student called "Student 1" has a finalised submission + And the student called "Student 2" has a finalised submission + And the student called "Student 3" has a finalised submission + + And the submission from "Student 1" is marked by "Marker 1" with: + | Mark | 70 | + | Comment | Excellent work! | + | Finalised | 1 | + + And the submission from "Student 2" is marked by "Marker 1" with: + | Mark | 75 | + | Comment | Superb work! | + | Finalised | 1 | + + And the submission from "Student 3" is marked by "Marker 2" with: + | Mark | 50 | + | Comment | I've seen worse... | + | Finalised | 1 | + + And the submission from "Student 1" is moderated by "Moderator 1" with: + | Agreement | agreed | + + And the submission from "Student 2" is moderated by "Moderator 1" with: + | Agreement | disagreed | + | Comment | I don't like it at all! | + + And I am on the "Course 1" "course" page logged in as "manager" + And I follow "Coursework 1" + And I press the release marks button + + Then I should see "Disagreed" in the "75" "table_row" + Then I should see "Released" in the "75" "table_row" + + And I log out + + # Student can see released feedback even its was not agreed on. + And I am on the "Course 1" "course" page logged in as "student2" + And I follow "Coursework 1" + Then I should see "Agreed feedback for Student 2" + And I should see "Marker 1" + And I should see "Superb work!" + And I should see "75" + And I should see "Released" + + @javascript + Scenario: Check moderation form + Given the following markers are allocated: + | student | assessor_1 | moderator | + | Student 1 | Marker 1 | Moderator 1 | + | Student 2 | Marker 1 | Moderator 1 | + | Student 3 | Marker 2 | Moderator 1 | + + And the student called "Student 1" has a finalised submission + And the student called "Student 2" has a finalised submission + And the student called "Student 3" has a finalised submission + + And the submission from "Student 1" is marked by "Marker 1" with: + | Mark | 70 | + | Comment | Excellent work! | + | Finalised | 1 | + + And the submission from "Student 2" is marked by "Marker 1" with: + | Mark | 75 | + | Comment | Superb work! | + | Finalised | 1 | + + And the submission from "Student 3" is marked by "Marker 2" with: + | Mark | 50 | + | Comment | I've seen worse... | + | Finalised | 1 | + + And the submission from "Student 1" is moderated by "Moderator 1" with: + | Agreement | agreed | + + And the submission from "Student 2" is moderated by "Moderator 1" with: + | Agreement | disagreed | + | Comment | I don't like it at all! | + + # Before marks have been released moderator can view and edit moderation. + And I am on the "Course 1" "course" page logged in as "moderator1" + And I follow "Coursework 1" + And I click on "Agreed" "link" in the ".mod-coursework-submissions-row:nth-child(1)" "css_element" + Then I should see "Moderation for" + And I should see "Moderator 1" + And "Save changes" "button" should exist + + And I am on the "Course 1" "course" page logged in as "manager" + And I follow "Coursework 1" + And I press the release marks button + And I log out + + # After marks have been released moderator can view but not edit moderation. + And I am on the "Course 1" "course" page logged in as "moderator1" + And I follow "Coursework 1" + And I click on "Agreed" "link" in the ".mod-coursework-submissions-row:nth-child(1)" "css_element" + Then I should see "Moderation for" + And I should see "Moderator 1" + And "Save changes" "button" should not exist diff --git a/tests/files_for_uploading/TestPDF1.pdf b/tests/files_for_uploading/TestPDF1.pdf new file mode 100644 index 00000000..b9ad0514 Binary files /dev/null and b/tests/files_for_uploading/TestPDF1.pdf differ diff --git a/tests/files_for_uploading/TestPDF2.pdf b/tests/files_for_uploading/TestPDF2.pdf new file mode 100644 index 00000000..3c8f031c Binary files /dev/null and b/tests/files_for_uploading/TestPDF2.pdf differ diff --git a/tests/files_for_uploading/TestPDF3.pdf b/tests/files_for_uploading/TestPDF3.pdf new file mode 100644 index 00000000..03695b0a Binary files /dev/null and b/tests/files_for_uploading/TestPDF3.pdf differ diff --git a/tests/files_for_uploading/Test_document.pdf b/tests/files_for_uploading/Test_document.pdf new file mode 100644 index 00000000..020ac8e5 Binary files /dev/null and b/tests/files_for_uploading/Test_document.pdf differ