MDL-41030 Behat: Add support for waiting for javascript

This is so that phantomjs (which runs faster than selenium) can pass the tests.
This commit is contained in:
Damyon Wiese
2013-12-06 12:13:39 +08:00
committed by David Monllao
parent 5d2b67228c
commit 6e2c417c1f
4 changed files with 124 additions and 34 deletions
+70
View File
@@ -755,6 +755,76 @@ M.util.init_block_hider = function(Y, config) {
});
};
/**
* @var pending_js - The keys are the list of all pending js actions.
* @type Object
*/
M.util.pending_js = [];
M.util.complete_js = [];
/**
* Register any long running javascript code with a unique identifier.
* Should be followed with a call to js_complete with a matching
* idenfitier when the code is complete. May also be called with no arguments
* to test if there is any js calls pending. This is relied on by behat so that
* it can wait for all pending updates before interacting with a page.
* @param String uniqid - optional, if provided,
* registers this identifier until js_complete is called.
* @return boolean - True if there is any pending js.
*/
M.util.js_pending = function(uniqid) {
if (uniqid !== false) {
M.util.pending_js.push(uniqid);
}
return M.util.pending_js.length;
};
/**
* Register listeners for Y.io start/end so we can wait for them in behat.
*/
M.util.js_watch_io = function() {
YUI.add('moodle-core-io', function(Y) {
Y.on('io:start', function(id) {
M.util.js_pending('io:' + id);
});
Y.on('io:end', function(id) {
M.util.js_complete('io:' + id);
});
});
YUI.applyConfig({
modules: {
'moodle-core-io': {
after: ['io-base']
},
'io-base': {
requires: ['moodle-core-io'],
}
}
});
};
// Start this asap.
M.util.js_pending('init');
M.util.js_watch_io();
/**
* Unregister any long running javascript code by unique identifier.
* This function should form a matching pair with js_pending
*
* @param String uniqid - required, unregisters this identifier
* @return boolean - True if there is any pending js.
*/
M.util.js_complete = function(uniqid) {
var index = M.util.pending_js.indexOf(uniqid);
if (index >= 0) {
M.util.complete_js.push(M.util.pending_js.splice(index, 1));
}
return M.util.pending_js.length;
};
/**
* Returns a string registered in advance for usage in JavaScript
*
+9 -3
View File
@@ -1043,14 +1043,18 @@ class page_requirements_manager {
public function js_init_code($jscode, $ondomready = false, array $module = null) {
$jscode = trim($jscode, " ;\n"). ';';
$uniqid = html_writer::random_id();
$startjs = " M.util.js_pending('" . $uniqid . "');";
$endjs = " M.util.js_complete('" . $uniqid . "');";
if ($module) {
$this->js_module($module);
$modulename = $module['name'];
$jscode = "Y.use('$modulename', function(Y) { $jscode });";
$jscode = "$startjs Y.use('$modulename', function(Y) { $jscode $endjs });";
}
if ($ondomready) {
$jscode = "Y.on('domready', function() { $jscode });";
$jscode = "$startjs Y.on('domready', function() { $jscode $endjs });";
}
$this->jsinitcode[] = $jscode;
@@ -1216,7 +1220,7 @@ class page_requirements_manager {
$output .= js_writer::function_call($data[0], $data[1], $data[2]);
}
if (!empty($ondomready)) {
$output = " Y.on('domready', function() {\n$output\n });";
$output = " Y.on('domready', function() {\n$output\n});";
}
}
return $output;
@@ -1463,6 +1467,8 @@ class page_requirements_manager {
// Add other requested modules.
$output = $this->get_extra_modules_code();
$this->js_init_code('M.util.js_complete("init");', true);
// All the other linked scripts - there should be as few as possible.
if ($this->jsincludes['footer']) {
foreach ($this->jsincludes['footer'] as $url) {
+25 -1
View File
@@ -60,6 +60,30 @@ class behat_forms extends behat_base {
$buttonnode->press();
}
/**
* Try a few times to set a field value as it may not be visible yet (TinyMCE).
*
* @param string $field
* @param string $value
*/
public function set_field_value($field, $value) {
$lastexception = null;
// Spin on this - certain fields, e.g. text editors (I'm looking at you TinyMCE) load slowly and randomly.
$retries = 5;
while ($retries > 0) {
try {
$field->set_value($value);
return;
} catch (Exception $e) {
usleep(100000);
$retries--;
$lastexception = $e;
}
}
// If we timeout - throw the last exception.
throw $lastexception;
}
/**
* Fills a moodle form with field/value data.
*
@@ -84,7 +108,7 @@ class behat_forms extends behat_base {
$field = behat_field_manager::get_form_field($fieldnode, $this->getSession());
// Delegates to the field class.
$field->set_value($value);
$this->set_field_value($field, $value);
}
}
+20 -30
View File
@@ -222,42 +222,32 @@ class behat_hooks extends behat_base {
}
/**
* Checks that all DOM is ready.
* Wait for JS to comlete.
*
* Executed only when running against a real browser.
*
* @AfterStep @javascript
* @BeforeStep @javascript
*/
public function after_step_javascript($event) {
// If it doesn't have definition or it fails there is no need to check it.
if ($event->getResult() != StepEvent::PASSED ||
!$event->hasDefinition()) {
return;
}
// 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")');
public function before_step_javascript($event) {
$lastpending = '';
// Wait for all pending JS to complete (max 10 seconds).
for ($i = 0; $i < 100; $i++) {
$pending = '';
try {
$pending = ($this->getSession()->evaluateScript('return (M && M.util && M.util.pending_js) ? M.util.pending_js.join(":") : "not loaded";'));
} catch (NoSuchWindow $nsw) {
// No javascript is running if there is no window right?
$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 ($pending === '') {
return;
}
$lastpending = $pending;
// 0.1 seconds.
usleep(100000);
}
// Timeout waiting for JS to complete.
// We could throw an exception here - as this is a likely indicator of slow JS or JS errors.
}
/**