Commit 05a5d547 authored by sam marshall's avatar sam marshall
Browse files

MDL-64979 Behat: Add option to increase timeouts

parent a713ed3b
...@@ -63,7 +63,7 @@ class behat_admin extends behat_base { ...@@ -63,7 +63,7 @@ class behat_admin extends behat_base {
$submitsearch = $this->find('css', 'form input[type=submit][name=search]'); $submitsearch = $this->find('css', 'form input[type=submit][name=search]');
$submitsearch->press(); $submitsearch->press();
$this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); $this->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
// Admin settings does not use the same DOM structure than other moodle forms // Admin settings does not use the same DOM structure than other moodle forms
// but we also need to use lib/behat/form_field/* to deal with the different moodle form elements. // but we also need to use lib/behat/form_field/* to deal with the different moodle form elements.
......
...@@ -813,6 +813,12 @@ $CFG->admin = 'admin'; ...@@ -813,6 +813,12 @@ $CFG->admin = 'admin';
// Example: // Example:
// $CFG->behat_usedeprecated = true; // $CFG->behat_usedeprecated = true;
// //
// If you are using a slow machine, it may help to increase the timeouts that Behat uses. The
// following example will increase timeouts by a factor of 3 (using 30 seconds instead of 10
// seconds, for instance).
// Example:
// $CFG->behat_increasetimeout = 3;
//
// Including feature files from directories outside the dirroot is possible if required. The setting // Including feature files from directories outside the dirroot is possible if required. The setting
// requires that the running user has executable permissions on all parent directories in the paths. // requires that the running user has executable permissions on all parent directories in the paths.
// Example: // Example:
......
...@@ -348,7 +348,7 @@ class behat_course extends behat_base { ...@@ -348,7 +348,7 @@ class behat_course extends behat_base {
$showlink->click(); $showlink->click();
if ($this->running_javascript()) { if ($this->running_javascript()) {
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); $this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
$this->i_wait_until_section_is_available($sectionnumber); $this->i_wait_until_section_is_available($sectionnumber);
} }
} }
...@@ -382,7 +382,7 @@ class behat_course extends behat_base { ...@@ -382,7 +382,7 @@ class behat_course extends behat_base {
); );
if ($this->running_javascript()) { if ($this->running_javascript()) {
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); $this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
$this->i_wait_until_section_is_available($sectionnumber); $this->i_wait_until_section_is_available($sectionnumber);
} }
} }
......
...@@ -67,13 +67,13 @@ class behat_groups extends behat_base { ...@@ -67,13 +67,13 @@ class behat_groups extends behat_base {
$script = "Syn.trigger('change', {}, {{ELEMENT}})"; $script = "Syn.trigger('change', {}, {{ELEMENT}})";
$driver->triggerSynScript($select->getXpath(), $script); $driver->triggerSynScript($select->getXpath(), $script);
} }
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); $this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
// Here we don't need to wait for the AJAX response. // Here we don't need to wait for the AJAX response.
$this->find_button(get_string('adduserstogroup', 'group'))->click(); $this->find_button(get_string('adduserstogroup', 'group'))->click();
// Wait for add/remove members page to be loaded. // Wait for add/remove members page to be loaded.
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); $this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
// Getting the option and selecting it. // Getting the option and selecting it.
$select = $this->find_field('addselect'); $select = $this->find_field('addselect');
...@@ -86,7 +86,7 @@ class behat_groups extends behat_base { ...@@ -86,7 +86,7 @@ class behat_groups extends behat_base {
$this->find_button(get_string('add'))->click(); $this->find_button(get_string('add'))->click();
// Wait for the page to load. // Wait for the page to load.
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); $this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
// Returning to the main groups page. // Returning to the main groups page.
$this->find_button(get_string('backtogroups', 'group'))->click(); $this->find_button(get_string('backtogroups', 'group'))->click();
......
...@@ -60,16 +60,28 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { ...@@ -60,16 +60,28 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
* A reduced timeout for cases where self::TIMEOUT is too much * A reduced timeout for cases where self::TIMEOUT is too much
* and a simple $this->getSession()->getPage()->find() could not * and a simple $this->getSession()->getPage()->find() could not
* be enough. * be enough.
*
* @deprecated since Moodle 3.7 MDL-64979 - please use get_reduced_timeout() instead
* @todo MDL-64982 This will be deleted in Moodle 4.1
* @see behat_base::get_reduced_timeout()
*/ */
const REDUCED_TIMEOUT = 2; const REDUCED_TIMEOUT = 2;
/** /**
* The timeout for each Behat step (load page, wait for an element to load...). * The timeout for each Behat step (load page, wait for an element to load...).
*
* @deprecated since Moodle 3.7 MDL-64979 - please use get_timeout() instead
* @todo MDL-64982 This will be deleted in Moodle 4.1
* @see behat_base::get_timeout()
*/ */
const TIMEOUT = 6; const TIMEOUT = 6;
/** /**
* And extended timeout for specific cases. * And extended timeout for specific cases.
*
* @deprecated since Moodle 3.7 MDL-64979 - please use get_extended_timeout() instead
* @todo MDL-64982 This will be deleted in Moodle 4.1
* @see behat_base::get_extended_timeout()
*/ */
const EXTENDED_TIMEOUT = 10; const EXTENDED_TIMEOUT = 10;
...@@ -167,7 +179,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { ...@@ -167,7 +179,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
// How much we will be waiting for the element to appear. // How much we will be waiting for the element to appear.
if (!$timeout) { if (!$timeout) {
$timeout = self::TIMEOUT; $timeout = self::get_timeout();
$microsleep = false; $microsleep = false;
} else { } else {
// Spinning each 0.1 seconds if the timeout was forced as we understand // Spinning each 0.1 seconds if the timeout was forced as we understand
...@@ -308,13 +320,13 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { ...@@ -308,13 +320,13 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
// Using default timeout which is pretty high. // Using default timeout which is pretty high.
if (!$timeout) { if (!$timeout) {
$timeout = self::TIMEOUT; $timeout = self::get_timeout();
} }
if ($microsleep) { if ($microsleep) {
// Will sleep 1/10th of a second by default for self::TIMEOUT seconds. // Will sleep 1/10th of a second by default for self::get_timeout() seconds.
$loops = $timeout * 10; $loops = $timeout * 10;
} else { } else {
// Will sleep for self::TIMEOUT seconds. // Will sleep for self::get_timeout() seconds.
$loops = $timeout; $loops = $timeout;
} }
...@@ -516,7 +528,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { ...@@ -516,7 +528,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
return false; return false;
}, },
array('selector' => $selector, 'locator' => $locator), array('selector' => $selector, 'locator' => $locator),
self::EXTENDED_TIMEOUT, self::get_extended_timeout(),
$exception, $exception,
true true
); );
...@@ -550,7 +562,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { ...@@ -550,7 +562,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
return false; return false;
}, },
array('selector' => $selector, 'locator' => $locator), array('selector' => $selector, 'locator' => $locator),
self::EXTENDED_TIMEOUT, self::get_extended_timeout(),
$exception, $exception,
true true
); );
...@@ -582,7 +594,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { ...@@ -582,7 +594,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
return false; return false;
}, },
$node, $node,
self::EXTENDED_TIMEOUT, self::get_extended_timeout(),
$exception, $exception,
true true
); );
...@@ -617,7 +629,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { ...@@ -617,7 +629,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
return false; return false;
}, },
array($node, $attribute, $attributevalue), array($node, $attribute, $attributevalue),
self::EXTENDED_TIMEOUT, self::get_extended_timeout(),
$exception, $exception,
true true
); );
...@@ -755,7 +767,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { ...@@ -755,7 +767,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
public static function wait_for_pending_js_in_session(Session $session) { public static function wait_for_pending_js_in_session(Session $session) {
// We don't use behat_base::spin() here as we don't want to end up with an exception // 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. // if the page & JSs don't finish loading properly.
for ($i = 0; $i < self::EXTENDED_TIMEOUT * 10; $i++) { for ($i = 0; $i < self::get_extended_timeout() * 10; $i++) {
$pending = ''; $pending = '';
try { try {
$jscode = trim(preg_replace('/\s+/', ' ', ' $jscode = trim(preg_replace('/\s+/', ' ', '
...@@ -796,11 +808,13 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { ...@@ -796,11 +808,13 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
} }
// Timeout waiting for JS to complete. It will be caught and forwarded to behat_hooks::i_look_for_exceptions(). // Timeout waiting for JS to complete. It will be caught 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 // It is unlikely that Javascript code of a page or an AJAX request needs more than get_extended_timeout() seconds
// to be loaded, although when pages contains Javascript errors M.util.js_complete() can not be executed, so the // 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. // 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 . throw new \Exception('Javascript code and/or AJAX requests are not ready after ' .
' seconds. There is a Javascript error or the code is extremely slow.'); self::get_extended_timeout() .
' seconds. There is a Javascript error or the code is extremely slow. ' .
'If you are using a slow machine, consider setting $CFG->behat_increasetimeout.');
} }
/** /**
...@@ -1029,4 +1043,54 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { ...@@ -1029,4 +1043,54 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
$driver->click($xpath); $driver->click($xpath);
} }
} }
/**
* Gets the required timeout in seconds.
*
* @param int $timeout One of the TIMEOUT constants
* @return int Actual timeout (in seconds)
*/
protected static function get_real_timeout(int $timeout) : int {
global $CFG;
if (!empty($CFG->behat_increasetimeout)) {
return $timeout * $CFG->behat_increasetimeout;
} else {
return $timeout;
}
}
/**
* Gets the default timeout.
*
* The timeout for each Behat step (load page, wait for an element to load...).
*
* @return int Timeout in seconds
*/
public static function get_timeout() : int {
return self::get_real_timeout(6);
}
/**
* Gets the reduced timeout.
*
* A reduced timeout for cases where self::get_timeout() is too much
* and a simple $this->getSession()->getPage()->find() could not
* be enough.
*
* @return int Timeout in seconds
*/
public static function get_reduced_timeout() : int {
return self::get_real_timeout(2);
}
/**
* Gets the extended timeout.
*
* A longer timeout for cases where the normal timeout is not enough.
*
* @return int Timeout in seconds
*/
public static function get_extended_timeout() : int {
return self::get_real_timeout(10);
}
} }
...@@ -62,7 +62,7 @@ class behat_form_filemanager extends behat_form_field { ...@@ -62,7 +62,7 @@ class behat_form_filemanager extends behat_form_field {
public function get_value() { public function get_value() {
// Wait until DOM and JS is ready. // Wait until DOM and JS is ready.
$this->session->wait(behat_base::TIMEOUT, behat_base::PAGE_READY_JS); $this->session->wait(behat_base::get_timeout(), behat_base::PAGE_READY_JS);
// Get the label to restrict the files to this single form field. // Get the label to restrict the files to this single form field.
$fieldlabel = $this->get_field_locator(); $fieldlabel = $this->get_field_locator();
......
...@@ -67,7 +67,7 @@ JS; ...@@ -67,7 +67,7 @@ JS;
$this->field->keyDown(13); $this->field->keyDown(13);
$this->field->keyPress(13); $this->field->keyPress(13);
$this->field->keyUp(13); $this->field->keyUp(13);
$this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS); $this->session->wait(behat_base::get_timeout() * 1000, behat_base::PAGE_READY_JS);
} }
} }
} }
...@@ -94,7 +94,7 @@ class behat_form_select extends behat_form_field { ...@@ -94,7 +94,7 @@ class behat_form_select extends behat_form_field {
} }
} }
} }
$this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS); $this->session->wait(behat_base::get_timeout() * 1000, behat_base::PAGE_READY_JS);
} }
} }
......
...@@ -307,7 +307,7 @@ class behat_app extends behat_base { ...@@ -307,7 +307,7 @@ class behat_app extends behat_base {
return 'mainpage'; return 'mainpage';
} }
throw new DriverException('Moodle app login URL prompt not found'); throw new DriverException('Moodle app login URL prompt not found');
}, self::EXTENDED_TIMEOUT, 30); }, behat_base::get_extended_timeout(), 30);
// If it's the login page, we automatically fill in the URL and leave it on the user/pass // If it's the login page, we automatically fill in the URL and leave it on the user/pass
// page. If it's the main page, we just leave it there. // page. If it's the main page, we just leave it there.
......
...@@ -126,7 +126,7 @@ class behat_forms extends behat_base { ...@@ -126,7 +126,7 @@ class behat_forms extends behat_base {
"//a[contains(concat(' ', @class, ' '), ' fheader ') and @aria-expanded = 'false']"; "//a[contains(concat(' ', @class, ' '), ' fheader ') and @aria-expanded = 'false']";
$collapseexpandlink = $this->find('xpath', $expandallxpath . '|' . $expandonlysection, $collapseexpandlink = $this->find('xpath', $expandallxpath . '|' . $expandonlysection,
false, false, self::REDUCED_TIMEOUT); false, false, behat_base::get_reduced_timeout());
$collapseexpandlink->click(); $collapseexpandlink->click();
} catch (ElementNotFoundException $e) { } catch (ElementNotFoundException $e) {
......
...@@ -174,7 +174,7 @@ class behat_general extends behat_base { ...@@ -174,7 +174,7 @@ class behat_general extends behat_base {
return true; return true;
}, },
$iframename, $iframename,
self::EXTENDED_TIMEOUT behat_base::get_extended_timeout()
); );
} }
...@@ -271,7 +271,7 @@ class behat_general extends behat_base { ...@@ -271,7 +271,7 @@ class behat_general extends behat_base {
return; return;
} }
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); $this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
} }
/** /**
...@@ -604,10 +604,10 @@ class behat_general extends behat_base { ...@@ -604,10 +604,10 @@ class behat_general extends behat_base {
"[count(descendant::*[contains(., $xpathliteral)]) = 0]"; "[count(descendant::*[contains(., $xpathliteral)]) = 0]";
// We should wait a while to ensure that the page is not still loading elements. // We should wait a while to ensure that the page is not still loading elements.
// Waiting less than self::TIMEOUT as we already waited for the DOM to be ready and // Waiting less than self::get_timeout() as we already waited for the DOM to be ready and
// all JS to be executed. // all JS to be executed.
try { try {
$nodes = $this->find_all('xpath', $xpath, false, false, self::REDUCED_TIMEOUT); $nodes = $this->find_all('xpath', $xpath, false, false, self::get_reduced_timeout());
} catch (ElementNotFoundException $e) { } catch (ElementNotFoundException $e) {
// All ok. // All ok.
return; return;
...@@ -644,7 +644,7 @@ class behat_general extends behat_base { ...@@ -644,7 +644,7 @@ class behat_general extends behat_base {
return true; return true;
}, },
array('nodes' => $nodes, 'text' => $text), array('nodes' => $nodes, 'text' => $text),
self::REDUCED_TIMEOUT, behat_base::get_reduced_timeout(),
false, false,
true true
); );
...@@ -728,7 +728,7 @@ class behat_general extends behat_base { ...@@ -728,7 +728,7 @@ class behat_general extends behat_base {
// We should wait a while to ensure that the page is not still loading elements. // 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. // Giving preference to the reliability of the results rather than to the performance.
try { try {
$nodes = $this->find_all('xpath', $xpath, false, $container, self::REDUCED_TIMEOUT); $nodes = $this->find_all('xpath', $xpath, false, $container, self::get_reduced_timeout());
} catch (ElementNotFoundException $e) { } catch (ElementNotFoundException $e) {
// All ok. // All ok.
return; return;
...@@ -754,7 +754,7 @@ class behat_general extends behat_base { ...@@ -754,7 +754,7 @@ class behat_general extends behat_base {
return true; return true;
}, },
array('nodes' => $nodes, 'text' => $text, 'element' => $element), array('nodes' => $nodes, 'text' => $text, 'element' => $element),
self::REDUCED_TIMEOUT, behat_base::get_reduced_timeout(),
false, false,
true true
); );
...@@ -932,7 +932,7 @@ class behat_general extends behat_base { ...@@ -932,7 +932,7 @@ class behat_general extends behat_base {
return $context->getSession()->getPage()->findAll($args['selector'], $args['locator']); return $context->getSession()->getPage()->findAll($args['selector'], $args['locator']);
}, },
$params, $params,
self::REDUCED_TIMEOUT, behat_base::get_reduced_timeout(),
$exception, $exception,
false false
); );
...@@ -1124,7 +1124,7 @@ class behat_general extends behat_base { ...@@ -1124,7 +1124,7 @@ class behat_general extends behat_base {
// Would be better to use a 1 second sleep because the element should not be there, // 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 // 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. // changing to 1 second sleep is not significant.
$this->find($selector, $locator, false, $containernode, self::REDUCED_TIMEOUT); $this->find($selector, $locator, false, $containernode, behat_base::get_reduced_timeout());
} catch (ElementNotFoundException $e) { } catch (ElementNotFoundException $e) {
// It passes. // It passes.
return; return;
...@@ -1390,7 +1390,7 @@ class behat_general extends behat_base { ...@@ -1390,7 +1390,7 @@ class behat_general extends behat_base {
return $this->download_file_from_link($link); return $this->download_file_from_link($link);
}, },
array('link' => $link), array('link' => $link),
self::EXTENDED_TIMEOUT, behat_base::get_extended_timeout(),
$exception $exception
); );
...@@ -1433,7 +1433,7 @@ class behat_general extends behat_base { ...@@ -1433,7 +1433,7 @@ class behat_general extends behat_base {
return $this->download_file_from_link($link); return $this->download_file_from_link($link);
}, },
array('link' => $link), array('link' => $link),
self::EXTENDED_TIMEOUT, behat_base::get_extended_timeout(),
$exception $exception
); );
......
...@@ -523,13 +523,13 @@ class behat_hooks extends behat_base { ...@@ -523,13 +523,13 @@ class behat_hooks extends behat_base {
* @AfterScenario @_switch_window * @AfterScenario @_switch_window
*/ */
public function after_scenario_switchwindow(AfterScenarioScope $scope) { public function after_scenario_switchwindow(AfterScenarioScope $scope) {
for ($count = 0; $count < self::EXTENDED_TIMEOUT; $count++) { for ($count = 0; $count < behat_base::get_extended_timeout(); $count++) {
try { try {
$this->getSession()->restart(); $this->getSession()->restart();
break; break;
} catch (DriverException $e) { } catch (DriverException $e) {
// Wait for timeout and try again. // Wait for timeout and try again.
sleep(self::TIMEOUT); sleep(self::get_timeout());
} }
} }
// If session is not restarted above then it will try to start session before next scenario // If session is not restarted above then it will try to start session before next scenario
......
...@@ -275,7 +275,7 @@ class behat_navigation extends behat_base { ...@@ -275,7 +275,7 @@ class behat_navigation extends behat_base {
$jscondition = '(document.evaluate("' . $pnode->getXpath() . '", document, null, '. $jscondition = '(document.evaluate("' . $pnode->getXpath() . '", document, null, '.
'XPathResult.ANY_TYPE, null).iterateNext().getAttribute(\'data-loaded\') == "true")'; 'XPathResult.ANY_TYPE, null).iterateNext().getAttribute(\'data-loaded\') == "true")';
$this->getSession()->wait(self::EXTENDED_TIMEOUT * 1000, $jscondition); $this->getSession()->wait(behat_base::get_extended_timeout() * 1000, $jscondition);
} }
} }
} }
......
...@@ -105,7 +105,7 @@ class behat_permissions extends behat_base { ...@@ -105,7 +105,7 @@ class behat_permissions extends behat_base {
$advancedtoggle->click(); $advancedtoggle->click();
// Wait for the page to load. // Wait for the page to load.
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); $this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
} }
} catch (Exception $e) { } catch (Exception $e) {
// We already are in advanced mode. // We already are in advanced mode.
......
...@@ -15,6 +15,9 @@ attribute on forms to avoid collisions in forms loaded in AJAX requests. ...@@ -15,6 +15,9 @@ attribute on forms to avoid collisions in forms loaded in AJAX requests.
It is recommended that privacy providers using this function call rewrite any long query into a number of separate It is recommended that privacy providers using this function call rewrite any long query into a number of separate
calls to add_from_sql for improved performance, and that the new argument is used. calls to add_from_sql for improved performance, and that the new argument is used.
This will allow queries to remain backwards-compatible with older versions of Moodle but will have significantly better performance in version supporting the innerjoin parameter. This will allow queries to remain backwards-compatible with older versions of Moodle but will have significantly better performance in version supporting the innerjoin parameter.
* Behat timeout constants behat_base::TIMEOUT, EXTENDED_TIMEOUT, and REDUCED_TIMEOUT have been
deprecated. Please instead use the functions behat_base::get_timeout(), get_extended_timeout(),
and get_reduced_timeout(). These allow for timeouts to be increased by a setting in config.php.
=== 3.6 === === 3.6 ===
......
...@@ -100,6 +100,6 @@ class behat_assignfeedback_editpdf extends behat_base { ...@@ -100,6 +100,6 @@ class behat_assignfeedback_editpdf extends behat_base {
]; ];
$js = implode(' && ', $conditions); $js = implode(' && ', $conditions);