MOBILE-4061 behat: Treat async calls

main
Pau Ferrer Ocaña 2022-06-14 14:09:26 +02:00
parent 9ce31948ad
commit ca87b084d2
4 changed files with 80 additions and 81 deletions

View File

@ -94,7 +94,7 @@ class behat_app extends behat_app_helper {
public function i_wait_the_app_to_restart() {
// Wait window to reload.
$this->spin(function() {
$result = $this->evaluate_script("return !window.behat;");
$result = $this->js("return !window.behat;");
if (!$result) {
throw new DriverException('Window is not reloading properly.');
@ -121,7 +121,7 @@ class behat_app extends behat_app_helper {
$containerName = json_encode($containerName);
$this->spin(function() use ($not, $locator, $containerName) {
$result = $this->evaluate_script("return window.behat.find($locator, $containerName);");
$result = $this->js("return window.behat.find($locator, $containerName);");
if ($not && $result === 'OK') {
throw new DriverException('Error, found an item that should not be found');
@ -147,7 +147,7 @@ class behat_app extends behat_app_helper {
$locator = $this->parse_element_locator($locator);
$this->spin(function() use ($locator) {
$result = $this->evaluate_script("return window.behat.scrollTo($locator);");
$result = $this->js("return window.behat.scrollTo($locator);");
if ($result !== 'OK') {
throw new DriverException('Error finding item - ' . $result);
@ -170,7 +170,7 @@ class behat_app extends behat_app_helper {
*/
public function i_load_more_items_in_the_app(bool $not = false) {
$this->spin(function() use ($not) {
$result = $this->evaluate_async_script('return window.behat.loadMoreItems();');
$result = $this->js('return await window.behat.loadMoreItems();');
if ($not && $result !== 'ERROR: All items are already loaded.') {
throw new DriverException('It should not have been possible to load more items');
@ -195,7 +195,7 @@ class behat_app extends behat_app_helper {
public function i_swipe_in_the_app(string $direction) {
$method = 'swipe' . ucwords($direction);
$this->evaluate_script("window.behat.getAngularInstance('ion-content', 'CoreSwipeNavigationDirective').$method()");
$this->js("window.behat.getAngularInstance('ion-content', 'CoreSwipeNavigationDirective').$method()");
$this->wait_for_pending_js();
@ -214,7 +214,7 @@ class behat_app extends behat_app_helper {
$locator = $this->parse_element_locator($locator);
$this->spin(function() use ($locator, $not) {
$result = $this->evaluate_script("return window.behat.isSelected($locator);");
$result = $this->js("return window.behat.isSelected($locator);");
switch ($result) {
case 'YES':
@ -318,7 +318,7 @@ class behat_app extends behat_app_helper {
$this->login($username);
}
$mycoursesfound = $this->evaluate_script("return window.behat.find({ text: 'My courses', selector: 'ion-tab-button'});");
$mycoursesfound = $this->js("return window.behat.find({ text: 'My courses', selector: 'ion-tab-button'});");
if ($mycoursesfound !== 'OK') {
// My courses not present enter from Dashboard.
@ -370,7 +370,7 @@ class behat_app extends behat_app_helper {
*/
public function i_press_the_standard_button_in_the_app(string $button) {
$this->spin(function() use ($button) {
$result = $this->evaluate_script("return window.behat.pressStandard('$button');");
$result = $this->js("return await window.behat.pressStandard('$button');");
if ($result !== 'OK') {
throw new DriverException('Error pressing standard button - ' . $result);
@ -408,7 +408,7 @@ class behat_app extends behat_app_helper {
],
]);
$this->evaluate_script("window.behat.notificationClicked($notification)");
$this->js("window.behat.notificationClicked($notification)");
$this->wait_for_pending_js();
}
@ -494,7 +494,7 @@ class behat_app extends behat_app_helper {
*/
public function i_close_the_popup_in_the_app() {
$this->spin(function() {
$result = $this->evaluate_script("return window.behat.closePopup();");
$result = $this->js("return window.behat.closePopup();");
if ($result !== 'OK') {
throw new DriverException('Error closing popup - ' . $result);
@ -532,7 +532,7 @@ class behat_app extends behat_app_helper {
$locator = $this->parse_element_locator($locator);
$this->spin(function() use ($locator) {
$result = $this->evaluate_script("return window.behat.press($locator);");
$result = $this->js("return await window.behat.press($locator);");
if ($result !== 'OK') {
throw new DriverException('Error pressing item - ' . $result);
@ -562,14 +562,14 @@ class behat_app extends behat_app_helper {
$this->spin(function() use ($selectedtext, $selected, $locator) {
// Don't do anything if the item is already in the expected state.
$result = $this->evaluate_script("return window.behat.isSelected($locator);");
$result = $this->js("return window.behat.isSelected($locator);");
if ($result === $selected) {
return true;
}
// Press item.
$result = $this->evaluate_script("return window.behat.press($locator);");
$result = $this->js("return await window.behat.press($locator);");
if ($result !== 'OK') {
throw new DriverException('Error pressing item - ' . $result);
@ -578,7 +578,7 @@ class behat_app extends behat_app_helper {
// Check that it worked as expected.
$this->wait_for_pending_js();
$result = $this->evaluate_script("return window.behat.isSelected($locator);");
$result = $this->js("return window.behat.isSelected($locator);");
switch ($result) {
case 'YES':
@ -612,7 +612,7 @@ class behat_app extends behat_app_helper {
$value = addslashes_js($value);
$this->spin(function() use ($field, $value) {
$result = $this->evaluate_script("return window.behat.setField(\"$field\", \"$value\");");
$result = $this->js("return await window.behat.setField(\"$field\", \"$value\");");
if ($result !== 'OK') {
throw new DriverException('Error setting field - ' . $result);
@ -651,7 +651,7 @@ class behat_app extends behat_app_helper {
*/
public function the_header_should_be_in_the_app(string $text) {
$this->spin(function() use ($text) {
$result = $this->evaluate_script('return window.behat.getHeader();');
$result = $this->js('return window.behat.getHeader();');
if (substr($result, 0, 3) !== 'OK:') {
throw new DriverException('Error getting header - ' . $result);
@ -732,7 +732,7 @@ class behat_app extends behat_app_helper {
* @When I run cron tasks in the app
*/
public function i_run_cron_tasks_in_the_app() {
$this->evaluate_script('window.behat.forceSyncExecution()');
$this->js('await window.behat.forceSyncExecution()');
$this->wait_for_pending_js();
}
@ -742,7 +742,7 @@ class behat_app extends behat_app_helper {
* @When I wait loading to finish in the app
*/
public function i_wait_loading_to_finish_in_the_app() {
$this->evaluate_script('window.behat.waitLoadingToFinish()');
$this->js('await window.behat.waitLoadingToFinish()');
$this->wait_for_pending_js();
}
@ -764,7 +764,7 @@ class behat_app extends behat_app_helper {
$this->getSession()->switchToWindow($names[1]);
}
$this->evaluate_script('window.close();');
$this->js('window.close();');
$this->getSession()->switchToWindow($names[0]);
}
@ -776,7 +776,7 @@ class behat_app extends behat_app_helper {
* @throws DriverException If the navigator.online mode is not available
*/
public function i_switch_offline_mode(string $offline) {
$this->evaluate_script("window.behat.network.setForceOffline($offline);");
$this->js("window.behat.network.setForceOffline($offline);");
}
}

View File

@ -318,7 +318,7 @@ class behat_app_helper extends behat_base {
$initOptions->skipOnBoarding = $options['skiponboarding'] ?? true;
$initOptions->configOverrides = $this->appconfig;
$this->evaluate_script('window.behatInit(' . json_encode($initOptions) . ');');
$this->js('window.behatInit(' . json_encode($initOptions) . ');');
} catch (Exception $error) {
throw new DriverException('Moodle App not running or not running on Automated mode.');
}
@ -434,19 +434,27 @@ class behat_app_helper extends behat_base {
}
/**
* Evaluate a script that returns a Promise.
* Evaluate and execute scripts checking for promises if needed.
*
* @param string $script
* @return mixed Resolved promise result.
*/
protected function evaluate_async_script(string $script) {
$script = preg_replace('/^return\s+/', '', $script);
$script = preg_replace('/;$/', '', $script);
protected function js(string $script) {
$scriptnoreturn = preg_replace('/^return\s+/', '', $script);
$scriptnoreturn = preg_replace('/;$/', '', $scriptnoreturn);
if (!preg_match('/^await\s+/', $scriptnoreturn)) {
// No async.
return $this->evaluate_script($script);
}
$script = preg_replace('/^await\s+/', '', $scriptnoreturn);
$start = microtime(true);
$promisevariable = 'PROMISE_RESULT_' . time();
$timeout = self::get_timeout();
$timeout = self::get_extended_timeout();
$this->evaluate_script("Promise.resolve($script)
$res = $this->evaluate_script("Promise.resolve($script)
.then(result => window.$promisevariable = result)
.catch(error => window.$promisevariable = 'Async code rejected: ' + error?.message);");
@ -455,6 +463,7 @@ class behat_app_helper extends behat_base {
throw new DriverException("Async script not resolved after $timeout seconds");
}
// 0.1 seconds.
usleep(100000);
} while (!$this->evaluate_script("return '$promisevariable' in window;"));
@ -514,7 +523,7 @@ class behat_app_helper extends behat_base {
$successXPath = '//page-core-mainmenu';
}
$this->handle_url_and_wait_page_to_load($url, $successXPath);
$this->handle_url($url, $successXPath);
}
/**
@ -529,7 +538,7 @@ class behat_app_helper extends behat_base {
$urlscheme = $this->get_mobile_url_scheme();
$url = "$urlscheme://link=" . urlencode($CFG->behat_wwwroot.$path);
$this->handle_url_and_wait_page_to_load($url);
$this->handle_url($url);
}
/**
@ -538,11 +547,13 @@ class behat_app_helper extends behat_base {
* @param string $customurl To navigate.
* @param string $successXPath The XPath of the element to lookat after navigation.
*/
protected function handle_url_and_wait_page_to_load(string $customurl, string $successXPath = '') {
protected function handle_url(string $customurl, string $successXPath = '') {
// Instead of using evaluate_async_script, we wait for the path to load.
$this->evaluate_script("return window.behat.handleCustomURL('$customurl')");
$result = $this->js("return await window.behat.handleCustomURL('$customurl');");
$this->wait_for_pending_js();
if ($result !== 'OK') {
throw new DriverException('Error handling url - ' . $result);
}
if (!empty($successXPath)) {
// Wait until the page appears.
@ -554,10 +565,9 @@ class behat_app_helper extends behat_base {
}
throw new DriverException('Moodle App custom URL page not loaded');
}, false, 30);
// Wait for JS to finish as well.
$this->wait_for_pending_js();
}
$this->wait_for_pending_js();
}
/**

View File

@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { CorePromisedValue } from '@classes/promised-value';
import { CoreUtils } from '@services/utils/utils';
import { NgZone } from '@singletons';
import { TestsBehatBlocking } from './behat-blocking';
import { TestBehatElementLocator } from './behat-runtime';
// Containers that block containers behind them.
@ -447,21 +447,23 @@ export class TestsBehatDomUtils {
element.scrollIntoView(false);
return new Promise<DOMRect>((resolve): void => {
requestAnimationFrame(() => {
const rect = element.getBoundingClientRect();
const promise = new CorePromisedValue<DOMRect>();
if (initialRect.y !== rect.y) {
setTimeout(() => {
resolve(rect);
}, 300);
requestAnimationFrame(() => {
const rect = element.getBoundingClientRect();
return;
}
if (initialRect.y !== rect.y) {
setTimeout(() => {
promise.resolve(rect);
}, 300);
resolve(rect);
});
return;
}
promise.resolve(rect);
});
return promise;
};
/**
@ -471,7 +473,7 @@ export class TestsBehatDomUtils {
*/
static async pressElement(element: HTMLElement): Promise<void> {
await NgZone.run(async () => {
const blockKey = TestsBehatBlocking.block();
const promise = new CorePromisedValue<void>();
// Events don't bubble up across Shadow DOM boundaries, and some buttons
// may not work without doing this.
@ -501,8 +503,10 @@ export class TestsBehatDomUtils {
element.dispatchEvent(new MouseEvent('mouseup', eventOptions));
element.click();
TestsBehatBlocking.unblock(blockKey);
promise.resolve();
}, 300);
return promise;
});
}
@ -514,7 +518,7 @@ export class TestsBehatDomUtils {
*/
static async setElementValue(element: HTMLInputElement | HTMLElement, value: string): Promise<void> {
await NgZone.run(async () => {
const blockKey = TestsBehatBlocking.block();
const promise = new CorePromisedValue<void>();
// Functions to get/set value depending on field type.
const setValue = (text: string) => {
@ -569,7 +573,9 @@ export class TestsBehatDomUtils {
element.dispatchEvent(event);
}
TestsBehatBlocking.unblock(blockKey);
promise.resolve();
return promise;
});
}

View File

@ -83,8 +83,6 @@ export class TestsBehatRuntime {
* @return OK if successful, or ERROR: followed by message.
*/
static async handleCustomURL(url: string): Promise<string> {
const blockKey = TestsBehatBlocking.block();
try {
await NgZone.run(async () => {
await CoreCustomURLSchemes.handleCustomURL(url);
@ -93,8 +91,6 @@ export class TestsBehatRuntime {
return 'OK';
} catch (error) {
return 'ERROR: ' + error.message;
} finally {
TestsBehatBlocking.unblock(blockKey);
}
}
@ -123,15 +119,9 @@ export class TestsBehatRuntime {
* @return Promise resolved if all handlers are executed successfully, rejected otherwise.
*/
static async forceSyncExecution(): Promise<void> {
const blockKey = TestsBehatBlocking.block();
try {
await NgZone.run(async () => {
await CoreCronDelegate.forceSyncExecution();
});
} finally {
TestsBehatBlocking.unblock(blockKey);
}
await NgZone.run(async () => {
await CoreCronDelegate.forceSyncExecution();
});
}
/**
@ -140,20 +130,13 @@ export class TestsBehatRuntime {
* @return Promise resolved when all components have been rendered.
*/
static async waitLoadingToFinish(): Promise<void> {
const blockKey = TestsBehatBlocking.block();
await NgZone.run(async () => {
try {
const elements = Array.from(document.body.querySelectorAll<HTMLElement>('core-loading'))
.filter((element) => CoreDom.isElementVisible(element));
const elements = Array.from(document.body.querySelectorAll<HTMLElement>('core-loading'))
.filter((element) => CoreDom.isElementVisible(element));
await Promise.all(elements.map(element =>
CoreComponentsRegistry.waitComponentReady(element, CoreLoadingComponent)));
} finally {
TestsBehatBlocking.unblock(blockKey);
}
await Promise.all(elements.map(element =>
CoreComponentsRegistry.waitComponentReady(element, CoreLoadingComponent)));
});
}
/**
@ -162,7 +145,7 @@ export class TestsBehatRuntime {
* @param button Type of button to press.
* @return OK if successful, or ERROR: followed by message.
*/
static pressStandard(button: string): string {
static async pressStandard(button: string): Promise<string> {
this.log('Action - Click standard button: ' + button);
// Find button
@ -194,7 +177,7 @@ export class TestsBehatRuntime {
}
// Click button
TestsBehatDomUtils.pressElement(foundButton);
await TestsBehatDomUtils.pressElement(foundButton);
return 'OK';
}
@ -348,7 +331,7 @@ export class TestsBehatRuntime {
* @param locator Element locator.
* @return OK if successful, or ERROR: followed by message
*/
static press(locator: TestBehatElementLocator): string {
static async press(locator: TestBehatElementLocator): Promise<string> {
this.log('Action - Press', locator);
try {
@ -358,7 +341,7 @@ export class TestsBehatRuntime {
return 'ERROR: No element matches locator to press.';
}
TestsBehatDomUtils.pressElement(found);
await TestsBehatDomUtils.pressElement(found);
return 'OK';
} catch (error) {
@ -397,7 +380,7 @@ export class TestsBehatRuntime {
* @param value New value
* @return OK or ERROR: followed by message
*/
static setField(field: string, value: string): string {
static async setField(field: string, value: string): Promise<string> {
this.log('Action - Set field ' + field + ' to: ' + value);
const found: HTMLElement | HTMLInputElement = TestsBehatDomUtils.findElementBasedOnText(
@ -408,7 +391,7 @@ export class TestsBehatRuntime {
return 'ERROR: No element matches field to set.';
}
TestsBehatDomUtils.setElementValue(found, value);
await TestsBehatDomUtils.setElementValue(found, value);
return 'OK';
}