Merge branch 'MDL-66550-master' of git://github.com/andrewnicols/moodle

This commit is contained in:
Jake Dallimore
2019-09-11 10:39:17 +08:00
7 changed files with 206 additions and 197 deletions
@@ -114,12 +114,8 @@ class behat_block_site_main_menu extends behat_base {
protected function get_site_menu_activity_element($element, $selectortype, $activityname) {
$activitynode = $this->get_site_menu_activity_node($activityname);
// Transforming to Behat selector/locator.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
$exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' .
$selectortype . '" in "' . $activityname . '" ');
return $this->find($selector, $locator, $exception, $activitynode);
$exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '${activityname}'");
return $this->find($selectortype, $element, $exception, $activitynode);
}
/**
@@ -114,12 +114,8 @@ class behat_block_social_activities extends behat_base {
protected function get_social_block_activity_element($element, $selectortype, $activityname) {
$activitynode = $this->get_social_block_activity_node($activityname);
// Transforming to Behat selector/locator.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
$exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' .
$selectortype . '" in "' . $activityname . '" ');
return $this->find($selector, $locator, $exception, $activitynode);
$exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '${activityname}'");
return $this->find($selectortype, $element, $exception, $activitynode);
}
/**
+2 -5
View File
@@ -1144,11 +1144,8 @@ class behat_course extends behat_base {
protected function get_activity_element($element, $selectortype, $activityname) {
$activitynode = $this->get_activity_node($activityname);
// Transforming to Behat selector/locator.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
$exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' . $selectortype . '" in "' . $activityname . '" ');
return $this->find($selector, $locator, $exception, $activitynode);
$exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '${activityname}'");
return $this->find($selectortype, $element, $exception, $activitynode);
}
/**
+5 -5
View File
@@ -195,9 +195,9 @@ Feature: We can set the grade to pass value
And I add a "Forum" to section "1" and I fill the form with:
| Forum name | Test Forum 1 |
| Description | Test |
| Aggregate type | Average of ratings |
| Grade to pass | 90 |
| scale[modgrade_point] | 60 |
| Ratings > Aggregate type | Average of ratings |
| Ratings > scale[modgrade_point] | 60 |
| Grade > Grade to pass | 90 |
Then I should see "The grade to pass can not be greater than the maximum possible grade 60"
Scenario: Set a valid grade to pass for forum activity
@@ -205,8 +205,8 @@ Feature: We can set the grade to pass value
And I add a "Forum" to section "1" and I fill the form with:
| Forum name | Test Forum 1 |
| Description | Test |
| Aggregate type | Average of ratings |
| Grade to pass | 90 |
| Ratings > Aggregate type | Average of ratings |
| Grade > Grade to pass | 90 |
And I navigate to "View > Grader report" in the course gradebook
And I turn editing mode on
And I click on "Edit forum Test Forum 1" "link"
+109 -90
View File
@@ -32,6 +32,7 @@ use Behat\Mink\Exception\DriverException;
use Behat\Mink\Exception\ExpectationException;
use Behat\Mink\Exception\ElementNotFoundException;
use Behat\Mink\Element\NodeElement;
use Behat\Mink\Element\Element;
use Behat\Mink\Session;
/**
@@ -115,14 +116,6 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
* @return NodeElement
*/
protected function find($selector, $locator, $exception = false, $node = false, $timeout = false) {
// Throw exception, so dev knows it is not supported.
if ($selector === 'named') {
$exception = 'Using the "named" selector is deprecated as of 3.1. '
.' Use the "named_partial" or use the "named_exact" selector instead.';
throw new ExpectationException($exception, $this->getSession());
}
// Returns the first match.
$items = $this->find_all($selector, $locator, $exception, $node, $timeout);
return count($items) ? reset($items) : null;
@@ -137,12 +130,11 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
* @param string $selector The selector type (css, xpath, named...)
* @param mixed $locator It depends on the $selector, can be the xpath, a name, a css locator...
* @param Exception $exception Otherwise we throw expcetion with generic info
* @param NodeElement $node Spins around certain DOM node instead of the whole page
* @param NodeElement $container Restrict the search to just children of the specified container
* @param int $timeout Forces a specific time out (in seconds). If 0 is provided the default timeout will be applied.
* @return array NodeElements list
*/
protected function find_all($selector, $locator, $exception = false, $node = false, $timeout = false) {
protected function find_all($selector, $locator, $exception = false, $container = false, $timeout = false) {
// Throw exception, so dev knows it is not supported.
if ($selector === 'named') {
$exception = 'Using the "named" selector is deprecated as of 3.1. '
@@ -152,7 +144,6 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
// Generic info.
if (!$exception) {
// With named selectors we can be more specific.
if (($selector == 'named_exact') || ($selector == 'named_partial')) {
$exceptiontype = $locator[0];
@@ -171,12 +162,6 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
$exception = new ElementNotFoundException($this->getSession(), $exceptiontype, null, $exceptionlocator);
}
$params = array('selector' => $selector, 'locator' => $locator);
// Pushing $node if required.
if ($node) {
$params['node'] = $node;
}
// How much we will be waiting for the element to appear.
if (!$timeout) {
$timeout = self::get_timeout();
@@ -188,24 +173,73 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
$microsleep = true;
}
// Normalise the values in order to perform the search.
[
'selector' => $selector,
'locator' => $locator,
'container' => $container,
] = $this->normalise_selector($selector, $locator, $container ?: $this->getSession()->getPage());
// Waits for the node to appear if it exists, otherwise will timeout and throw the provided exception.
return $this->spin(
function($context, $args) {
// If no DOM node provided look in all the page.
if (empty($args['node'])) {
return $context->getSession()->getPage()->findAll($args['selector'], $args['locator']);
}
return $args['node']->findAll($args['selector'], $args['locator']);
},
$params,
$timeout,
$exception,
$microsleep
function() use ($selector, $locator, $container) {
return $container->findAll($selector, $locator);
}, [], $timeout, $exception, $microsleep
);
}
/**
* Normalise the locator and selector.
*
* @param string $selector The type of thing to search
* @param mixed $locator The locator value. Can be an array, but is more likely a string.
* @param Element $container An optional container to search within
* @return array The selector, locator, and container to search within
*/
public function normalise_selector(string $selector, $locator, Element $container): array {
// Check for specific transformations for this selector type.
$transformfunction = "transform_find_for_{$selector}";
if (method_exists('behat_selectors', $transformfunction)) {
// A selector-specific transformation exists.
// Perform initial transformation of the selector within the current container.
[
'selector' => $selector,
'locator' => $locator,
'container' => $container,
] = behat_selectors::{$transformfunction}($this, $locator, $container);
}
// Normalise the css and xpath selector types.
if ('css_element' === $selector) {
$selector = 'css';
} else if ('xpath_element' === $selector) {
$selector = 'xpath';
}
// Convert to named_partial where the selector type is not named_partial, named_exact, xpath, or css.
$converttonamed = !$this->getSession()->getSelectorsHandler()->isSelectorRegistered($selector);
$converttonamed = $converttonamed && 'xpath' !== $selector;
if ($converttonamed) {
$allowedpartialselectors = behat_partial_named_selector::get_allowed_selectors();
$allowedexactselectors = behat_exact_named_selector::get_allowed_selectors();
if (isset($allowedpartialselectors[$selector])) {
$locator = behat_selectors::normalise_named_selector($allowedpartialselectors[$selector], $locator);
$selector = 'named_partial';
} else if (isset($allowedexactselectors[$selector])) {
$locator = behat_selectors::normalise_named_selector($allowedexactselectors[$selector], $locator);
$selector = 'named_exact';
} else {
throw new ExpectationException("The '{$selector}' selector type is not registered.", $this);
}
}
return [
'selector' => $selector,
'locator' => $locator,
'container' => $container,
];
}
/**
* Finds DOM nodes in the page using named selectors.
*
@@ -229,27 +263,14 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
* @return NodeElement
*/
public function __call($name, $arguments) {
if (substr($name, 0, 5) !== 'find_') {
throw new coding_exception('The "' . $name . '" method does not exist');
if (substr($name, 0, 5) === 'find_') {
return call_user_func_array([$this, 'find'], array_merge(
[substr($name, 5)],
$arguments
));
}
// Only the named selector identifier.
$cleanname = substr($name, 5);
// All named selectors shares the interface.
if (count($arguments) !== 1) {
throw new coding_exception('The "' . $cleanname . '" named selector needs the locator as it\'s single argument');
}
// Redirecting execution to the find method with the specified selector.
// It will detect if it's pointing to an unexisting named selector.
return $this->find('named_partial',
array(
$cleanname,
behat_context_helper::escape($arguments[0])
)
);
throw new coding_exception("The '{$name}' method does not exist");
}
/**
@@ -360,12 +381,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
* @return NodeElement
*/
protected function get_selected_node($selectortype, $element) {
// Getting Mink selector and locator.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
// Returns the NodeElement.
return $this->find($selector, $locator);
return $this->find($selectortype, $element);
}
/**
@@ -377,7 +393,6 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
* @return NodeElement
*/
protected function get_text_selector_node($selectortype, $element) {
// Getting Mink selector and locator.
list($selector, $locator) = $this->transform_text_selector($selectortype, $element);
@@ -396,18 +411,13 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
* @return NodeElement
*/
protected function get_node_in_container($selectortype, $element, $containerselectortype, $containerelement) {
// Gets the container, it will always be text based.
$containernode = $this->get_text_selector_node($containerselectortype, $containerelement);
list($selector, $locator) = $this->transform_selector($selectortype, $element);
// Specific exception giving info about where can't we find the element.
$locatorexceptionmsg = $element . '" in the "' . $containerelement. '" "' . $containerselectortype. '"';
$exception = new ElementNotFoundException($this->getSession(), $selectortype, null, $locatorexceptionmsg);
// Looks for the requested node inside the container node.
return $this->find($selector, $locator, $exception, $containernode);
return $this->find($selectortype, $element, $exception, $containernode);
}
/**
@@ -426,14 +436,18 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
* @return array Contains the selector and the locator expected by Mink.
*/
protected function transform_selector($selectortype, $element) {
// Here we don't know if an allowed text selector is being used.
$selectors = behat_selectors::get_allowed_selectors();
if (!isset($selectors[$selectortype])) {
throw new ExpectationException('The "' . $selectortype . '" selector type does not exist', $this->getSession());
}
return behat_selectors::get_behat_selector($selectortype, $element, $this->getSession());
[
'selector' => $selector,
'locator' => $locator,
] = $this->normalise_selector($selectortype, $element, $this->getSession()->getPage());
return [$selector, $locator];
}
/**
@@ -485,63 +499,68 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
* Spins around an element until it exists
*
* @throws ExpectationException
* @param string $element
* @param string $locator
* @param string $selectortype
* @return void
*/
protected function ensure_element_exists($element, $selectortype) {
// Getting the behat selector & locator.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
protected function ensure_element_exists($locator, $selectortype) {
// Exception if it timesout and the element is still there.
$msg = 'The "' . $element . '" element does not exist and should exist';
$msg = "The '{$locator}' element does not exist and should";
$exception = new ExpectationException($msg, $this->getSession());
// Normalise the values in order to perform the search.
[
'selector' => $selector,
'locator' => $locator,
'container' => $container,
] = $this->normalise_selector($selectortype, $locator, $this->getSession()->getPage());
// It will stop spinning once the find() method returns true.
$this->spin(
function($context, $args) {
// We don't use behat_base::find as it is already spinning.
if ($context->getSession()->getPage()->find($args['selector'], $args['locator'])) {
function() use ($selector, $locator, $container) {
if ($container->find($selector, $locator)) {
return true;
}
return false;
},
array('selector' => $selector, 'locator' => $locator),
[],
self::get_extended_timeout(),
$exception,
true
);
}
/**
* Spins until the element does not exist
*
* @throws ExpectationException
* @param string $element
* @param string $locator
* @param string $selectortype
* @return void
*/
protected function ensure_element_does_not_exist($element, $selectortype) {
// Getting the behat selector & locator.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
protected function ensure_element_does_not_exist($locator, $selectortype) {
// Exception if it timesout and the element is still there.
$msg = 'The "' . $element . '" element exists and should not exist';
$msg = "The '{$locator}' element exists and should not exist";
$exception = new ExpectationException($msg, $this->getSession());
// Normalise the values in order to perform the search.
[
'selector' => $selector,
'locator' => $locator,
'container' => $container,
] = $this->normalise_selector($selectortype, $locator, $this->getSession()->getPage());
// It will stop spinning once the find() method returns false.
$this->spin(
function($context, $args) {
// We don't use behat_base::find() as we are already spinning.
if (!$context->getSession()->getPage()->find($args['selector'], $args['locator'])) {
return true;
function() use ($selector, $locator, $container) {
if ($container->find($selector, $locator)) {
return false;
}
return false;
return true;
},
array('selector' => $selector, 'locator' => $locator),
// Note: We cannot use $this because the find will then be $this->find(), which leads us to a nested spin().
// We cannot nest spins because the outer spin times out before the inner spin completes.
[],
self::get_extended_timeout(),
$exception,
true
+43 -24
View File
@@ -27,6 +27,7 @@ require_once(__DIR__ . '/exact_named_selector.php');
require_once(__DIR__ . '/partial_named_selector.php');
use Behat\Mink\Exception\ExpectationException as ExpectationException;
use Behat\Mink\Element\Element;
/**
* Moodle selectors manager.
@@ -47,32 +48,14 @@ class behat_selectors {
* @return array Contains the selector and the locator expected by Mink.
*/
public static function get_behat_selector($selectortype, $element, Behat\Mink\Session $session) {
// Note: This function is not deprecated, but not the recommended way of doing things.
[
'selector' => $selector,
'locator' => $locator,
] = $session->normalise_selector($selectortype, $element, $session->getPage());
// CSS and XPath selectors locator is one single argument.
if ($selectortype == 'css_element' || $selectortype == 'xpath_element') {
$selector = str_replace('_element', '', $selectortype);
$locator = $element;
} else {
// Named selectors uses arrays as locators including the type of named selector.
$allowedselectors = self::get_allowed_selectors();
if (!isset($allowedselectors[$selectortype])) {
throw new ExpectationException('The "' . $selectortype . '" selector not registered.', $session);
}
$locator = array($allowedselectors[$selectortype], behat_context_helper::escape($element));
// Get the selector which should be used.
$allowedpartialselectors = behat_partial_named_selector::get_allowed_selectors();
$allowedexactselectors = behat_exact_named_selector::get_allowed_selectors();
if (isset($allowedpartialselectors[$selectortype])) {
$selector = 'named_partial';
} else if (isset($allowedexactselectors[$selectortype])) {
$selector = 'named_exact';
} else {
throw new ExpectationException('The "' . $selectortype . '" selector not registered.', $session);
}
}
return array($selector, $locator);
return [$selector, $locator];
}
/**
@@ -98,4 +81,40 @@ class behat_selectors {
behat_exact_named_selector::get_allowed_text_selectors()
);
}
/**
* Normalise the selector and locator for a named partial.
*
* @param string $selector The selector name
* @param string $locator The value to normalise
* @return array
*/
public static function normalise_named_selector(string $selector, string $locator): array {
return [
$selector,
behat_context_helper::escape($locator),
];
}
/**
* Transform the selector for a field.
*
* @param string $label The label to find
* @param Element $container The container to look within
* @return array The selector, locator, and container to search within
*/
public static function transform_find_for_field(behat_base $context, string $label, Element $container): array {
$hasfieldset = strpos($label, '>');
if (false !== $hasfieldset) {
[$containerlabel, $label] = explode(">", $label, 2);
$container = $context->find_fieldset(trim($containerlabel), $container);
$label = trim($label);
}
return [
'selector' => 'named_partial',
'locator' => self::normalise_named_selector('field', $label),
'container' => $container,
];
}
}
+43 -61
View File
@@ -412,24 +412,22 @@ class behat_general extends behat_base {
* @param string $containerelement
* @param string $containerselectortype
*/
public function i_drag_and_i_drop_it_in($element, $selectortype, $containerelement, $containerselectortype) {
list($sourceselector, $sourcelocator) = $this->transform_selector($selectortype, $element);
$sourcexpath = $this->getSession()->getSelectorsHandler()->selectorToXpath($sourceselector, $sourcelocator);
list($containerselector, $containerlocator) = $this->transform_selector($containerselectortype, $containerelement);
$destinationxpath = $this->getSession()->getSelectorsHandler()->selectorToXpath($containerselector, $containerlocator);
$node = $this->get_selected_node("xpath_element", $sourcexpath);
if (!$node->isVisible()) {
throw new ExpectationException('"' . $sourcexpath . '" "xpath_element" is not visible', $this->getSession());
}
$node = $this->get_selected_node("xpath_element", $destinationxpath);
if (!$node->isVisible()) {
throw new ExpectationException('"' . $destinationxpath . '" "xpath_element" is not visible', $this->getSession());
public function i_drag_and_i_drop_it_in($source, $sourcetype, $target, $targettype) {
if (!$this->running_javascript()) {
throw new DriverException('Drag and drop steps require javascript');
}
$this->getSession()->getDriver()->dragTo($sourcexpath, $destinationxpath);
$source = $this->find($sourcetype, $source);
$target = $this->find($targettype, $target);
if (!$source->isVisible()) {
throw new ExpectationException("'{$source}' '{$sourcetype}' is not visible", $this->getSession());
}
if (!$target->isVisible()) {
throw new ExpectationException("'{$target}' '{$targettype}' is not visible", $this->getSession());
}
$this->getSession()->getDriver()->dragTo($source->getXpath(), $target->getXpath());
}
/**
@@ -968,12 +966,8 @@ EOF;
* @param string $selectortype The selector type
*/
public function should_exist($element, $selectortype) {
// Getting Mink selector and locator.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
// Will throw an ElementNotFoundException if it does not exist.
$this->find($selector, $locator);
$this->find($selectortype, $element);
}
/**
@@ -987,35 +981,27 @@ EOF;
* @param string $selectortype The selector type
*/
public function should_not_exist($element, $selectortype) {
// Getting Mink selector and locator.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
// Will throw an ElementNotFoundException if it does not exist, but, actually it should not exist, so we try &
// catch it.
try {
// Using directly the spin method as we want a reduced timeout but there is no
// need for a 0.1 seconds interval because in the optimistic case we will timeout.
$params = array('selector' => $selector, 'locator' => $locator);
// The exception does not really matter as we will catch it and will never "explode".
$exception = new ElementNotFoundException($this->getSession(), $selectortype, null, $element);
// Using the spin method as we want a reduced timeout but there is no need for a 0.1 seconds interval
// because in the optimistic case we will timeout.
// If all goes good it will throw an ElementNotFoundExceptionn that we will catch.
$this->spin(
function($context, $args) {
return $context->getSession()->getPage()->findAll($args['selector'], $args['locator']);
},
$params,
behat_base::get_reduced_timeout(),
$exception,
false
function($context, $args) use ($selectortype, $element) {
return $this->find($selectortype, $element);
}, [], behat_base::get_reduced_timeout(), $exception, false
);
} catch (ElementNotFoundException $e) {
// It passes.
// We expect the element to not be found.
return;
}
throw new ExpectationException('The "' . $element . '" "' . $selectortype .
'" exists in the current page', $this->getSession());
// The element was found and should not have been. Throw an exception.
throw new ExpectationException("The '{$element}' '{$selectortype}' exists in the current page", $this->getSession());
}
/**
@@ -1159,16 +1145,14 @@ EOF;
*/
public function should_exist_in_the($element, $selectortype, $containerelement, $containerselectortype) {
// Get the container node.
$containernode = $this->get_selected_node($containerselectortype, $containerelement);
list($selector, $locator) = $this->transform_selector($selectortype, $element);
$containernode = $this->find($containerselectortype, $containerelement);
// Specific exception giving info about where can't we find the element.
$locatorexceptionmsg = $element . '" in the "' . $containerelement. '" "' . $containerselectortype. '"';
$locatorexceptionmsg = "{$element} in the {$containerelement} {$containerselectortype}";
$exception = new ElementNotFoundException($this->getSession(), $selectortype, null, $locatorexceptionmsg);
// Looks for the requested node inside the container node.
$this->find($selector, $locator, $exception, $containernode);
$this->find($selectortype, $element, $exception, $containernode);
}
/**
@@ -1184,26 +1168,24 @@ EOF;
* @param string $containerselectortype The container locator
*/
public function should_not_exist_in_the($element, $selectortype, $containerelement, $containerselectortype) {
// Get the container node.
$containernode = $this->find($containerselectortype, $containerelement);
// Get the container node; here we throw an exception
// if the container node does not exist.
$containernode = $this->get_selected_node($containerselectortype, $containerelement);
list($selector, $locator) = $this->transform_selector($selectortype, $element);
// Will throw an ElementNotFoundException if it does not exist, but, actually
// it should not exist, so we try & catch it.
// Will throw an ElementNotFoundException if it does not exist, but, actually it should not exist, so we try &
// catch it.
try {
// Would be better to use a 1 second sleep because the element should not be there,
// but we would need to duplicate the whole find_all() logic to do it, the benefit of
// changing to 1 second sleep is not significant.
$this->find($selector, $locator, false, $containernode, behat_base::get_reduced_timeout());
// Looks for the requested node inside the container node.
$this->find($selectortype, $element, false, $containernode, behat_base::get_reduced_timeout());
} catch (ElementNotFoundException $e) {
// It passes.
// We expect the element to not be found.
return;
}
throw new ExpectationException('The "' . $element . '" "' . $selectortype . '" exists in the "' .
$containerelement . '" "' . $containerselectortype . '"', $this->getSession());
// The element was found and should not have been. Throw an exception.
throw new ExpectationException(
"The '{$element}' '{$selectortype}' exists in the '{$containerelement}' '{$containerselectortype}'",
$this->getSession()
);
}
/**
@@ -1794,8 +1776,8 @@ EOF;
if (!$this->running_javascript()) {
throw new DriverException('Checking focus on an element requires JavaScript');
}
list($a, $b) = $this->transform_selector($nodeselectortype, $nodeelement);
$element = $this->find($a, $b);
$element = $this->find($nodeselectortype, $nodeelement);
$xpath = addslashes_js($element->getXpath());
$script = 'return (function() { return document.activeElement === document.evaluate("' . $xpath . '",
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; })(); ';