Merge branch 'MDL-42625_master' of git://github.com/dmonllao/moodle

This commit is contained in:
Eloy Lafuente (stronk7) 2013-12-10 23:29:06 +01:00
commit ebc77165a4
51 changed files with 991 additions and 335 deletions

View file

@ -68,6 +68,7 @@ class behat_deprecated extends behat_base {
// Looking for the element DOM node inside the specified row.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
$elementnode = $this->find($selector, $locator, false, $rownode);
$this->ensure_element_is_visible($elementnode);
$elementnode->click();
}

View file

@ -69,6 +69,9 @@ class behat_forms extends behat_base {
*/
public function i_fill_the_moodle_form_with(TableNode $data) {
// We ensure that all the editors are loaded and we can interact with them.
$this->ensure_editors_are_loaded();
// Expand all fields in case we have.
$this->expand_all_fields();
@ -171,31 +174,11 @@ class behat_forms extends behat_base {
public function select_option($option, $select) {
$selectnode = $this->find_field($select);
$selectnode->selectOption($option);
// Adding a click as Selenium requires it to fire some JS events.
if ($this->running_javascript()) {
// In some browsers the selectOption actions can perform a page reload
// so we need to ensure the element is still available to continue interacting
// with it. We don't wait here.
if (!$this->getSession()->getDriver()->find($selectnode->getXpath())) {
return;
}
// Single select needs an extra click in the option.
if (!$selectnode->hasAttribute('multiple')) {
// Avoid quotes problems.
$option = $this->getSession()->getSelectorsHandler()->xpathLiteral($option);
$xpath = "//option[(./@value=$option or normalize-space(.)=$option)]";
$optionnode = $this->find('xpath', $xpath, false, $selectnode);
$optionnode->click();
} else {
// Multiple ones needs the click in the select.
$selectnode->click();
}
}
// 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);
}
/**
@ -225,6 +208,8 @@ class behat_forms extends behat_base {
*/
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();
}
@ -238,6 +223,8 @@ class behat_forms extends behat_base {
*/
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();
}

View file

@ -124,7 +124,20 @@ class behat_general extends behat_base {
* @param string $iframename
*/
public function switch_to_iframe($iframename) {
$this->getSession()->switchToIFrame($iframename);
// We spin to give time to the iframe to be loaded.
// Using extended timeout as we don't know about which
// kind of iframe will be loaded.
$this->spin(
function($context, $iframename) {
$context->getSession()->switchToIFrame($iframename);
// If no exception we are done.
return true;
},
$iframename,
self::EXTENDED_TIMEOUT
);
}
/**
@ -173,6 +186,7 @@ class behat_general extends behat_base {
public function click_link($link) {
$linknode = $this->find_link($link);
$this->ensure_node_is_visible($linknode);
$linknode->click();
}
@ -202,7 +216,56 @@ class behat_general extends behat_base {
throw new DriverException('Waits are disabled in scenarios without Javascript support');
}
$this->getSession()->wait(self::TIMEOUT, '(document.readyState === "complete")');
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
}
/**
* Waits until the editors are all completely loaded.
*
* @Given /^I wait until the editors are loaded$/
* @throws DriverException
*/
public function wait_until_editors_are_loaded() {
if (!$this->running_javascript()) {
throw new DriverException('Editors are not loaded when running without Javascript support');
}
$this->ensure_editors_are_loaded();
}
/**
* Waits until the provided element selector exists in the DOM
*
* Using the protected method as this method will be usually
* called by other methods which are not returning a set of
* steps and performs the actions directly, so it would not
* be executed if it returns another step.
* @Given /^I wait until "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" exists$/
* @param string $element
* @param string $selector
* @return void
*/
public function wait_until_exists($element, $selectortype) {
$this->ensure_element_exists($element, $selectortype);
}
/**
* Waits until the provided element does not exist in the DOM
*
* Using the protected method as this method will be usually
* called by other methods which are not returning a set of
* steps and performs the actions directly, so it would not
* be executed if it returns another step.
* @Given /^I wait until "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" does not exist$/
* @param string $element
* @param string $selector
* @return void
*/
public function wait_until_does_not_exists($element, $selectortype) {
$this->ensure_element_does_not_exist($element, $selectortype);
}
/**
@ -230,6 +293,7 @@ class behat_general extends behat_base {
// Gets the node based on the requested selector type and locator.
$node = $this->get_selected_node($selectortype, $element);
$this->ensure_node_is_visible($node);
$node->click();
}
@ -245,6 +309,7 @@ class behat_general extends behat_base {
public function i_click_on_in_the($element, $selectortype, $nodeelement, $nodeselectortype) {
$node = $this->get_node_in_container($selectortype, $element, $nodeselectortype, $nodeelement);
$this->ensure_node_is_visible($node);
$node->click();
}
@ -298,6 +363,9 @@ class behat_general extends behat_base {
/**
* Checks, that the specified 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.
*
* @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" should not be visible$/
* @throws ElementNotFoundException
* @throws ExpectationException
@ -381,24 +449,35 @@ class behat_general extends behat_base {
$xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" .
"[count(descendant::*[contains(., $xpathliteral)]) = 0]";
// Wait until it finds the text, otherwise custom exception.
try {
$nodes = $this->find_all('xpath', $xpath);
// We also check for the element visibility when running JS tests.
if ($this->running_javascript()) {
foreach ($nodes as $node) {
if ($node->isVisible()) {
return;
}
}
throw new ExpectationException("'{$text}' text was found but was not visible", $this->getSession());
}
} catch (ElementNotFoundException $e) {
throw new ExpectationException('"' . $text . '" text was not found in the page', $this->getSession());
}
// If we are not running javascript we have enough with the
// element existing as we can't check if it is visible.
if (!$this->running_javascript()) {
return;
}
// We spin as we don't have enough checking that the element is there, we
// should also ensure that the element is visible.
$this->spin(
function($context, $args) {
foreach ($args['nodes'] as $node) {
if ($node->isVisible()) {
return true;
}
}
// 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)
);
}
/**
@ -410,16 +489,43 @@ class behat_general extends behat_base {
*/
public function assert_page_not_contains_text($text) {
// Delegating the process to assert_page_contains_text.
// Looking for all the matching nodes without any other descendant matching the
// same xpath (we are using contains(., ....).
$xpathliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($text);
$xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" .
"[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.
try {
$this->assert_page_contains_text($text);
} catch (ExpectationException $e) {
// It should not appear, so this is good.
$nodes = $this->find_all('xpath', $xpath);
} catch (ElementNotFoundException $e) {
// All ok.
return;
}
// If the page contains the text this is failing.
throw new ExpectationException('"' . $text . '" text was found in the page', $this->getSession());
// If we are not running javascript we have enough with the
// element existing as we can't check if it is hidden.
if (!$this->running_javascript()) {
throw new ExpectationException('"' . $text . '" text was found in the page', $this->getSession());
}
// If the element is there we should be sure that it is not visible.
$this->spin(
function($context, $args) {
foreach ($args['nodes'] as $node) {
if ($node->isVisible()) {
throw new ExpectationException('"' . $args['text'] . '" text was found in the page', $context->getSession());
}
}
// If non of the found nodes is visible we consider that the text is not visible.
return true;
},
array('nodes' => $nodes, 'text' => $text)
);
}
/**
@ -446,22 +552,30 @@ class behat_general extends behat_base {
// Wait until it finds the text inside the container, otherwise custom exception.
try {
$nodes = $this->find_all('xpath', $xpath, false, $container);
} catch (ElementNotFoundException $e) {
throw new ExpectationException('"' . $text . '" text was not found in the "' . $element . '" element', $this->getSession());
}
// We also check for the element visibility when running JS tests.
if ($this->running_javascript()) {
foreach ($nodes as $node) {
// If we are not running javascript we have enough with the
// element existing as we can't check if it is visible.
if (!$this->running_javascript()) {
return;
}
// We also check the element visibility when running JS tests.
$this->spin(
function($context, $args) {
foreach ($args['nodes'] as $node) {
if ($node->isVisible()) {
return;
return true;
}
}
throw new ExpectationException("'{$text}' text was found in the {$element} element but was not visible", $this->getSession());
}
} catch (ElementNotFoundException $e) {
throw new ExpectationException('"' . $text . '" text was not found in the ' . $element . ' element', $this->getSession());
}
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)
);
}
/**
@ -476,18 +590,45 @@ class behat_general extends behat_base {
*/
public function assert_element_not_contains_text($text, $element, $selectortype) {
// Delegating the process to assert_element_contains_text.
// Getting the container where the text should be found.
$container = $this->get_selected_node($selectortype, $element);
// Looking for all the matching nodes without any other descendant matching the
// same xpath (we are using contains(., ....).
$xpathliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($text);
$xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" .
"[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.
try {
$this->assert_element_contains_text($text, $element, $selectortype);
} catch (ExpectationException $e) {
// It should not appear, so this is good.
// We only catch ExpectationException as ElementNotFoundException
// will be thrown if the container does not exist.
$nodes = $this->find_all('xpath', $xpath, false, $container);
} catch (ElementNotFoundException $e) {
// All ok.
return;
}
// If the element contains the text this is failing.
throw new ExpectationException('"' . $text . '" text was found in the ' . $element . ' element', $this->getSession());
// If we are not running javascript we have enough with the
// element not being found as we can't check if it is visible.
if (!$this->running_javascript()) {
throw new ExpectationException('"' . $text . '" text was found in the "' . $element . '" element', $this->getSession());
}
// We need to ensure all the found nodes are hidden.
$this->spin(
function($context, $args) {
foreach ($args['nodes'] as $node) {
if ($node->isVisible()) {
throw new ExpectationException('"' . $args['text'] . '" text was found in the "' . $args['element'] . '" element', $context->getSession());
}
}
// If all the found nodes are hidden we are happy.
return true;
},
array('nodes' => $nodes, 'text' => $text, 'element' => $element)
);
}
/**

View file

@ -222,42 +222,68 @@ class behat_hooks extends behat_base {
}
/**
* Checks that all DOM is ready.
* Wait for JS to complete before beginning interacting with the DOM.
*
* Executed only when running against a real browser.
*
* @BeforeStep @javascript
*/
public function before_step_javascript($event) {
$this->wait_for_pending_js();
}
/**
* Wait for JS to complete after finishing the step.
*
* With this we ensure that there are not AJAX calls
* still in progress.
*
* Executed only when running against a real browser.
*
* @AfterStep @javascript
*/
public function after_step_javascript($event) {
$this->wait_for_pending_js();
}
// If it doesn't have definition or it fails there is no need to check it.
if ($event->getResult() != StepEvent::PASSED ||
!$event->hasDefinition()) {
return;
}
/**
* Waits for all the JS to be loaded.
*
* @throws NoSuchWindow
* @throws UnknownError
* @return bool True or false depending whether all the JS is loaded or not.
*/
protected function wait_for_pending_js() {
// Wait until the page is ready.
// We are already checking that we use a JS browser, this could
// change in case we use another JS driver.
try {
// Safari and Internet Explorer requires time between steps,
// otherwise Selenium tries to click in the previous page's DOM.
if ($this->getSession()->getDriver()->getBrowserName() == 'safari' ||
$this->getSession()->getDriver()->getBrowserName() == 'internet explorer') {
$this->getSession()->wait(self::TIMEOUT * 1000, false);
} else {
// With other browsers we just wait for the DOM ready.
$this->getSession()->wait(self::TIMEOUT * 1000, '(document.readyState === "complete")');
// We don't use behat_base::spin() here as we don't want to end up with an exception
// if the page & JSs don't finish loading properly.
for ($i = 0; $i < self::EXTENDED_TIMEOUT * 10; $i++) {
$pending = '';
try {
$jscode = 'return ' . self::PAGE_READY_JS . ' ? "" : M.util.pending_js.join(":");';
$pending = $this->getSession()->evaluateScript($jscode);
} catch (NoSuchWindow $nsw) {
// We catch an exception here, in case we just closed the window we were interacting with.
// No javascript is running if there is no window right?
$pending = '';
} catch (UnknownError $e) {
// Same exception as before, but some combinations of browser + OS reports it as an unknown error
// exception.
$pending = '';
}
} catch (NoSuchWindow $e) {
// If we were interacting with a popup window it will not exists after closing it.
} catch (UnknownError $e) {
// Custom exception to provide more feedback about possible solutions.
$this->throw_unknown_exception($e);
// If there are no pending JS we stop waiting.
if ($pending === '') {
return true;
}
// 0.1 seconds.
usleep(100000);
}
// Timeout waiting for JS to complete.
// TODO MDL-43173 We should fail the scenarios if JS loading times out.
return false;
}
/**

View file

@ -76,6 +76,7 @@ class behat_navigation extends behat_base {
$exception = new ExpectationException('The "' . $nodetext . '" node can not be expanded', $this->getSession());
$node = $this->find('xpath', $xpath, $exception);
$this->ensure_node_is_visible($node);
$node->click();
}

View file

@ -95,7 +95,16 @@ class behat_permissions extends behat_base {
try {
$advancedtoggle = $this->find_button(get_string('showadvanced', 'form'));
if ($advancedtoggle) {
$this->getSession()->getPage()->pressButton(get_string('showadvanced', 'form'));
// As we are interacting with a moodle form we wait for the editor to be ready
// otherwise we may have problems when setting values on it or clicking on elements
// as the position of the elements will change once the editor is loaded.
$this->ensure_editors_are_loaded();
$advancedtoggle->click();
// Wait for the page to load.
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
}
} catch (Exception $e) {
// We already are in advanced mode.