diff --git a/local_moodleappbehat/tests/behat/behat_app.php b/local_moodleappbehat/tests/behat/behat_app.php index cbb164b32..15e4b0eef 100644 --- a/local_moodleappbehat/tests/behat/behat_app.php +++ b/local_moodleappbehat/tests/behat/behat_app.php @@ -936,6 +936,38 @@ class behat_app extends behat_app_helper { }); } + /** + * Check that the app opened a url. + * + * @Then /^the app should( not)? have opened url "([^"]+)"(?: with contents "([^"]+)")?(?: (once|\d+ times))?$/ + * @param bool $not Whether to check if the app did not open the url + * @param string $urlpattern Url pattern + * @param string $contents Url contents + * @param string $times How many times the url should have been opened + */ + public function the_app_should_have_opened_url(bool $not, string $urlpattern, ?string $contents = null, ?string $times = null) { + if (is_null($times) || $times === 'once') { + $times = 1; + } else { + $times = intval(substr($times, 0, strlen($times) - 6)); + } + + $this->spin(function() use ($not, $urlpattern, $contents, $times) { + $result = $this->runtime_js("hasOpenedUrl('$urlpattern', '$contents', $times)"); + + // TODO process times + if ($not && $result === 'OK') { + throw new DriverException('Error, an url was opened that should not have'); + } + + if (!$not && $result !== 'OK') { + throw new DriverException('Error asserting that url was opened - ' . $result); + } + + return true; + }); + } + /** * Switches to a newly-opened browser tab. * diff --git a/src/core/tests/behat/open_files.feature b/src/core/tests/behat/open_files.feature index 6e4fd1f7b..fcd8c19dc 100644 --- a/src/core/tests/behat/open_files.feature +++ b/src/core/tests/behat/open_files.feature @@ -35,25 +35,20 @@ Feature: It opens files properly. Then I should find "This file may not work as expected on this device" in the app When I press "Open file" in the app - Then the app should have opened a browser tab with url "^blob:" + Then the app should have opened url "^blob:" with contents "Test resource A rtf.rtf file" once - When I switch to the browser tab opened by the app - Then I should see "Test resource A rtf.rtf file" - - When I close the browser tab opened by the app - And I press "Open" in the app + When I press "Open" in the app Then I should find "This file may not work as expected on this device" in the app When I select "Don't show again." in the app And I press "Open file" in the app - Then the app should have opened a browser tab with url "^blob:" + Then the app should have opened url "^blob:" with contents "Test resource A rtf.rtf file" 2 times - When I close the browser tab opened by the app - And I press "Open" in the app - Then the app should have opened a browser tab with url "^blob:" + When I press "Open" in the app + Then I should not find "This file may not work as expected on this device" in the app + And the app should have opened url "^blob:" with contents "Test resource A rtf.rtf file" 3 times - When I close the browser tab opened by the app - And I press the back button in the app + When I press the back button in the app And I press "Test DOC" in the app And I press "Open" in the app Then I should find "This file may not work as expected on this device" in the app diff --git a/src/core/utils/types.ts b/src/core/utils/types.ts index d0131a35e..30a311d82 100644 --- a/src/core/utils/types.ts +++ b/src/core/utils/types.ts @@ -23,6 +23,12 @@ export type Constructor = { new(...args: any[]): T }; */ export type Equal = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? true : false; +/** + * Helper to get closure args. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type GetClosureArgs = T extends (...args: infer TArgs) => any ? TArgs : never; + /** * Helper type to flatten complex types. */ diff --git a/src/testing/services/behat-runtime.ts b/src/testing/services/behat-runtime.ts index aa4945adc..ea9ad550d 100644 --- a/src/testing/services/behat-runtime.ts +++ b/src/testing/services/behat-runtime.ts @@ -31,6 +31,7 @@ import { CoreNavigator, CoreNavigatorService } from '@services/navigator'; import { CoreSwipeNavigationDirective } from '@directives/swipe-navigation'; import { Swiper } from 'swiper'; import { LocalNotificationsMock } from '@features/emulator/services/local-notifications'; +import { GetClosureArgs } from '@/core/utils/types'; /** * Behat runtime servive with public API. @@ -39,6 +40,10 @@ import { LocalNotificationsMock } from '@features/emulator/services/local-notifi export class TestingBehatRuntimeService { protected initialized = false; + protected openedUrls: { + args: GetClosureArgs; + contents?: string; + }[] = []; get cronDelegate(): CoreCronDelegateService { return CoreCronDelegate.instance; @@ -90,6 +95,14 @@ export class TestingBehatRuntimeService { document.cookie = 'MoodleAppConfig=' + JSON.stringify(options.configOverrides); CoreConfig.patchEnvironment(options.configOverrides, { patchDefault: true }); } + + // Spy on window.open. + const originalOpen = window.open.bind(window); + window.open = (...args) => { + this.openedUrls.push({ args }); + + return originalOpen(...args); + }; } /** @@ -274,6 +287,45 @@ export class TestingBehatRuntimeService { } } + /** + * Check whether the given url has been opened in the app. + * + * @param urlPattern Url pattern. + * @param contents Url contents. + * @param times How many times it should have been opened. + * @returns OK if successful, or ERROR: followed by message + */ + async hasOpenedUrl(urlPattern: string, contents: string, times: number): Promise { + const urlRegExp = new RegExp(urlPattern); + const urlMatches = await Promise.all(this.openedUrls.map(async (openedUrl) => { + const renderedUrl = openedUrl.args[0]?.toString() ?? ''; + + if (!urlRegExp.test(renderedUrl)) { + return false; + } + + if (contents && !('contents' in openedUrl)) { + const response = await fetch(renderedUrl); + + openedUrl.contents = await response.text(); + } + + if (contents && contents !== openedUrl.contents) { + return false; + } + + return true; + })); + + if (urlMatches.filter(matches => !!matches).length === times) { + return 'OK'; + } + + return times === 1 + ? `ERROR: Url matching '${urlPattern}' with '${contents}' contents has not been opened once` + : `ERROR: Url matching '${urlPattern}' with '${contents}' contents has not been opened ${times} times`; + } + /** * Load more items form an active list with infinite loader. *