diff --git a/local-moodleappbehat/tests/behat/behat_app.php b/local-moodleappbehat/tests/behat/behat_app.php index f4d3946ed..a77ffc09d 100644 --- a/local-moodleappbehat/tests/behat/behat_app.php +++ b/local-moodleappbehat/tests/behat/behat_app.php @@ -408,7 +408,7 @@ class behat_app extends behat_app_helper { ], ]); - $this->evaluate_script("return window.pushNotifications.notificationClicked($notification)"); + $this->evaluate_script("window.behat.notificationClicked($notification)"); $this->wait_for_pending_js(); } @@ -717,25 +717,8 @@ class behat_app extends behat_app_helper { * @When I run cron tasks in the app */ public function i_run_cron_tasks_in_the_app() { - $session = $this->getSession(); - - // Force cron tasks execution and wait until they are completed. - $operationid = random_string(); - - $session->executeScript( - "cronProvider.forceSyncExecution().then(() => { window['behat_{$operationid}_completed'] = true; });" - ); - $this->spin( - function() use ($session, $operationid) { - return $session->evaluateScript("window['behat_{$operationid}_completed'] || false"); - }, - false, - 60, - new ExpectationException('Forced cron tasks in the app took too long to complete', $session) - ); - - // Trigger Angular change detection. - $this->trigger_angular_change_detection(); + $this->evaluate_script('window.behat.forceSyncExecution()'); + $this->wait_for_pending_js(); } /** @@ -744,28 +727,8 @@ 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() { - $session = $this->getSession(); - - $this->spin( - function() use ($session) { - $this->trigger_angular_change_detection(); - - $nodes = $this->find_all('css', 'core-loading ion-spinner'); - - foreach ($nodes as $node) { - if (!$node->isVisible()) { - continue; - } - - return false; - } - - return true; - }, - false, - 60, - new ExpectationException('"Loading took too long to complete', $session) - ); + $this->evaluate_script('window.behat.waitLoadingToFinish()'); + $this->wait_for_pending_js(); } /** @@ -786,7 +749,7 @@ class behat_app extends behat_app_helper { $this->getSession()->switchToWindow($names[1]); } - $this->execute_script('window.close();'); + $this->evaluate_script('window.close();'); $this->getSession()->switchToWindow($names[0]); } @@ -798,7 +761,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->execute_script("appProvider.setForceOffline($offline);"); + $this->evaluate_script("window.behat.network.setForceOffline($offline);"); } } diff --git a/local-moodleappbehat/tests/behat/behat_app_helper.php b/local-moodleappbehat/tests/behat/behat_app_helper.php index 57134b662..52f3c287d 100644 --- a/local-moodleappbehat/tests/behat/behat_app_helper.php +++ b/local-moodleappbehat/tests/behat/behat_app_helper.php @@ -318,7 +318,7 @@ class behat_app_helper extends behat_base { $initOptions->skipOnBoarding = $options['skiponboarding'] ?? true; $initOptions->configOverrides = $this->appconfig; - $this->execute_script('window.behatInit(' . json_encode($initOptions) . ');'); + $this->evaluate_script('window.behatInit(' . json_encode($initOptions) . ');'); } catch (Exception $error) { throw new DriverException('Moodle App not running or not running on Automated mode.'); } @@ -433,14 +433,6 @@ class behat_app_helper extends behat_base { } } - - /** - * Trigger Angular change detection. - */ - protected function trigger_angular_change_detection() { - $this->getSession()->executeScript('ngZone.run(() => {});'); - } - /** * Evaluate a script that returns a Promise. * diff --git a/src/core/features/pushnotifications/services/pushnotifications.ts b/src/core/features/pushnotifications/services/pushnotifications.ts index ddd86d114..cae2c2b49 100644 --- a/src/core/features/pushnotifications/services/pushnotifications.ts +++ b/src/core/features/pushnotifications/services/pushnotifications.ts @@ -431,7 +431,7 @@ export class CorePushNotificationsProvider { /** * Function called when a push notification is clicked. Redirect the user to the right state. * - * @param notification Notification. + * @param data Notification data. * @return Promise resolved when done. */ async notificationClicked(data: CorePushNotificationsNotificationBasicData): Promise { diff --git a/src/core/initializers/prepare-automated-tests.ts b/src/core/initializers/prepare-automated-tests.ts index a0c9fdc44..ff773f9aa 100644 --- a/src/core/initializers/prepare-automated-tests.ts +++ b/src/core/initializers/prepare-automated-tests.ts @@ -12,35 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { ApplicationRef, NgZone as NgZoneService } from '@angular/core'; -import { CorePushNotifications, CorePushNotificationsProvider } from '@features/pushnotifications/services/pushnotifications'; -import { CoreApp, CoreAppProvider } from '@services/app'; -import { CoreConfig, CoreConfigProvider } from '@services/config'; -import { CoreCronDelegate, CoreCronDelegateService } from '@services/cron'; +import { CoreAppProvider } from '@services/app'; import { CoreDB, CoreDbProvider } from '@services/db'; -import { CoreCustomURLSchemes, CoreCustomURLSchemesProvider } from '@services/urlschemes'; -import { Application, NgZone } from '@singletons'; type AutomatedTestsWindow = Window & { - appRef?: ApplicationRef; - appProvider?: CoreAppProvider; dbProvider?: CoreDbProvider; - configProvider?: CoreConfigProvider; - cronProvider?: CoreCronDelegateService; - ngZone?: NgZoneService; - pushNotifications?: CorePushNotificationsProvider; - urlSchemes?: CoreCustomURLSchemesProvider; }; function initializeAutomatedTestsWindow(window: AutomatedTestsWindow) { - window.appRef = Application.instance; - window.appProvider = CoreApp.instance; window.dbProvider = CoreDB.instance; - window.configProvider = CoreConfig.instance; - window.cronProvider = CoreCronDelegate.instance; - window.ngZone = NgZone.instance; - window.pushNotifications = CorePushNotifications.instance; - window.urlSchemes = CoreCustomURLSchemes.instance; } export default function(): void { diff --git a/src/testing/services/behat-dom.ts b/src/testing/services/behat-dom.ts index 049eaa076..e00e29fc3 100644 --- a/src/testing/services/behat-dom.ts +++ b/src/testing/services/behat-dom.ts @@ -468,7 +468,7 @@ export class TestsBehatDomUtils { * @param element Element to press. */ static async pressElement(element: HTMLElement): Promise { - NgZone.run(async () => { + await NgZone.run(async () => { const blockKey = TestsBehatBlocking.block(); // Events don't bubble up across Shadow DOM boundaries, and some buttons @@ -511,7 +511,7 @@ export class TestsBehatDomUtils { * @param value Value to be set. */ static async setElementValue(element: HTMLElement, value: string): Promise { - NgZone.run(async () => { + await NgZone.run(async () => { const blockKey = TestsBehatBlocking.block(); // Functions to get/set value depending on field type. diff --git a/src/testing/services/behat-runtime.ts b/src/testing/services/behat-runtime.ts index f14a7227f..42b281885 100644 --- a/src/testing/services/behat-runtime.ts +++ b/src/testing/services/behat-runtime.ts @@ -19,6 +19,15 @@ import { CoreLoginHelperProvider } from '@features/login/services/login-helper'; import { CoreConfig } from '@services/config'; import { EnvironmentConfig } from '@/types/config'; import { NgZone } from '@singletons'; +import { CoreNetwork } from '@services/network'; +import { + CorePushNotifications, + CorePushNotificationsNotificationBasicData, +} from '@features/pushnotifications/services/pushnotifications'; +import { CoreCronDelegate } from '@services/cron'; +import { CoreLoadingComponent } from '@components/loading/loading'; +import { CoreComponentsRegistry } from '@singletons/components-registry'; +import { CoreDom } from '@singletons/dom'; /** * Behat runtime servive with public API. @@ -46,6 +55,10 @@ export class TestsBehatRuntime { scrollTo: TestsBehatRuntime.scrollTo, setField: TestsBehatRuntime.setField, handleCustomURL: TestsBehatRuntime.handleCustomURL, + notificationClicked: TestsBehatRuntime.notificationClicked, + forceSyncExecution: TestsBehatRuntime.forceSyncExecution, + waitLoadingToFinish: TestsBehatRuntime.waitLoadingToFinish, + network: CoreNetwork.instance, }; if (!options) { @@ -85,6 +98,64 @@ export class TestsBehatRuntime { } } + /** + * Function called when a push notification is clicked. Redirect the user to the right state. + * + * @param data Notification data. + * @return Promise resolved when done. + */ + static async notificationClicked(data: CorePushNotificationsNotificationBasicData): Promise { + const blockKey = TestsBehatBlocking.block(); + + try { + await NgZone.run(async () => { + await CorePushNotifications.notificationClicked(data); + }); + } finally { + TestsBehatBlocking.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. + */ + static async forceSyncExecution(): Promise { + const blockKey = TestsBehatBlocking.block(); + + try { + await NgZone.run(async () => { + await CoreCronDelegate.forceSyncExecution(); + }); + } finally { + TestsBehatBlocking.unblock(blockKey); + } + } + + /** + * Wait all controlled components to be rendered. + * + * @return Promise resolved when all components have been rendered. + */ + static async waitLoadingToFinish(): Promise { + const blockKey = TestsBehatBlocking.block(); + + await NgZone.run(async () => { + try { + const elements = Array.from(document.body.querySelectorAll('core-loading')) + .filter((element) => CoreDom.isElementVisible(element)); + + await Promise.all(elements.map(element => + CoreComponentsRegistry.waitComponentReady(element, CoreLoadingComponent))); + } finally { + TestsBehatBlocking.unblock(blockKey); + } + }); + + } + /** * Function to find and click an app standard button. *