From 33fa8e7dba998db00523d9b5558fc3bb14b0ea90 Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Thu, 16 Jan 2014 02:38:24 +0100 Subject: [PATCH 1/7] MDL-43713 behat: improve multi-select support This patch implements: 1) Normalization of options. Before the patch options in a select were being returned as "op1 op2 op3" by selenium and "op1 op2 op3" by goutte. With the patch, those lists are always returned like "op1, op2, op3". If real commas are needed when handling multiple selects they should be escaped with backslash in feature files. 2) Support for selecting multiple options. Before the patch only one option was selected and a new selection was cleaning the previous one. With the patch it's possible to pass "op1, op2" in these steps: - I fill the moodle form with (table) - I select "OPTION_STRING" from "SELECT_STRING" 3) Ability to match multiple options in this steps. Before the patch matching of multiple was really random, now every every passed option ("opt1, opt2") is individually verified. It applies to these 2 steps: - the "ELEMENT" select box should contain "OPTIONS" - the "ELEMENT" select box should not contain "OPTIONS" 4) Two new steps able to verify if a form have some options selected or no: - the "ELEMENT" select box should contain "OPTIONS" selected - the "ELEMENT" select box should contain "OPTIONS" not selected 5) Change get_value from xpath search to Mink's getValue() that is immediate (does not need form submission) and works for all browsers but Safari, that fails because of the extra ->click() issued. Note all the changes 1-4 only affect to multi-select fields. Single selects should continue working 100% the same. The change 5) causes Safari to fail. The problem has been traced down to the extra ->click() present there. Anyway there are not test cases requiring that "immediate" evaluation right now. Only the special feature file attached verifies it. --- lib/behat/form_field/behat_form_select.php | 192 ++++++++++++++++++--- lib/tests/behat/behat_forms.php | 182 +++++++++++++++++-- 2 files changed, 334 insertions(+), 40 deletions(-) diff --git a/lib/behat/form_field/behat_form_select.php b/lib/behat/form_field/behat_form_select.php index 5be8135633e..12d7c863864 100644 --- a/lib/behat/form_field/behat_form_select.php +++ b/lib/behat/form_field/behat_form_select.php @@ -38,52 +38,192 @@ require_once(__DIR__ . '/behat_form_field.php'); class behat_form_select extends behat_form_field { /** - * Sets the value of a single select. + * Sets the value(s) of a select element. * - * @param string $value +r * Seems an easy select, but there are lots of combinations + * of browsers and operative systems and each one manages the + * autosubmits and the multiple option selects in a different way. + * + * @param string $value plain value or comma separated values if multiple. Commas in values escaped with backslash. * @return void */ public function set_value($value) { - $this->field->selectOption($value); - // Adding a click as Selenium requires it to fire some JS events. + // In some browsers we select an option and it triggers all the + // autosubmits and works as expected but not in all of them, so we + // try to catch all the possibilities to make this function work as + // expected. + + // Get the internal id of the element we are going to click. + // This kind of internal IDs are only available in the selenium wire + // protocol, so only available using selenium drivers, phantomjs and family. if ($this->running_javascript()) { + $currentelementid = $this->get_internal_field_id(); + } - // In some browsers the selectOption actions can perform a page reload - // so we need to ensure the element is still available to continue interacting - // with it. We don't wait here. - if (!$this->session->getDriver()->find($this->field->getXpath())) { + // Is the select multiple? + $multiple = $this->field->hasAttribute('multiple'); + + // By default, assume the passed value is a non-multiple option. + $options = array(trim($value)); + + // Here we select the option(s). + if ($multiple) { + // Split and decode values. Comma separated list of values allowed. With valuable commas escaped with backslash. + $options = preg_replace('/\\\,/', ',', preg_split('/(?field->selectOption(trim($option), $afterfirstoption); + $afterfirstoption = true; + } + } else { + // This is a single select, let's pass the last one specified. + $this->field->selectOption(end($options)); + } + + // With JS disabled this is enough and we finish here. + if (!$this->running_javascript()) { + return; + } + + // With JS enabled we add more clicks as some selenium + // drivers requires it to fire JS events. + + // In some browsers the selectOption actions can perform a form submit or reload page + // so we need to ensure the element is still available to continue interacting + // with it. We don't wait here. + $selectxpath = $this->field->getXpath(); + if (!$this->session->getDriver()->find($selectxpath)) { + return; + } + + // We also check the selenium internal element id, if it have changed + // we are dealing with an autosubmit that was already executed, and we don't to + // execute anything else as the action we wanted was already performed. + if ($currentelementid != $this->get_internal_field_id()) { + return; + } + + // We also check that the option(s) are still there. We neither wait. + foreach ($options as $option) { + $valueliteral = $this->session->getSelectorsHandler()->xpathLiteral(trim($option)); + $optionxpath = $selectxpath . "/descendant::option[(./@value=$valueliteral or normalize-space(.)=$valueliteral)]"; + if (!$this->session->getDriver()->find($optionxpath)) { + return; + } + } + + // Single select sometimes needs an extra click in the option. + if (!$multiple) { + + // Using the driver direcly because Element methods are messy when dealing + // with elements inside containers. + $optionnodes = $this->session->getDriver()->find($optionxpath); + if ($optionnodes) { + // Wrapped in a try & catch as we can fall into race conditions + // and the element may not be there. + try { + + // Wait for all the possible AJAX requests that have been + // already triggered by selectOption() to be finished. + $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS); + + current($optionnodes)->click(); + } catch (Exception $e) { + // We continue and return as this means that the element is not there or it is not the same. + return; + } + } + + } else { + + // Wait for all the possible AJAX requests that have been + // already triggered by selectOption() to be finished. + $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS); + + // Wrapped in a try & catch as we can fall into race conditions + // and the element may not be there. + try { + // Multiple ones needs the click in the select. + $this->field->click(); + } catch (Exception $e) { + // We continue and return as this means that the element is not there or it is not the same. return; } - // Single select needs an extra click in the option. - if (!$this->field->hasAttribute('multiple')) { + // We ensure that the option is still there. + if (!$this->session->getDriver()->find($optionxpath)) { + return; + } - $value = $this->session->getSelectorsHandler()->xpathLiteral($value); + // Wait for all the possible AJAX requests that have been + // already triggered by selectOption() to be finished. + $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS); - // Using the driver direcly because Element methods are messy when dealing - // with elements inside containers. - $optionxpath = $this->field->getXpath() . - "/descendant::option[(./@value=$value or normalize-space(.)=$value)]"; - $optionnodes = $this->session->getDriver()->find($optionxpath); - if ($optionnodes) { - current($optionnodes)->click(); + // Wrapped in a try & catch as we can fall into race conditions + // and the element may not be there. + try { + // Repeating the select(s) as some drivers (chrome that I know) are moving + // to another option after the general select field click above. + foreach ($options as $option) { + $this->field->selectOption(trim($option), true); } - - } else { - // Multiple ones needs the click in the select. - $this->field->click(); + } catch (Exception $e) { + // We continue and return as this means that the element is not there or it is not the same. + return; } } } /** - * Returns the text of the current value. + * Returns the text of the currently selected options. * - * @return string + * @return string Comma separated if multiple options are selected. Commas in option texts escaped with backslash. */ public function get_value() { - $selectedoption = $this->field->find('xpath', '//option[@selected="selected"]'); - return $selectedoption->getText(); + + // Is the select multiple? + $multiple = $this->field->hasAttribute('multiple'); + + $selectedoptions = array(); // To accumulate found selected options. + + // Selenium getValue() implementation breaks - separates - values having + // commas within them, so we'll be looking for options with the 'selected' attribute instead. + if ($this->running_javascript()) { + // Get all the options in the select and extract their value/text pairs. + $alloptions = $this->field->findAll('xpath', '//option'); + foreach ($alloptions as $option) { + // Is it selected? + if ($option->hasAttribute('selected')) { + if ($multiple) { + // If the select is multiple, text commas must be encoded. + $selectedoptions[] = trim(str_replace(',', '\,', $option->getText())); + } else { + $selectedoptions[] = trim($option->getText()); + } + } + } + + // Goutte does not keep the 'selected' attribute updated, but its getValue() returns + // the selected elements correctly, also those having commas within them. + } else { + $values = $this->field->getValue(); + // Get all the options in the select and extract their value/text pairs. + $alloptions = $this->field->findAll('xpath', '//option'); + foreach ($alloptions as $option) { + // Is it selected? + if (in_array($option->getValue(), $values)) { + if ($multiple) { + // If the select is multiple, text commas must be encoded. + $selectedoptions[] = trim(str_replace(',', '\,', $option->getText())); + } else { + $selectedoptions[] = trim($option->getText()); + } + } + } + } + + return implode(', ', $selectedoptions); } } diff --git a/lib/tests/behat/behat_forms.php b/lib/tests/behat/behat_forms.php index b33931fcc98..acce54f4c85 100644 --- a/lib/tests/behat/behat_forms.php +++ b/lib/tests/behat/behat_forms.php @@ -245,6 +245,9 @@ class behat_forms extends behat_base { /** * Checks that the form element field have the specified value. * + * NOTE: This method/step does not support all fields. Namely, multi-select ones aren't supported. + * @todo: MDL-43738 would try to put some better support here for that multi-select and others. + * * @Then /^the "(?P(?:[^"]|\\")*)" field should match "(?P(?:[^"]|\\")*)" value$/ * @throws ExpectationException * @throws ElementNotFoundException Thrown by behat_base::find @@ -297,20 +300,41 @@ class behat_forms extends behat_base { * @throws ExpectationException * @throws ElementNotFoundException Thrown by behat_base::find * @param string $select The select element name - * @param string $option The option text/value + * @param string $option The option text/value. Plain value or comma separated + * values if multiple. Commas in multiple values escaped with backslash. */ public function the_select_box_should_contain($select, $option) { $selectnode = $this->find_field($select); + $multiple = $selectnode->hasAttribute('multiple'); + $optionsarr = array(); // Array of passed value/text options to test. - $regex = '/' . preg_quote($option, '/') . '/ui'; - if (!preg_match($regex, $selectnode->getText())) { - throw new ExpectationException( - 'The select box "' . $select . '" does not contains the option "' . $option . '"', - $this->getSession() - ); + if ($multiple) { + // Can pass multiple comma separated, with valuable commas escaped with backslash. + foreach (preg_replace('/\\\,/', ',', preg_split('/(?findAll('xpath', '//option'); + $values = array(); + foreach ($options as $opt) { + $values[trim($opt->getValue())] = trim($opt->getText()); + } + + foreach ($optionsarr as $opt) { + // Verify every option is a valid text or value. + if (!in_array($opt, $values) && !array_key_exists($opt, $values)) { + throw new ExpectationException( + 'The select box "' . $select . '" does not contain the option "' . $opt . '"', + $this->getSession() + ); + } + } } /** @@ -320,18 +344,148 @@ class behat_forms extends behat_base { * @throws ExpectationException * @throws ElementNotFoundException Thrown by behat_base::find * @param string $select The select element name - * @param string $option The option text/value + * @param string $option The option text/value. Plain value or comma separated + * values if multiple. Commas in multiple values escaped with backslash. */ public function the_select_box_should_not_contain($select, $option) { $selectnode = $this->find_field($select); + $multiple = $selectnode->hasAttribute('multiple'); + $optionsarr = array(); // Array of passed value/text options to test. - $regex = '/' . preg_quote($option, '/') . '/ui'; - if (preg_match($regex, $selectnode->getText())) { - throw new ExpectationException( - 'The select box "' . $select . '" contains the option "' . $option . '"', - $this->getSession() - ); + if ($multiple) { + // Can pass multiple comma separated, with valuable commas escaped with backslash. + foreach (preg_replace('/\\\,/', ',', preg_split('/(?findAll('xpath', '//option'); + $values = array(); + foreach ($options as $opt) { + $values[trim($opt->getValue())] = trim($opt->getText()); + } + + foreach ($optionsarr as $opt) { + // Verify every option is not a valid text or value. + if (in_array($opt, $values) || array_key_exists($opt, $values)) { + throw new ExpectationException( + 'The select box "' . $select . '" contains the option "' . $opt . '"', + $this->getSession() + ); + } + } + } + + /** + * Checks, that given select box contains the specified option selected. + * + * @Then /^the "(?P(?:[^"]|\\")*)" select box should contain "(?P(?:[^"]|\\")*)" selected$/ + * @throws ExpectationException + * @throws ElementNotFoundException Thrown by behat_base::find + * @param string $select The select element name + * @param string $option The option text. Plain value or comma separated + * values if multiple. Commas in multiple values escaped with backslash. + */ + public function the_select_box_should_contain_selected($select, $option) { + + $selectnode = $this->find_field($select); + $multiple = $selectnode->hasAttribute('multiple'); + $optionsarr = array(); // Array of passed text options to test. + $selectedarr = array(); // Array of selected text options. + + if ($multiple) { + // Can pass multiple comma separated, with valuable commas escaped with backslash. + foreach (preg_replace('/\\\,/', ',', preg_split('/(?getSession()); + $value = $field->get_value(); + + if ($multiple) { + // Can be multiple comma separated, with valuable commas escaped with backslash. + foreach (preg_replace('/\\\,/', ',', preg_split('/(?getSession() + ); + } + } + } + + /** + * Checks, that given select box contains the specified option not selected. + * + * @Then /^the "(?P(?:[^"]|\\")*)" select box should contain "(?P(?:[^"]|\\")*)" not selected$/ + * @throws ExpectationException + * @throws ElementNotFoundException Thrown by behat_base::find + * @param string $select The select element name + * @param string $option The option text. Plain value or comma separated + * values if multiple. Commas in multiple values escaped with backslash. + */ + public function the_select_box_should_contain_not_selected($select, $option) { + + $selectnode = $this->find_field($select); + $multiple = $selectnode->hasAttribute('multiple'); + $optionsarr = array(); // Array of passed text options to test. + $selectedarr = array(); // Array of selected text options. + + // First of all, the option(s) must exist, delegate it. Plain and raw. + $this->the_select_box_should_contain($select, $option); + + if ($multiple) { + // Can pass multiple comma separated, with valuable commas escaped with backslash. + foreach (preg_replace('/\\\,/', ',', preg_split('/(?getSession()); + $value = $field->get_value(); + + if ($multiple) { + // Can be multiple comma separated, with valuable commas escaped with backslash. + foreach (preg_replace('/\\\,/', ',', preg_split('/(?getSession() + ); + } } } From 6013abc914258d9133d6f64fd00e181fe9102174 Mon Sep 17 00:00:00 2001 From: David Monllao Date: Fri, 24 Jan 2014 13:29:31 +0800 Subject: [PATCH 2/7] MDL-43738 behat: Functional tests for fields management We are cheating with this .feature file as this is not a behaviour and we are just testing the framework, but in this case I think that everybody will agree on the need of doing it, as every browser behaves differently and minor changes in the getters and setters can easily end up breaking the behaviours. All credit goes to Eloy Lafuente (stronk7). --- .../tests/behat/get_and_set_fields.feature | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 admin/tool/behat/tests/behat/get_and_set_fields.feature diff --git a/admin/tool/behat/tests/behat/get_and_set_fields.feature b/admin/tool/behat/tests/behat/get_and_set_fields.feature new file mode 100644 index 00000000000..f4239508f1d --- /dev/null +++ b/admin/tool/behat/tests/behat/get_and_set_fields.feature @@ -0,0 +1,176 @@ +@tool_behat +Feature: Verify that all form fields values can be get and set + In order to use behat steps definitions + As a test writer + I need to verify it all works in real moodle forms + + Background: + Given the following "courses" exists: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "users" exists: + | username | email | firstname | lastname | + | student1 | s1@asd.com | Student | 1 | + | student2 | s2@asd.com | Student | 2 | + | student3 | s3@asd.com | Student | 3 | + And the following "course enrolments" exists: + | user | course | role | + | student1 | C1 | student | + | student2 | C1 | student | + | student3 | C1 | student | + | admin | C1 | editingteacher | + And the following "groups" exists: + | name | description | course | idnumber | + | Group 1 | G1 description | C1 | G1 | + | Group 2 | G1 description | C1 | G2 | + And the following "group members" exists: + | user | group | + | student1 | G1 | + | student2 | G1 | + | student2 | G2 | + | student3 | G2 | + And I log in as "admin" + And I expand "Site administration" node + And I expand "Appearance" node + And I follow "Manage tags" + # Select (multi-select) - We will check "I fill in...". + And I fill in "otagsadd" with "OT1, OT2, OT3, OT4, OT5" + And I press "Add official tags" + And I am on homepage + And I follow "Course 1" + And I turn editing mode on + And I add a "Wiki" to section "1" and I fill the form with: + | Wiki name | Test this one | + | Description | Test this one | + | First page name | Test this one | + | Wiki mode | Collaborative wiki | + | Visible | Hide | + And I follow "Test this one" + And I press "Create page" + # Select (multi-select) - Checking "the select box should contain". + And the "tags[officialtags][]" select box should contain "OT1" + And the "tags[officialtags][]" select box should contain "OT2" + And the "tags[officialtags][]" select box should contain "OT3" + And the "tags[officialtags][]" select box should contain "OT4" + And the "tags[officialtags][]" select box should contain "OT5" + And the "tags[officialtags][]" select box should contain "OT1, OT2, OT3, OT4, OT5" + And the "tags[officialtags][]" select box should contain "OT5, OT4, OT3, OT2, OT1" + And the "tags[officialtags][]" select box should not contain "OT6" + And the "tags[officialtags][]" select box should not contain "OT7" + And the "tags[officialtags][]" select box should not contain "OT6, OT7" + # Text (textarea & editor) & Select (multi-select) - Checking "I fill the moodle form with". + When I fill the moodle form with: + | HTML format | Student page contents to be tagged | + | tags[officialtags][] | OT1, OT3, OT5 | + And I press "Save" + Then I should see "Student page contents to be tagged" in the "region-main" "region" + And I should see "OT1" in the ".wiki-tags" "css_element" + And I should see "OT3" in the ".wiki-tags" "css_element" + And I should see "OT5" in the ".wiki-tags" "css_element" + And I should not see "OT2" in the ".wiki-tags" "css_element" + And I should not see "OT4" in the ".wiki-tags" "css_element" + And I follow "Edit" + # Select (multi-select) - Checking "I select from". + And I select "OT2, OT4" from "tags[officialtags][]" + And I press "Save" + And I should see "OT2" in the ".wiki-tags" "css_element" + And I should see "OT4" in the ".wiki-tags" "css_element" + And I should not see "OT1" in the ".wiki-tags" "css_element" + And I should not see "OT3" in the ".wiki-tags" "css_element" + And I should not see "OT5" in the ".wiki-tags" "css_element" + And I follow "Edit" + # Select (multi-select) - Checking "the field should match" and "the field does not match value". + And the "tags[officialtags][]" field should match "OT2, OT4" value + And the field "tags[officialtags][]" does not match value "OT4" + And the field "tags[officialtags][]" does not match value "OT2" + And the field "tags[officialtags][]" does not match value "OT1, OT3, OT5" + And I press "Cancel" + And I follow "Edit settings" + And I expand all fieldsets + # Checkbox - Checking "I check". + And I check "Display description on course page" + # Checkbox - Checking "I fill the moodle form with:". + And I fill the moodle form with: + | Force format | 1 | + # Checkbox - Checking "the field should match" and "the field does not match value". + And the "Display description on course page" field should match "1" value + And the field "Display description on course page" does not match value "" + And I press "Save and return to course" + And I should see "Test this one" + And I follow "Test this one" + And I follow "Edit settings" + # Checkbox - Checking "the field should match" and "the following fields match these values". + And the following fields match these values: + | Display description on course page | 1 | + | Default format | HTML | + | Wiki name | Test this one | + And the "Force format" field should match "1" value + # Select (simple) - Checking "I fill the moodle form with:". + And I fill the moodle form with: + | Default format | NWiki | + | Display description on course page | | + # Checkbox - Checking "I uncheck". + And I uncheck "Force format" + # Select (simple) - Checking "I select from". + And I select "Separate groups" from "Group mode" + And I press "Save and display" + And I follow "Edit settings" + And the following fields match these values: + | Default format | NWiki | + | Group mode | Separate groups | + | Display description on course page | | + | Force format | | + # All fields - Checking "the following fields do not match these values". + And the following fields do not match these values: + | Wiki name | Test this one baby | + | Default format | HTML | + | Force format | 1 | + And I press "Cancel" + And I follow "Course 1" + # Radio - Checking "I check" and "the field should match". + And I add a "Choice" to section "1" and I fill the form with: + | Choice name | Test choice name | + | Description | Test choice description | + | Allow choice to be updated | Yes | + | Option 1 | one | + | Option 2 | two | + | Option 3 | three | + And I follow "Test choice name" + And I check "one" + And I press "Save my choice" + And the "one" field should match "1" value + And the "two" field should match "" value + + Scenario: with JS disabled all form fields getters and setters works as expected + + @javascript + Scenario: with JS enabled all form fields getters and setters works as expected + Then I follow "Course 1" + And I expand "Users" node + And I follow "Groups" + # Select (multi-select & AJAX) - Checking "I select from" and "select box should contain". + And I select "Group 2" from "groups" + And the "members" select box should contain "Student 2" + And the "members" select box should contain "Student 3" + And the "members" select box should not contain "Student 1" + And I select "Group 1" from "groups" + And the "members" select box should contain "Student 1" + And the "members" select box should contain "Student 2" + And the "members" select box should not contain "Student 3" + # Checkbox (AJAX) - Checking "I check" and "I fill the moodle form with". + And I follow "Course 1" + And I add a "Lesson" to section "1" + And I fill the moodle form with: + | Name | Test lesson | + | available[enabled] | 1 | + And I check "deadline[enabled]" + # Checkbox (AJAX) - Checking "field should match" before saving. + And the "available[enabled]" field should match "1" value + And the "available[day]" "field" should be enabled + And the "deadline[enabled]" field should match "1" value + And I press "Save and display" + And I follow "Edit settings" + And the "available[enabled]" field should match "1" value + And the "available[day]" "field" should be enabled + And the "deadline[enabled]" field should match "1" value + And I press "Cancel" From cdbd4326163968871d6a4d81dfb88a0507d51198 Mon Sep 17 00:00:00 2001 From: David Monllao Date: Fri, 24 Jan 2014 13:24:22 +0800 Subject: [PATCH 3/7] MDL-43738 behat: Normalization and major refactoring of getters and setters Every single step that sets or gets a value from a field has been updated to follow the same behaviour both when using it through a single step or through generic steps like "I fill the moodle form with:", to resume all the changes: - Created a behat_form_group to re-guess the field type and act appropriately setting and getting it's value - Normalize all getters and setters to use behat_form_field children - Complete behat_form_checkbox to trigger the appropiate JS event needed to perform some JS scripts that are listening - Refactor MDL-43713 multi-select changes and remove the two new steps introduced there as that behaviour can be managed from the generic getter --- lib/behat/form_field/behat_form_checkbox.php | 50 +++- .../form_field/behat_form_date_selector.php | 10 +- .../behat_form_date_time_selector.php | 11 +- lib/behat/form_field/behat_form_field.php | 24 ++ lib/behat/form_field/behat_form_group.php | 42 +++ lib/behat/form_field/behat_form_radio.php | 42 ++- lib/behat/form_field/behat_form_select.php | 176 ++++++++++--- lib/tests/behat/behat_forms.php | 245 +++++++----------- 8 files changed, 396 insertions(+), 204 deletions(-) create mode 100644 lib/behat/form_field/behat_form_group.php diff --git a/lib/behat/form_field/behat_form_checkbox.php b/lib/behat/form_field/behat_form_checkbox.php index fb220d1cf35..69a9cbc8b34 100644 --- a/lib/behat/form_field/behat_form_checkbox.php +++ b/lib/behat/form_field/behat_form_checkbox.php @@ -47,18 +47,37 @@ class behat_form_checkbox extends behat_form_field { */ public function set_value($value) { - if (!$this->running_javascript()) { - $this->field->check(); - return; - } - if (!empty($value) && !$this->field->isChecked()) { + + if (!$this->running_javascript()) { + $this->field->check(); + return; + } + // Check it if it should be checked and it is not. $this->field->click(); + // Trigger the onchange event as triggered when 'checking' the checkbox. + $this->session->getDriver()->triggerSynScript( + $this->field->getXPath(), + "Syn.trigger('change', {}, {{ELEMENT}})" + ); + } else if (empty($value) && $this->field->isChecked()) { + + if (!$this->running_javascript()) { + $this->field->uncheck(); + return; + } + // Uncheck if it is checked and shouldn't. $this->field->click(); + + // Trigger the onchange event as triggered when 'checking' the checkbox. + $this->session->getDriver()->triggerSynScript( + $this->field->getXPath(), + "Syn.trigger('change', {}, {{ELEMENT}})" + ); } } @@ -70,4 +89,25 @@ class behat_form_checkbox extends behat_form_field { public function get_value() { return $this->field->isChecked(); } + + /** + * Is it enabled? + * + * @param string $expectedvalue Anything !empty() is considered checked. + * @return bool + */ + public function matches($expectedvalue = false) { + + $ischecked = $this->field->isChecked(); + + // Any non-empty value provided means that it should be checked. + if (!empty($expectedvalue) && $ischecked) { + return true; + } else if (empty($expectedvalue) && !$ischecked) { + return true; + } + + return false; + } + } diff --git a/lib/behat/form_field/behat_form_date_selector.php b/lib/behat/form_field/behat_form_date_selector.php index 2d86a768cf6..4fc8e47646b 100644 --- a/lib/behat/form_field/behat_form_date_selector.php +++ b/lib/behat/form_field/behat_form_date_selector.php @@ -25,14 +25,13 @@ // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. -require_once(__DIR__ . '/behat_form_select.php'); +require_once(__DIR__ . '/behat_form_group.php'); /** * Date form field. * - * Simple extension of behat_form_select to allow date-type - * select fields to be filled like select elements instead of - * text elements. + * Simple extension of behat_form_group to allow the different + * date_selector fields to be filled according to it's type. * * This class will be refactored in case we are interested in * creating more complex formats to fill date and date-time fields. @@ -42,4 +41,5 @@ require_once(__DIR__ . '/behat_form_select.php'); * @copyright 2013 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class behat_form_date_selector extends behat_form_select {} +class behat_form_date_selector extends behat_form_group { +} diff --git a/lib/behat/form_field/behat_form_date_time_selector.php b/lib/behat/form_field/behat_form_date_time_selector.php index 22c4befa610..19181cf6b40 100644 --- a/lib/behat/form_field/behat_form_date_time_selector.php +++ b/lib/behat/form_field/behat_form_date_time_selector.php @@ -25,21 +25,18 @@ // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. -require_once(__DIR__ . '/behat_form_select.php'); +require_once(__DIR__ . '/behat_form_date_selector.php'); /** * Date time form field. * - * Simple extension of behat_form_select to allow datetime-type - * select fields to be filled like select elements instead of - * text elements. - * * This class will be refactored in case we are interested in - * creating more complex formats to fill date and date-time fields. + * creating more complex formats to fill date-time fields. * * @package core_form * @category test * @copyright 2013 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class behat_form_date_time_selector extends behat_form_select {} +class behat_form_date_time_selector extends behat_form_date_selector { +} diff --git a/lib/behat/form_field/behat_form_field.php b/lib/behat/form_field/behat_form_field.php index 8d978c0a7a8..f1279a829e8 100644 --- a/lib/behat/form_field/behat_form_field.php +++ b/lib/behat/form_field/behat_form_field.php @@ -95,6 +95,30 @@ class behat_form_field { } } + /** + * Generic match implementation + * + * Will work well with text-based fields, extension required + * for most of the other cases. + * + * @param string $expectedvalue + * @return bool The provided value matches the field value? + */ + public function matches($expectedvalue) { + + // If we are not dealing with a text-based tag try to find the most appropiate + // behat_form_* class to deal with it. + if ($instance = $this->guess_type()) { + return $instance->matches($expectedvalue); + } + + // Text-based comparison. + if (trim($expectedvalue) != trim($this->get_value())) { + return false; + } + return true; + } + /** * Guesses the element type we are dealing with in case is not a text-based element. * diff --git a/lib/behat/form_field/behat_form_group.php b/lib/behat/form_field/behat_form_group.php new file mode 100644 index 00000000000..533a787cd7e --- /dev/null +++ b/lib/behat/form_field/behat_form_group.php @@ -0,0 +1,42 @@ +. + +/** + * Generic group field class. + * + * @package core_form + * @category test + * @copyright 2014 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_form_field.php'); + +/** + * Class to re-guess the field type as grouped fields can have different field types. + * + * When filling fields in a fgroup field element we don't know what kind + * of field are we dealing with, so we should re-guess it. + * + * @package core_form + * @category test + * @copyright 2014 David Monllaó + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class behat_form_group extends behat_form_field { +} diff --git a/lib/behat/form_field/behat_form_radio.php b/lib/behat/form_field/behat_form_radio.php index 1ca020ebec3..27b5d005631 100644 --- a/lib/behat/form_field/behat_form_radio.php +++ b/lib/behat/form_field/behat_form_radio.php @@ -31,7 +31,8 @@ require_once(__DIR__ . '/behat_form_checkbox.php'); * Radio input form field. * * Extends behat_form_checkbox as the set_value() behaviour - * is the same. + * is the same and it behaves closer to a checkbox than to + * a text field. * * This form field type can be added to forms as any other * moodle form element, but it does not make sense without @@ -51,9 +52,46 @@ class behat_form_radio extends behat_form_checkbox { /** * Returns the radio input value attribute. * + * Here we can not extend behat_form_checkbox because + * isChecked() does internally a (bool)getValue() and + * it is not good for radio buttons. + * * @return string The value attribute */ public function get_value() { - return $this->field->getValue(); + return (bool)$this->field->getAttribute('checked'); + } + + /** + * Sets the value of a radio + * + * Partially overwriting behat_form_checkbox + * implementation as when JS is disabled we + * can not check() and we should use setValue() + * + * @param string $value + * @return void + */ + public function set_value($value) { + + if ($this->running_javascript()) { + parent::set_value($value); + } else { + // Goutte does not accept a check nor a click in an input[type=radio]. + $this->field->setValue($this->field->getAttribute('value')); + } + } + + /** + * Returns whether the provided value matches the current value or not. + * + * @param string $expectedvalue + * @return bool + */ + public function matches($expectedvalue = false) { + if (trim($expectedvalue) != trim($this->get_value())) { + return false; + } + return true; } } diff --git a/lib/behat/form_field/behat_form_select.php b/lib/behat/form_field/behat_form_select.php index 12d7c863864..86d6b781734 100644 --- a/lib/behat/form_field/behat_form_select.php +++ b/lib/behat/form_field/behat_form_select.php @@ -40,7 +40,7 @@ class behat_form_select extends behat_form_field { /** * Sets the value(s) of a select element. * -r * Seems an easy select, but there are lots of combinations + * Seems an easy select, but there are lots of combinations * of browsers and operative systems and each one manages the * autosubmits and the multiple option selects in a different way. * @@ -93,6 +93,7 @@ r * Seems an easy select, but there are lots of combinations // In some browsers the selectOption actions can perform a form submit or reload page // so we need to ensure the element is still available to continue interacting // with it. We don't wait here. + // getXpath() does not send a query to selenium, so we don't need to wrap it in a try & catch. $selectxpath = $this->field->getXpath(); if (!$this->session->getDriver()->find($selectxpath)) { return; @@ -105,30 +106,34 @@ r * Seems an easy select, but there are lots of combinations return; } - // We also check that the option(s) are still there. We neither wait. - foreach ($options as $option) { - $valueliteral = $this->session->getSelectorsHandler()->xpathLiteral(trim($option)); - $optionxpath = $selectxpath . "/descendant::option[(./@value=$valueliteral or normalize-space(.)=$valueliteral)]"; - if (!$this->session->getDriver()->find($optionxpath)) { - return; - } + // Wait for all the possible AJAX requests that have been + // already triggered by selectOption() to be finished. + $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS); + + // Wrapped in try & catch as the element may disappear if an AJAX request was submitted. + try { + $multiple = $this->field->hasAttribute('multiple'); + } catch (Exception $e) { + // We do not specify any specific Exception type as there are + // different exceptions that can be thrown by the driver and + // we can not control them all, also depending on the selenium + // version the exception type can change. + return; } // Single select sometimes needs an extra click in the option. if (!$multiple) { + // Var $options only contains 1 option. + $optionxpath = $this->get_option_xpath(end($options), $selectxpath); + // Using the driver direcly because Element methods are messy when dealing // with elements inside containers. - $optionnodes = $this->session->getDriver()->find($optionxpath); - if ($optionnodes) { + if ($optionnodes = $this->session->getDriver()->find($optionxpath)) { + // Wrapped in a try & catch as we can fall into race conditions // and the element may not be there. try { - - // Wait for all the possible AJAX requests that have been - // already triggered by selectOption() to be finished. - $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS); - current($optionnodes)->click(); } catch (Exception $e) { // We continue and return as this means that the element is not there or it is not the same. @@ -138,10 +143,6 @@ r * Seems an easy select, but there are lots of combinations } else { - // Wait for all the possible AJAX requests that have been - // already triggered by selectOption() to be finished. - $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS); - // Wrapped in a try & catch as we can fall into race conditions // and the element may not be there. try { @@ -152,22 +153,28 @@ r * Seems an easy select, but there are lots of combinations return; } - // We ensure that the option is still there. - if (!$this->session->getDriver()->find($optionxpath)) { - return; + // We also check that the option(s) are still there. We neither wait. + foreach ($options as $option) { + $optionxpath = $this->get_option_xpath($option, $selectxpath); + if (!$this->session->getDriver()->find($optionxpath)) { + return; + } } // Wait for all the possible AJAX requests that have been - // already triggered by selectOption() to be finished. + // already triggered by clicking on the field to be finished. $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS); // Wrapped in a try & catch as we can fall into race conditions // and the element may not be there. try { + // Repeating the select(s) as some drivers (chrome that I know) are moving // to another option after the general select field click above. + $afterfirstoption = false; foreach ($options as $option) { - $this->field->selectOption(trim($option), true); + $this->field->selectOption(trim($option), $afterfirstoption); + $afterfirstoption = true; } } catch (Exception $e) { // We continue and return as this means that the element is not there or it is not the same. @@ -182,6 +189,96 @@ r * Seems an easy select, but there are lots of combinations * @return string Comma separated if multiple options are selected. Commas in option texts escaped with backslash. */ public function get_value() { + return $this->get_selected_options(); + } + + /** + * Returns whether the provided argument matches the current value. + * + * @param mixed $expectedvalue + * @return bool + */ + public function matches($expectedvalue) { + + $multiple = $this->field->hasAttribute('multiple'); + + // Same implementation as the parent if it is a single select. + if (!$multiple) { + if (trim($expectedvalue) != trim($this->get_value())) { + return false; + } + return true; + } + + // We are dealing with a multi-select. + + // Can pass multiple comma separated, with valuable commas escaped with backslash. + $expectedarr = array(); // Array of passed text options to test. + + // Unescape + trim all options and flip it to have the expected values as keys. + $expectedoptions = $this->get_unescaped_options($expectedvalue); + + // Get currently selected option's texts. + $texts = $this->get_selected_options(true); + $selectedoptiontexts = $this->get_unescaped_options($texts); + + // Get currently selected option's values. + $values = $this->get_selected_options(false); + $selectedoptionvalues = $this->get_unescaped_options($values); + + // Precheck to speed things up. + if (count($expectedoptions) !== count($selectedoptiontexts) || + count($expectedoptions) !== count($selectedoptionvalues)) { + return false; + } + + // We check against string-ordered lists of options. + if ($expectedoptions != $selectedoptiontexts && + $expectedoptions != $selectedoptionvalues) { + return false; + } + + return true; + } + + /** + * Cleans the list of options and returns it as a string separating options with |||. + * + * @param string $value The string containing the escaped options. + * @return string The options + */ + protected function get_unescaped_options($value) { + + // Can be multiple comma separated, with valuable commas escaped with backslash. + $optionsarray = array_map( + 'trim', + preg_replace('/\\\,/', ',', + preg_split('/(?field->hasAttribute('multiple'); @@ -198,17 +295,24 @@ r * Seems an easy select, but there are lots of combinations if ($option->hasAttribute('selected')) { if ($multiple) { // If the select is multiple, text commas must be encoded. - $selectedoptions[] = trim(str_replace(',', '\,', $option->getText())); + $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}())); } else { - $selectedoptions[] = trim($option->getText()); + $selectedoptions[] = trim($option->{$method}()); } } } - // Goutte does not keep the 'selected' attribute updated, but its getValue() returns - // the selected elements correctly, also those having commas within them. } else { + // Goutte does not keep the 'selected' attribute updated, but its getValue() returns + // the selected elements correctly, also those having commas within them. + + // Goutte returns the values as an array or as a string depending + // on whether multiple options are selected or not. $values = $this->field->getValue(); + if (!is_array($values)) { + $values = array($values); + } + // Get all the options in the select and extract their value/text pairs. $alloptions = $this->field->findAll('xpath', '//option'); foreach ($alloptions as $option) { @@ -216,9 +320,9 @@ r * Seems an easy select, but there are lots of combinations if (in_array($option->getValue(), $values)) { if ($multiple) { // If the select is multiple, text commas must be encoded. - $selectedoptions[] = trim(str_replace(',', '\,', $option->getText())); + $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}())); } else { - $selectedoptions[] = trim($option->getText()); + $selectedoptions[] = trim($option->{$method}()); } } } @@ -226,4 +330,16 @@ r * Seems an easy select, but there are lots of combinations return implode(', ', $selectedoptions); } + + /** + * Returns the opton XPath based on it's select xpath. + * + * @param string $option + * @param string $selectxpath + * @return string xpath + */ + protected function get_option_xpath($option, $selectxpath) { + $valueliteral = $this->session->getSelectorsHandler()->xpathLiteral(trim($option)); + return $selectxpath . "/descendant::option[(./@value=$valueliteral or normalize-space(.)=$valueliteral)]"; + } } diff --git a/lib/tests/behat/behat_forms.php b/lib/tests/behat/behat_forms.php index acce54f4c85..0dd34ea72a6 100644 --- a/lib/tests/behat/behat_forms.php +++ b/lib/tests/behat/behat_forms.php @@ -147,7 +147,7 @@ class behat_forms extends behat_base { } /** - * Fills in form field with specified id|name|label|value. + * Fills in form text field with specified id|name|label|value. It works with text-based fields. * * @When /^I fill in "(?P(?:[^"]|\\")*)" with "(?P(?:[^"]|\\")*)"$/ * @throws ElementNotFoundException Thrown by behat_base::find @@ -155,9 +155,7 @@ class behat_forms extends behat_base { * @param string $value */ public function fill_field($field, $value) { - - $fieldnode = $this->find_field($field); - $fieldnode->setValue($value); + $this->set_field_value($field, $value); } /** @@ -169,33 +167,7 @@ class behat_forms extends behat_base { * @param string $select */ public function select_option($option, $select) { - - $selectnode = $this->find_field($select); - $selectnode->selectOption($option); - - // Adding a click as Selenium requires it to fire some JS events. - if ($this->running_javascript()) { - - // In some browsers the selectOption actions can perform a page reload - // so we need to ensure the element is still available to continue interacting - // with it. We don't wait here. - if (!$this->getSession()->getDriver()->find($selectnode->getXpath())) { - return; - } - - // Single select needs an extra click in the option. - if (!$selectnode->hasAttribute('multiple')) { - - // Avoid quotes problems. - $option = $this->getSession()->getSelectorsHandler()->xpathLiteral($option); - $xpath = "//option[(./@value=$option or normalize-space(.)=$option)]"; - $optionnode = $this->find('xpath', $xpath, false, $selectnode); - $optionnode->click(); - } else { - // Multiple ones needs the click in the select. - $selectnode->click(); - } - } + $this->set_field_value($select, $option); } /** @@ -206,14 +178,7 @@ class behat_forms extends behat_base { * @param string $radio The radio button id, name or label value */ public function select_radio($radio) { - - $radionode = $this->find_radio($radio); - $radionode->check(); - - // Adding a click as Selenium requires it to fire some JS events. - if ($this->running_javascript()) { - $radionode->click(); - } + $this->set_field_value($radio, 1); } /** @@ -224,9 +189,7 @@ class behat_forms extends behat_base { * @param string $option */ public function check_option($option) { - - $checkboxnode = $this->find_field($option); - $checkboxnode->check(); + $this->set_field_value($option, 1); } /** @@ -237,16 +200,11 @@ class behat_forms extends behat_base { * @param string $option */ public function uncheck_option($option) { - - $checkboxnode = $this->find_field($option); - $checkboxnode->uncheck(); + $this->set_field_value($option, ''); } /** - * Checks that the form element field have the specified value. - * - * NOTE: This method/step does not support all fields. Namely, multi-select ones aren't supported. - * @todo: MDL-43738 would try to put some better support here for that multi-select and others. + * Checks that the field matches the specified value. When using multi-select fields use commas to separate selected options. * * @Then /^the "(?P(?:[^"]|\\")*)" field should match "(?P(?:[^"]|\\")*)" value$/ * @throws ExpectationException @@ -260,10 +218,10 @@ class behat_forms extends behat_base { // Get the field. $field = behat_field_manager::get_form_field($fieldnode, $this->getSession()); - $fieldvalue = $field->get_value(); // Checks if the provided value matches the current field value. - if (trim($value) != trim($fieldvalue)) { + if (!$field->matches($value)) { + $fieldvalue = $field->get_value(); throw new ExpectationException( 'The \'' . $locator . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' , $this->getSession() @@ -271,26 +229,91 @@ class behat_forms extends behat_base { } } + /** + * Checks that the form element field does not match the specified value. + * + * @Then /^the field "(?P(?:[^"]|\\")*)" does not match value "(?P(?:[^"]|\\")*)"$/ + * @throws ExpectationException + * @throws ElementNotFoundException Thrown by behat_base::find + * @param string $field + * @param string $value + * @return void + */ + public function the_field_does_not_match_value($field, $value) { + + $fieldnode = $this->find_field($field); + + // Get the field. + $formfield = behat_field_manager::get_form_field($fieldnode, $this->getSession()); + + // Checks if the provided value matches the current field value. + if ($formfield->matches($value)) { + $fieldvalue = $formfield->get_value(); + throw new ExpectationException( + 'The \'' . $field . '\' value matches \'' . $value . '\' and it should not match it' , + $this->getSession() + ); + } + } + + /** + * Checks if fields values matches the provided values. Provide a table with field/value data. + * + * @Then /^the following fields match these values:$/ + * @throws ExpectationException + * @param TableNode $data Pairs of | field | value | + */ + public function the_following_fields_match_these_values(TableNode $data) { + + // Expand all fields in case we have. + $this->expand_all_fields(); + + $datahash = $data->getRowsHash(); + + // The action depends on the field type. + foreach ($datahash as $locator => $value) { + $this->the_field_should_match_value($locator, $value); + } + } + + /** + * Checks that fields values do not match the provided values. Provide a table with field/value data. + * + * @Then /^the following fields do not match these values:$/ + * @throws ExpectationException + * @param TableNode $data Pairs of | field | value | + */ + public function the_following_fields_do_not_match_these_values(TableNode $data) { + + // Expand all fields in case we have. + $this->expand_all_fields(); + + $datahash = $data->getRowsHash(); + + // The action depends on the field type. + foreach ($datahash as $locator => $value) { + $this->the_field_does_not_match_value($locator, $value); + } + } + /** * Checks, that checkbox with specified in|name|label|value is checked. * * @Then /^the "(?P(?:[^"]|\\")*)" checkbox should be checked$/ - * @see Behat\MinkExtension\Context\MinkContext * @param string $checkbox */ public function assert_checkbox_checked($checkbox) { - $this->assertSession()->checkboxChecked($checkbox); + $this->the_field_should_match_value($checkbox, 1); } /** * Checks, that checkbox with specified in|name|label|value is unchecked. * * @Then /^the "(?P(?:[^"]|\\")*)" checkbox should not be checked$/ - * @see Behat\MinkExtension\Context\MinkContext * @param string $checkbox */ public function assert_checkbox_not_checked($checkbox) { - $this->assertSession()->checkboxNotChecked($checkbox); + $this->the_field_should_match_value($checkbox, ''); } /** @@ -382,111 +405,23 @@ class behat_forms extends behat_base { } /** - * Checks, that given select box contains the specified option selected. + * Generic field setter. * - * @Then /^the "(?P(?:[^"]|\\")*)" select box should contain "(?P(?:[^"]|\\")*)" selected$/ - * @throws ExpectationException - * @throws ElementNotFoundException Thrown by behat_base::find - * @param string $select The select element name - * @param string $option The option text. Plain value or comma separated - * values if multiple. Commas in multiple values escaped with backslash. - */ - public function the_select_box_should_contain_selected($select, $option) { - - $selectnode = $this->find_field($select); - $multiple = $selectnode->hasAttribute('multiple'); - $optionsarr = array(); // Array of passed text options to test. - $selectedarr = array(); // Array of selected text options. - - if ($multiple) { - // Can pass multiple comma separated, with valuable commas escaped with backslash. - foreach (preg_replace('/\\\,/', ',', preg_split('/(?getSession()); - $value = $field->get_value(); - - if ($multiple) { - // Can be multiple comma separated, with valuable commas escaped with backslash. - foreach (preg_replace('/\\\,/', ',', preg_split('/(?getSession() - ); - } - } - } - - /** - * Checks, that given select box contains the specified option not selected. + * Internal API method, a generic *I set "VALUE" to "FIELD" field* + * could be created based on it. * - * @Then /^the "(?P(?:[^"]|\\")*)" select box should contain "(?P(?:[^"]|\\")*)" not selected$/ - * @throws ExpectationException - * @throws ElementNotFoundException Thrown by behat_base::find - * @param string $select The select element name - * @param string $option The option text. Plain value or comma separated - * values if multiple. Commas in multiple values escaped with backslash. + * @param string $fieldlocator The pointer to the field, it will depend on the field type. + * @param string $value + * @return void */ - public function the_select_box_should_contain_not_selected($select, $option) { + protected function set_field_value($fieldlocator, $value) { - $selectnode = $this->find_field($select); - $multiple = $selectnode->hasAttribute('multiple'); - $optionsarr = array(); // Array of passed text options to test. - $selectedarr = array(); // Array of selected text options. + $node = $this->find_field($fieldlocator); - // First of all, the option(s) must exist, delegate it. Plain and raw. - $this->the_select_box_should_contain($select, $option); - - if ($multiple) { - // Can pass multiple comma separated, with valuable commas escaped with backslash. - foreach (preg_replace('/\\\,/', ',', preg_split('/(?getSession()); - $value = $field->get_value(); - - if ($multiple) { - // Can be multiple comma separated, with valuable commas escaped with backslash. - foreach (preg_replace('/\\\,/', ',', preg_split('/(?getSession() - ); - } - } + // We delegate to behat_form_field class, it will + // guess the type properly as it is a select tag. + $field = behat_field_manager::get_form_field($node, $this->getSession()); + $field->set_value($value); } } From b8939c9318ae4ef22b67e423fb9e9143f93eed91 Mon Sep 17 00:00:00 2001 From: David Monllao Date: Mon, 10 Feb 2014 11:55:49 +0800 Subject: [PATCH 4/7] MDL-43738 behat: New step to adapt to radio buttons behaviour Now we interact with radio fields through it's value or text. --- .../behat/tests/behat/data_generators.feature | 4 +- .../tests/behat/edit_permissions.feature | 20 +++++----- lib/tests/behat/behat_data_generators.php | 6 +-- lib/tests/behat/behat_permissions.php | 40 +++++++++++++++++++ 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/admin/tool/behat/tests/behat/data_generators.feature b/admin/tool/behat/tests/behat/data_generators.feature index 7895d396d26..0e95aebeab8 100644 --- a/admin/tool/behat/tests/behat/data_generators.feature +++ b/admin/tool/behat/tests/behat/data_generators.feature @@ -90,10 +90,10 @@ Feature: Set up contextual data for tests And I expand "Users" node And I follow "Permissions" And I select "Student (1)" from "Advanced role override" - Then the "mod/forum:editanypost" field should match "1" value + Then "mod/forum:editanypost" capability has "Allow" permission And I press "Cancel" And I select "Teacher (1)" from "Advanced role override" - And the "mod/forum:replynews" field should match "-1" value + And "mod/forum:replynews" capability has "Prevent" permission And I press "Cancel" Scenario: Add course enrolments diff --git a/admin/tool/behat/tests/behat/edit_permissions.feature b/admin/tool/behat/tests/behat/edit_permissions.feature index b16b0e0c8a0..a7935b9cbae 100644 --- a/admin/tool/behat/tests/behat/edit_permissions.feature +++ b/admin/tool/behat/tests/behat/edit_permissions.feature @@ -25,10 +25,10 @@ Feature: Edit capabilities | moodle/grade:managesharedforms | Prevent | | moodle/course:request | Prohibit | When I follow "Edit Teacher role" - Then the "block/mnet_hosts:myaddinstance" field should match "1" value - And the "moodle/community:add" field should match "0" value - And the "moodle/grade:managesharedforms" field should match "-1" value - And the "moodle/course:request" field should match "-1000" value + Then "block/mnet_hosts:myaddinstance" capability has "Allow" permission + And "moodle/community:add" capability has "Not set" permission + And "moodle/grade:managesharedforms" capability has "Prevent" permission + And "moodle/course:request" capability has "Prohibit" permission @javascript Scenario: Course capabilities overrides @@ -41,9 +41,9 @@ Feature: Edit capabilities | mod/forum:editanypost | Prevent | | mod/forum:addquestion | Allow | When I select "Student (3)" from "Advanced role override" - Then the "mod/forum:deleteanypost" field should match "-1000" value - And the "mod/forum:editanypost" field should match "-1" value - And the "mod/forum:addquestion" field should match "1" value + Then "mod/forum:deleteanypost" capability has "Prohibit" permission + And "mod/forum:editanypost" capability has "Prevent" permission + And "mod/forum:addquestion" capability has "Allow" permission @javascript Scenario: Module capabilities overrides @@ -60,6 +60,6 @@ Feature: Edit capabilities | mod/forum:editanypost | Prevent | | mod/forum:addquestion | Allow | When I select "Student (3)" from "Advanced role override" - Then the "mod/forum:deleteanypost" field should match "-1000" value - And the "mod/forum:editanypost" field should match "-1" value - And the "mod/forum:addquestion" field should match "1" value + Then "mod/forum:deleteanypost" capability has "Prohibit" permission + And "mod/forum:editanypost" capability has "Prevent" permission + And "mod/forum:addquestion" capability has "Allow" permission diff --git a/lib/tests/behat/behat_data_generators.php b/lib/tests/behat/behat_data_generators.php index 0a5b39007a7..18dfb22bf57 100644 --- a/lib/tests/behat/behat_data_generators.php +++ b/lib/tests/behat/behat_data_generators.php @@ -299,13 +299,13 @@ class behat_data_generators extends behat_base { $context = $this->get_context($data['contextlevel'], $data['reference']); switch ($data['permission']) { - case self::cap_allow: + case get_string('allow', 'role'): $permission = CAP_ALLOW; break; - case self::cap_prevent: + case get_string('prevent', 'role'): $permission = CAP_PREVENT; break; - case self::cap_prohibit: + case get_string('prohibit', 'role'): $permission = CAP_PROHIBIT; break; default: diff --git a/lib/tests/behat/behat_permissions.php b/lib/tests/behat/behat_permissions.php index 2d5f2f04fa6..4b6560e173b 100644 --- a/lib/tests/behat/behat_permissions.php +++ b/lib/tests/behat/behat_permissions.php @@ -133,4 +133,44 @@ class behat_permissions extends behat_base { } } + /** + * Checks if the capability has the specified permission. Works in the role definition advanced page. + * + * @Then /^"(?P(?:[^"]|\\")*)" capability has "(?PNot set|Allow|Prevent|Prohibit)" permission$/ + * @throws ExpectationException + * @param string $capabilityname + * @param string $permission + * @return void + */ + public function capability_has_permission($capabilityname, $permission) { + + // We already know the name, so we just need the value. + $radioxpath = "//table[@class='rolecap']/descendant::input[@type='radio']" . + "[@name='" . $capabilityname . "'][@checked]"; + + $checkedradio = $this->find('xpath', $radioxpath); + + switch ($permission) { + case get_string('notset', 'role'): + $perm = CAP_INHERIT; + break; + case get_string('allow', 'role'): + $perm = CAP_ALLOW; + break; + case get_string('prevent', 'role'): + $perm = CAP_PREVENT; + break; + case get_string('prohibit', 'role'): + $perm = CAP_PROHIBIT; + break; + default: + throw new ExpectationException('"' . $permission . '" permission does not exist', $this->getSession()); + break; + } + + if ($checkedradio->getAttribute('value') != $perm) { + throw new ExpectationException('"' . $capabilityname . '" permission is not "' . $permission . '"', $this->getSession()); + } + } + } From c8cd93583d7c73782adef4e454286d7ef9815162 Mon Sep 17 00:00:00 2001 From: David Monllao Date: Mon, 10 Feb 2014 16:16:32 +0800 Subject: [PATCH 5/7] MDL-43738 behat: Backporting new 2.7 methods to 2.5 We encourage backporting new feature files, adding here also these Moodle 2.7 new step definitions so future features will be easier to backport as we would be able to directly cherry-pick them. --- lib/tests/behat/behat_forms.php | 68 +++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/lib/tests/behat/behat_forms.php b/lib/tests/behat/behat_forms.php index 0dd34ea72a6..d1f411709d6 100644 --- a/lib/tests/behat/behat_forms.php +++ b/lib/tests/behat/behat_forms.php @@ -60,6 +60,29 @@ class behat_forms extends behat_base { $buttonnode->press(); } + /** + * Fills a form with field/value data. More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps. + * + * Backport of Moodle 2.7 method to help backporting + * features to 2.5. + * + * @Given /^I set the following fields to these values:$/ + * @throws ElementNotFoundException Thrown by behat_base::find + * @param TableNode $data + */ + public function i_set_the_following_fields_to_these_values(TableNode $data) { + + // Expand all fields in case we have. + $this->expand_all_fields(); + + $datahash = $data->getRowsHash(); + + // The action depends on the field type. + foreach ($datahash as $locator => $value) { + $this->set_field_value($locator, $value); + } + } + /** * Fills a moodle form with field/value data. * @@ -146,6 +169,22 @@ class behat_forms extends behat_base { } + /** + * Sets the specified value to the field. + * + * Backport of Moodle 2.7 method to help backporting + * features to 2.5. + * + * @Given /^I set the field "(?P(?:[^"]|\\")*)" to "(?P(?:[^"]|\\")*)"$/ + * @throws ElementNotFoundException Thrown by behat_base::find + * @param string $field + * @param string $value + * @return void + */ + public function i_set_the_field_to($field, $value) { + $this->set_field_value($field, $value); + } + /** * Fills in form text field with specified id|name|label|value. It works with text-based fields. * @@ -229,6 +268,35 @@ class behat_forms extends behat_base { } } + /** + * Checks, the field matches the value. More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps. + * + * Backport of Moodle 2.7 method to help backporting + * features to 2.5. + * + * @Then /^the field "(?P(?:[^"]|\\")*)" matches value "(?P(?:[^"]|\\")*)"$/ + * @throws ElementNotFoundException Thrown by behat_base::find + * @param string $field + * @param string $value + * @return void + */ + public function the_field_matches_value($field, $value) { + + $fieldnode = $this->find_field($field); + + // Get the field. + $formfield = behat_field_manager::get_form_field($fieldnode, $this->getSession()); + + // Checks if the provided value matches the current field value. + if (!$formfield->matches($value)) { + $fieldvalue = $formfield->get_value(); + throw new ExpectationException( + 'The \'' . $field . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' , + $this->getSession() + ); + } + } + /** * Checks that the form element field does not match the specified value. * From 67a73f7457a340a5b706a385c1f1df72edb184de Mon Sep 17 00:00:00 2001 From: David Monllao Date: Fri, 31 Jan 2014 12:29:37 +0800 Subject: [PATCH 6/7] MDL-43738 behat: Backport to 2.5 Other issues this patch depends on were not backported to 2.5 so we need to cover it: - Add hardcoded waits as MDL-42625 was not integrated - Add behat_form_field::get_internal_field_id() - Use document.readyState rather than our custom JS query (MDL-42625) --- .../tests/behat/get_and_set_fields.feature | 3 +++ lib/behat/form_field/behat_form_field.php | 17 +++++++++++++++++ lib/behat/form_field/behat_form_select.php | 6 +++--- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/admin/tool/behat/tests/behat/get_and_set_fields.feature b/admin/tool/behat/tests/behat/get_and_set_fields.feature index f4239508f1d..8750ad38614 100644 --- a/admin/tool/behat/tests/behat/get_and_set_fields.feature +++ b/admin/tool/behat/tests/behat/get_and_set_fields.feature @@ -150,10 +150,13 @@ Feature: Verify that all form fields values can be get and set And I follow "Groups" # Select (multi-select & AJAX) - Checking "I select from" and "select box should contain". And I select "Group 2" from "groups" + And I wait "5" seconds And the "members" select box should contain "Student 2" And the "members" select box should contain "Student 3" And the "members" select box should not contain "Student 1" And I select "Group 1" from "groups" + And I wait "5" seconds + And the "members" select box should contain "Student 2" And the "members" select box should contain "Student 1" And the "members" select box should contain "Student 2" And the "members" select box should not contain "Student 3" diff --git a/lib/behat/form_field/behat_form_field.php b/lib/behat/form_field/behat_form_field.php index f1279a829e8..5951defb84e 100644 --- a/lib/behat/form_field/behat_form_field.php +++ b/lib/behat/form_field/behat_form_field.php @@ -190,4 +190,21 @@ class behat_form_field { return get_class($this->session->getDriver()) !== 'Behat\Mink\Driver\GoutteDriver'; } + /** + * Gets the field internal id used by selenium wire protocol. + * + * Only available when running_javascript(). + * + * @throws coding_exception + * @return int + */ + protected function get_internal_field_id() { + + if (!$this->running_javascript()) { + throw new coding_exception('You can only get an internal ID using the selenium driver.'); + } + + return $this->session->getDriver()->getWebDriverSession()->element('xpath', $this->field->getXPath())->getID(); + } + } diff --git a/lib/behat/form_field/behat_form_select.php b/lib/behat/form_field/behat_form_select.php index 86d6b781734..d0911d6c8b2 100644 --- a/lib/behat/form_field/behat_form_select.php +++ b/lib/behat/form_field/behat_form_select.php @@ -108,7 +108,7 @@ class behat_form_select extends behat_form_field { // Wait for all the possible AJAX requests that have been // already triggered by selectOption() to be finished. - $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS); + $this->session->wait(behat_base::TIMEOUT * 1000, '(document.readyState === "complete")'); // Wrapped in try & catch as the element may disappear if an AJAX request was submitted. try { @@ -163,7 +163,7 @@ class behat_form_select extends behat_form_field { // Wait for all the possible AJAX requests that have been // already triggered by clicking on the field to be finished. - $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS); + $this->session->wait(behat_base::TIMEOUT * 1000, '(document.readyState === "complete")'); // Wrapped in a try & catch as we can fall into race conditions // and the element may not be there. @@ -258,7 +258,7 @@ class behat_form_select extends behat_form_field { ); // Sort by value (keeping the keys is irrelevant). - core_collator::asort($optionsarray, SORT_STRING); + collatorlib::asort($optionsarray, SORT_STRING); // Returning it as a string which is easier to match against other values. return implode('|||', $optionsarray); From 39ce052c66ed2d0bbadcf3ca6f0533ffeb8bf012 Mon Sep 17 00:00:00 2001 From: David Monllao Date: Thu, 27 Feb 2014 16:39:25 +0800 Subject: [PATCH 7/7] MDL-43738 behat: Refactoring the field type guesser The previous behaviour delegates the responsability of overwriting the 3 public methods to the child classes, now the field type or moodleform field is guessed earlier in the execution flow. Other changes introduced here: - Fix wrong moodleform detection when there is a form in the page but the field we are dealing with is not inside it. - Some coding style changes. --- lib/behat/behat_field_manager.php | 66 +++++++++++- .../form_field/behat_form_date_selector.php | 3 - lib/behat/form_field/behat_form_editor.php | 4 +- lib/behat/form_field/behat_form_field.php | 100 ++++++------------ lib/behat/form_field/behat_form_group.php | 5 +- .../form_field/behat_form_modvisible.php | 3 +- lib/behat/form_field/behat_form_radio.php | 5 +- lib/behat/form_field/behat_form_select.php | 5 +- .../form_field/behat_form_selectyesno.php | 3 +- lib/behat/form_field/behat_form_text.php | 69 ++++++++++++ lib/behat/form_field/behat_form_textarea.php | 39 +++++++ 11 files changed, 218 insertions(+), 84 deletions(-) create mode 100644 lib/behat/form_field/behat_form_text.php create mode 100644 lib/behat/form_field/behat_form_textarea.php diff --git a/lib/behat/behat_field_manager.php b/lib/behat/behat_field_manager.php index b83b18e2b90..7454da8890c 100644 --- a/lib/behat/behat_field_manager.php +++ b/lib/behat/behat_field_manager.php @@ -80,9 +80,16 @@ class behat_field_manager { global $CFG; + // If the field is not part of a moodleform, we should still try to find out + // which field type are we dealing with. + if ($type == 'field' && + $guessedtype = self::guess_field_type($fieldnode, $session)) { + $type = $guessedtype; + } + $classname = 'behat_form_' . $type; - // Fallsback on the default form field if nothing specific exists. + // Fallsback on the type guesser if nothing specific exists. $classpath = $CFG->libdir . '/behat/form_field/' . $classname . '.php'; if (!file_exists($classpath)) { $classname = 'behat_form_field'; @@ -94,6 +101,58 @@ class behat_field_manager { return new $classname($session, $fieldnode); } + /** + * Guesses a basic field type and returns it. + * + * This method is intended to detect HTML form fields when no + * moodleform-specific elements have been detected. + * + * @param NodeElement $fieldnode + * @param Session $session + * @return string|bool The field type or false. + */ + public static function guess_field_type(NodeElement $fieldnode, Session $session) { + + // Textareas are considered text based elements. + $tagname = strtolower($fieldnode->getTagName()); + if ($tagname == 'textarea') { + + // If there is an iframe with $id + _ifr there a TinyMCE editor loaded. + $xpath = '//iframe[@id="' . $fieldnode->getAttribute('id') . '_ifr"]'; + if ($session->getPage()->find('xpath', $xpath)) { + return 'editor'; + } + return 'textarea'; + + } else if ($tagname == 'input') { + $type = $fieldnode->getAttribute('type'); + switch ($type) { + case 'text': + case 'password': + case 'email': + case 'file': + return 'text'; + case 'checkbox': + return 'checkbox'; + break; + case 'radio': + return 'radio'; + break; + default: + // Here we return false because all text-based + // fields should be included in the first switch case. + return false; + } + + } else if ($tagname == 'select') { + // Select tag. + return 'select'; + } + + // We can not provide a closer field type. + return false; + } + /** * Detects when the field is a moodleform field type. * @@ -108,7 +167,10 @@ class behat_field_manager { protected static function is_moodleform_field(NodeElement $fieldnode) { // We already waited when getting the NodeElement and we don't want an exception if it's not part of a moodleform. - $parentformfound = $fieldnode->find('xpath', "/ancestor::form[contains(concat(' ', normalize-space(@class), ' '), ' mform ')]/fieldset"); + $parentformfound = $fieldnode->find('xpath', + "/ancestor::fieldset" . + "/ancestor::form[contains(concat(' ', normalize-space(@class), ' '), ' mform ')]" + ); return ($parentformfound != false); } diff --git a/lib/behat/form_field/behat_form_date_selector.php b/lib/behat/form_field/behat_form_date_selector.php index 4fc8e47646b..71c477c9596 100644 --- a/lib/behat/form_field/behat_form_date_selector.php +++ b/lib/behat/form_field/behat_form_date_selector.php @@ -30,9 +30,6 @@ require_once(__DIR__ . '/behat_form_group.php'); /** * Date form field. * - * Simple extension of behat_form_group to allow the different - * date_selector fields to be filled according to it's type. - * * This class will be refactored in case we are interested in * creating more complex formats to fill date and date-time fields. * diff --git a/lib/behat/form_field/behat_form_editor.php b/lib/behat/form_field/behat_form_editor.php index 5c82bcfc6ca..457a8867464 100644 --- a/lib/behat/form_field/behat_form_editor.php +++ b/lib/behat/form_field/behat_form_editor.php @@ -27,7 +27,7 @@ use Behat\Mink\Element\NodeElement as NodeElement; -require_once(__DIR__ . '/behat_form_field.php'); +require_once(__DIR__ . '/behat_form_textarea.php'); /** * Moodle editor field. @@ -38,7 +38,7 @@ require_once(__DIR__ . '/behat_form_field.php'); * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class behat_form_editor extends behat_form_field { +class behat_form_editor extends behat_form_textarea { /** * Sets the value to a field. diff --git a/lib/behat/form_field/behat_form_field.php b/lib/behat/form_field/behat_form_field.php index 5951defb84e..4d3f1b497fa 100644 --- a/lib/behat/form_field/behat_form_field.php +++ b/lib/behat/form_field/behat_form_field.php @@ -69,14 +69,11 @@ class behat_form_field { * @return void */ public function set_value($value) { - - // If we are not dealing with a text-based tag try to find the most appropiate - // behat_form_* class to deal with it. - if ($instance = $this->guess_type()) { - $instance->set_value($value); - } else { - $this->field->setValue($value); - } + // We delegate to the best guess, if we arrived here + // using the generic behat_form_field is because we are + // dealing with a fgroup element. + $instance = $this->guess_type(); + return $instance->set_value($value); } /** @@ -85,14 +82,11 @@ class behat_form_field { * @return string */ public function get_value() { - - // If we are not dealing with a text-based tag try to find the most appropiate - // behat_form_* class to deal with it. - if ($instance = $this->guess_type()) { - return $instance->get_value(); - } else { - return $this->field->getValue(); - } + // We delegate to the best guess, if we arrived here + // using the generic behat_form_field is because we are + // dealing with a fgroup element. + $instance = $this->guess_type(); + return $instance->get_value(); } /** @@ -105,18 +99,11 @@ class behat_form_field { * @return bool The provided value matches the field value? */ public function matches($expectedvalue) { - - // If we are not dealing with a text-based tag try to find the most appropiate - // behat_form_* class to deal with it. - if ($instance = $this->guess_type()) { - return $instance->matches($expectedvalue); - } - - // Text-based comparison. - if (trim($expectedvalue) != trim($this->get_value())) { - return false; - } - return true; + // We delegate to the best guess, if we arrived here + // using the generic behat_form_field is because we are + // dealing with a fgroup element. + $instance = $this->guess_type(); + return $instance->matches($expectedvalue); } /** @@ -130,52 +117,17 @@ class behat_form_field { * moodle form elements we will need to refactor this simple HTML elements * guess method. * - * @return mixed False if no need for an special behat_form_*, otherwise the behat_form_* + * @return behat_form_field */ private function guess_type() { global $CFG; - // Textareas are considered text based elements. - $tagname = strtolower($this->field->getTagName()); - if ($tagname == 'textarea') { - - if (!$this->running_javascript()) { - return false; - } - - // If there is an iframe with $id + _ifr there a TinyMCE editor loaded. - $xpath = '//iframe[@id="' . $this->field->getAttribute('id') . '_ifr"]'; - if (!$this->session->getPage()->find('xpath', $xpath)) { - - // Generic one if it is a normal textarea. - return false; - } - - $classname = 'behat_form_editor'; - - } else if ($tagname == 'input') { - $type = $this->field->getAttribute('type'); - switch ($type) { - case 'text': - return false; - case 'checkbox': - $classname = 'behat_form_checkbox'; - break; - case 'radio': - $classname = 'behat_form_radio'; - break; - default: - return false; - } - - } else if ($tagname == 'select') { - // Select tag. - $classname = 'behat_form_select'; - - } else { - return false; + // We default to the text-based field if nothing was detected. + if (!$type = behat_field_manager::guess_field_type($this->field, $this->session)) { + $type = 'text'; } + $classname = 'behat_form_' . $type; $classpath = $CFG->dirroot . '/lib/behat/form_field/' . $classname . '.php'; require_once($classpath); return new $classname($this->session, $this->field); @@ -207,4 +159,16 @@ class behat_form_field { return $this->session->getDriver()->getWebDriverSession()->element('xpath', $this->field->getXPath())->getID(); } + /** + * Checks if the provided text matches the field value. + * + * @param string $expectedvalue + * @return bool + */ + protected function text_matches($expectedvalue) { + if (trim($expectedvalue) != trim($this->get_value())) { + return false; + } + return true; + } } diff --git a/lib/behat/form_field/behat_form_group.php b/lib/behat/form_field/behat_form_group.php index 533a787cd7e..3e8b2e58fd8 100644 --- a/lib/behat/form_field/behat_form_group.php +++ b/lib/behat/form_field/behat_form_group.php @@ -30,8 +30,9 @@ require_once(__DIR__ . '/behat_form_field.php'); /** * Class to re-guess the field type as grouped fields can have different field types. * - * When filling fields in a fgroup field element we don't know what kind - * of field are we dealing with, so we should re-guess it. + * When filling fields inside a fgroup field element we don't know what kind + * of field are we dealing with, so we should re-guess it as behat_form_field + * does. * * @package core_form * @category test diff --git a/lib/behat/form_field/behat_form_modvisible.php b/lib/behat/form_field/behat_form_modvisible.php index 04e65ffa634..74752b91d17 100644 --- a/lib/behat/form_field/behat_form_modvisible.php +++ b/lib/behat/form_field/behat_form_modvisible.php @@ -38,4 +38,5 @@ require_once(__DIR__ . '/behat_form_select.php'); * @copyright 2013 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class behat_form_modvisible extends behat_form_select {} +class behat_form_modvisible extends behat_form_select { +} diff --git a/lib/behat/form_field/behat_form_radio.php b/lib/behat/form_field/behat_form_radio.php index 27b5d005631..8304133dae6 100644 --- a/lib/behat/form_field/behat_form_radio.php +++ b/lib/behat/form_field/behat_form_radio.php @@ -89,9 +89,6 @@ class behat_form_radio extends behat_form_checkbox { * @return bool */ public function matches($expectedvalue = false) { - if (trim($expectedvalue) != trim($this->get_value())) { - return false; - } - return true; + return $this->text_matches($expectedvalue); } } diff --git a/lib/behat/form_field/behat_form_select.php b/lib/behat/form_field/behat_form_select.php index d0911d6c8b2..dcc6acef7dd 100644 --- a/lib/behat/form_field/behat_form_select.php +++ b/lib/behat/form_field/behat_form_select.php @@ -204,7 +204,10 @@ class behat_form_select extends behat_form_field { // Same implementation as the parent if it is a single select. if (!$multiple) { - if (trim($expectedvalue) != trim($this->get_value())) { + $cleanexpectedvalue = trim($expectedvalue); + $selectedtext = trim($this->get_selected_options()); + $selectedvalue = trim($this->get_selected_options(false)); + if ($cleanexpectedvalue != $selectedvalue && $cleanexpectedvalue != $selectedtext) { return false; } return true; diff --git a/lib/behat/form_field/behat_form_selectyesno.php b/lib/behat/form_field/behat_form_selectyesno.php index 3759a17a21e..73f8bef779e 100644 --- a/lib/behat/form_field/behat_form_selectyesno.php +++ b/lib/behat/form_field/behat_form_selectyesno.php @@ -38,4 +38,5 @@ require_once(__DIR__ . '/behat_form_select.php'); * @copyright 2013 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class behat_form_selectyesno extends behat_form_select {} +class behat_form_selectyesno extends behat_form_select { +} diff --git a/lib/behat/form_field/behat_form_text.php b/lib/behat/form_field/behat_form_text.php new file mode 100644 index 00000000000..dffdf86ffa2 --- /dev/null +++ b/lib/behat/form_field/behat_form_text.php @@ -0,0 +1,69 @@ +. + +/** + * Text field class. + * + * @package core_form + * @category test + * @copyright 2014 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_form_field.php'); + +/** + * Class for test-based fields. + * + * @package core_form + * @category test + * @copyright 2014 David Monllaó + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class behat_form_text extends behat_form_field { + + /** + * Sets the value to a field. + * + * @param string $value + * @return void + */ + public function set_value($value) { + $this->field->setValue($value); + } + + /** + * Returns the current value of the element. + * + * @return string + */ + public function get_value() { + return $this->field->getValue(); + } + + /** + * Matches the provided value against the current field value. + * + * @param string $expectedvalue + * @return bool The provided value matches the field value? + */ + public function matches($expectedvalue) { + return $this->text_matches($expectedvalue); + } + +} diff --git a/lib/behat/form_field/behat_form_textarea.php b/lib/behat/form_field/behat_form_textarea.php new file mode 100644 index 00000000000..c1730c287f9 --- /dev/null +++ b/lib/behat/form_field/behat_form_textarea.php @@ -0,0 +1,39 @@ +. + +/** + * Textarea field class. + * + * @package core_form + * @category test + * @copyright 2014 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_form_text.php'); + +/** + * Textarea field class. + * + * @package core_form + * @category test + * @copyright 2014 David Monllaó + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class behat_form_textarea extends behat_form_text { +}