diff --git a/admin/tool/admin_presets/tests/behat/behat_admin_presets.php b/admin/tool/admin_presets/tests/behat/behat_admin_presets.php index 23b8f9ff31a0c..d6eaffe2b3201 100644 --- a/admin/tool/admin_presets/tests/behat/behat_admin_presets.php +++ b/admin/tool/admin_presets/tests/behat/behat_admin_presets.php @@ -62,6 +62,8 @@ class behat_admin_presets extends behat_base { * @throws ExpectationException */ final public function following_in_the_should_download_between_and_bytes(string $link, string $selectortype, + // TO BE DEPRECATED. + string $nodeelement, string $nodeselectortype, int $minexpectedsize, int $maxexpectedsize): void { // If the minimum is greater than the maximum then swap the values. if ((int) $minexpectedsize > (int) $maxexpectedsize) { diff --git a/admin/tool/admin_presets/tests/behat/download.feature b/admin/tool/admin_presets/tests/behat/download.feature index 2866028325f8d..f05f08d9685f5 100644 --- a/admin/tool/admin_presets/tests/behat/download.feature +++ b/admin/tool/admin_presets/tests/behat/download.feature @@ -10,11 +10,14 @@ Feature: I can download a preset Given I log in as "admin" And I navigate to "Site admin presets" in site administration When I open the action menu in "Custom preset" "table_row" - Then following "Download" "link" in the "Custom preset" "table_row" should download between "0" and "5000" bytes + # Failing. + Then following "Download" should download a file that: + | Contains | Custom preset | @javascript Scenario: Core preset settings can be downloaded Given I log in as "admin" And I navigate to "Site admin presets" in site administration When I open the action menu in "Starter" "table_row" - Then following "Download" "link" in the "Starter" "table_row" should download between "0" and "5000" bytes + Then following "Download" should download a file that: + | Contains | Starter | diff --git a/admin/tool/dataprivacy/tests/behat/dataexport.feature b/admin/tool/dataprivacy/tests/behat/dataexport.feature index 0efed8fed2502..f5651c1ac15ed 100644 --- a/admin/tool/dataprivacy/tests/behat/dataexport.feature +++ b/admin/tool/dataprivacy/tests/behat/dataexport.feature @@ -54,7 +54,8 @@ Feature: Data export from the privacy API And I reload the page And I should see "Download ready" in the "Victim User 1" "table_row" And I open the action menu in "Victim User 1" "table_row" - And following "Download" should download between "1" and "200000" bytes + And following "Download" should download a file that: + | Contains | index.html | And the following config values are set as admin: | privacyrequestexpiry | 1 | tool_dataprivacy | And I wait "1" seconds @@ -89,7 +90,8 @@ Feature: Data export from the privacy API And I reload the page And I should see "Download ready" in the "Export all of my personal data" "table_row" And I open the action menu in "Victim User 1" "table_row" - And following "Download" should download between "1" and "200000" bytes + And following "Download" should download a file that: + | Contains | index.html | And the following config values are set as admin: | privacyrequestexpiry | 1 | tool_dataprivacy | @@ -126,7 +128,8 @@ Feature: Data export from the privacy API And I reload the page And I should see "Download ready" in the "Victim User 1" "table_row" And I open the action menu in "Victim User 1" "table_row" - And following "Download" should download between "1" and "200000" bytes + And following "Download" should download a file that: + | Contains | index.html | And the following config values are set as admin: | privacyrequestexpiry | 1 | tool_dataprivacy | @@ -185,7 +188,8 @@ Feature: Data export from the privacy API And I reload the page And I should see "Download ready" in the "Victim User 1" "table_row" And I open the action menu in "Victim User 1" "table_row" - And following "Download" should download between "1" and "170000" bytes + Then following "Download" should download a file that: + | Contains | index.html | And the following config values are set as admin: | privacyrequestexpiry | 1 | tool_dataprivacy | And I wait "1" seconds @@ -229,7 +233,8 @@ Feature: Data export from the privacy API And I reload the page And I should see "Download ready" in the "Victim User 1" "table_row" And I open the action menu in "Victim User 1" "table_row" - And following "Download" should download between "1" and "180000" bytes + And following "Download" should download a file that: + | Contains | index.html | @javascript Scenario: Filter before export data for a user and download it in the view request action @@ -262,4 +267,5 @@ Feature: Data export from the privacy API And I reload the page And I should see "Download ready" in the "Victim User 1" "table_row" And I open the action menu in "Victim User 1" "table_row" - And following "Download" should download between "1" and "180000" bytes + And following "Download" should download a file that: + | Contains | index.html | diff --git a/analytics/tests/behat/manage_models.feature b/analytics/tests/behat/manage_models.feature index 13e721e327592..e0bdd8e9b66f1 100644 --- a/analytics/tests/behat/manage_models.feature +++ b/analytics/tests/behat/manage_models.feature @@ -147,7 +147,8 @@ Feature: Manage analytics models When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row" And I choose "Export" in the open action menu And I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row" - And following "Export" should download between "100" and "500" bytes + And following "Download" should download a file that: + | Contains | model-config.json | Scenario: Check invalid site elements When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row" diff --git a/lib/tests/behat/behat_download.php b/lib/tests/behat/behat_download.php new file mode 100644 index 0000000000000..446745b721255 --- /dev/null +++ b/lib/tests/behat/behat_download.php @@ -0,0 +1,188 @@ +. + +/** + * Steps definitions to verify a downloaded file. + * + * @package core + * @category test + * @copyright 2023 Simey Lameze + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use Behat\Gherkin\Node\TableNode; +use Behat\Mink\Exception\ExpectationException; + +require_once(__DIR__ . '/../../behat/behat_base.php'); + +/** + * Steps definitions to verify a downloaded file. + * + * @package core + * @category test + * @copyright 2023 Simey Lameze + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class behat_download extends behat_base { + + /** + * Downloads the file from a link on the page and checks the size is in a given range. + * + * @Then /^following "(?P[^"]*)" should download a "(?P[^"]*)" file that:$/ + * | Contains | abc | + * | Does not contain | xyz | + */ + public function following_should_download_a_file_that(string $link, string $format, TableNode $table): void { + + // Find the link. + $linknode = $this->find_link($link); + $this->ensure_node_is_visible($linknode); + + // Get the href and check it. + $url = $linknode->getAttribute('href'); + if (!$url) { + throw new ExpectationException( + 'Download link does not have href attribute', + $this->getSession(), + ); + } + if (!preg_match('~^https?://~', $url)) { + throw new ExpectationException( + "Download link not an absolute URL: {$url}", + $this->getSession(), + ); + } + + $behatgeneralcontext = behat_context_helper::get('behat_general'); + $exception = new ExpectationException( + "Error while downloading data from {$link}", + $this->getSession(), + ); + + // It will stop spinning once file is downloaded or time out. + $filecontent = $this->spin( + fn($context, $link) => $behatgeneralcontext->download_file_from_link($link), + $link, + behat_base::get_extended_timeout(), + $exception + ); + + if (!$rows = $table->getRows()) { + // Nothing further to verify. + return; + } + + // TODO: + // 2) Assert multiple files in the zip archive. + // 3) Validate txt, gift and png files. + // 4) Validate "Does not contain." + // 5) Validate "Contains" with multiple strings. + // 6) Validate "Does not contain PHP errors" -> (which can be based on look_for_exceptions from lib/behat/classes/behat_session_trait.php + $this->validate_mime_type($filecontent, $format); + + $assertmethod = 'assert_file_type_' . strtolower($format); + foreach ($rows as $row) { + $assertion = strtolower(trim($row[0])); + match ($assertion) { + 'contains' => $this->$assertmethod($filecontent, $row[1]), + default => throw new ExpectationException( + "Invalid assertion: {$assertion}", + $this->getSession(), + ) + }; + } + } + + protected function validate_mime_type(string $filecontent, string $format): void { + + $finfo = new \finfo(FILEINFO_MIME_TYPE); + $mimetype = $finfo->buffer($filecontent); + + $validmimetypes = match ($format) { + 'zip' => ['application/zip', 'application/x-zip-compressed', 'multipart/x-zip'], + 'xml' => ['application/xml', 'text/xml'], + 'txt' => ['text/plain'], + 'gift' => ['text/plain'], + 'png' => ['image/png'], + default => [$format], // If the format is not one of the above, assume it is the only valid MIME type + }; + + if (!in_array($mimetype, $validmimetypes)) { + throw new ExpectationException( + "The file downloaded should have been a {$format} file, but got {$mimetype} instead.", + $this->getSession(), + ); + } + } + + protected function assert_file_type_xml(string $filecontent, string $contains): void { + + // Load the XML content into a SimpleXMLElement object + $xml = new SimpleXMLElement($filecontent); + + // Use xpath to search for the string in the XML content + $result = $xml->xpath("//*[contains(text(), '$contains')]"); + + // If the result is empty, the string was not found in the XML content + if (empty($result)) { + throw new ExpectationException( + "The string '{$contains}' was not found in the XML content.", + $this->getSession(), + ); + } + } + + protected function assert_file_type_zip(string $filecontent, string $expectedfile): void { + + // Save the file to disk. + $tempdir = make_request_directory(); + $filepath = $tempdir . '/downloaded.zip'; + file_put_contents($filepath, $filecontent); + + $zip = new ZipArchive(); + $res = $zip->open($filepath); + + if ($res !== true) { + throw new ExpectationException( + "Failed to open zip file.", + $this->getSession(), + ); + } + + // Check if the expected file exists in the zip archive. + if ($zip->locateName($expectedfile) === false) { + throw new ExpectationException( + "The file '{$expectedfile}' was not found in the zip archive.", + $this->getSession(), + ); + } + } + + protected function assert_file_type_text(string $filecontent, string $contains): void { + + // Check if the string is present in the file content. + if (!str_contains($filecontent, $contains)) { + throw new ExpectationException( + "The string '{$contains}' was not found in the file content.", + $this->getSession(), + ); + } + } + + protected function assert_file_type_gift(string $filecontent, string $contains): void { + $this->assert_file_type_text($filecontent, $contains); + } +} diff --git a/mod/data/tests/behat/data_presets.feature b/mod/data/tests/behat/data_presets.feature index ff04cc5dc191f..96b7d7760441f 100644 --- a/mod/data/tests/behat/data_presets.feature +++ b/mod/data/tests/behat/data_presets.feature @@ -300,10 +300,12 @@ Feature: Users can view and manage data presets # The teacher should be able to export any saved preset. And I open the action menu in "Saved preset by teacher1" "table_row" Then I should see "Export" - And following "Export" "link" in the "Saved preset by teacher1" "table_row" should download between "1" and "5000" bytes + Then following "Export" should download a file that: + | Contains | Saved preset by teacher1 | And I open the action menu in "Saved preset 1" "table_row" And I should see "Export" - And following "Export" "link" in the "Saved preset 1" "table_row" should download between "1" and "5000" bytes + And following "Export" should download a file that: + | Contains | Saved preset 1 | @javascript @_file_upload Scenario Outline: Admins and Teachers can load a preset from a file diff --git a/mod/folder/tests/behat/recent_activity.feature b/mod/folder/tests/behat/recent_activity.feature index 158461a8d89a6..154a08ea1570b 100644 --- a/mod/folder/tests/behat/recent_activity.feature +++ b/mod/folder/tests/behat/recent_activity.feature @@ -36,5 +36,6 @@ Feature: Files added in folder activity are visible in the recent activity block And "//img[@alt='empty.txt']" "xpath_element" should exist And "//img[contains(@src, 'preview=tinyicon')]" "xpath_element" should exist # Confirm files are downloadable - And following "empty.txt" should download between "1" and "3000" bytes - And following "gd-logo.png" should download between "1" and "3000" bytes + Then following "Export" should download a file that: + | Is a valid txt file and contains | | + | Is a valid png file | | diff --git a/mod/glossary/tests/behat/create_entry.feature b/mod/glossary/tests/behat/create_entry.feature index 1895bab567842..1dcce0445cea1 100644 --- a/mod/glossary/tests/behat/create_entry.feature +++ b/mod/glossary/tests/behat/create_entry.feature @@ -45,4 +45,5 @@ Feature: Create a glossary entry. When I am on the "Test glossary" "glossary activity" page logged in as teacher1 Then I should see "Entry 1" And I should see "musicians.xml" - And following "musicians.xml" should download between "1" and "3000" bytes + And following "musicians.xml" should download a file that: + | Contains | Musicians | diff --git a/question/format/aiken/tests/behat/aiken_export.feature b/question/format/aiken/tests/behat/aiken_export.feature index 28f20d56f5b3d..57d458278fc6b 100644 --- a/question/format/aiken/tests/behat/aiken_export.feature +++ b/question/format/aiken/tests/behat/aiken_export.feature @@ -26,7 +26,9 @@ Feature: Test exporting questions using Aiken format. When I am on the "Course 1" "core_question > course question export" page logged in as "teacher1" And I set the field "id_format_aiken" to "1" When I press "Export questions to file" - Then following "click here" should download between "68" and "70" bytes + Then following "click here" should download a file that: + | Contains | Multi-choice-001 | + | Contains | Multi-choice-002 | # If the download step is the last in the scenario then we can sometimes run # into the situation where the download page causes a http redirect but behat # has already conducted its reset (generating an error). By putting a logout diff --git a/question/format/gift/tests/behat/import_export.feature b/question/format/gift/tests/behat/import_export.feature index fc00ec7300fb7..a7176df80e979 100644 --- a/question/format/gift/tests/behat/import_export.feature +++ b/question/format/gift/tests/behat/import_export.feature @@ -31,7 +31,8 @@ Feature: Test importing questions from GIFT format. And I am on the "Course 1" "core_question > course question export" page And I set the field "id_format_gift" to "1" And I press "Export questions to file" - And following "click here" should download between "1500" and "1800" bytes + And following "click here" should download a "gift" file that: + | Contains | What's between orange and green in the spectrum? | @javascript @_file_upload Scenario: import a GIFT file which specifies the category diff --git a/question/format/xml/tests/behat/import_export.feature b/question/format/xml/tests/behat/import_export.feature index c09746929aebd..8b53b4f9c81a1 100644 --- a/question/format/xml/tests/behat/import_export.feature +++ b/question/format/xml/tests/behat/import_export.feature @@ -34,7 +34,9 @@ Feature: Test importing questions from Moodle XML format. And I set the field "id_format_xml" to "1" And I set the field "Export category" to "TrueFalse" And I press "Export questions to file" - Then following "click here" should download between "57100" and "58150" bytes + Then following "click here" should download a file that: + | Contains | Moodle acronym (False) | + | Contains | Moodle acronym (True) | @javascript @_file_upload Scenario: import some multiple choice questions from Moodle XML format diff --git a/question/type/ddimageortext/tests/behat/export.feature b/question/type/ddimageortext/tests/behat/export.feature index 06d83e284ea30..a5a509405a5df 100644 --- a/question/type/ddimageortext/tests/behat/export.feature +++ b/question/type/ddimageortext/tests/behat/export.feature @@ -26,7 +26,8 @@ Feature: Test exporting drag and drop onto image questions When I am on the "Course 1" "core_question > course question export" page logged in as teacher And I set the field "id_format_xml" to "1" And I press "Export questions to file" - Then following "click here" should download between "18600" and "19150" bytes + Then following "click here" should download a file that: + | Contains | Drag onto image | # If the download step is the last in the scenario then we can sometimes run # into the situation where the download page causes a http redirect but behat # has already conducted its reset (generating an error). By putting a logout diff --git a/question/type/ddmarker/tests/behat/export.feature b/question/type/ddmarker/tests/behat/export.feature index edb928239ae14..a6032543890e8 100644 --- a/question/type/ddmarker/tests/behat/export.feature +++ b/question/type/ddmarker/tests/behat/export.feature @@ -26,7 +26,8 @@ Feature: Test exporting drag and drop markers questions When I am on the "Course 1" "core_question > course question export" page logged in as teacher And I set the field "id_format_xml" to "1" And I press "Export questions to file" - Then following "click here" should download between "233700" and "233950" bytes + Then following "click here" should download a file that: + | Contains | Drag markers | # If the download step is the last in the scenario then we can sometimes run # into the situation where the download page causes a http redirect but behat # has already conducted its reset (generating an error). By putting a logout diff --git a/question/type/ddwtos/tests/behat/export.feature b/question/type/ddwtos/tests/behat/export.feature index a74915b78e509..78b6ea734609b 100644 --- a/question/type/ddwtos/tests/behat/export.feature +++ b/question/type/ddwtos/tests/behat/export.feature @@ -26,7 +26,8 @@ Feature: Test exporting drag and drop into text questions When I am on the "Course 1" "core_question > course question export" page logged in as teacher And I set the field "id_format_xml" to "1" And I press "Export questions to file" - And following "click here" should download between "1550" and "1700" bytes + And following "click here" should download a file that: + | Contains | Drag to text | # If the download step is the last in the scenario then we can sometimes run # into the situation where the download page causes a http redirect but behat # has already conducted its reset (generating an error). By putting a logout diff --git a/question/type/description/tests/behat/export.feature b/question/type/description/tests/behat/export.feature index 28cc5cda0947b..1feceb8794472 100644 --- a/question/type/description/tests/behat/export.feature +++ b/question/type/description/tests/behat/export.feature @@ -25,7 +25,8 @@ Feature: Test exporting Description questions When I am on the "Course 1" "core_question > course question export" page logged in as teacher And I set the field "id_format_xml" to "1" And I press "Export questions to file" - Then following "click here" should download between "650" and "900" bytes + Then following "click here" should download a file that: + | Contains | description-001 | # If the download step is the last in the scenario then we can sometimes run # into the situation where the download page causes a http redirect but behat # has already conducted its reset (generating an error). By putting a logout diff --git a/question/type/essay/tests/behat/export.feature b/question/type/essay/tests/behat/export.feature index e319fcdff4410..83c6bf641777e 100644 --- a/question/type/essay/tests/behat/export.feature +++ b/question/type/essay/tests/behat/export.feature @@ -27,7 +27,10 @@ Feature: Test exporting Essay questions When I am on the "Course 1" "core_question > course question export" page logged in as teacher And I set the field "id_format_xml" to "1" And I press "Export questions to file" - Then following "click here" should download between "3000" and "3500" bytes + Then following "click here" should download a file that: + | Contains | essay-001 | + | Contains | essay-002 | + | Contains | essay-003 | # If the download step is the last in the scenario then we can sometimes run # into the situation where the download page causes a http redirect but behat # has already conducted its reset (generating an error). By putting a logout diff --git a/question/type/gapselect/tests/behat/import_test.feature b/question/type/gapselect/tests/behat/import_test.feature index af9104c9b8414..ec57b23809adb 100644 --- a/question/type/gapselect/tests/behat/import_test.feature +++ b/question/type/gapselect/tests/behat/import_test.feature @@ -32,7 +32,8 @@ Feature: Import and export select missing words questions And I am on the "Course 1" "core_question > course question export" page logged in as teacher And I set the field "id_format_xml" to "1" And I press "Export questions to file" - And following "click here" should download between "1650" and "1800" bytes + And following "click here" should download a file that: + | Contains | Select missing words 001 | # If the download step is the last in the scenario then we can sometimes run # into the situation where the download page causes a http redirect but behat # has already conducted its reset (generating an error). By putting a logout diff --git a/question/type/match/tests/behat/export.feature b/question/type/match/tests/behat/export.feature index 6d28231d648b5..6571f3b9da38f 100644 --- a/question/type/match/tests/behat/export.feature +++ b/question/type/match/tests/behat/export.feature @@ -25,7 +25,8 @@ Feature: Test exporting Matching questions When I am on the "Course 1" "core_question > course question export" page logged in as teacher And I set the field "id_format_xml" to "1" And I press "Export questions to file" - Then following "click here" should download between "1600" and "1750" bytes + Then following "click here" should download a file that: + | Contains | matching-001 | # If the download step is the last in the scenario then we can sometimes run # into the situation where the download page causes a http redirect but behat # has already conducted its reset (generating an error). By putting a logout diff --git a/question/type/multichoice/tests/behat/export.feature b/question/type/multichoice/tests/behat/export.feature index 46fd6fec54235..7b35ab0220d53 100644 --- a/question/type/multichoice/tests/behat/export.feature +++ b/question/type/multichoice/tests/behat/export.feature @@ -26,7 +26,9 @@ Feature: Test exporting Multiple choice questions When I am on the "Course 1" "core_question > course question export" page logged in as teacher And I set the field "id_format_xml" to "1" And I press "Export questions to file" - Then following "click here" should download between "3900" and "4100" bytes + Then following "click here" should download a file that: + | Contains | Multi-choice-001 | + | Contains | Multi-choice-002 | # If the download step is the last in the scenario then we can sometimes run # into the situation where the download page causes a http redirect but behat # has already conducted its reset (generating an error). By putting a logout diff --git a/question/type/numerical/tests/behat/export.feature b/question/type/numerical/tests/behat/export.feature index df9728ab09f25..6337583a32822 100644 --- a/question/type/numerical/tests/behat/export.feature +++ b/question/type/numerical/tests/behat/export.feature @@ -26,7 +26,9 @@ Feature: Test exporting Numerical questions When I am on the "Course 1" "core_question > course question export" page logged in as teacher And I set the field "id_format_xml" to "1" And I press "Export questions to file" - Then following "click here" should download between "3650" and "3750" bytes + Then following "click here" should download a file that: + | Contains | Numerical-001 | + | Contains | Numerical-002 | # If the download step is the last in the scenario then we can sometimes run # into the situation where the download page causes a http redirect but behat # has already conducted its reset (generating an error). By putting a logout diff --git a/question/type/ordering/tests/behat/export.feature b/question/type/ordering/tests/behat/export.feature index 5b218a159af25..1dd4525b51395 100644 --- a/question/type/ordering/tests/behat/export.feature +++ b/question/type/ordering/tests/behat/export.feature @@ -25,7 +25,8 @@ Feature: Test exporting Ordering questions When I am on the "Course 1" "core_question > course question export" page logged in as teacher1 And I set the field "id_format_xml" to "1" And I press "Export questions to file" - Then following "click here" should download between "1700" and "2350" bytes + Then following "click here" should download a file that: + | Contains | Moodle | # If the download step is the last in the scenario then we can sometimes run # into the situation where the download page causes a http redirect but behat # has already conducted its reset (generating an error). By putting a logout diff --git a/question/type/shortanswer/tests/behat/export.feature b/question/type/shortanswer/tests/behat/export.feature index 04f4a5fb26233..a711de9beb847 100644 --- a/question/type/shortanswer/tests/behat/export.feature +++ b/question/type/shortanswer/tests/behat/export.feature @@ -25,7 +25,8 @@ Feature: Test exporting Short answer questions When I am on the "Course 1" "core_question > course question export" page logged in as teacher And I set the field "id_format_xml" to "1" And I press "Export questions to file" - Then following "click here" should download between "1200" and "1450" bytes + Then following "click here" should download a file that: + | Contains | shortanswer-001 | # If the download step is the last in the scenario then we can sometimes run # into the situation where the download page causes a http redirect but behat # has already conducted its reset (generating an error). By putting a logout diff --git a/question/type/truefalse/tests/behat/export.feature b/question/type/truefalse/tests/behat/export.feature index fb9f4005a2906..f641fbb6b1141 100644 --- a/question/type/truefalse/tests/behat/export.feature +++ b/question/type/truefalse/tests/behat/export.feature @@ -25,7 +25,8 @@ Feature: Test exporting True/False questions When I am on the "Course 1" "core_question > course question export" page logged in as teacher And I set the field "id_format_xml" to "1" And I press "Export questions to file" - Then following "click here" should download between "1000" and "1200" bytes + Then following "click here" should download a file that: + | Contains | true-false-001 | # If the download step is the last in the scenario then we can sometimes run # into the situation where the download page causes a http redirect but behat # has already conducted its reset (generating an error). By putting a logout