moodle/lib/behat/behat_field_manager.php

344 lines
12 KiB
PHP
Raw Normal View History

<?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/>.
/**
* Form fields helper.
*
* @package core
* @category test
* @copyright 2013 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.
use Behat\Mink\Session as Session,
Behat\Mink\Element\NodeElement as NodeElement,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
Behat\MinkExtension\Context\RawMinkContext as RawMinkContext;
/**
* Helper to interact with form fields.
*
* @package core
* @category test
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_field_manager {
/**
* Gets an instance of the form field from it's label
*
* @param string $label
* @param RawMinkContext $context
* @return behat_form_field
*/
public static function get_form_field_from_label($label, RawMinkContext $context) {
// There are moodle form elements that are not directly related with
// a basic HTML form field, we should also take care of them.
// The DOM node.
$fieldnode = $context->find_field($label);
// The behat field manager.
$field = self::get_form_field($fieldnode, $context->getSession());
return $field;
}
/**
* Gets an instance of the form field.
*
* Not all the fields are part of a moodle form, in this
* cases it fallsback to the generic form field. Also note
* that this generic field type is using a generic setValue()
* method from the Behat API, which is not always good to set
* the value of form elements.
*
* @param NodeElement $fieldnode
* @param Session $session The behat browser session
* @return behat_form_field
*/
public static function get_form_field(NodeElement $fieldnode, Session $session) {
// Get the field type if is part of a moodleform.
if (self::is_moodleform_field($fieldnode)) {
$type = self::get_field_node_type($fieldnode, $session);
}
// If is not a moodleforms field use the base field type.
if (empty($type)) {
$type = 'field';
}
return self::get_field_instance($type, $fieldnode, $session);
}
/**
* Returns the appropiate behat_form_field according to the provided type.
*
* It defaults to behat_form_field.
*
* @param string $type The field type (checkbox, date_selector, text...)
* @param NodeElement $fieldnode
* @param Session $session The behat session
* @return behat_form_field
*/
public static function get_field_instance($type, NodeElement $fieldnode, Session $session) {
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 type guesser if nothing specific exists.
$classpath = $CFG->libdir . '/behat/form_field/' . $classname . '.php';
if (!file_exists($classpath)) {
$classname = 'behat_form_field';
$classpath = $CFG->libdir . '/behat/form_field/' . $classname . '.php';
}
// Returns the instance.
require_once($classpath);
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) {
[
'document' => $document,
'node' => $node,
] = self::get_dom_elements_for_node($fieldnode, $session);
// If the type is explicitly set on the element pointed to by the label - use it.
if ($fieldtype = $node->getAttribute('data-fieldtype')) {
return self::normalise_fieldtype($fieldtype);
}
// Textareas are considered text based elements.
$tagname = strtolower($node->nodeName);
if ($tagname == 'textarea') {
$xpath = new \DOMXPath($document);
// If there is an iframe with $id + _ifr there a TinyMCE editor loaded.
if ($xpath->query('//div[@id="' . $node->getAttribute('id') . 'editable"]')->count() !== 0) {
return 'editor';
}
return 'textarea';
}
if ($tagname == 'input') {
switch ($node->getAttribute('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;
}
}
if ($tagname == 'select') {
// Select tag.
return 'select';
}
if ($tagname == 'span') {
if ($node->hasAttribute('data-inplaceeditable') && $node->getAttribute('data-inplaceeditable')) {
// Determine appropriate editable type of this field (text or select).
if ($node->getAttribute('data-type') == 'select') {
return 'inplaceeditable_select';
} else {
return 'inplaceeditable';
}
}
}
if ($tagname == 'div') {
if ($node->getAttribute('role') == 'combobox') {
return 'select_menu';
}
}
// We can not provide a closer field type.
return false;
}
/**
* Detects when the field is a moodleform field type.
*
* Note that there are fields inside moodleforms that are not
* moodleform element; this method can not detect this, this will
* be managed by get_field_node_type, after failing to find the form
* element element type.
*
* @param NodeElement $fieldnode
* @return bool
*/
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 ')]"
);
return ($parentformfound != false);
}
/**
* Get the DOMDocument and DOMElement for a NodeElement.
*
* @param NodeElement $fieldnode
* @param Session $session
* @return array
*/
protected static function get_dom_elements_for_node(NodeElement $fieldnode, Session $session): array {
$html = $session->getPage()->getContent();
$document = new \DOMDocument();
$previousinternalerrors = libxml_use_internal_errors(true);
$document->loadHTML($html, LIBXML_HTML_NODEFDTD | LIBXML_BIGLINES);
libxml_clear_errors();
libxml_use_internal_errors($previousinternalerrors);
$xpath = new \DOMXPath($document);
$node = $xpath->query($fieldnode->getXpath())->item(0);
return [
'document' => $document,
'node' => $node,
];
}
/**
* Recursive method to find the field type.
*
* Depending on the field the felement class node is in a level or in another. We
* look recursively for a parent node with a 'felement' class to find the field type.
*
* @param NodeElement $fieldnode The current node.
* @param Session $session The behat browser session
* @return null|string A text description of the node type, or null if one could not be accurately determined
*/
protected static function get_field_node_type(NodeElement $fieldnode, Session $session): ?string {
[
'document' => $document,
'node' => $node,
] = self::get_dom_elements_for_node($fieldnode, $session);
return self::get_field_type($document, $node, $session);
}
/**
* Get the field type from the specified DOMElement.
*
* @param \DOMDocument $document
* @param \DOMElement $node
* @param Session $session
* @return null|string
*/
protected static function get_field_type(\DOMDocument $document, \DOMElement $node, Session $session): ?string {
$xpath = new \DOMXPath($document);
if ($node->getAttribute('name') === 'availabilityconditionsjson') {
// Special handling for availability field which requires custom JavaScript.
return 'availability';
}
if ($node->nodeName == 'html') {
// The top of the document has been reached.
return null;
}
MDL-55839 behat: Behat fixes for theme_noname This is a squash of many small fixes. The list of changes are: Initial set of behat selectors overrides required for new theme. set core behat selectors if not overridden by theme Remove pause Fix for checkboxes not saving properly Custom override for click in the user menu Fix for behat guessing the field type. Behat fix for guessing the correct field type. Fix for missing closeElementBefore mforms mess. Fix for textareas not displaying their contents. Fix availability form element Fix action menu selectors for activities in a course page Fix textareas not retaining their values Fix selector for blocks I had to change the single select template to use an input instead of a button because of a behat bug we have not tracked down yet. Fix for frozen attribute in form elements Use input instead of button while behat is still broken Fix for navigation selector override Behat selector fixes and multi-select fixes for templated select Fix for configure blocks selector + booleans for autocomplete Fixes for course_summary behat tests Allow optgroups in single_select Behat fixes for block_navigation Fixes for url form element and activity chooser selectors blocks_social - fix specific selector for behat Fix export_for_template for selectgroups element. Dont set empty id on the form Fix direct call to behat_navigation in backup context Checkbox empty value fix Fix for assign grading panel popouts file picker overrides to find a file manager in the page Fix chat tests to use element name Make selectors in mod_data behat less specific MDL-55839: recent activity behat test relies on time() SQUASH Honour mform element "class" attribute on the container SQUASH Fixes for behat in mod_forum SQUASH search form template showing value as text next to the button SQUASH undo double selectgroup fix SQUASH - The slightly different structure to action menus breaks course drag and drop SQUASH Wrap loading icon in a span so the selector still works. SQUASH Fix fragile javascript in report_loglive SQUASH Fix tabtree, and selectgroups for mod_feedback Also - do-not enhance the action menu - we are dropping support for this. SQUASH Fix filemanager selector and pix_icon template for mod_forum tests. SQUASH Fixes to breadcrumb and add formid to single select (fixes mod_glossary tests) SQUASH lesson behat fixes. 95% of lesson styles were rubbish so they were disgarded. SQUASH Fix lti behat tests - they were cheating and using a context step from quiz SQUASH Behat fixes for quiz with new theme SQUASH behat fixes for mod_wiki SQUASH Workshop behat fixes SQUASH Frozen form elements cannot use a readonly form element because it screws with the form JS "disabledIf" stuff. SQUASH Fixes for grades behat tests SQUASH behat fixes for groups SQUASH blacklist action menu tests SQUASH Fix for tag form element missing "manage standard tags" link SQUASH Incomplete fixes for course behat tests SQUASH behat fixes for badges SQUASH Tags fixes for behat SQUASH filepicker fixes for behat SQUASH Abort guessing field type when we hit 'html' SQUASH Fix for admin/tool/behat tests SQUASH fix for admin tool behat tests SQUASH fix for availabilty conditions hiding "aria-hidden" elements SQUASH give the blocks more room. We get behat files because nav tree nodes are not in view SQUASH Blocks fixed Fix behaviour of unchecked checkboxes SQUASH Remove incorrect step in user test SQUASH behat fixes for course tests Part of MDL-55071
2016-08-29 14:57:46 +08:00
// If the type is explictly set on the element pointed to by the label - use it.
$fieldtype = $node->getAttribute('data-fieldtype');
if ($fieldtype) {
return self::normalise_fieldtype($fieldtype);
MDL-55839 behat: Behat fixes for theme_noname This is a squash of many small fixes. The list of changes are: Initial set of behat selectors overrides required for new theme. set core behat selectors if not overridden by theme Remove pause Fix for checkboxes not saving properly Custom override for click in the user menu Fix for behat guessing the field type. Behat fix for guessing the correct field type. Fix for missing closeElementBefore mforms mess. Fix for textareas not displaying their contents. Fix availability form element Fix action menu selectors for activities in a course page Fix textareas not retaining their values Fix selector for blocks I had to change the single select template to use an input instead of a button because of a behat bug we have not tracked down yet. Fix for frozen attribute in form elements Use input instead of button while behat is still broken Fix for navigation selector override Behat selector fixes and multi-select fixes for templated select Fix for configure blocks selector + booleans for autocomplete Fixes for course_summary behat tests Allow optgroups in single_select Behat fixes for block_navigation Fixes for url form element and activity chooser selectors blocks_social - fix specific selector for behat Fix export_for_template for selectgroups element. Dont set empty id on the form Fix direct call to behat_navigation in backup context Checkbox empty value fix Fix for assign grading panel popouts file picker overrides to find a file manager in the page Fix chat tests to use element name Make selectors in mod_data behat less specific MDL-55839: recent activity behat test relies on time() SQUASH Honour mform element "class" attribute on the container SQUASH Fixes for behat in mod_forum SQUASH search form template showing value as text next to the button SQUASH undo double selectgroup fix SQUASH - The slightly different structure to action menus breaks course drag and drop SQUASH Wrap loading icon in a span so the selector still works. SQUASH Fix fragile javascript in report_loglive SQUASH Fix tabtree, and selectgroups for mod_feedback Also - do-not enhance the action menu - we are dropping support for this. SQUASH Fix filemanager selector and pix_icon template for mod_forum tests. SQUASH Fixes to breadcrumb and add formid to single select (fixes mod_glossary tests) SQUASH lesson behat fixes. 95% of lesson styles were rubbish so they were disgarded. SQUASH Fix lti behat tests - they were cheating and using a context step from quiz SQUASH Behat fixes for quiz with new theme SQUASH behat fixes for mod_wiki SQUASH Workshop behat fixes SQUASH Frozen form elements cannot use a readonly form element because it screws with the form JS "disabledIf" stuff. SQUASH Fixes for grades behat tests SQUASH behat fixes for groups SQUASH blacklist action menu tests SQUASH Fix for tag form element missing "manage standard tags" link SQUASH Incomplete fixes for course behat tests SQUASH behat fixes for badges SQUASH Tags fixes for behat SQUASH filepicker fixes for behat SQUASH Abort guessing field type when we hit 'html' SQUASH Fix for admin/tool/behat tests SQUASH fix for admin tool behat tests SQUASH fix for availabilty conditions hiding "aria-hidden" elements SQUASH give the blocks more room. We get behat files because nav tree nodes are not in view SQUASH Blocks fixed Fix behaviour of unchecked checkboxes SQUASH Remove incorrect step in user test SQUASH behat fixes for course tests Part of MDL-55071
2016-08-29 14:57:46 +08:00
}
if ($xpath->query('/ancestor::*[@data-passwordunmaskid]', $node)->count() !== 0) {
// This element has a passwordunmaskid as a parent.
return 'passwordunmask';
}
// Fetch the parentnode only once.
$parentnode = $node->parentNode;
if ($parentnode instanceof \DOMDocument) {
return null;
}
// Check the parent fieldtype before we check classes.
$fieldtype = $parentnode->getAttribute('data-fieldtype');
if ($fieldtype) {
return self::normalise_fieldtype($fieldtype);
}
// We look for a parent node with 'felement' class.
if ($class = $parentnode->getAttribute('class')) {
if (strstr($class, 'felement') != false) {
// Remove 'felement f' from class value.
return substr($class, 10);
}
// Stop propagation through the DOM, if it does not have a felement is not part of a moodle form.
if (strstr($class, 'fcontainer') != false) {
return null;
}
}
// Move up the tree.
return self::get_field_type($document, $parentnode, $session);
}
/**
* Normalise the field type.
*
* @param string $fieldtype
* @return string
*/
protected static function normalise_fieldtype(string $fieldtype): string {
if ($fieldtype === 'tags') {
return 'autocomplete';
}
return $fieldtype;
}
}