diff --git a/admin/tool/behat/tests/behat/nasty_strings.feature b/admin/tool/behat/tests/behat/nasty_strings.feature new file mode 100644 index 00000000000..913579863fb --- /dev/null +++ b/admin/tool/behat/tests/behat/nasty_strings.feature @@ -0,0 +1,60 @@ +@tool_behat +Feature: Transform steps arguments + In order to write tests with complex nasty arguments + As a tests writer + I need to apply some transformations to the steps arguments + + Background: + Given I am on homepage + And the following "courses" exists: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And I log in as "admin" + And I follow "Admin User" + And I follow "Edit profile" + + Scenario: Use nasty strings on steps arguments + When I fill in "Surname" with "$NASTYSTRING1" + And I fill in "Description" with "$NASTYSTRING2" + And I fill in "City/town" with "$NASTYSTRING3" + And I press "Update profile" + And I follow "Edit profile" + Then I should not see "NASTYSTRING" + And the "Surname" field should match "$NASTYSTRING1" value + And the "City/town" field should match "$NASTYSTRING3" value + + Scenario: Use nasty strings on table nodes + When I fill the moodle form with: + | Surname | $NASTYSTRING1 | + | Description | $NASTYSTRING2 | + | City/town | $NASTYSTRING3 | + And I press "Update profile" + And I follow "Edit profile" + Then I should not see "NASTYSTRING" + And the "Surname" field should match "$NASTYSTRING1" value + And the "City/town" field should match "$NASTYSTRING3" value + + Scenario: Use double quotes + When I fill the moodle form with: + | First name | va"lue1 | + | Description | va\"lue2 | + And I fill in "City/town" with "va\"lue3" + And I press "Update profile" + And I follow "Edit profile" + Then I should not see "NASTYSTRING" + And the "First name" field should match "va\"lue1" value + And the "Description" field should match "va\"lue2" value + And the "City/town" field should match "va\"lue3" value + + @javascript + Scenario: Nasty strings with other contents + When I fill in "First name" with "My Firstname $NASTYSTRING1" + And I fill the moodle form with: + | Surname | My Surname $NASTYSTRING2 | + And I press "Update profile" + And I follow "Edit profile" + Then I should not see "NASTYSTRING" + And I should see "My Firstname" + And I should see "My Surname" + And the "First name" field should match "My Firstname $NASTYSTRING1" value + And the "Surname" field should match "My Surname $NASTYSTRING2" value diff --git a/course/tests/behat/behat_course.php b/course/tests/behat/behat_course.php index e20cdc5e49c..0f1e0ca0e0b 100644 --- a/course/tests/behat/behat_course.php +++ b/course/tests/behat/behat_course.php @@ -83,9 +83,6 @@ class behat_course extends behat_base { */ public function i_add_to_section($activity, $section) { - $activity = $this->fixStepArgument($activity); - $section = $this->fixStepArgument($section); - // Clicks add activity or resource section link. $sectionxpath = "//*[@id='section-" . $section . "']/*/*/*/div[@class='section-modchooser']/span/a"; $sectionnode = $this->find('xpath', $sectionxpath); diff --git a/lib/behat/behat_base.php b/lib/behat/behat_base.php index 41505e0791b..d703a77ce31 100644 --- a/lib/behat/behat_base.php +++ b/lib/behat/behat_base.php @@ -51,21 +51,6 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { */ const TIMEOUT = 6; - /** - * Returns fixed step argument (with \\" replaced back to "). - * - * \\ is the chars combination to add when you - * want to escape the " character that is used as var - * delimiter. - * - * @see Behat\MinkExtension\Context\MinkContext - * @param string $argument - * @return string - */ - protected function fixStepArgument($argument) { - return str_replace('\\"', '"', $argument); - } - /** * Locates url, based on provided path. * Override to provide custom routing mechanism. diff --git a/lib/behat/form_field/behat_form_editor.php b/lib/behat/form_field/behat_form_editor.php index c2bf778612a..04e0b3c3054 100644 --- a/lib/behat/form_field/behat_form_editor.php +++ b/lib/behat/form_field/behat_form_editor.php @@ -49,10 +49,21 @@ class behat_form_editor extends behat_form_field { */ public function set_value($value) { - // Set the value to the iframe and save it to the textarea. - $editorid = $this->field->getAttribute('id'); - $this->session->executeScript('tinyMCE.get("'.$editorid.'").setContent("' . $value . '");'); - $this->session->executeScript('tinyMCE.get("'.$editorid.'").save();'); + // If tinyMCE var exists means that we are using that editor. + if ($this->is_editor_available()) { + + // Set the value to the iframe and save it to the textarea. + $editorid = $this->field->getAttribute('id'); + + $this->session->executeScript(' + tinyMCE.get("'.$editorid.'").setContent("' . $value . '"); + tinyMCE.get("'.$editorid.'").save(); + '); + + } else { + // Set the value to a textarea otherwise. + parent::set_value($value); + } } /** @@ -62,12 +73,36 @@ class behat_form_editor extends behat_form_field { */ public function get_value() { - // Save the current iframe value in case default value has been edited. - $editorid = $this->field->getAttribute('id'); - $this->session->executeScript('tinyMCE.get("'.$editorid.'").save();'); + // If tinyMCE var exists means that we are using that editor. + if ($this->is_editor_available()) { + + // Save the current iframe value in case default value has been edited. + $editorid = $this->field->getAttribute('id'); + $this->session->executeScript('tinyMCE.get("'.$editorid.'").save();'); + } return $this->field->getValue(); } + /** + * Returns if the HTML editor is available. + * + * The editor availability depends on the driver running the tests; Goutte + * can not execute Javascript, also some Moodle settings disables the HTML + * editor. + * + * @return bool + */ + protected function is_editor_available() { + + // Non-JS drivers throws exceptions when running JS. + try { + $available = $this->session->evaluateScript('return (typeof tinyMCE != "undefined")'); + } catch (Exception $e) { + $available = false; + } + + return $available; + } } diff --git a/lib/testing/classes/nasty_strings.php b/lib/testing/classes/nasty_strings.php new file mode 100644 index 00000000000..e200263ad9d --- /dev/null +++ b/lib/testing/classes/nasty_strings.php @@ -0,0 +1,116 @@ +. + +/** + * Nasty strings to use in tests. + * + * @package core + * @category test + * @copyright 2013 David Monllaó + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +/** + * Nasty strings manager. + * + * Responds to nasty strings requests with a random string of the list + * to try with different combinations in different places. + * + * @package core + * @category test + * @copyright 2013 David Monllaó + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class nasty_strings { + + /** + * List of different strings to fill fields and assert against them + * + * Non of these strings can be a part of another one, this would not be good + * when using more one string at the same time and asserting results. + * + * @static + * @var array + */ + protected static $strings = array( + '< > & < > & \' \\" \ \'$@NULL@$ @@TEST@@ \\\" \\ , ; : .  日本語 ­ % %% ', + '& \' \\" \ \'$@NULL@$ < > & < > @@TEST@@ \\\" \\ , ; : .  日本語 ­ % %% ', + '< > & < > & \' \\" \ \\\" \\ , ; : .  \'$@NULL@$ @@TEST@@ 日本語 ­ % %% ', + '< > & < > & \' \\" \ \'$@NULL@$ 日本語 ­ % %%@@TEST@@ \\\" \\ , ; : .  ', + '< > & < > \\\" \\ , ; : .  日本語& \' \\" \ \'$@NULL@$ @@TEST@@ ­ % %% ', + '\' \\" \ \'$@NULL@$ @@TEST@@ < > & < > & \\\" \\ , ; : .  日本語 ­ % %% ', + '\\\" \\ , ; : .  日本語 ­ % < > & < > & \' \\" \ \'$@NULL@$ @@TEST@@ %% ', + '< > & < > & \' \\" \ \'$@NULL@$ 日本語 ­ % %% @@TEST@@ \\\" \\ , ; : .  ', + '.  日本語& \' \\"< > & < > \\\" \\ , ; : \ \'$@NULL@$ @@TEST@@ ­ % %% ', + '& \' \\" \ < > & < > \\\" \\ , ; : .  日本語\'$@NULL@$ @@TEST@@ ­ % %% ', + ); + + /** + * Already used nasty strings. + * + * This array will be cleaned before each scenario. + * + * @static + * @var array + */ + protected static $usedstrings = array(); + + /** + * Returns a nasty string and stores the key mapping. + * + * @static + * @param string $key The key + * @return string + */ + public static function get($key) { + + // If have been used during the this tests return it. + if (isset(self::$usedstrings[$key])) { + return self::$strings[self::$usedstrings[$key]]; + } + + // Getting non-used random string. + do { + $index = self::random_index(); + } while (in_array($index, self::$usedstrings)); + + // Mark the string as already used. + self::$usedstrings[$key] = $index; + + return self::$strings[$index]; + } + + /** + * Resets the used strings var. + * @static + * @return void + */ + public static function reset_used_strings() { + self::$usedstrings = array(); + } + + /** + * Returns a random index. + * @static + * @return int + */ + protected static function random_index() { + return mt_rand(0, count(self::$strings) - 1); + } + +} diff --git a/lib/tests/behat/behat_forms.php b/lib/tests/behat/behat_forms.php index c9df3e9720e..260adae0450 100644 --- a/lib/tests/behat/behat_forms.php +++ b/lib/tests/behat/behat_forms.php @@ -52,7 +52,6 @@ class behat_forms extends behat_base { * @throws ElementNotFoundException Thrown by behat_base::find */ public function press_button($button) { - $button = $this->fixStepArgument($button); // Ensures the button is present. $buttonnode = $this->find_button($button); @@ -75,9 +74,6 @@ class behat_forms extends behat_base { unset($fieldnode); - // Removing \\ that escapes " of the steps arguments. - $locator = $this->fixStepArgument($locator); - // Getting the NodeElement. $fieldnode = $this->find_field($locator); @@ -85,7 +81,6 @@ class behat_forms extends behat_base { $field = $this->get_field($fieldnode, $locator); // Delegates to the field class. - $value = $this->fixStepArgument($value); $field->set_value($value); } } @@ -97,8 +92,6 @@ class behat_forms extends behat_base { * @throws ElementNotFoundException Thrown by behat_base::find */ public function fill_field($field, $value) { - $field = $this->fixStepArgument($field); - $value = $this->fixStepArgument($value); $fieldnode = $this->find_field($field); $fieldnode->setValue($value); @@ -111,8 +104,6 @@ class behat_forms extends behat_base { * @throws ElementNotFoundException Thrown by behat_base::find */ public function select_option($option, $select) { - $select = $this->fixStepArgument($select); - $option = $this->fixStepArgument($option); $selectnode = $this->find_field($select); $selectnode->selectOption($option); @@ -128,7 +119,6 @@ class behat_forms extends behat_base { * @throws ElementNotFoundException Thrown by behat_base::find */ public function check_option($option) { - $option = $this->fixStepArgument($option); $checkboxnode = $this->find_field($option); $checkboxnode->check(); @@ -141,7 +131,6 @@ class behat_forms extends behat_base { * @throws ElementNotFoundException Thrown by behat_base::find */ public function uncheck_option($option) { - $option = $this->fixStepArgument($option); $checkboxnode = $this->find_field($option); $checkboxnode->uncheck(); @@ -158,9 +147,6 @@ class behat_forms extends behat_base { */ public function the_field_should_match_value($locator, $value) { - $locator = $this->fixStepArgument($locator); - $value = $this->fixStepArgument($value); - $fieldnode = $this->find_field($locator); // Gets the field instance. @@ -182,7 +168,6 @@ class behat_forms extends behat_base { * @see Behat\MinkExtension\Context\MinkContext */ public function assert_checkbox_checked($checkbox) { - $checkbox = $this->fixStepArgument($checkbox); $this->assertSession()->checkboxChecked($checkbox); } @@ -193,7 +178,6 @@ class behat_forms extends behat_base { * @see Behat\MinkExtension\Context\MinkContext */ public function assert_checkbox_not_checked($checkbox) { - $checkbox = $this->fixStepArgument($checkbox); $this->assertSession()->checkboxNotChecked($checkbox); } @@ -208,9 +192,6 @@ class behat_forms extends behat_base { */ public function the_select_box_should_contain($select, $option) { - $select = $this->fixStepArgument($select); - $option = $this->fixStepArgument($option); - $selectnode = $this->find_field($select); $regex = '/' . preg_quote($option, '/') . '/ui'; @@ -234,9 +215,6 @@ class behat_forms extends behat_base { */ public function the_select_box_should_not_contain($select, $option) { - $select = $this->fixStepArgument($select); - $option = $this->fixStepArgument($option); - $selectnode = $this->find_field($select); $regex = '/' . preg_quote($option, '/') . '/ui'; @@ -258,8 +236,6 @@ class behat_forms extends behat_base { protected function get_field(NodeElement $fieldnode, $locator) { global $CFG; - $locator = $this->fixStepArgument($locator); - // Get the field type. $type = $this->get_node_type($fieldnode, $locator); $classname = 'behat_form_' . $type; @@ -289,8 +265,6 @@ class behat_forms extends behat_base { */ protected function get_node_type(NodeElement $fieldnode, $locator) { - $locator = $this->fixStepArgument($locator); - // We look for a parent node with 'felement' class. if ($class = $fieldnode->getParent()->getAttribute('class')) { diff --git a/lib/tests/behat/behat_general.php b/lib/tests/behat/behat_general.php index 7fe1bb736f1..9a2c794485f 100644 --- a/lib/tests/behat/behat_general.php +++ b/lib/tests/behat/behat_general.php @@ -60,7 +60,6 @@ class behat_general extends behat_base { * @throws ElementNotFoundException Thrown by behat_base::find */ public function click_link($link) { - $link = $this->fixStepArgument($link); $linknode = $this->find_link($link); $linknode->click(); @@ -109,7 +108,7 @@ class behat_general extends behat_base { * @Then /^I should see "(?P(?:[^"]|\\")*)"$/ */ public function assert_page_contains_text($text) { - $this->assertSession()->pageTextContains($this->fixStepArgument($text)); + $this->assertSession()->pageTextContains($text); } /** @@ -119,7 +118,7 @@ class behat_general extends behat_base { * @Then /^I should not see "(?P(?:[^"]|\\")*)"$/ */ public function assert_page_not_contains_text($text) { - $this->assertSession()->pageTextNotContains($this->fixStepArgument($text)); + $this->assertSession()->pageTextNotContains($text); } /** @@ -128,7 +127,7 @@ class behat_general extends behat_base { * @Then /^I should see "(?P(?:[^"]|\\")*)" in the "(?P(?:[^"]|\\")*)" element$/ */ public function assert_element_contains_text($text, $element) { - $this->assertSession()->elementTextContains('css', $element, $this->fixStepArgument($text)); + $this->assertSession()->elementTextContains('css', $element, $text); } /** @@ -137,7 +136,7 @@ class behat_general extends behat_base { * @Then /^I should not see "(?P(?:[^"]|\\")*)" in the "(?P(?:[^"]|\\")*)" element$/ */ public function assert_element_not_contains_text($text, $element) { - $this->assertSession()->elementTextNotContains('css', $element, $this->fixStepArgument($text)); + $this->assertSession()->elementTextNotContains('css', $element, $text); } /** diff --git a/lib/tests/behat/behat_hooks.php b/lib/tests/behat/behat_hooks.php index f7aa002bc6c..87243264cc5 100644 --- a/lib/tests/behat/behat_hooks.php +++ b/lib/tests/behat/behat_hooks.php @@ -76,6 +76,7 @@ class behat_hooks extends behat_base { require_once(__DIR__ . '/../../behat/classes/behat_command.php'); require_once(__DIR__ . '/../../behat/classes/util.php'); require_once(__DIR__ . '/../../testing/classes/test_lock.php'); + require_once(__DIR__ . '/../../testing/classes/nasty_strings.php'); // Avoids vendor/bin/behat to be executed directly without test environment enabled // to prevent undesired db & dataroot modifications, this is also checked @@ -119,6 +120,12 @@ class behat_hooks extends behat_base { behat_util::reset_database(); behat_util::reset_dataroot(); + purge_all_caches(); + accesslib_clear_all_caches(true); + + // Reset the nasty strings list used during the last test. + nasty_strings::reset_used_strings(); + // Assing valid data to admin user (some generator-related code needs a valid user). $user = $DB->get_record('user', array('username' => 'admin')); session_set_user($user); diff --git a/lib/tests/behat/behat_navigation.php b/lib/tests/behat/behat_navigation.php index ad5031e8de3..13f282d6cdd 100644 --- a/lib/tests/behat/behat_navigation.php +++ b/lib/tests/behat/behat_navigation.php @@ -47,8 +47,6 @@ class behat_navigation extends behat_base { */ public function i_expand_node($nodetext) { - $nodetext = $this->fixStepArgument($nodetext); - $xpath = "//ul[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')] /descendant::li /descendant::p[contains(concat(' ', normalize-space(@class), ' '), ' branch')] diff --git a/lib/tests/behat/behat_transformations.php b/lib/tests/behat/behat_transformations.php new file mode 100644 index 00000000000..9f3b2b8f265 --- /dev/null +++ b/lib/tests/behat/behat_transformations.php @@ -0,0 +1,135 @@ +. + +/** + * Behat arguments transformations. + * + * This methods are used by Behat CLI command. + * + * @package core + * @category test + * @copyright 2012 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__ . '/../../behat/behat_base.php'); + +use Behat\Gherkin\Node\TableNode; + +/** + * Transformations to apply to steps arguments. + * + * This methods are applied to the steps arguments that matches + * the regular expressions specified in the @Transform tag. + * + * @package core + * @category test + * @copyright 2013 David Monllaó + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class behat_transformations extends behat_base { + + /** + * Removes escaped argument delimiters. + * + * We use double quotes as arguments delimiters and + * to add the " as part of an argument we escape it + * with a backslash, this method removes this backslash. + * + * @Transform /^((.*)"(.*))$/ + * @param string $string + * @return string The string with the arguments fixed. + */ + public function arg_replace_slashes($string) { + return $this->replace_slashes($string); + } + + /** + * Replaces $NASTYSTRING vars for a nasty string. + * + * @Transform /^((.*)\$NASTYSTRING(\d)(.*))$/ + * @param string $argument The whole argument value. + * @return string + */ + public function arg_replace_nasty_strings($argument) { + return $this->replace_nasty_strings($argument); + } + + /** + * Transformations for TableNode arguments. + * + * All the transformations have to be applied to tables, + * adding them in a different method for Behat API restrictions. + * + * @Transform /^table:(.*)/ + * @param TableNode $table + * @return TableNode The transformed table + */ + public function tablenode_transformations(TableNode $tablenode) { + + // Walk through all values including the optional headers. + $rows = $tablenode->getRows(); + foreach ($rows as $rowkey => $row) { + foreach ($row as $colkey => $value) { + + // TableNodes values doesn't need to be escaped, but maybe somebody does it. + $rows[$rowkey][$colkey] = $this->replace_slashes($value); + + // Transforms vars into nasty strings. + if (preg_match('/\$NASTYSTRING(\d)/', $rows[$rowkey][$colkey])) { + $rows[$rowkey][$colkey] = $this->replace_nasty_strings($rows[$rowkey][$colkey]); + } + } + } + + // Return the transformed TableNode. + $tablenode->setRows($rows); + return $tablenode; + } + + /** + * Removes the escaped double quotes. + * + * Method reused by TableNode transformation. + * + * @param string $string + * @return string + */ + public function replace_slashes($string) { + return str_replace('\"', '"', $string); + } + + /** + * Replaces $NASTYSTRING vars for a nasty string. + * + * Method reused by TableNode tranformation. + * + * @param string $string + * @return string + */ + public function replace_nasty_strings($string) { + return preg_replace_callback( + '/\$NASTYSTRING(\d)/', + function ($matches) { + return nasty_strings::get($matches[0]); + }, + $string + ); + } + +}