From feff5a87f828d7a599d861d5da30b7c8cae7c3b5 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 19 Nov 2024 08:09:15 +0100 Subject: [PATCH 1/4] MOBILE-4028 logout: Refactor logout process Now it uses a logout page so Angular guards are triggered before doing the logout process. --- .../services/contentlinks-delegate.ts | 11 +- src/core/features/login/login.module.ts | 4 + .../features/login/pages/logout/logout.html | 3 + .../features/login/pages/logout/logout.ts | 104 ++++++++++++++++++ .../features/login/services/login-helper.ts | 19 ++-- .../components/user-menu/user-menu.ts | 16 +-- .../policy/pages/site-policy/site-policy.ts | 4 +- .../features/sitehome/tests/links.test.ts | 1 + src/core/services/navigator.ts | 7 +- src/core/services/sites.ts | 41 ++++--- src/core/services/tests/sites.test.ts | 4 - src/core/services/urlschemes.ts | 7 +- upgrade.txt | 4 + 13 files changed, 161 insertions(+), 64 deletions(-) create mode 100644 src/core/features/login/pages/logout/logout.html create mode 100644 src/core/features/login/pages/logout/logout.ts diff --git a/src/core/features/contentlinks/services/contentlinks-delegate.ts b/src/core/features/contentlinks/services/contentlinks-delegate.ts index 2b3e1a93c..b83b4109e 100644 --- a/src/core/features/contentlinks/services/contentlinks-delegate.ts +++ b/src/core/features/contentlinks/services/contentlinks-delegate.ts @@ -216,16 +216,7 @@ export class CoreContentLinksDelegateService { } // Site is logged out, authenticate first before treating the URL. - const willReload = await CoreSites.logoutForRedirect(siteId, { - urlToOpen: url, - }); - - if (!willReload) { - // Load the site with the redirect data. - await CoreSites.loadSite(siteId, { - urlToOpen: url, - }); - } + await CoreSites.logout({ urlToOpen: url, siteId }); }; }); diff --git a/src/core/features/login/login.module.ts b/src/core/features/login/login.module.ts index b0d3d8fbd..8030f6411 100644 --- a/src/core/features/login/login.module.ts +++ b/src/core/features/login/login.module.ts @@ -41,6 +41,10 @@ const appRoutes: Routes = [ loadChildren: () => import('./login-lazy.module'), canActivate: [redirectGuard], }, + { + path: 'logout', + loadComponent: () => import('@features/login/pages/logout/logout'), + }, ]; @NgModule({ diff --git a/src/core/features/login/pages/logout/logout.html b/src/core/features/login/pages/logout/logout.html new file mode 100644 index 000000000..1e61e89a5 --- /dev/null +++ b/src/core/features/login/pages/logout/logout.html @@ -0,0 +1,3 @@ + + + diff --git a/src/core/features/login/pages/logout/logout.ts b/src/core/features/login/pages/logout/logout.ts new file mode 100644 index 000000000..db0bc10ff --- /dev/null +++ b/src/core/features/login/pages/logout/logout.ts @@ -0,0 +1,104 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, OnInit } from '@angular/core'; +import { CoreSites } from '@services/sites'; +import { CoreConstants } from '@/core/constants'; +import { CoreNavigationOptions, CoreNavigator, CoreRedirectPayload } from '@services/navigator'; +import { CoreSharedModule } from '@/core/shared.module'; +import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins'; +import { CoreRedirects } from '@singletons/redirects'; + +/** + * Page that logs the user out. + */ +@Component({ + selector: 'page-core-login-logout', + templateUrl: 'logout.html', + standalone: true, + imports: [ + CoreSharedModule, + ], +}) +export default class CoreLoginLogoutPage implements OnInit { + + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + const siteId = CoreNavigator.getRouteParam('siteId') ?? CoreConstants.NO_SITE_ID; + const logoutOptions = { + forceLogout: CoreNavigator.getRouteBooleanParam('forceLogout'), + removeAccount: CoreNavigator.getRouteBooleanParam('removeAccount') ?? !!CoreConstants.CONFIG.removeaccountonlogout, + }; + const redirectData = { + redirectPath: CoreNavigator.getRouteParam('redirectPath'), + redirectOptions: CoreNavigator.getRouteParam('redirectOptions'), + urlToOpen: CoreNavigator.getRouteParam('urlToOpen'), + }; + + if (!CoreSites.isLoggedIn()) { + // This page shouldn't open if user isn't logged in, but if that happens just navigate to the right page. + await this.navigateAfterLogout(siteId, redirectData); + + return; + } + + const shouldReload = CoreSitePlugins.hasSitePluginsLoaded; + if (shouldReload && (siteId !== CoreConstants.NO_SITE_ID || redirectData.redirectPath || redirectData.urlToOpen)) { + // The app will reload and we need to open a page that isn't the default page. Store the redirect first. + CoreRedirects.storeRedirect(siteId, redirectData); + } + + await CoreSites.internalLogout(logoutOptions); + + if (shouldReload) { + // We need to reload the app to unload all the plugins. Leave the logout page first. + await CoreNavigator.navigate('/login', { reset: true }); + + window.location.reload(); + + return; + } + + await this.navigateAfterLogout(siteId, redirectData); + } + + /** + * Navigate to the right page after logout is done. + * + * @param siteId Site ID to load. + * @param redirectData Redirect data. + */ + protected async navigateAfterLogout(siteId: string, redirectData: CoreRedirectPayload): Promise { + if (siteId === CoreConstants.NO_SITE_ID) { + // No site to load now, just navigate. + await CoreNavigator.navigate(redirectData.redirectPath ?? '/login/sites', { + ...redirectData.redirectOptions, + reset: true, + }); + + return; + } + + // Load the site and navigate. + const loggedIn = await CoreSites.loadSite(siteId, redirectData); + if (!loggedIn) { + return; // Session expired. + } + + await CoreNavigator.navigateToSiteHome({ params: redirectData, preferCurrentTab: false, siteId }); + } + +} diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index 9be527c82..23ee93791 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -412,22 +412,19 @@ export class CoreLoginHelperProvider { * @returns Promise resolved when done. */ async goToAddSite(setRoot = false, showKeyboard = false): Promise { - let path = '/login/sites'; - let params: Params = { openAddSite: true , showKeyboard }; - if (CoreSites.isLoggedIn()) { - const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, { - redirectPath: path, - redirectOptions: { params }, + // Logout first. + await CoreSites.logout({ + siteId: CoreConstants.NO_SITE_ID, + redirectPath: '/login/sites', + redirectOptions: { params: { openAddSite: true , showKeyboard } }, }); - if (willReload) { - return; - } - } else { - [path, params] = await this.getAddSiteRouteInfo(showKeyboard); + return; } + const [path, params] = await this.getAddSiteRouteInfo(showKeyboard); + await CoreNavigator.navigate(path, { params, reset: setRoot }); } diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.ts b/src/core/features/mainmenu/components/user-menu/user-menu.ts index e52071150..1aecec73f 100644 --- a/src/core/features/mainmenu/components/user-menu/user-menu.ts +++ b/src/core/features/mainmenu/components/user-menu/user-menu.ts @@ -18,7 +18,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { CoreSite } from '@classes/sites/site'; import { CoreSiteInfo } from '@classes/sites/unauthenticated-site'; import { CoreFilter } from '@features/filter/services/filter'; -import { CoreLoginHelper } from '@features/login/services/login-helper'; import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config'; import { CoreUserSupport } from '@features/user/services/support'; import { CoreUser, CoreUserProfile } from '@features/user/services/user'; @@ -33,8 +32,9 @@ import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CorePromiseUtils } from '@singletons/promise-utils'; -import { ModalController, Translate } from '@singletons'; +import { ModalController } from '@singletons'; import { Subscription } from 'rxjs'; +import { CoreLoginHelper } from '@features/login/services/login-helper'; /** * Component to display a user menu. @@ -208,12 +208,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { * @param event Click event */ async logout(event: Event): Promise { - if (CoreNavigator.currentRouteCanBlockLeave()) { - await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks')); - - return; - } - if (this.removeAccountOnLogout) { // Ask confirm. const siteName = this.siteName ? @@ -242,12 +236,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { * @param event Click event */ async switchAccounts(event: Event): Promise { - if (CoreNavigator.currentRouteCanBlockLeave()) { - await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks')); - - return; - } - const thisModal = await ModalController.getTop(); event.preventDefault(); diff --git a/src/core/features/policy/pages/site-policy/site-policy.ts b/src/core/features/policy/pages/site-policy/site-policy.ts index 94be08c61..dfab2c14f 100644 --- a/src/core/features/policy/pages/site-policy/site-policy.ts +++ b/src/core/features/policy/pages/site-policy/site-policy.ts @@ -280,9 +280,7 @@ export class CorePolicySitePolicyPage implements OnInit, OnDestroy { * @returns Promise resolved when done. */ async cancel(): Promise { - await CorePromiseUtils.ignoreErrors(CoreSites.logout()); - - await CoreNavigator.navigate('/login/sites', { reset: true }); + await CoreSites.logout(); } /** diff --git a/src/core/features/sitehome/tests/links.test.ts b/src/core/features/sitehome/tests/links.test.ts index f137e69ec..cf4b21326 100644 --- a/src/core/features/sitehome/tests/links.test.ts +++ b/src/core/features/sitehome/tests/links.test.ts @@ -32,6 +32,7 @@ describe('Site Home link handlers', () => { isStoredRootURL: () => Promise.resolve({ siteIds: [siteId] }), getSite: () => Promise.resolve(new CoreSite(siteId, siteUrl, '')), getSiteIdsFromUrl: () => Promise.resolve([siteId]), + getCurrentSiteId: () => siteId, })); mockSingleton(CoreLoginHelper, { getAvailableSites: async () => [{ url: siteUrl, name: 'Example Campus' }] }); diff --git a/src/core/services/navigator.ts b/src/core/services/navigator.ts index 6632359f7..2d07de2d3 100644 --- a/src/core/services/navigator.ts +++ b/src/core/services/navigator.ts @@ -220,14 +220,13 @@ export class CoreNavigatorService { // If we are logged into a different site, log out first. if (CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() !== siteId) { - const willReload = await CoreSites.logoutForRedirect(siteId, { + await CoreSites.logout({ redirectPath: path, redirectOptions: options || {}, + siteId, }); - if (willReload) { - return true; - } + return true; } // If the path doesn't belong to a site, call standard navigation. diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts index 1dfdd725e..b761cc9f6 100644 --- a/src/core/services/sites.ts +++ b/src/core/services/sites.ts @@ -141,14 +141,6 @@ export class CoreSitesProvider { // Remove version classes from body. CoreHTMLClasses.removeSiteClasses(); - - // Go to sites page when user is logged out. - await CoreNavigator.navigate('/login/sites', { reset: true }); - - if (CoreSitePlugins.hasSitePluginsLoaded) { - // Temporary fix. Reload the page to unload all plugins. - window.location.reload(); - } }); CoreEvents.on(CoreEvents.LOGIN, async (data) => { @@ -964,7 +956,7 @@ export class CoreSitesProvider { promise.finally(() => { if (siteId) { // Logout the currentSite and expire the token. - this.logout(); + this.internalLogout(); this.setSiteLoggedOut(siteId); } }); @@ -1123,7 +1115,7 @@ export class CoreSitesProvider { this.logger.debug(`Delete site ${siteId}`); if (this.currentSite !== undefined && this.currentSite.id == siteId) { - this.logout(); + this.internalLogout(); } const site = await this.getSite(siteId); @@ -1457,10 +1449,23 @@ export class CoreSitesProvider { /** * Logout the user. * - * @param options Logout options. - * @returns Promise resolved when the user is logged out. + * @param options Options. */ async logout(options: CoreSitesLogoutOptions = {}): Promise { + await CoreNavigator.navigate('/logout', { + params: { ...options }, + reset: true, + }); + } + + /** + * Logout the user. + * This function is for internal usage, please use CoreSites.logout instead. The reason this function is public is because + * it's called from the CoreLoginLogoutPage page. + * + * @param options Logout options. + */ + async internalLogout(options: InternalLogoutOptions = {}): Promise { if (!this.currentSite) { return; } @@ -1494,6 +1499,7 @@ export class CoreSitesProvider { * @param siteId Site that will be opened after logout. * @param redirectData Page/url to open after logout. * @returns Promise resolved with boolean: true if app will be reloaded after logout. + * @deprecated since 5.0. Use CoreSites.logout instead, it automatically handles redirects. */ async logoutForRedirect(siteId: string, redirectData: CoreRedirectPayload): Promise { if (!this.currentSite) { @@ -1505,7 +1511,7 @@ export class CoreSitesProvider { CoreRedirects.storeRedirect(siteId, redirectData); } - await this.logout(); + await this.internalLogout(); return CoreSitePlugins.hasSitePluginsLoaded; } @@ -2480,7 +2486,14 @@ export type CoreSitesLoginTokenResponse = { /** * Options for logout. */ -export type CoreSitesLogoutOptions = { +export type CoreSitesLogoutOptions = CoreRedirectPayload & InternalLogoutOptions & { + siteId?: string; // Site ID to load after logout. +}; + +/** + * Options for internal logout. + */ +type InternalLogoutOptions = { forceLogout?: boolean; // If true, site will be marked as logged out, no matter the value tool_mobile_forcelogout. removeAccount?: boolean; // If true, site will be removed too after logout. }; diff --git a/src/core/services/tests/sites.test.ts b/src/core/services/tests/sites.test.ts index 68e477405..961c84708 100644 --- a/src/core/services/tests/sites.test.ts +++ b/src/core/services/tests/sites.test.ts @@ -16,7 +16,6 @@ import { CoreEvents } from '@singletons/events'; import { CoreLang, CoreLangProvider } from '@services/lang'; import { mock, mockSingleton } from '@/testing/utils'; -import { CoreNavigator, CoreNavigatorService } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { Http } from '@singletons'; import { of } from 'rxjs'; @@ -34,13 +33,10 @@ describe('CoreSitesProvider', () => { }); it('cleans up on logout', async () => { - const navigator: CoreNavigatorService = mockSingleton(CoreNavigator, ['navigate']); - CoreSites.initialize(); CoreEvents.trigger(CoreEvents.LOGOUT); expect(langProvider.clearCustomStrings).toHaveBeenCalled(); - expect(navigator.navigate).toHaveBeenCalledWith('/login/sites', { reset: true }); }); it('adds ionic platform and theme classes', async () => { diff --git a/src/core/services/urlschemes.ts b/src/core/services/urlschemes.ts index 65cd87bc4..833bdcc8e 100644 --- a/src/core/services/urlschemes.ts +++ b/src/core/services/urlschemes.ts @@ -432,14 +432,13 @@ export class CoreCustomURLSchemesProvider { // Ask the user before changing site. await CoreDomUtils.showConfirm(Translate.instant('core.contentlinks.confirmurlothersite')); - const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, { + await CoreSites.logout({ + siteId: CoreConstants.NO_SITE_ID, redirectPath: '/login/credentials', redirectOptions: { params: pageParams }, }); - if (willReload) { - return; - } + return; } await CoreNavigator.navigateToLoginCredentials(pageParams); diff --git a/upgrade.txt b/upgrade.txt index edb6425e0..a9429f01f 100644 --- a/upgrade.txt +++ b/upgrade.txt @@ -2,6 +2,10 @@ This file describes API changes in the Moodle App that affect site plugins, info For more information about upgrading, read the official documentation: https://moodledev.io/general/app/upgrading/ +=== 5.0.0 === + + - The logout process has been refactored, now it uses a logout page to trigger Angular guards. CoreSites.logout now uses this process, and CoreSites.logoutForRedirect is deprecated and shouldn't be used anymore. + === 4.5.0 === - Ionic has been upgraded to major version 8. See breaking changes and upgrade guide here: https://ionicframework.com/docs/updating/8-0 From e671ce437a4573bf1c30f09a44e2496b57213cff Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 19 Nov 2024 16:16:10 +0100 Subject: [PATCH 2/4] MOBILE-4028 links: Fix opening links to sites with token expired --- .../services/contentlinks-delegate.ts | 22 +++++++--- .../features/sitehome/tests/links.test.ts | 1 + src/core/services/navigator.ts | 40 +++++++++++++++---- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/core/features/contentlinks/services/contentlinks-delegate.ts b/src/core/features/contentlinks/services/contentlinks-delegate.ts index b83b4109e..d76bb7ca0 100644 --- a/src/core/features/contentlinks/services/contentlinks-delegate.ts +++ b/src/core/features/contentlinks/services/contentlinks-delegate.ts @@ -19,6 +19,7 @@ import { CoreUrl } from '@singletons/url'; import { makeSingleton } from '@singletons'; import { CoreText } from '@singletons/text'; import { CorePromiseUtils } from '@singletons/promise-utils'; +import { CoreNavigator } from '@services/navigator'; /** * Interface that all handlers must implement. @@ -208,15 +209,24 @@ export class CoreContentLinksDelegateService { // Wrap the action function in our own function to treat logged out sites. const actionFunction = action.action; action.action = async (siteId) => { - const site = await CoreSites.getSite(siteId); + if (!CoreSites.isLoggedIn()) { + // Not logged in, load site first. + const loggedIn = await CoreSites.loadSite(siteId, { urlToOpen: url }); + if (loggedIn) { + await CoreNavigator.navigateToSiteHome({ params: { urlToOpen: url } }); + } - if (!site.isLoggedOut()) { - // Call the action now. - return actionFunction(siteId); + return; } - // Site is logged out, authenticate first before treating the URL. - await CoreSites.logout({ urlToOpen: url, siteId }); + if (siteId !== CoreSites.getCurrentSiteId()) { + // Different site, logout and login first before treating the URL because token could be expired. + await CoreSites.logout({ urlToOpen: url, siteId }); + + return; + } + + actionFunction(siteId); }; }); diff --git a/src/core/features/sitehome/tests/links.test.ts b/src/core/features/sitehome/tests/links.test.ts index cf4b21326..c0f58e06f 100644 --- a/src/core/features/sitehome/tests/links.test.ts +++ b/src/core/features/sitehome/tests/links.test.ts @@ -33,6 +33,7 @@ describe('Site Home link handlers', () => { getSite: () => Promise.resolve(new CoreSite(siteId, siteUrl, '')), getSiteIdsFromUrl: () => Promise.resolve([siteId]), getCurrentSiteId: () => siteId, + isLoggedIn: () => true, })); mockSingleton(CoreLoginHelper, { getAvailableSites: async () => [{ url: siteUrl, name: 'Example Campus' }] }); diff --git a/src/core/services/navigator.ts b/src/core/services/navigator.ts index 2d07de2d3..0c840b043 100644 --- a/src/core/services/navigator.ts +++ b/src/core/services/navigator.ts @@ -195,7 +195,7 @@ export class CoreNavigatorService { async navigateToSiteHome(options: Omit & { siteId?: string } = {}): Promise { const siteId = options.siteId ?? CoreSites.getCurrentSiteId(); const landingPagePath = CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() === siteId ? - this.getLandingTabPage() : 'main'; + this.getLandingTabPage() : ''; return this.navigateToSitePath(landingPagePath, { ...options, @@ -221,8 +221,7 @@ export class CoreNavigatorService { // If we are logged into a different site, log out first. if (CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() !== siteId) { await CoreSites.logout({ - redirectPath: path, - redirectOptions: options || {}, + ...this.getRedirectDataForSitePath(path, options), siteId, }); @@ -242,10 +241,7 @@ export class CoreNavigatorService { const modal = await CoreLoadings.show(); try { - const loggedIn = await CoreSites.loadSite(siteId, { - redirectPath: path, - redirectOptions: options, - }); + const loggedIn = await CoreSites.loadSite(siteId, this.getRedirectDataForSitePath(path, options)); if (!loggedIn) { // User has been redirected to the login page and will be redirected to the site path after login. @@ -263,6 +259,31 @@ export class CoreNavigatorService { return this.navigateToMainMenuPath(path, navigationOptions); } + /** + * Get the redirect data to use when navigating to a site path. + * + * @param path Site path. + * @param options Navigation options. + * @returns Redirect data. + */ + protected getRedirectDataForSitePath(path: string, options: CoreNavigationOptions = {}): CoreRedirectPayload { + if (!path || path.match(/^\/?main\/?$/)) { + // Navigating to main, obtain the redirect from the navigation parameters (if any). + // If there is no redirect path or url to open, use 'main' to open the site's main menu. + return { + redirectPath: !options.params?.redirectPath && !options.params?.urlToOpen ? 'main' : options.params?.redirectPath, + redirectOptions: options.params?.redirectOptions, + urlToOpen: options.params?.urlToOpen, + }; + } + + // Use the path to navigate as the redirect path. + return { + redirectPath: path, + redirectOptions: options || {}, + }; + } + /** * Get the active route path. * @@ -546,6 +567,11 @@ export class CoreNavigatorService { ...options, }; + if (!path || path.match(/^\/?main\/?$/)) { + // Navigating to main, nothing else to do. + return this.navigate('/main', options); + } + path = path.replace(/^(\.|\/main)?\//, ''); const pathRoot = /^[^/]+/.exec(path)?.[0] ?? ''; From 822fadbd6421f9bedb53baf3e78ae14a1e82b845 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 20 Nov 2024 15:31:52 +0100 Subject: [PATCH 3/4] MOBILE-4028 behat: Add behats to test logout and redirects --- .../tests/behat/behat_app.php | 3 +- .../tests/behat/behat_app_helper.php | 77 +++++++-- .../login/tests/behat/basic_usage.feature | 7 - .../features/login/tests/behat/logout.feature | 160 ++++++++++++++++++ .../tests/behat/navigation_deeplinks.feature | 102 ++++++++++- src/testing/services/behat-runtime.ts | 24 ++- 6 files changed, 342 insertions(+), 31 deletions(-) create mode 100755 src/core/features/login/tests/behat/logout.feature diff --git a/local_moodleappbehat/tests/behat/behat_app.php b/local_moodleappbehat/tests/behat/behat_app.php index 012aff300..a8e5bbc33 100644 --- a/local_moodleappbehat/tests/behat/behat_app.php +++ b/local_moodleappbehat/tests/behat/behat_app.php @@ -604,6 +604,7 @@ class behat_app extends behat_app_helper { $data = $data->getColumnsHash()[0]; $title = array_keys($data)[0]; $data = (object) $data; + $username = $data->user ?? ''; switch ($title) { case 'discussion': @@ -645,7 +646,7 @@ class behat_app extends behat_app_helper { throw new DriverException('Invalid custom link title - ' . $title); } - $this->open_moodleapp_custom_url($pageurl); + $this->open_moodleapp_custom_url($pageurl, '', $username); } /** diff --git a/local_moodleappbehat/tests/behat/behat_app_helper.php b/local_moodleappbehat/tests/behat/behat_app_helper.php index 4621b6a03..d255237d2 100644 --- a/local_moodleappbehat/tests/behat/behat_app_helper.php +++ b/local_moodleappbehat/tests/behat/behat_app_helper.php @@ -366,12 +366,15 @@ class behat_app_helper extends behat_base { * * @param string $script * @param bool $blocking + * @param string $texttofind If set, when this text is found the operation is considered finished. This is useful for + * operations that might expect user input before finishing, like a confirm modal. * @return mixed Result. */ - protected function zone_js(string $script, bool $blocking = false) { + protected function zone_js(string $script, bool $blocking = false, string $texttofind = '') { $blockingjson = json_encode($blocking); + $locatortofind = !empty($texttofind) ? json_encode((object) ['text' => $texttofind]) : null; - return $this->runtime_js("runInZone(() => window.behat.$script, $blockingjson)"); + return $this->runtime_js("runInZone(() => window.behat.$script, $blockingjson, $locatortofind)"); } /** @@ -411,16 +414,14 @@ class behat_app_helper extends behat_base { $privatetoken = $usertoken->privatetoken; } - // Generate custom URL. - $parsed_url = parse_url($CFG->behat_wwwroot); - $site = $parsed_url['host'] ?? ''; - $site .= isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; - $site .= $parsed_url['path'] ?? ''; - $url = $this->get_mobile_url_scheme() . "://$username@$site?token=$token&privatetoken=$privatetoken"; + $url = $this->generate_custom_url([ + 'username' => $username, + 'token' => $token, + 'privatetoken' => $privatetoken, + 'redirect' => $path, + ]); - if (!empty($path)) { - $url .= '&redirect='.urlencode($CFG->behat_wwwroot.$path); - } else { + if (empty($path)) { $successXPath = '//page-core-mainmenu'; } @@ -434,14 +435,54 @@ class behat_app_helper extends behat_base { * * @param string $path To navigate. * @param string $successXPath The XPath of the element to lookat after navigation. + * @param string $username The username to use. */ - protected function open_moodleapp_custom_url(string $path, string $successXPath = '') { + protected function open_moodleapp_custom_url(string $path, string $successXPath = '', string $username = '') { global $CFG; - $urlscheme = $this->get_mobile_url_scheme(); - $url = "$urlscheme://link=" . urlencode($CFG->behat_wwwroot.$path); + $url = $this->generate_custom_url([ + 'username' => $username, + 'redirect' => $path, + ]); - $this->handle_url($url); + $this->handle_url($url, $successXPath, $username ? 'This link belongs to another site' : ''); + } + + /** + * Generates a custom URL to be treated by the app. + * + * @param array $data Data to generate the URL. + */ + protected function generate_custom_url(array $data): string { + global $CFG; + + $parsed_url = parse_url($CFG->behat_wwwroot); + $parameters = []; + + $url = $this->get_mobile_url_scheme() . '://' . ($parsed_url['scheme'] ?? 'http') . '://'; + if (!empty($data['username'])) { + $url .= $data['username'] . '@'; + } + $url .= $parsed_url['host'] ?? ''; + $url .= isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; + $url .= $parsed_url['path'] ?? ''; + + if (!empty($data['token'])) { + $parameters[] = 'token=' . $data['token']; + if (!empty($data['privatetoken'])) { + $parameters[] = 'privatetoken=' . $data['privatetoken']; + } + } + + if (!empty($data['redirect'])) { + $parameters[] = 'redirect=' . urlencode($data['redirect']); + } + + if (!empty($parameters)) { + $url .= '?' . implode('&', $parameters); + } + + return $url; } /** @@ -449,9 +490,11 @@ class behat_app_helper extends behat_base { * * @param string $customurl To navigate. * @param string $successXPath The XPath of the element to lookat after navigation. + * @param string $texttofind If set, when this text is found the operation is considered finished. This is useful for + * operations that might expect user input before finishing, like a confirm modal. */ - protected function handle_url(string $customurl, string $successXPath = '') { - $result = $this->zone_js("customUrlSchemes.handleCustomURL('$customurl')"); + protected function handle_url(string $customurl, string $successXPath = '', string $texttofind = '') { + $result = $this->zone_js("customUrlSchemes.handleCustomURL('$customurl')", false, $texttofind); if ($result !== 'OK') { throw new DriverException('Error handling url - ' . $customurl . ' - '.$result); diff --git a/src/core/features/login/tests/behat/basic_usage.feature b/src/core/features/login/tests/behat/basic_usage.feature index 2285a9d93..39bde7341 100755 --- a/src/core/features/login/tests/behat/basic_usage.feature +++ b/src/core/features/login/tests/behat/basic_usage.feature @@ -66,13 +66,6 @@ Feature: Test basic usage of login in app And I press "Connect to your site" in the app Then I should find "Can't connect to site" in the app - Scenario: Log out from the app - Given I entered the app as "student1" - And I press the user menu button in the app - When I press "Log out" in the app - And I wait the app to restart - Then the header should be "Accounts" in the app - Scenario: Delete an account Given I entered the app as "student1" When I log out in the app diff --git a/src/core/features/login/tests/behat/logout.feature b/src/core/features/login/tests/behat/logout.feature new file mode 100755 index 000000000..15b1ebb73 --- /dev/null +++ b/src/core/features/login/tests/behat/logout.feature @@ -0,0 +1,160 @@ +@core_login @app @javascript +Feature: Test different cases of logout and switch account + I need different logout use cases to work + + Background: + Given the following "users" exist: + | username | firstname | lastname | + | student1 | david | student | + | student2 | pau | student2 | + + Scenario: Log out and re-login + Given I entered the app as "student1" + When I press the user menu button in the app + And I press "Log out" in the app + And I wait the app to restart + Then the header should be "Accounts" in the app + + When I press "david student" in the app + Then the header should be "Reconnect" in the app + And I should find "david student" in the app + + When I set the following fields to these values in the app: + | Password | student1 | + And I press "Log in" near "Lost password?" in the app + Then the header should be "Acceptance test site" in the app + + Scenario: Exit account using switch account and re-enter + Given I entered the app as "student1" + When I press the user menu button in the app + And I press "Switch account" in the app + And I press "Add" in the app + And I wait the app to restart + Then I should find "Connect to Moodle" in the app + + When I go back in the app + And I press "david student" in the app + Then the header should be "Acceptance test site" in the app + + Scenario: Exit account using switch account and re-enter when forcelogout is enabled + Given the following config values are set as admin: + | forcelogout | 1 | tool_mobile | + And I entered the app as "student1" + When I press the user menu button in the app + And I press "Switch account" in the app + And I press "Add" in the app + And I wait the app to restart + And I go back in the app + And I press "david student" in the app + Then the header should be "Reconnect" in the app + And I should find "david student" in the app + + Scenario: Switch to a different account + Given I entered the app as "student1" + And I entered the app as "student2" + When I press the user menu button in the app + Then I should find "pau student2" in the app + + When I press "Switch account" in the app + And I press "david student" in the app + And I wait the app to restart + Then the header should be "Acceptance test site" in the app + + When I press the user menu button in the app + Then I should find "david student" in the app + + Scenario: Logout when there is unsaved data + Given the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + And the following "activities" exist: + | activity | name | intro | course | idnumber | + | forum | Test forum | Test forum | C1 | forum | + And the following forum discussions exist in course "Course 1": + | forum | user | name | message | + | Test forum | student1 | Forum topic 1 | Forum message 1 | + | Test forum | student1 | Forum topic 2 | Forum message 2 | + And I entered the course "Course 1" as "student1" in the app + And I change viewport size to "1200x640" in the app + + When I press "Test forum" in the app + And I press "Add discussion topic" in the app + And I set the following fields to these values in the app: + | Subject | My happy subject | + | Message | An awesome message | + And I press the user menu button in the app + And I press "Log out" in the app + Then I should find "Are you sure you want to leave this page?" in the app + + # Check that the app continues working fine if the user cancels the logout. + When I press "Cancel" in the app + And I press "Forum topic 1" in the app + And I press "OK" in the app + Then I should find "Forum message 1" in the app + + When I press "Forum topic 2" in the app + Then I should find "Forum message 2" in the app + + # Now confirm the logout. + When I press "Add discussion topic" in the app + And I set the following fields to these values in the app: + | Subject | My happy subject | + | Message | An awesome message | + And I press the user menu button in the app + And I press "Log out" in the app + And I press "OK" in the app + And I wait the app to restart + Then the header should be "Accounts" in the app + + Scenario: Switch account when there is unsaved data + Given the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + And the following "activities" exist: + | activity | name | intro | course | idnumber | + | forum | Test forum | Test forum | C1 | forum | + And the following forum discussions exist in course "Course 1": + | forum | user | name | message | + | Test forum | student1 | Forum topic 1 | Forum message 1 | + | Test forum | student1 | Forum topic 2 | Forum message 2 | + And I entered the app as "student2" + And I entered the course "Course 1" as "student1" in the app + And I change viewport size to "1200x640" in the app + + When I press "Test forum" in the app + And I press "Add discussion topic" in the app + And I set the following fields to these values in the app: + | Subject | My happy subject | + | Message | An awesome message | + And I press the user menu button in the app + And I press "Switch account" in the app + And I press "pau student2" in the app + Then I should find "Are you sure you want to leave this page?" in the app + + # Check that the app continues working fine if the user cancels the switch account. + When I press "Cancel" in the app + And I press "Forum topic 1" in the app + And I press "OK" in the app + Then I should find "Forum message 1" in the app + + When I press "Forum topic 2" in the app + Then I should find "Forum message 2" in the app + + # Now confirm the switch account. + When I press "Add discussion topic" in the app + And I set the following fields to these values in the app: + | Subject | My happy subject | + | Message | An awesome message | + And I press the user menu button in the app + And I press "Switch account" in the app + And I press "pau student2" in the app + And I press "OK" in the app + And I wait the app to restart + And I press the user menu button in the app + Then I should find "pau student2" in the app diff --git a/src/core/tests/behat/navigation_deeplinks.feature b/src/core/tests/behat/navigation_deeplinks.feature index 41bd254c2..7f858d53b 100644 --- a/src/core/tests/behat/navigation_deeplinks.feature +++ b/src/core/tests/behat/navigation_deeplinks.feature @@ -3,9 +3,9 @@ Feature: It navigates properly using deep links. Background: Given the following "users" exist: - | username | - | student1 | - | student2 | + | username | firstname | lastname | + | student1 | david | student | + | student2 | pau | student2 | And the following "courses" exist: | fullname | shortname | | Course 1 | C1 | @@ -20,7 +20,6 @@ Feature: It navigates properly using deep links. | forum | user | name | message | | Test forum | student1 | Forum topic | Forum message | And the following config values are set as admin: - | forcelogout | 1 | tool_mobile | | defaulthomepage | 0 | | Scenario: Receive a push notification @@ -78,3 +77,98 @@ Feature: It navigates properly using deep links. When I go back in the app Then I should find "Site home" in the app But I should not find "Test forum" in the app + + Scenario: Open a deep link in a different account not stored in the app + Given I entered the app as "student1" + When I open a custom link in the app for: + | discussion | user | + | Forum topic | student2 | + Then I should find "This link belongs to another site" in the app + + When I press "OK" in the app + And I wait the app to restart + Then the header should be "Log in" in the app + + When I set the following fields to these values in the app: + | Password | student2 | + And I press "Log in" near "Lost password?" in the app + Then I should find "Forum topic" in the app + And I should find "Forum message" in the app + + When I go back to the root page in the app + And I press the user menu button in the app + Then I should find "pau student2" in the app + + Scenario: Open a deep link in a different account stored in the app + Given I entered the app as "student2" + And I entered the app as "student1" + When I open a custom link in the app for: + | discussion | user | + | Forum topic | student2 | + Then I should find "This link belongs to another site" in the app + + When I press "OK" in the app + And I wait the app to restart + Then I should find "Forum topic" in the app + And I should find "Forum message" in the app + + When I go back to the root page in the app + And I press the user menu button in the app + Then I should find "pau student2" in the app + + Scenario: Open a deep link in a different account stored in the app but logged out + Given I entered the app as "student2" + And I press the user menu button in the app + And I press "Log out" in the app + And I wait the app to restart + And I entered the app as "student1" + When I open a custom link in the app for: + | discussion | user | + | Forum topic | student2 | + Then I should find "This link belongs to another site" in the app + + When I press "OK" in the app + And I wait the app to restart + Then the header should be "Reconnect" in the app + And I should find "pau student2" in the app + + When I set the following fields to these values in the app: + | Password | student2 | + And I press "Log in" near "Lost password?" in the app + Then I should find "Forum topic" in the app + And I should find "Forum message" in the app + + When I go back to the root page in the app + And I press the user menu button in the app + Then I should find "pau student2" in the app + + Scenario: Open a deep link in a different account when there is unsaved data + Given I entered the app as "student2" + And I entered the forum activity "Test forum" on course "Course 1" as "student1" in the app + When I press "Add discussion topic" in the app + And I set the following fields to these values in the app: + | Subject | My happy subject | + | Message | An awesome message | + And I open a custom link in the app for: + | discussion | user | + | Forum topic | student2 | + Then I should find "This link belongs to another site" in the app + + When I press "OK" in the app + Then I should find "Are you sure you want to leave this page?" in the app + + When I press "Cancel" in the app + Then I should not find "Forum message" in the app + + When I open a custom link in the app for: + | discussion | user | + | Forum topic | student2 | + And I press "OK" in the app + And I press "OK" in the app + And I wait the app to restart + Then I should find "Forum topic" in the app + And I should find "Forum message" in the app + + When I go back to the root page in the app + And I press the user menu button in the app + Then I should find "pau student2" in the app diff --git a/src/testing/services/behat-runtime.ts b/src/testing/services/behat-runtime.ts index f6c7d7ed0..74d0b717a 100644 --- a/src/testing/services/behat-runtime.ts +++ b/src/testing/services/behat-runtime.ts @@ -136,19 +136,39 @@ export class TestingBehatRuntimeService { * Run an operation inside the angular zone and return result. * * @param operation Operation callback. + * @param blocking Whether the operation is blocking or not. + * @param locatorToFind If set, when this locator is found the operation is considered finished. This is useful for + * operations that might expect user input before finishing, like a confirm modal. * @returns OK if successful, or ERROR: followed by message. */ - async runInZone(operation: () => unknown, blocking: boolean = false): Promise { + async runInZone( + operation: () => unknown, + blocking: boolean = false, + locatorToFind?: TestingBehatElementLocator, + ): Promise { const blockKey = blocking && TestingBehatBlocking.block(); + let interval: number | undefined; try { - await NgZone.run(operation); + await new Promise((resolve, reject) => { + Promise.resolve(NgZone.run(operation)).then(resolve).catch(reject); + + if (locatorToFind) { + interval = window.setInterval(() => { + if (TestingBehatDomUtils.findElementBasedOnText(locatorToFind, { onlyClickable: false })) { + clearInterval(interval); + resolve(); + } + }, 500); + } + }); return 'OK'; } catch (error) { return 'ERROR: ' + error.message; } finally { blockKey && TestingBehatBlocking.unblock(blockKey); + window.clearInterval(interval); } } From 9a929f684c637932ae5f53b78f2c4910ab26669c Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 21 Nov 2024 15:29:18 +0100 Subject: [PATCH 4/4] MOBILE-4028 behat: Fix test after changes in links workflow Before MOBILE-4028, links could be treated before authenticating to a site, and this meant that the 'navigateToModule' function used 'getAndOpenCourse', which opened first the course and then the activity. Now the links are treated after authenticating, and this means that the activity is opened directly, without opening the course first. --- .../assign/tests/behat/basic_usage.feature | 14 ++-- .../tests/behat/basic_usage.feature | 6 +- .../choice/tests/behat/basic_usage.feature | 6 +- src/addons/mod/data/tests/behat/sync.feature | 71 +++++++++++-------- .../mod/forum/tests/behat/basic_usage.feature | 3 +- .../mod/forum/tests/behat/groups.feature | 19 +++-- .../glossary/tests/behat/basic_usage.feature | 3 +- .../mod/quiz/tests/behat/can_review.feature | 5 +- .../survey/tests/behat/basic_usage.feature | 5 +- .../workshop/tests/behat/basic_usage.feature | 3 +- .../tests/behat/showloginform_setting.feature | 2 +- .../settings/tests/behat/sync.feature | 3 +- src/core/tests/behat/open_files.feature | 3 +- 13 files changed, 89 insertions(+), 54 deletions(-) diff --git a/src/addons/mod/assign/tests/behat/basic_usage.feature b/src/addons/mod/assign/tests/behat/basic_usage.feature index 9cbf92c17..9adc224cb 100755 --- a/src/addons/mod/assign/tests/behat/basic_usage.feature +++ b/src/addons/mod/assign/tests/behat/basic_usage.feature @@ -132,7 +132,8 @@ Feature: Test basic usage of assignment activity in app Then I should find "No attempt" in the app Scenario: Add submission offline (online text) & Submit for grading offline & Sync submissions - Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app + Given I entered the course "Course 1" as "student1" in the app + And I press "assignment1" in the app When I press "Add submission" in the app And I switch network connection to offline And I set the field "Online text submissions" to "Submission test" in the app @@ -150,7 +151,8 @@ Feature: Test basic usage of assignment activity in app But I should not find "This Assignment has offline data to be synchronised." in the app Scenario: Edit an offline submission before synchronising it - Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app + Given I entered the course "Course 1" as "student1" in the app + And I press "assignment1" in the app When I press "Add submission" in the app And I switch network connection to offline And I set the field "Online text submissions" to "Submission test original offline" in the app @@ -178,8 +180,9 @@ Feature: Test basic usage of assignment activity in app @lms_from4.5 Scenario: Remove submission offline and syncrhonize it - Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app - And I press "Add submission" in the app + Given I entered the course "Course 1" as "student1" in the app + And I press "assignment1" in the app + When I press "Add submission" in the app And I set the field "Online text submissions" to "Submission test" in the app And I press "Save" in the app Then I should find "Draft (not submitted)" in the app @@ -223,7 +226,8 @@ Feature: Test basic usage of assignment activity in app @lms_from4.5 Scenario: Add submission offline after removing a submission offline - Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app + Given I entered the course "Course 1" as "student1" in the app + And I press "assignment1" in the app When I press "Add submission" in the app And I set the field "Online text submissions" to "Submission test online" in the app And I press "Save" in the app diff --git a/src/addons/mod/bigbluebuttonbn/tests/behat/basic_usage.feature b/src/addons/mod/bigbluebuttonbn/tests/behat/basic_usage.feature index 739cada78..f7260640f 100755 --- a/src/addons/mod/bigbluebuttonbn/tests/behat/basic_usage.feature +++ b/src/addons/mod/bigbluebuttonbn/tests/behat/basic_usage.feature @@ -25,7 +25,8 @@ Feature: Test basic usage of BBB activity in app | bigbluebuttonbn | BBB 1 | Test BBB description | C1 | bbb1 | 0 | ## 1 January 2050 00:00 ## | 0 | | bigbluebuttonbn | BBB 2 | Test BBB description | C1 | bbb2 | 0 | 0 | ## 1 January 2000 00:00 ## | | bigbluebuttonbn | BBB 3 | Test BBB description | C1 | bbb3 | 0 | ## 1 January 2000 00:00 ## | ## 1 January 2050 00:00 ## | - And I entered the bigbluebuttonbn activity "BBB 1" on course "Course 1" as "student1" in the app + And I entered the course "Course 1" as "student1" in the app + And I press "BBB 1" in the app Then I should find "The session has not started yet." in the app And I should find "Saturday, 1 January 2050, 12:00 AM" within "Open" "ion-item" in the app @@ -107,7 +108,8 @@ Feature: Test basic usage of BBB activity in app | bigbluebuttonbn | Room & recordings | C1 | bbb1 | 0 | | bigbluebuttonbn | Room only | C1 | bbb2 | 1 | | bigbluebuttonbn | Recordings only | C1 | bbb3 | 2 | - And I entered the bigbluebuttonbn activity "Room & recordings" on course "Course 1" as "student1" in the app + And I entered the course "Course 1" as "student1" in the app + And I press "Room & recordings" in the app Then I should find "This room is ready. You can join the session now." in the app And I should be able to press "Join session" in the app And I should find "Recordings" in the app diff --git a/src/addons/mod/choice/tests/behat/basic_usage.feature b/src/addons/mod/choice/tests/behat/basic_usage.feature index 5d087bfdf..fb44f8a54 100755 --- a/src/addons/mod/choice/tests/behat/basic_usage.feature +++ b/src/addons/mod/choice/tests/behat/basic_usage.feature @@ -21,7 +21,8 @@ Feature: Test basic usage of choice activity in app Given the following "activities" exist: | activity | name | intro | course | idnumber | option | allowmultiple | allowupdate | showresults | | choice | Test single choice name | Test single choice description | C1 | choice1 | Option 1, Option 2, Option 3 | 0 | 0 | 1 | - And I entered the choice activity "Test single choice name" on course "Course 1" as "student1" in the app + And I entered the course "Course 1" as "student1" in the app + And I press "Test single choice name" in the app When I select "Option 1" in the app And I select "Option 2" in the app And I press "Save my choice" in the app @@ -74,7 +75,8 @@ Feature: Test basic usage of choice activity in app Given the following "activities" exist: | activity | name | intro | course | idnumber | option | allowmultiple | allowupdate | showresults | | choice | Test single choice name | Test single choice description | C1 | choice1 | Option 1, Option 2, Option 3 | 0 | 0 | 1 | - And I entered the choice activity "Test single choice name" on course "Course 1" as "student1" in the app + And I entered the course "Course 1" as "student1" in the app + And I press "Test single choice name" in the app When I select "Option 1" in the app And I switch network connection to offline And I select "Option 2" in the app diff --git a/src/addons/mod/data/tests/behat/sync.feature b/src/addons/mod/data/tests/behat/sync.feature index 50dccc49a..94d2e5a9c 100644 --- a/src/addons/mod/data/tests/behat/sync.feature +++ b/src/addons/mod/data/tests/behat/sync.feature @@ -28,9 +28,11 @@ Feature: Users can store entries in database activities when offline and sync wh | data1 | text | Description | Link description | Scenario: Create entry (offline) - Given I entered the data activity "Web links" on course "Course 1" as "student1" in the app + Given I entered the course "Course 1" as "student1" in the app + And I press "Web links" in the app And I switch network connection to offline - And I should find "No entries yet" in the app + Then I should find "No entries yet" in the app + When I press "Add entry" in the app And I set the following fields to these values in the app: | URL | https://moodle.org/ | @@ -39,29 +41,33 @@ Feature: Users can store entries in database activities when offline and sync wh Then I should find "https://moodle.org/" in the app And I should find "Moodle community site" in the app And I should find "This Database has offline data to be synchronised" in the app - And I go back in the app + + When I go back in the app And I switch network connection to wifi And I press "Web links" near "General" in the app - And I should find "https://moodle.org/" in the app + Then I should find "https://moodle.org/" in the app And I should find "Moodle community site" in the app And I should not find "This Database has offline data to be synchronised" in the app Scenario: Update entry (offline) & Delete entry (offline) - Given I entered the data activity "Web links" on course "Course 1" as "student1" in the app - And I should find "No entries yet" in the app - And I press "Add entry" in the app + Given I entered the course "Course 1" as "student1" in the app + And I press "Web links" in the app + Then I should find "No entries yet" in the app + + When I press "Add entry" in the app And I set the following fields to these values in the app: | URL | https://moodle.org/ | | Description | Moodle community site | And I press "Save" near "Web links" in the app - And I should find "https://moodle.org/" in the app + Then I should find "https://moodle.org/" in the app And I should find "Moodle community site" in the app - And I press "Information" in the app + + When I press "Information" in the app And I press "Download" in the app And I wait until the page is ready And I close the popup in the app And I switch network connection to offline - When I press "Actions menu" in the app + And I press "Actions menu" in the app And I press "Edit" in the app And I set the following fields to these values in the app: | URL | https://moodlecloud.com/ | @@ -72,55 +78,64 @@ Feature: Users can store entries in database activities when offline and sync wh And I should find "https://moodlecloud.com/" in the app And I should find "Moodle Cloud" in the app And I should find "This Database has offline data to be synchronised" in the app - And I go back in the app + + When I go back in the app And I switch network connection to wifi And I press "Web links" near "General" in the app - And I should not find "https://moodle.org/" in the app + Then I should not find "https://moodle.org/" in the app And I should not find "Moodle community site" in the app And I should find "https://moodlecloud.com/" in the app And I should find "Moodle Cloud" in the app And I should not find "This Database has offline data to be synchronised" in the app - And I press "Information" in the app + + When I press "Information" in the app And I press "Refresh" in the app And I wait until the page is ready And I switch network connection to offline And I press "Actions menu" in the app And I press "Delete" in the app - And I should find "Are you sure you want to delete this entry?" in the app - And I press "Delete" in the app - And I should find "https://moodlecloud.com/" in the app + Then I should find "Are you sure you want to delete this entry?" in the app + + When I press "Delete" in the app + Then I should find "https://moodlecloud.com/" in the app And I should find "Moodle Cloud" in the app And I should find "This Database has offline data to be synchronised" in the app - And I go back in the app + + When I go back in the app And I switch network connection to wifi And I press "Web links" near "General" in the app - And I should not find "https://moodlecloud.com/" in the app + Then I should not find "https://moodlecloud.com/" in the app And I should not find "Moodle Cloud" in the app And I should not find "This Database has offline data to be synchronised" in the app Scenario: Students can undo deleting entries to a database in the app while offline - Given I entered the data activity "Web links" on course "Course 1" as "student1" in the app - And I should find "No entries yet" in the app - And I press "Add entry" in the app + Given I entered the course "Course 1" as "student1" in the app + And I press "Web links" in the app + Then I should find "No entries yet" in the app + + When I press "Add entry" in the app And I set the following fields to these values in the app: | URL | https://moodle.org/ | | Description | Moodle community site | And I press "Save" near "Web links" in the app - And I should find "https://moodle.org/" in the app + Then I should find "https://moodle.org/" in the app And I should find "Moodle community site" in the app - And I press "Information" in the app + + When I press "Information" in the app And I press "Download" in the app And I wait until the page is ready And I close the popup in the app - When I switch network connection to offline + And I switch network connection to offline And I press "Actions menu" in the app And I press "Delete" in the app - And I should find "Are you sure you want to delete this entry?" in the app - And I press "Delete" in the app - And I should find "https://moodle.org/" in the app + Then I should find "Are you sure you want to delete this entry?" in the app + + When I press "Delete" in the app + Then I should find "https://moodle.org/" in the app And I should find "Moodle community site" in the app And I should find "This Database has offline data to be synchronised" in the app - And I press "Actions menu" in the app + + When I press "Actions menu" in the app And I press "Restore" in the app And I go back in the app And I switch network connection to wifi diff --git a/src/addons/mod/forum/tests/behat/basic_usage.feature b/src/addons/mod/forum/tests/behat/basic_usage.feature index e0aebd9be..61dffc7b6 100755 --- a/src/addons/mod/forum/tests/behat/basic_usage.feature +++ b/src/addons/mod/forum/tests/behat/basic_usage.feature @@ -276,7 +276,8 @@ Feature: Test basic usage of forum activity in app But I should not find "Not sent" in the app Scenario: New discussion offline & Sync Forum - Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app + Given I entered the course "Course 1" as "student1" in the app + And I press "Test forum name" in the app When I switch network connection to offline And I press "Add discussion topic" in the app And I set the following fields to these values in the app: diff --git a/src/addons/mod/forum/tests/behat/groups.feature b/src/addons/mod/forum/tests/behat/groups.feature index 8c7aba79e..3cbdc09ef 100755 --- a/src/addons/mod/forum/tests/behat/groups.feature +++ b/src/addons/mod/forum/tests/behat/groups.feature @@ -35,7 +35,8 @@ Feature: Test usage of forum activity with groups in app | forum2 | Disc vis ALL | Disc vis ALL | Disc vis ALL content | All participants | Scenario: Student can only see the right groups - Given I entered the forum activity "Separate groups forum" on course "Course 1" as "student1" in the app + Given I entered the course "Course 1" as "student1" in the app + And I press "Separate groups forum" in the app Then I should find "Disc sep G1" in the app And I should find "Disc sep ALL" in the app But I should not find "Disc sep G2" in the app @@ -65,7 +66,8 @@ Feature: Test usage of forum activity with groups in app But I should not find "Disc vis G2" in the app Scenario: Teacher can see all groups - Given I entered the forum activity "Separate groups forum" on course "Course 1" as "teacher1" in the app + Given I entered the course "Course 1" as "teacher1" in the app + And I press "Separate groups forum" in the app When I press "Separate groups" in the app Then I should find "All participants" in the app And I should find "Group 1" in the app @@ -107,7 +109,8 @@ Feature: Test usage of forum activity with groups in app But I should not find "Disc vis G2" in the app Scenario: Student can only add discussions in his groups - Given I entered the forum activity "Separate groups forum" on course "Course 1" as "student1" in the app + Given I entered the course "Course 1" as "student1" in the app + And I press "Separate groups forum" in the app When I press "Add discussion topic" in the app And I press "Advanced" in the app Then I should not find "Post a copy to all groups" in the app @@ -165,8 +168,9 @@ Feature: Test usage of forum activity with groups in app Then I should find "My happy subject" in the app Scenario: Teacher can add discussion to any group - Given I entered the forum activity "Separate groups forum" on course "Course 1" as "teacher1" in the app - And I press "Separate groups" in the app + Given I entered the course "Course 1" as "teacher1" in the app + And I press "Separate groups forum" in the app + When I press "Separate groups" in the app And I press "All participants" in the app And I press "Add discussion topic" in the app And I press "Advanced" in the app @@ -279,8 +283,9 @@ Feature: Test usage of forum activity with groups in app Then I should find "My third subject" in the app Scenario: Teacher can post a copy in all groups - Given I entered the forum activity "Separate groups forum" on course "Course 1" as "teacher1" in the app - And I press "Separate groups" in the app + Given I entered the course "Course 1" as "teacher1" in the app + And I press "Separate groups forum" in the app + When I press "Separate groups" in the app And I press "Group 1" in the app And I press "Add discussion topic" in the app And I press "Advanced" in the app diff --git a/src/addons/mod/glossary/tests/behat/basic_usage.feature b/src/addons/mod/glossary/tests/behat/basic_usage.feature index b11156c46..c3ccd71ad 100644 --- a/src/addons/mod/glossary/tests/behat/basic_usage.feature +++ b/src/addons/mod/glossary/tests/behat/basic_usage.feature @@ -52,7 +52,8 @@ Feature: Test basic usage of glossary in app Scenario: Navigate to glossary terms by link (auto-linking) Given the "glossary" filter is "on" - And I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app + And I entered the course "Course 1" as "student1" in the app + And I press "Test glossary" in the app Then the header should be "Test glossary" in the app And I should find "Eggplant" in the app And I should find "Cucumber" in the app diff --git a/src/addons/mod/quiz/tests/behat/can_review.feature b/src/addons/mod/quiz/tests/behat/can_review.feature index 870be3bb2..364d1dfeb 100644 --- a/src/addons/mod/quiz/tests/behat/can_review.feature +++ b/src/addons/mod/quiz/tests/behat/can_review.feature @@ -49,8 +49,9 @@ Feature: Users can only review attempts that are allowed to be reviewed | 1 | True | Scenario: Can review only when the attempt is allowed to be reviewed - Given I entered the quiz activity "Quiz review after immed" on course "Course 1" as "student1" in the app - And I press "Attempt 1" in the app + Given I entered the course "Course 1" as "student1" in the app + And I press "Quiz review after immed" in the app + When I press "Attempt 1" in the app Then I should not be able to press "Review" in the app When I go back in the app diff --git a/src/addons/mod/survey/tests/behat/basic_usage.feature b/src/addons/mod/survey/tests/behat/basic_usage.feature index 894f90f37..5ee4ed3e6 100755 --- a/src/addons/mod/survey/tests/behat/basic_usage.feature +++ b/src/addons/mod/survey/tests/behat/basic_usage.feature @@ -238,8 +238,9 @@ Feature: Test basic usage of survey activity in app Given the following "activities" exist: | activity | name | intro | template | course | idnumber | groupmode | | survey | Test survey critical incidents | Test survey1 | 5 | C1 | survey1 | 0 | - Given I entered the survey activity "Test survey critical incidents" on course "Course 1" as "student1" in the app - And I switch network connection to offline + And I entered the course "Course 1" as "student1" in the app + And I press "Test survey critical incidents" in the app + When I switch network connection to offline And I press "Submit" in the app And I press "OK" in the app Then I should see "This Survey has offline data to be synchronised." diff --git a/src/addons/mod/workshop/tests/behat/basic_usage.feature b/src/addons/mod/workshop/tests/behat/basic_usage.feature index 06441f66d..1199f166f 100644 --- a/src/addons/mod/workshop/tests/behat/basic_usage.feature +++ b/src/addons/mod/workshop/tests/behat/basic_usage.feature @@ -130,7 +130,8 @@ Feature: Test basic usage of workshop activity in app | \mod_workshop\event\course_module_viewed | workshop | Test workshop | Course 1 | Scenario: Prefetch a workshop - Given I entered the workshop activity "workshop" on course "Course 1" as "teacher1" in the app + Given I entered the course "Course 1" as "teacher1" in the app + And I press "workshop" in the app When I press "Information" in the app And I press "Download" in the app And I press "Close" in the app diff --git a/src/core/features/login/tests/behat/showloginform_setting.feature b/src/core/features/login/tests/behat/showloginform_setting.feature index 7da2d8b77..0c6a2581c 100644 --- a/src/core/features/login/tests/behat/showloginform_setting.feature +++ b/src/core/features/login/tests/behat/showloginform_setting.feature @@ -48,7 +48,7 @@ Feature: Test showloginform setting in the app And I press "Moodle Mobile" in the app And I press "Developer options" in the app And I press "Always show login form" in the app - And I go back 4 times in the app + And I go back to the root page in the app And I press "david student" in the app Then the header should be "Reconnect" in the app And I should find "Log in" "ion-button" in the app diff --git a/src/core/features/settings/tests/behat/sync.feature b/src/core/features/settings/tests/behat/sync.feature index 22020eec2..564ab37b1 100644 --- a/src/core/features/settings/tests/behat/sync.feature +++ b/src/core/features/settings/tests/behat/sync.feature @@ -19,7 +19,8 @@ Feature: It synchronise sites properly Scenario: Sync the current site # Add something offline - Given I entered the choice activity "Sync choice" on course "Course 1" as "student1" in the app + Given I entered the course "Course 1" as "student1" in the app + And I press "Sync choice" in the app When I switch network connection to offline And I select "Option 1" in the app And I press "Save my choice" in the app diff --git a/src/core/tests/behat/open_files.feature b/src/core/tests/behat/open_files.feature index 9e7e097b9..9f675e067 100644 --- a/src/core/tests/behat/open_files.feature +++ b/src/core/tests/behat/open_files.feature @@ -21,7 +21,8 @@ Feature: It opens files properly. | resource | Test DOC | Test DOC description | 5 | C1 | A doc.doc | And the following config values are set as admin: | filetypeexclusionlist | rtf,doc | tool_mobile | - And I entered the resource activity "Test TXT" on course "Course 1" as "student1" in the app + And I entered the course "Course 1" as "student1" in the app + And I press "Test TXT" in the app When I press "Open" in the app Then the app should have opened a browser tab with url "^blob:"