Merge branch 'MDL-43738_26' of git://github.com/dmonllao/moodle into MOODLE_26_STABLE

This commit is contained in:
Damyon Wiese
2014-03-04 13:28:18 +08:00
19 changed files with 1009 additions and 179 deletions
@@ -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
@@ -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
@@ -0,0 +1,173 @@
@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 the following "activities" exists:
| activity | course | idnumber | name | intro | firstpagetitle | wikimode | visible |
| wiki | C1 | wiki1 | Test this one | Test this one | Test this one | collaborative | 0 |
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 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"
+64 -2
View File
@@ -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);
}
+45 -5
View File
@@ -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;
}
}
@@ -25,15 +25,11 @@
// 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.
*
* This class will be refactored in case we are interested in
* creating more complex formats to fill date and date-time fields.
*
@@ -42,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_date_selector extends behat_form_select {}
class behat_form_date_selector extends behat_form_group {
}
@@ -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 {
}
+2 -2
View File
@@ -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.
+45 -61
View File
@@ -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,28 @@ class behat_form_field {
* @return string
*/
public function get_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->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();
}
/**
* 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) {
// 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);
}
/**
@@ -106,53 +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 {
// We can not provide a closer field type.
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);
@@ -181,10 +156,19 @@ class behat_form_field {
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();
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;
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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 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
* @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 {
}
@@ -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 {
}
+37 -2
View File
@@ -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,43 @@ 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) {
return $this->text_matches($expectedvalue);
}
}
+203 -27
View File
@@ -38,13 +38,13 @@ 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.
*
* 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 diferent way.
* autosubmits and the multiple option selects in a different way.
*
* @param string $value
* @param string $value plain value or comma separated values if multiple. Commas in values escaped with backslash.
* @return void
*/
public function set_value($value) {
@@ -61,8 +61,26 @@ class behat_form_select extends behat_form_field {
$currentelementid = $this->get_internal_field_id();
}
// Here we select an option.
$this->field->selectOption($value);
// 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('/(?<!\\\),/', $value));
// This is a multiple select, let's pass the multiple flag after first option.
$afterfirstoption = false;
foreach ($options as $option) {
$this->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()) {
@@ -75,6 +93,7 @@ class behat_form_select extends behat_form_field {
// 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;
@@ -87,12 +106,9 @@ class behat_form_select extends behat_form_field {
return;
}
// We also check that the option is still there. We neither wait.
$valueliteral = $this->session->getSelectorsHandler()->xpathLiteral($value);
$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 {
@@ -105,17 +121,16 @@ class behat_form_select extends behat_form_field {
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);
// 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 {
@@ -138,21 +153,29 @@ class behat_form_select extends behat_form_field {
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 as some drivers (chrome that I know) are moving
// Repeating the select(s) as some drivers (chrome that I know) are moving
// to another option after the general select field click above.
$this->field->selectOption($value);
$afterfirstoption = false;
foreach ($options as $option) {
$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.
return;
@@ -161,12 +184,165 @@ class behat_form_select extends behat_form_field {
}
/**
* 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();
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) {
$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;
}
// 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('/(?<!\\\),/', $value)
)
);
// Sort by value (keeping the keys is irrelevant).
core_collator::asort($optionsarray, SORT_STRING);
// Returning it as a string which is easier to match against other values.
return implode('|||', $optionsarray);
}
/**
* Returns the field selected values.
*
* Externalized from the common behat_form_field API method get_value() as
* matches() needs to check against both values and texts.
*
* @param bool $returntexts Returns the options texts or the options values.
* @return string
*/
protected function get_selected_options($returntexts = true) {
$method = 'getText';
if ($returntexts === false) {
$method = 'getValue';
}
// 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->{$method}()));
} else {
$selectedoptions[] = trim($option->{$method}());
}
}
}
} 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) {
// 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->{$method}()));
} else {
$selectedoptions[] = trim($option->{$method}());
}
}
}
}
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)]";
}
}
@@ -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 {
}
+69
View File
@@ -0,0 +1,69 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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);
}
}
@@ -0,0 +1,39 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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 {
}
+3 -3
View File
@@ -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:
+223 -50
View File
@@ -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.6.
*
* @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.
*
@@ -154,7 +177,23 @@ class behat_forms extends behat_base {
}
/**
* Fills in form field with specified id|name|label|value.
* Sets the specified value to the field.
*
* Backport of Moodle 2.7 method to help backporting
* features to 2.6.
*
* @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/
* @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.
*
* @When /^I fill in "(?P<field_string>(?:[^"]|\\")*)" with "(?P<value_string>(?:[^"]|\\")*)"$/
* @throws ElementNotFoundException Thrown by behat_base::find
@@ -162,9 +201,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);
}
/**
@@ -176,13 +213,7 @@ class behat_forms extends behat_base {
* @param string $select
*/
public function select_option($option, $select) {
$selectnode = $this->find_field($select);
// We delegate to behat_form_field class, it will
// guess the type properly as it is a select tag.
$selectformfield = behat_field_manager::get_form_field($selectnode, $this->getSession());
$selectformfield->set_value($option);
$this->set_field_value($select, $option);
}
/**
@@ -193,14 +224,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);
}
/**
@@ -211,11 +235,7 @@ class behat_forms extends behat_base {
* @param string $option
*/
public function check_option($option) {
// We don't delegate to behat_form_checkbox as the
// step is explicitly saying I check.
$checkboxnode = $this->find_field($option);
$checkboxnode->check();
$this->set_field_value($option, 1);
}
/**
@@ -226,15 +246,11 @@ class behat_forms extends behat_base {
* @param string $option
*/
public function uncheck_option($option) {
// We don't delegate to behat_form_checkbox as the
// step is explicitly saying I uncheck.
$checkboxnode = $this->find_field($option);
$checkboxnode->uncheck();
$this->set_field_value($option, '');
}
/**
* Checks that the form element field have the specified value.
* Checks that the field matches the specified value. When using multi-select fields use commas to separate selected options.
*
* @Then /^the "(?P<field_string>(?:[^"]|\\")*)" field should match "(?P<value_string>(?:[^"]|\\")*)" value$/
* @throws ExpectationException
@@ -248,10 +264,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()
@@ -259,26 +275,120 @@ 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.6.
*
* @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/
* @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.
*
* @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" does not match value "(?P<value_string>(?:[^"]|\\")*)"$/
* @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_string>(?:[^"]|\\")*)" 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_string>(?:[^"]|\\")*)" 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, '');
}
/**
@@ -288,20 +398,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('/(?<!\\\),/', $option)) as $opt) {
$optionsarr[] = trim($opt);
}
} else {
// Only one option has been passed.
$optionsarr[] = trim($option);
}
// Now get all the values and texts in the select.
$options = $selectnode->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()
);
}
}
}
/**
@@ -311,19 +442,61 @@ 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('/(?<!\\\),/', $option)) as $opt) {
$optionsarr[] = trim($opt);
}
} else {
// Only one option has been passed.
$optionsarr[] = trim($option);
}
// Now get all the values and texts in the select.
$options = $selectnode->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()
);
}
}
}
/**
* Generic field setter.
*
* Internal API method, a generic *I set "VALUE" to "FIELD" field*
* could be created based on it.
*
* @param string $fieldlocator The pointer to the field, it will depend on the field type.
* @param string $value
* @return void
*/
protected function set_field_value($fieldlocator, $value) {
$node = $this->find_field($fieldlocator);
// 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);
}
}
+40
View File
@@ -142,4 +142,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_string>(?:[^"]|\\")*)" capability has "(?P<permission_string>Not 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());
}
}
}