MDL-43837 behat: Adding reduced timeouts

There are steps where we want to check that
"something" does not appear in the page
or does not exist. We still have to spin
but we don't need to spin for 6 seconds.
This commit is contained in:
David Monllao 2014-02-13 11:47:50 +08:00
parent ec4c8161a4
commit 74c78e7483
3 changed files with 108 additions and 27 deletions

View File

@ -52,6 +52,15 @@ use Behat\Mink\Exception\ExpectationException as ExpectationException,
*/
class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
/**
* Small timeout.
*
* A reduced timeout for cases where self::TIMEOUT is too much
* and a simple $this->getSession()->getPage()->find() could not
* be enough.
*/
const REDUCED_TIMEOUT = 2;
/**
* The timeout for each Behat step (load page, wait for an element to load...).
*/
@ -88,12 +97,13 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
* @param mixed $locator It depends on the $selector, can be the xpath, a name, a css locator...
* @param Exception $exception Otherwise we throw exception with generic info
* @param NodeElement $node Spins around certain DOM node instead of the whole page
* @param int $timeout Forces a specific time out (in seconds).
* @return NodeElement
*/
protected function find($selector, $locator, $exception = false, $node = false) {
protected function find($selector, $locator, $exception = false, $node = false, $timeout = false) {
// Returns the first match.
$items = $this->find_all($selector, $locator, $exception, $node);
$items = $this->find_all($selector, $locator, $exception, $node, $timeout);
return count($items) ? reset($items) : null;
}
@ -107,9 +117,10 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
* @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 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) {
protected function find_all($selector, $locator, $exception = false, $node = false, $timeout = false) {
// Generic info.
if (!$exception) {
@ -138,6 +149,17 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
$params['node'] = $node;
}
// How much we will be waiting for the element to appear.
if (!$timeout) {
$timeout = self::TIMEOUT;
$microsleep = false;
} else {
// Spinning each 0.1 seconds if the timeout was forced as we understand
// that is a special case and is good to refine the performance as much
// as possible.
$microsleep = true;
}
// Waits for the node to appear if it exists, otherwise will timeout and throw the provided exception.
return $this->spin(
function($context, $args) {
@ -170,8 +192,9 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
return $context->getSession()->getDriver()->find(implode('|', $unions));
},
$params,
self::TIMEOUT,
$exception
$timeout,
$exception,
$microsleep
);
}
@ -569,7 +592,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
// If there are no editors we don't need to wait.
try {
$this->find('css', '.mceEditor');
$this->find('css', '.mceEditor', false, false, self::REDUCED_TIMEOUT);
} catch (ElementNotFoundException $e) {
return;
}

View File

@ -110,18 +110,20 @@ class behat_forms extends behat_base {
// We ensure that all the editors are loaded and we can interact with them.
$this->ensure_editors_are_loaded();
// behat_base::find() throws an exception if there are no elements, we should not fail a test because of this.
// We already know that we waited for the DOM and the JS to be loaded, even the editor
// so, we will use the reduced timeout as it is a common task and we should save time.
try {
// Expand fieldsets link.
$collapseexpandlink = $this->find('xpath', "//div[@class='collapsible-actions']" .
$xpath = "//div[@class='collapsible-actions']" .
"/descendant::a[contains(concat(' ', @class, ' '), ' collapseexpand ')]" .
"[not(contains(concat(' ', @class, ' '), ' collapse-all '))]"
);
"[not(contains(concat(' ', @class, ' '), ' collapse-all '))]";
$collapseexpandlink = $this->find('xpath', $xpath, false, false, self::REDUCED_TIMEOUT);
$collapseexpandlink->click();
} catch (ElementNotFoundException $e) {
// We continue if there are not expandable fields.
// The behat_base::find() method throws an exception if there are no elements,
// we should not fail a test because of this. We continue if there are not expandable fields.
}
// Different try & catch as we can have expanded fieldsets with advanced fields on them.
@ -132,7 +134,9 @@ class behat_forms extends behat_base {
"[contains(concat(' ', normalize-space(@class), ' '), ' moreless-toggler')]";
// We don't wait here as we already waited when getting the expand fieldsets links.
$showmores = $this->getSession()->getPage()->findAll('xpath', $showmorexpath);
if (!$showmores = $this->getSession()->getPage()->findAll('xpath', $showmorexpath)) {
return;
}
// Funny thing about this, with findAll() we specify a pattern and each element matching the pattern is added to the array
// with of xpaths with a [0], [1]... sufix, but when we click on an element it does not matches the specified xpath

View File

@ -346,10 +346,12 @@ class behat_general extends behat_base {
}
/**
* Checks, that the specified element is not visible. Only available in tests using Javascript.
* Checks, that the existing element is not visible. Only available in tests using Javascript.
*
* As a "not" method, it's performance is not specially good as we should ensure that the element
* have time to appear.
* As a "not" method, it's performance could not be good, but in this
* case the performance is good because the element must exist,
* otherwise there would be a ElementNotFoundException, also here we are
* not spinning until the element is visible.
*
* @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" should not be visible$/
* @throws ElementNotFoundException
@ -396,7 +398,12 @@ class behat_general extends behat_base {
}
/**
* Checks, that the specified element is not visible inside the specified container. Only available in tests using Javascript.
* Checks, that the existing element is not visible inside the existing container. Only available in tests using Javascript.
*
* As a "not" method, it's performance could not be good, but in this
* case the performance is good because the element must exist,
* otherwise there would be a ElementNotFoundException, also here we are
* not spinning until the element is visible.
*
* @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" should not be visible$/
* @throws ElementNotFoundException
@ -447,7 +454,8 @@ class behat_general extends behat_base {
}
// We spin as we don't have enough checking that the element is there, we
// should also ensure that the element is visible.
// should also ensure that the element is visible. Using microsleep as this
// is a repeated step and global performance is important.
$this->spin(
function($context, $args) {
@ -460,7 +468,10 @@ class behat_general extends behat_base {
// If non of the nodes is visible we loop again.
throw new ExpectationException('"' . $args['text'] . '" text was found but was not visible', $context->getSession());
},
array('nodes' => $nodes, 'text' => $text)
array('nodes' => $nodes, 'text' => $text),
false,
false,
true
);
}
@ -481,9 +492,10 @@ class behat_general extends behat_base {
"[count(descendant::*[contains(., $xpathliteral)]) = 0]";
// We should wait a while to ensure that the page is not still loading elements.
// Giving preference to the reliability of the results rather than to the performance.
// Waiting less than self::TIMEOUT as we already waited for the DOM to be ready and
// all JS to be executed.
try {
$nodes = $this->find_all('xpath', $xpath);
$nodes = $this->find_all('xpath', $xpath, false, false, self::REDUCED_TIMEOUT);
} catch (ElementNotFoundException $e) {
// All ok.
return;
@ -508,7 +520,10 @@ class behat_general extends behat_base {
// If non of the found nodes is visible we consider that the text is not visible.
return true;
},
array('nodes' => $nodes, 'text' => $text)
array('nodes' => $nodes, 'text' => $text),
self::REDUCED_TIMEOUT,
false,
true
);
}
@ -547,7 +562,8 @@ class behat_general extends behat_base {
return;
}
// We also check the element visibility when running JS tests.
// We also check the element visibility when running JS tests. Using microsleep as this
// is a repeated step and global performance is important.
$this->spin(
function($context, $args) {
@ -559,7 +575,10 @@ class behat_general extends behat_base {
throw new ExpectationException('"' . $args['text'] . '" text was found in the "' . $args['element'] . '" element but was not visible', $context->getSession());
},
array('nodes' => $nodes, 'text' => $text, 'element' => $element)
array('nodes' => $nodes, 'text' => $text, 'element' => $element),
false,
false,
true
);
}
@ -587,7 +606,7 @@ class behat_general extends behat_base {
// We should wait a while to ensure that the page is not still loading elements.
// Giving preference to the reliability of the results rather than to the performance.
try {
$nodes = $this->find_all('xpath', $xpath, false, $container);
$nodes = $this->find_all('xpath', $xpath, false, $container, self::REDUCED_TIMEOUT);
} catch (ElementNotFoundException $e) {
// All ok.
return;
@ -612,7 +631,10 @@ class behat_general extends behat_base {
// If all the found nodes are hidden we are happy.
return true;
},
array('nodes' => $nodes, 'text' => $text, 'element' => $element)
array('nodes' => $nodes, 'text' => $text, 'element' => $element),
self::REDUCED_TIMEOUT,
false,
true
);
}
@ -771,8 +793,28 @@ class behat_general extends behat_base {
*/
public function should_not_exists($element, $selectortype) {
// Getting Mink selector and locator.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
try {
$this->should_exists($element, $selectortype);
// 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);
// 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,
false,
$exception,
self::REDUCED_TIMEOUT
);
throw new ExpectationException('The "' . $element . '" "' . $selectortype . '" exists in the current page', $this->getSession());
} catch (ElementNotFoundException $e) {
// It passes.
@ -828,8 +870,20 @@ class behat_general extends behat_base {
* @param string $containerselectortype The container locator
*/
public function should_not_exist_in_the($element, $selectortype, $containerelement, $containerselectortype) {
// 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 exists, so we try & catch it.
try {
$this->should_exist_in_the($element, $selectortype, $containerelement, $containerselectortype);
// 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, self::REDUCED_TIMEOUT);
throw new ExpectationException('The "' . $element . '" "' . $selectortype . '" exists in the "' .
$containerelement . '" "' . $containerselectortype . '"', $this->getSession());
} catch (ElementNotFoundException $e) {