From 52259b421f12b5dcd49fc2978f080de17b10cead Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Thu, 30 Jun 2022 13:48:43 +0200 Subject: [PATCH] MOBILE-4110 behat: Clean up js calls --- .../tests/behat/behat_app.php | 56 ++++++++--------- .../tests/behat/behat_app_helper.php | 48 ++++++++++---- .../behat/classes/performance_measure.php | 2 +- src/testing/services/behat-runtime.ts | 63 +++++++------------ 4 files changed, 88 insertions(+), 81 deletions(-) diff --git a/local-moodleappbehat/tests/behat/behat_app.php b/local-moodleappbehat/tests/behat/behat_app.php index 05e89be8e..4b7bbbf70 100644 --- a/local-moodleappbehat/tests/behat/behat_app.php +++ b/local-moodleappbehat/tests/behat/behat_app.php @@ -96,7 +96,7 @@ class behat_app extends behat_app_helper { public function i_wait_the_app_to_restart() { // Wait window to reload. $this->spin(function() { - if ($this->js('window.behat.hasInitialized()')) { + if ($this->runtime_js('hasInitialized()')) { // Behat runtime shouldn't be initialized after reload. throw new DriverException('Window is not reloading properly.'); } @@ -114,18 +114,18 @@ class behat_app extends behat_app_helper { * @Then /^I should( not)? find (".+")( inside the .+)? in the app$/ * @param bool $not Whether assert that the element was not found * @param string $locator Element locator - * @param string $containerName Container name + * @param string $container Container name */ - public function i_find_in_the_app(bool $not, string $locator, string $containerName = '') { + public function i_find_in_the_app(bool $not, string $locator, string $container = '') { $locator = $this->parse_element_locator($locator); - if (!empty($containerName)) { - preg_match('/^ inside the (.+)$/', $containerName, $matches); - $containerName = $matches[1]; + if (!empty($container)) { + preg_match('/^ inside the (.+)$/', $container, $matches); + $container = $matches[1]; } - $containerName = json_encode($containerName); + $options = json_encode(['containerName' => $container]); - $this->spin(function() use ($not, $locator, $containerName) { - $result = $this->js("return window.behat.find($locator, { containerName: $containerName });"); + $this->spin(function() use ($not, $locator, $options) { + $result = $this->runtime_js("find($locator, $options)"); if ($not && $result === 'OK') { throw new DriverException('Error, found an element that should not be found'); @@ -151,7 +151,7 @@ class behat_app extends behat_app_helper { $locator = $this->parse_element_locator($locator); $this->spin(function() use ($locator) { - $result = $this->js("return window.behat.scrollTo($locator);"); + $result = $this->runtime_js("scrollTo($locator)"); if ($result !== 'OK') { throw new DriverException('Error finding element - ' . $result); @@ -174,7 +174,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->js('return await window.behat.loadMoreItems();'); + $result = $this->runtime_js('loadMoreItems()'); if ($not && $result !== 'ERROR: All items are already loaded.') { throw new DriverException('It should not have been possible to load more items'); @@ -199,7 +199,7 @@ class behat_app extends behat_app_helper { public function i_swipe_in_the_app(string $direction) { $method = 'swipe' . ucwords($direction); - $this->js("window.behat.getAngularInstance('ion-content', 'CoreSwipeNavigationDirective').$method()"); + $this->runtime_js("getAngularInstance('ion-content', 'CoreSwipeNavigationDirective').$method()"); $this->wait_for_pending_js(); @@ -218,7 +218,7 @@ class behat_app extends behat_app_helper { $locator = $this->parse_element_locator($locator); $this->spin(function() use ($locator, $not) { - $result = $this->js("return window.behat.isSelected($locator);"); + $result = $this->runtime_js("isSelected($locator)"); switch ($result) { case 'YES': @@ -325,7 +325,7 @@ class behat_app extends behat_app_helper { $this->login($username); } - $mycoursesfound = $this->js("return window.behat.find({ text: 'My courses', selector: 'ion-tab-button'});"); + $mycoursesfound = $this->runtime_js("find({ text: 'My courses', selector: 'ion-tab-button'})"); if ($mycoursesfound !== 'OK') { // My courses not present enter from Dashboard. @@ -381,7 +381,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->js("return await window.behat.pressStandard('$button');"); + $result = $this->runtime_js("pressStandard('$button')"); if ($result !== 'OK') { throw new DriverException('Error pressing standard button - ' . $result); @@ -419,7 +419,7 @@ class behat_app extends behat_app_helper { ], ]); - $this->js("window.behat.notificationClicked($notification)"); + $this->zone_js("pushNotifications.notificationClicked($notification)", true); $this->wait_for_pending_js(); } @@ -507,7 +507,7 @@ class behat_app extends behat_app_helper { */ public function i_close_the_popup_in_the_app() { $this->spin(function() { - $result = $this->js("return window.behat.closePopup();"); + $result = $this->runtime_js('closePopup()'); if ($result !== 'OK') { throw new DriverException('Error closing popup - ' . $result); @@ -545,7 +545,7 @@ class behat_app extends behat_app_helper { $locator = $this->parse_element_locator($locator); $this->spin(function() use ($locator) { - $result = $this->js("return await window.behat.press($locator);"); + $result = $this->runtime_js("press($locator)"); if ($result !== 'OK') { throw new DriverException('Error pressing item - ' . $result); @@ -588,7 +588,7 @@ class behat_app extends behat_app_helper { $locator = $this->parse_element_locator($locator); $this->spin(function() use ($not, $locator) { - $result = $this->js("return window.behat.find($locator, { onlyClickable: true });"); + $result = $this->runtime_js("find($locator, { onlyClickable: true })"); if ($not && $result === 'OK') { throw new DriverException('Error, found a clickable element that should not be found'); @@ -622,14 +622,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->js("return window.behat.isSelected($locator);"); + $result = $this->runtime_js("isSelected($locator)"); if ($result === $selected) { return true; } // Press element. - $result = $this->js("return await window.behat.press($locator);"); + $result = $this->runtime_js("press($locator)"); if ($result !== 'OK') { throw new DriverException('Error pressing element - ' . $result); @@ -638,7 +638,7 @@ class behat_app extends behat_app_helper { // Check that it worked as expected. $this->wait_for_pending_js(); - $result = $this->js("return window.behat.isSelected($locator);"); + $result = $this->runtime_js("isSelected($locator)"); switch ($result) { case 'YES': @@ -672,7 +672,7 @@ class behat_app extends behat_app_helper { $value = addslashes_js($value); $this->spin(function() use ($field, $value) { - $result = $this->js("return await window.behat.setField(\"$field\", \"$value\");"); + $result = $this->runtime_js("setField('$field', '$value')"); if ($result !== 'OK') { throw new DriverException('Error setting field - ' . $result); @@ -711,7 +711,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->js('return window.behat.getHeader();'); + $result = $this->runtime_js('getHeader()'); if (substr($result, 0, 3) !== 'OK:') { throw new DriverException('Error getting header - ' . $result); @@ -792,7 +792,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->js('await window.behat.forceSyncExecution()'); + $this->zone_js('cronDelegate.forceSyncExecution()'); $this->wait_for_pending_js(); } @@ -802,7 +802,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->js('await window.behat.waitLoadingToFinish()'); + $this->runtime_js('waitLoadingToFinish()'); $this->wait_for_pending_js(); } @@ -824,7 +824,7 @@ class behat_app extends behat_app_helper { $this->getSession()->switchToWindow($names[1]); } - $this->js('window.close();'); + $this->js('window.close()'); $this->getSession()->switchToWindow($names[0]); } @@ -836,7 +836,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->js("window.behat.network.setForceOffline($offline);"); + $this->runtime_js("network.setForceOffline($offline)"); } } diff --git a/local-moodleappbehat/tests/behat/behat_app_helper.php b/local-moodleappbehat/tests/behat/behat_app_helper.php index 646b6e141..a9e469535 100644 --- a/local-moodleappbehat/tests/behat/behat_app_helper.php +++ b/local-moodleappbehat/tests/behat/behat_app_helper.php @@ -313,12 +313,12 @@ class behat_app_helper extends behat_base { try { // Init Behat JavaScript runtime. + $initoptions = json_encode([ + 'skipOnBoarding' => $options['skiponboarding'] ?? true, + 'configOverrides' => $this->appconfig, + ]); - $initOptions = new StdClass(); - $initOptions->skipOnBoarding = $options['skiponboarding'] ?? true; - $initOptions->configOverrides = $this->appconfig; - - $this->js('window.behat.init(' . json_encode($initOptions) . ');'); + $this->runtime_js("init($initoptions)"); } catch (Exception $error) { throw new DriverException('Moodle App not running or not running on Automated mode.'); } @@ -456,7 +456,7 @@ class behat_app_helper extends behat_base { $res = $this->evaluate_script("Promise.resolve($script) .then(result => window.$promisevariable = result) - .catch(error => window.$promisevariable = 'Async code rejected: ' + error?.message);"); + .catch(error => window.$promisevariable = 'Async code rejected: ' + error?.message)"); do { if (microtime(true) - $start > $timeout) { @@ -465,15 +465,42 @@ class behat_app_helper extends behat_base { // 0.1 seconds. usleep(100000); - } while (!$this->evaluate_script("return '$promisevariable' in window;")); + } while (!$this->evaluate_script("'$promisevariable' in window")); - $result = $this->evaluate_script("return window.$promisevariable;"); + $result = $this->evaluate_script("window.$promisevariable"); - $this->evaluate_script("delete window.$promisevariable;"); + $this->evaluate_script("delete window.$promisevariable"); + + if (is_string($result) && strrpos($result, 'Async code rejected:') === 0) { + throw new DriverException($result); + } return $result; } + /** + * Evaluate and execute methods from the Behat runtime. + * + * @param string $script + * @return mixed Result. + */ + protected function runtime_js(string $script) { + return $this->js("window.behat.$script"); + } + + /** + * Evaluate and execute methods from the Behat runtime inside the Angular zone. + * + * @param string $script + * @param bool $blocking + * @return mixed Result. + */ + protected function zone_js(string $script, bool $blocking = false) { + $blockingjson = json_encode($blocking); + + return $this->runtime_js("runInZone(() => window.behat.$script, $blockingjson)"); + } + /** * Opens a custom URL for automatic login and redirect from the Moodle App (and waits to finish.) * @@ -548,8 +575,7 @@ class behat_app_helper extends behat_base { * @param string $successXPath The XPath of the element to lookat after navigation. */ protected function handle_url(string $customurl, string $successXPath = '') { - // Instead of using evaluate_async_script, we wait for the path to load. - $result = $this->js("return await window.behat.handleCustomURL('$customurl');"); + $result = $this->zone_js("customUrlSchemes.handleCustomURL('$customurl')"); if ($result !== 'OK') { throw new DriverException('Error handling url - ' . $result); diff --git a/local-moodleappbehat/tests/behat/classes/performance_measure.php b/local-moodleappbehat/tests/behat/classes/performance_measure.php index f56125ae5..73e37287c 100644 --- a/local-moodleappbehat/tests/behat/classes/performance_measure.php +++ b/local-moodleappbehat/tests/behat/classes/performance_measure.php @@ -178,7 +178,7 @@ class performance_measure implements behat_app_listener { * @return int Current time in milliseconds. */ private function now(): int { - return $this->driver->evaluateScript('Date.now();'); + return $this->driver->evaluateScript('Date.now()'); } /** diff --git a/src/testing/services/behat-runtime.ts b/src/testing/services/behat-runtime.ts index 8492d09a7..d8448e035 100644 --- a/src/testing/services/behat-runtime.ts +++ b/src/testing/services/behat-runtime.ts @@ -14,17 +14,14 @@ import { TestingBehatDomUtils } from './behat-dom'; import { TestingBehatBlocking } from './behat-blocking'; -import { CoreCustomURLSchemes } from '@services/urlschemes'; +import { CoreCustomURLSchemes, CoreCustomURLSchemesProvider } from '@services/urlschemes'; import { CoreLoginHelperProvider } from '@features/login/services/login-helper'; import { CoreConfig } from '@services/config'; import { EnvironmentConfig } from '@/types/config'; import { makeSingleton, NgZone } from '@singletons'; import { CoreNetwork, CoreNetworkService } from '@services/network'; -import { - CorePushNotifications, - CorePushNotificationsNotificationBasicData, -} from '@features/pushnotifications/services/pushnotifications'; -import { CoreCronDelegate } from '@services/cron'; +import { CorePushNotifications, CorePushNotificationsProvider } from '@features/pushnotifications/services/pushnotifications'; +import { CoreCronDelegate, CoreCronDelegateService } from '@services/cron'; import { CoreLoadingComponent } from '@components/loading/loading'; import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreDom } from '@singletons/dom'; @@ -40,10 +37,22 @@ export class TestingBehatRuntimeService { protected initialized = false; + get cronDelegate(): CoreCronDelegateService { + return CoreCronDelegate.instance; + } + + get customUrlSchemes(): CoreCustomURLSchemesProvider { + return CoreCustomURLSchemes.instance; + } + get network(): CoreNetworkService { return CoreNetwork.instance; } + get pushNotifications(): CorePushNotificationsProvider { + return CorePushNotifications.instance; + } + /** * Init behat functions and set options like skipping onboarding. * @@ -78,53 +87,25 @@ export class TestingBehatRuntimeService { } /** - * Handles a custom URL. + * Run an operation inside the angular zone and return result. * - * @param url Url to open. + * @param operation Operation callback. * @return OK if successful, or ERROR: followed by message. */ - async handleCustomURL(url: string): Promise { + async runInZone(operation: () => unknown, blocking: boolean = false): Promise { + const blockKey = blocking && TestingBehatBlocking.block(); + try { - await NgZone.run(async () => { - await CoreCustomURLSchemes.handleCustomURL(url); - }); + await NgZone.run(operation); return 'OK'; } catch (error) { return 'ERROR: ' + error.message; - } - } - - /** - * Function called when a push notification is clicked. Redirect the user to the right state. - * - * @param data Notification data. - * @return Promise resolved when done. - */ - async notificationClicked(data: CorePushNotificationsNotificationBasicData): Promise { - const blockKey = TestingBehatBlocking.block(); - - try { - await NgZone.run(async () => { - await CorePushNotifications.notificationClicked(data); - }); } finally { - TestingBehatBlocking.unblock(blockKey); + blockKey && TestingBehatBlocking.unblock(blockKey); } } - /** - * Force execution of synchronization cron tasks without waiting for the scheduled time. - * Please notice that some tasks may not be executed depending on the network connection and sync settings. - * - * @return Promise resolved if all handlers are executed successfully, rejected otherwise. - */ - async forceSyncExecution(): Promise { - await NgZone.run(async () => { - await CoreCronDelegate.forceSyncExecution(); - }); - } - /** * Wait all controlled components to be rendered. *