Merge branch 'wip-mdl-53381-m29' of https://github.com/rajeshtaneja/moodle into MOODLE_29_STABLE

This commit is contained in:
Andrew Nicols
2016-04-06 12:24:58 +08:00
3 changed files with 178 additions and 201 deletions
+14 -70
View File
@@ -47,34 +47,15 @@ class behat_auth extends behat_base {
* @Given /^I log in as "(?P<username_string>(?:[^"]|\\")*)"$/
*/
public function i_log_in_as($username) {
// Visit login page.
$this->getSession()->visit($this->locate_path('login/index.php'));
// Running this step using the API rather than a chained step because
// we need to see if the 'Log in' link is available or we need to click
// the dropdown to expand the navigation bar before.
$this->getSession()->visit($this->locate_path('/'));
// Enter username and password.
$this->execute('behat_forms::i_set_the_field_to', array('Username', $this->escape($username)));
$this->execute('behat_forms::i_set_the_field_to', array('Password', $this->escape($username)));
// Generic steps (we will prefix them later expanding the navigation dropdown if necessary).
$steps = array(
new Given('I click on "' . get_string('login') . '" "link" in the ".logininfo" "css_element"'),
new Given('I set the field "' . get_string('username') . '" to "' . $this->escape($username) . '"'),
new Given('I set the field "' . get_string('password') . '" to "'. $this->escape($username) . '"'),
new Given('I press "' . get_string('login') . '"')
);
// If Javascript is disabled we have enough with these steps.
if (!$this->running_javascript()) {
return $steps;
}
// Wait for the homepage to be ready.
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
// If it is needed, it expands the navigation bar with the 'Log in' link.
if ($clicknavbar = $this->get_expand_navbar_step()) {
array_unshift($steps, $clicknavbar);
}
return $steps;
// Press log in button, no need to check for exceptions as it will checked after this step execution.
$this->execute('behat_forms::press_button', get_string('login'));
}
/**
@@ -83,53 +64,16 @@ class behat_auth extends behat_base {
* @Given /^I log out$/
*/
public function i_log_out() {
$steps = array(new When('I follow "' . get_string('logout') . '"'));
// No need to check anything else if we run without JS.
if (!$this->running_javascript()) {
return $steps;
}
// There is no longer any need to worry about whether the navigation
// bar needs to be expanded; user_menu now lives outside the
// hamburger.
// However, the user menu *always* needs to be expanded.
$xpath = "//div[@class='usermenu']//a[contains(concat(' ', @class, ' '), ' toggle-display ')]";
array_unshift($steps, new When('I click on "'.$xpath.'" "xpath_element"'));
return $steps;
}
/**
* Returns a step to open the navigation bar if it is needed.
*
* The top log in and log out links are hidden when middle or small
* size windows (or devices) are used. This step returns a step definition
* clicking to expand the navbar if it is hidden.
*
* @return Given|bool A step definition or false if there is no need to show the navbar.
*/
protected function get_expand_navbar_step() {
// Checking if we need to click the navbar button to show the navigation menu, it
// is hidden by default when using clean theme and a medium or small screen size.
// The DOM and the JS should be all ready and loaded. Running without spinning
// as this is a widely used step and we can not spend time here trying to see
// a DOM node that is not always there (at the moment clean is not even the
// default theme...).
$navbuttonjs = "return (
Y.one('.btn-navbar') &&
Y.one('.btn-navbar').getComputedStyle('display') !== 'none'
)";
// Adding an extra click we need to show the 'Log in' link.
if (!$this->getSession()->getDriver()->evaluateScript($navbuttonjs)) {
return false;
// However, the user menu *always* needs to be expanded. if running JS.
if ($this->running_javascript()) {
$xpath = "//div[@class='usermenu']//a[contains(concat(' ', @class, ' '), ' toggle-display ')]";
$this->execute('behat_general::i_click_on', array($xpath, "xpath_element"));
}
return new Given('I click on ".btn-navbar" "css_element"');
// No need to check for exceptions as it will checked after this step execution.
$this->execute('behat_general::click_link', get_string('logout'));
}
}
+162
View File
@@ -637,4 +637,166 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
}
$this->getSession()->getDriver()->resizeWindow($width, $height);
}
/**
* Waits for all the JS to be loaded.
*
* @throws \Exception
* @throws NoSuchWindow
* @throws UnknownError
* @return bool True or false depending whether all the JS is loaded or not.
*/
public function wait_for_pending_js() {
// Waiting for JS is only valid for JS scenarios.
if (!$this->running_javascript()) {
return;
}
// 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 function() {
if (typeof M === "undefined") {
if (document.readyState === "complete") {
return "";
} else {
return "incomplete";
}
} else if (' . self::PAGE_READY_JS . ') {
return "";
} else if (typeof M.util !== "undefined") {
return M.util.pending_js.join(":");
} else {
return "incomplete";
}
}();';
$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) {
// M is not defined when the window or the frame don't exist anymore.
if (strstr($e->getMessage(), 'M is not defined') != false) {
$pending = '';
}
}
// If there are no pending JS we stop waiting.
if ($pending === '') {
return true;
}
// 0.1 seconds.
usleep(100000);
}
// Timeout waiting for JS to complete. It will be catched and forwarded to behat_hooks::i_look_for_exceptions().
// It is unlikely that Javascript code of a page or an AJAX request needs more than self::EXTENDED_TIMEOUT seconds
// to be loaded, although when pages contains Javascript errors M.util.js_complete() can not be executed, so the
// number of JS pending code and JS completed code will not match and we will reach this point.
throw new \Exception('Javascript code and/or AJAX requests are not ready after ' . self::EXTENDED_TIMEOUT .
' seconds. There is a Javascript error or the code is extremely slow.');
}
/**
* Internal step definition to find exceptions, debugging() messages and PHP debug messages.
*
* Part of behat_hooks class as is part of the testing framework, is auto-executed
* after each step so no features will splicitly use it.
*
* @throws Exception Unknown type, depending on what we caught in the hook or basic \Exception.
* @see Moodle\BehatExtension\Tester\MoodleStepTester
*/
public function look_for_exceptions() {
// Wrap in try in case we were interacting with a closed window.
try {
// Exceptions.
$exceptionsxpath = "//div[@data-rel='fatalerror']";
// Debugging messages.
$debuggingxpath = "//div[@data-rel='debugging']";
// PHP debug messages.
$phperrorxpath = "//div[@data-rel='phpdebugmessage']";
// Any other backtrace.
$othersxpath = "(//*[contains(., ': call to ')])[1]";
$xpaths = array($exceptionsxpath, $debuggingxpath, $phperrorxpath, $othersxpath);
$joinedxpath = implode(' | ', $xpaths);
// Joined xpath expression. Most of the time there will be no exceptions, so this pre-check
// is faster than to send the 4 xpath queries for each step.
if (!$this->getSession()->getDriver()->find($joinedxpath)) {
return;
}
// Exceptions.
if ($errormsg = $this->getSession()->getPage()->find('xpath', $exceptionsxpath)) {
// Getting the debugging info and the backtrace.
$errorinfoboxes = $this->getSession()->getPage()->findAll('css', 'div.alert-error');
// If errorinfoboxes is empty, try find notifytiny (original) class.
if (empty($errorinfoboxes)) {
$errorinfoboxes = $this->getSession()->getPage()->findAll('css', 'div.notifytiny');
}
$errorinfo = $this->get_debug_text($errorinfoboxes[0]->getHtml()) . "\n" .
$this->get_debug_text($errorinfoboxes[1]->getHtml());
$msg = "Moodle exception: " . $errormsg->getText() . "\n" . $errorinfo;
throw new \Exception(html_entity_decode($msg));
}
// Debugging messages.
if ($debuggingmessages = $this->getSession()->getPage()->findAll('xpath', $debuggingxpath)) {
$msgs = array();
foreach ($debuggingmessages as $debuggingmessage) {
$msgs[] = $this->get_debug_text($debuggingmessage->getHtml());
}
$msg = "debugging() message/s found:\n" . implode("\n", $msgs);
throw new \Exception(html_entity_decode($msg));
}
// PHP debug messages.
if ($phpmessages = $this->getSession()->getPage()->findAll('xpath', $phperrorxpath)) {
$msgs = array();
foreach ($phpmessages as $phpmessage) {
$msgs[] = $this->get_debug_text($phpmessage->getHtml());
}
$msg = "PHP debug message/s found:\n" . implode("\n", $msgs);
throw new \Exception(html_entity_decode($msg));
}
// Any other backtrace.
// First looking through xpath as it is faster than get and parse the whole page contents,
// we get the contents and look for matches once we found something to suspect that there is a backtrace.
if ($this->getSession()->getDriver()->find($othersxpath)) {
$backtracespattern = '/(line [0-9]* of [^:]*: call to [\->&;:a-zA-Z_\x7f-\xff][\->&;:a-zA-Z0-9_\x7f-\xff]*)/';
if (preg_match_all($backtracespattern, $this->getSession()->getPage()->getContent(), $backtraces)) {
$msgs = array();
foreach ($backtraces[0] as $backtrace) {
$msgs[] = $backtrace . '()';
}
$msg = "Other backtraces found:\n" . implode("\n", $msgs);
throw new \Exception(htmlentities($msg));
}
}
} catch (NoSuchWindow $e) {
// If we were interacting with a popup window it will not exists after closing it.
}
}
/**
* Helper function to execute api in a given context.
*
* @param string $contextapi context in which api is defined.
* @param array $params list of params to pass.
* @throws Exception
*/
protected function execute($contextapi, $params = array()) {
if (!is_array($params)) {
$params = array($params);
}
// Get required context and execute the api.
$contextapi = explode("::", $contextapi);
$context = behat_context_helper::get($contextapi[0]);
call_user_func_array(array($context, $contextapi[1]), $params);
// NOTE: Wait for pending js and look for exception are not optional, as this might lead to unexpected results.
// So don't make them optional for performance reasons.
// Wait for pending js.
$this->wait_for_pending_js();
// Look for exceptions.
$this->look_for_exceptions();
}
}
+2 -131
View File
@@ -489,62 +489,6 @@ class behat_hooks extends behat_base {
return array($dir, $filename);
}
/**
* Waits for all the JS to be loaded.
*
* @throws \Exception
* @throws NoSuchWindow
* @throws UnknownError
* @return bool True or false depending whether all the JS is loaded or not.
*/
protected function wait_for_pending_js() {
// 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 =
'if (typeof M === "undefined") {
if (document.readyState === "complete") {
return "";
} else {
return "incomplete";
}
} else if (' . self::PAGE_READY_JS . ') {
return "";
} else {
return 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) {
// M is not defined when the window or the frame don't exist anymore.
if (strstr($e->getMessage(), 'M is not defined') != false) {
$pending = '';
}
}
// If there are no pending JS we stop waiting.
if ($pending === '') {
return true;
}
// 0.1 seconds.
usleep(100000);
}
// Timeout waiting for JS to complete. It will be catched and forwarded to behat_hooks::i_look_for_exceptions().
// It is unlikely that Javascript code of a page or an AJAX request needs more than self::EXTENDED_TIMEOUT seconds
// to be loaded, although when pages contains Javascript errors M.util.js_complete() can not be executed, so the
// number of JS pending code and JS completed code will not match and we will reach this point.
throw new \Exception('Javascript code and/or AJAX requests are not ready after ' . self::EXTENDED_TIMEOUT .
' seconds. There is a Javascript error or the code is extremely slow.');
}
/**
* Internal step definition to find exceptions, debugging() messages and PHP debug messages.
*
@@ -562,82 +506,9 @@ class behat_hooks extends behat_base {
throw self::$currentstepexception;
}
// Wrap in try in case we were interacting with a closed window.
try {
// Look for exceptions displayed on page.
$this->look_for_exceptions();
// Exceptions.
$exceptionsxpath = "//div[@data-rel='fatalerror']";
// Debugging messages.
$debuggingxpath = "//div[@data-rel='debugging']";
// PHP debug messages.
$phperrorxpath = "//div[@data-rel='phpdebugmessage']";
// Any other backtrace.
$othersxpath = "(//*[contains(., ': call to ')])[1]";
$xpaths = array($exceptionsxpath, $debuggingxpath, $phperrorxpath, $othersxpath);
$joinedxpath = implode(' | ', $xpaths);
// Joined xpath expression. Most of the time there will be no exceptions, so this pre-check
// is faster than to send the 4 xpath queries for each step.
if (!$this->getSession()->getDriver()->find($joinedxpath)) {
return;
}
// Exceptions.
if ($errormsg = $this->getSession()->getPage()->find('xpath', $exceptionsxpath)) {
// Getting the debugging info and the backtrace.
$errorinfoboxes = $this->getSession()->getPage()->findAll('css', 'div.alert-error');
// If errorinfoboxes is empty, try find notifytiny (original) class.
if (empty($errorinfoboxes)) {
$errorinfoboxes = $this->getSession()->getPage()->findAll('css', 'div.notifytiny');
}
$errorinfo = $this->get_debug_text($errorinfoboxes[0]->getHtml()) . "\n" .
$this->get_debug_text($errorinfoboxes[1]->getHtml());
$msg = "Moodle exception: " . $errormsg->getText() . "\n" . $errorinfo;
throw new \Exception(html_entity_decode($msg));
}
// Debugging messages.
if ($debuggingmessages = $this->getSession()->getPage()->findAll('xpath', $debuggingxpath)) {
$msgs = array();
foreach ($debuggingmessages as $debuggingmessage) {
$msgs[] = $this->get_debug_text($debuggingmessage->getHtml());
}
$msg = "debugging() message/s found:\n" . implode("\n", $msgs);
throw new \Exception(html_entity_decode($msg));
}
// PHP debug messages.
if ($phpmessages = $this->getSession()->getPage()->findAll('xpath', $phperrorxpath)) {
$msgs = array();
foreach ($phpmessages as $phpmessage) {
$msgs[] = $this->get_debug_text($phpmessage->getHtml());
}
$msg = "PHP debug message/s found:\n" . implode("\n", $msgs);
throw new \Exception(html_entity_decode($msg));
}
// Any other backtrace.
// First looking through xpath as it is faster than get and parse the whole page contents,
// we get the contents and look for matches once we found something to suspect that there is a backtrace.
if ($this->getSession()->getDriver()->find($othersxpath)) {
$backtracespattern = '/(line [0-9]* of [^:]*: call to [\->&;:a-zA-Z_\x7f-\xff][\->&;:a-zA-Z0-9_\x7f-\xff]*)/';
if (preg_match_all($backtracespattern, $this->getSession()->getPage()->getContent(), $backtraces)) {
$msgs = array();
foreach ($backtraces[0] as $backtrace) {
$msgs[] = $backtrace . '()';
}
$msg = "Other backtraces found:\n" . implode("\n", $msgs);
throw new \Exception(htmlentities($msg));
}
}
} catch (NoSuchWindow $e) {
// If we were interacting with a popup window it will not exists after closing it.
}
}
/**