From 77c5418f0c3faeb00875b3d999d4bb21c8f7c835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 22 Feb 2022 13:45:37 +0100 Subject: [PATCH 1/2] Improve location of elements and containers --- tests/behat/app_behat_runtime.js | 119 +++++++++++++++++++++++++------ tests/behat/behat_app.php | 16 +++-- 2 files changed, 109 insertions(+), 26 deletions(-) diff --git a/tests/behat/app_behat_runtime.js b/tests/behat/app_behat_runtime.js index 0f61bcc3c..7d04487c1 100644 --- a/tests/behat/app_behat_runtime.js +++ b/tests/behat/app_behat_runtime.js @@ -224,13 +224,13 @@ }; /** - * Finds elements within a given container. + * Finds elements within a given container with exact info. * * @param {HTMLElement} container Parent element to search the element within * @param {string} text Text to look for - * @return {HTMLElement} Elements containing the given text + * @return {Array} Elements containing the given text with exact boolean. */ - const findElementsBasedOnTextWithin = (container, text) => { + const findElementsBasedOnTextWithinWithExact = (container, text) => { const elements = []; const attributesSelector = `[aria-label*="${text}"], a[title*="${text}"], img[alt*="${text}"]`; @@ -238,7 +238,8 @@ if (!isElementVisible(foundByAttributes, container)) continue; - elements.push(foundByAttributes); + const exact = foundByAttributes.title == text || foundByAttributes.alt == text || foundByAttributes.ariaLabel == text; + elements.push({ element: foundByAttributes, exact: exact }); } const treeWalker = document.createTreeWalker( @@ -269,7 +270,7 @@ while (currentNode = treeWalker.nextNode()) { if (currentNode instanceof Text) { if (currentNode.textContent.includes(text)) { - elements.push(currentNode.parentElement); + elements.push({ element: currentNode.parentElement, exact: currentNode.textContent.trim() == text }); } continue; @@ -278,7 +279,7 @@ const labelledBy = currentNode.getAttribute('aria-labelledby'); const labelElement = labelledBy && container.querySelector(`#${labelledBy}`); if (labelElement && labelElement.innerText && labelElement.innerText.includes(text)) { - elements.push(currentNode); + elements.push({ element: currentNode, exact: labelElement.innerText.trim() == text }); continue; } @@ -296,12 +297,13 @@ } if (childNode.matches(attributesSelector)) { - elements.push(childNode); + const exact = childNode.title == text || childNode.alt == text || childNode.ariaLabel == text; + elements.push({ element: childNode, exact: exact}); continue; } - elements.push(...findElementsBasedOnTextWithin(childNode, text)); + elements.push(...findElementsBasedOnTextWithinWithExact(childNode, text)); } } } @@ -309,6 +311,24 @@ return elements; }; + /** + * Finds elements within a given container. + * + * @param {HTMLElement} container Parent element to search the element within. + * @param {string} text Text to look for. + * @return {HTMLElement[]} Elements containing the given text. + */ + const findElementsBasedOnTextWithin = (container, text) => { + const elements = findElementsBasedOnTextWithinWithExact(container, text); + + // Give more relevance to exact matches. + elements.sort((a, b) => { + return b.exact - a.exact; + }); + + return elements.map(element => element.element); + }; + /** * Given a list of elements, get the top ancestors among all of them. * @@ -365,19 +385,78 @@ return getClosestMatching(element.parentElement, selector, container); }; + /** + * Function to find top container element. + * + * @param {string} containerName Whether to search inside the a container name. + * @return {HTMLElement} Found top container element. + */ + const getCurrentTopContainerElement = function (containerName) { + let topContainer; + let containers; + + switch (containerName) { + case 'html': + containers = document.querySelectorAll('html'); + break; + case 'toast': + containers = document.querySelectorAll('ion-app ion-toast.hydrated'); + containers = Array.from(containers).map(container => container.shadowRoot.querySelector('.toast-container')); + break; + case 'alert': + containers = document.querySelectorAll('ion-app ion-alert.hydrated'); + break; + case 'action-sheet': + containers = document.querySelectorAll('ion-app ion-action-sheet.hydrated'); + break; + case 'modal': + containers = document.querySelectorAll('ion-app ion-modal.hydrated'); + break; + case 'popover': + containers = document.querySelectorAll('ion-app ion-popover.hydrated'); + break; + default: + // Other containerName or not implemented. + const containerSelector = 'ion-alert, ion-popover, ion-action-sheet, ion-modal, page-core-mainmenu, ion-app'; + containers = document.querySelectorAll(containerSelector); + } + + if (containers.length > 0) { + // Get the one with more zIndex. + topContainer = Array.from(containers).reduce((a, b) => { + return getComputedStyle(a).zIndex > getComputedStyle(b).zIndex ? a : b; + }, containers[0]); + } + + if (containerName == 'page' || containerName == 'split-view content') { + // Find non hidden pages inside the container. + let pageContainers = topContainer.querySelectorAll('.ion-page:not(.ion-page-hidden)'); + pageContainers = Array.from(pageContainers).filter((page) => { + return !page.closest('.ion-page.ion-page-hidden'); + }); + + if (pageContainers.length > 0) { + // Get the more general one to avoid failing. + topContainer = pageContainers[0]; + } + + if (containerName == 'split-view content') { + topContainer = topContainer.querySelector('core-split-view ion-router-outlet'); + } + } + + return topContainer; + } + /** * Function to find elements based on their text or Aria label. * * @param {object} locator Element locator. - * @param {boolean} insideSplitView Whether to search only inside the split view contents. + * @param {string} containerName Whether to search only inside a specific container. * @return {HTMLElement} Found elements */ - const findElementsBasedOnText = function(locator, insideSplitView) { - let topContainer = document.querySelector('ion-alert, ion-popover, ion-action-sheet, core-ion-tab.show-tab ion-page.show-page, ion-page.show-page, html'); - - if (insideSplitView) { - topContainer = topContainer.querySelector('core-split-view ion-router-outlet'); - } + const findElementsBasedOnText = function(locator, containerName) { + let topContainer = getCurrentTopContainerElement(containerName); let container = topContainer; @@ -544,20 +623,20 @@ * Function to find an arbitrary element based on its text or aria label. * * @param {object} locator Element locator. - * @param {boolean} insideSplitView Whether to search only inside the split view contents. + * @param {string} containerName Whether to search only inside a specific container content. * @return {string} OK if successful, or ERROR: followed by message */ - const behatFind = function(locator, insideSplitView) { - log('Action - Find', { locator, insideSplitView }); + const behatFind = function(locator, containerName) { + log('Action - Find', { locator, containerName }); try { - const element = findElementsBasedOnText(locator, insideSplitView)[0]; + const element = findElementsBasedOnText(locator, containerName)[0]; if (!element) { return 'ERROR: No matches for text'; } - log('Action - Found', { locator, insideSplitView, element }); + log('Action - Found', { locator, containerName, element }); return 'OK'; } catch (error) { return 'ERROR: ' + error.message; diff --git a/tests/behat/behat_app.php b/tests/behat/behat_app.php index e9a1901ad..d056fc1be 100644 --- a/tests/behat/behat_app.php +++ b/tests/behat/behat_app.php @@ -154,18 +154,22 @@ class behat_app extends behat_base { /** * Finds elements in the app. * - * @Then /^I should( not)? find (".+")( inside the split-view content)? in the app$/ + * @Then /^I should( not)? find (".+")( inside the .+)? in the app$/ * @param bool $not * @param string $locator - * @param bool $insidesplitview + * @param string $containerName */ - public function i_find_in_the_app(bool $not, string $locator, bool $insidesplitview = false) { + public function i_find_in_the_app(bool $not, string $locator, string $containerName = '') { $locator = $this->parse_element_locator($locator); $locatorjson = json_encode($locator); - $insidesplitviewjson = json_encode($insidesplitview); + if (!empty($containerName)) { + preg_match('/^ inside the (.+)$/', $containerName, $matches); + $containerName = $matches[1]; + } + $containerName = json_encode($containerName); - $this->spin(function() use ($not, $locatorjson, $insidesplitviewjson) { - $result = $this->evaluate_script("return window.behat.find($locatorjson, $insidesplitviewjson);"); + $this->spin(function() use ($not, $locatorjson, $containerName) { + $result = $this->evaluate_script("return window.behat.find($locatorjson, $containerName);"); if ($not && $result === 'OK') { throw new DriverException('Error, found an item that should not be found'); From 338258a4adcfdc63e20cef178bde232769f67baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 22 Feb 2022 11:00:18 +0100 Subject: [PATCH 2/2] Fix broken behat tests --- .../tests/behat/app_basic_usage.feature | 2 +- mod/choice/tests/behat/app_basic_usage.feature | 6 +++--- mod/course/tests/behat/app_basic_usage.feature | 4 +--- mod/courses/tests/behat/app_basic_usage.feature | 5 ++--- mod/forum/tests/behat/app_basic_usage.feature | 16 ++++++++-------- mod/quiz/tests/behat/app_basic_usage.feature | 2 +- tests/behat/navigation_externallinks.feature | 3 ++- tests/behat/settings_navigation.feature | 4 ++-- 8 files changed, 20 insertions(+), 22 deletions(-) diff --git a/mod/assignment/tests/behat/app_basic_usage.feature b/mod/assignment/tests/behat/app_basic_usage.feature index e3b536b98..1b084985a 100755 --- a/mod/assignment/tests/behat/app_basic_usage.feature +++ b/mod/assignment/tests/behat/app_basic_usage.feature @@ -120,7 +120,7 @@ Feature: Test basic usage of assignment activity in app When I switch offline mode to "false" And I press the back button in the app And I press "assignment1" in the app - And I press "Display options" in the app + And I press "Information" in the app And I press "Refresh" in the app Then I should find "Submitted for grading" in the app But I should not find "This Assignment has offline data to be synchronised." in the app diff --git a/mod/choice/tests/behat/app_basic_usage.feature b/mod/choice/tests/behat/app_basic_usage.feature index 2f0fd786d..a51feb430 100755 --- a/mod/choice/tests/behat/app_basic_usage.feature +++ b/mod/choice/tests/behat/app_basic_usage.feature @@ -93,7 +93,7 @@ Feature: Test basic usage of choice activity in app And I press "Test single choice name" in the app Then I should find "Test single choice description" in the app - When I press "Display options" in the app + When I press "Information" in the app And I press "Refresh" in the app Then I should find "Option 1: 0" in the app And I should find "Option 2: 1" in the app @@ -132,7 +132,7 @@ Feature: Test basic usage of choice activity in app | choice | Test multi choice name | Test multi choice description | C1 | choice2 | Option 1, Option 2, Option 3 | 1 | 1 | 1 | | choice | Test single choice name | Test single choice description | C1 | choice1 | Option 1, Option 2, Option 3 | 0 | 0 | 1 | When I enter the course "Course 1" as "student1" in the app - And I press "Display options" in the app + And I press "Course summary" in the app And I press "Course downloads" in the app And I press "Download" within "Test single choice name" "ion-item" in the app Then I should find "Downloaded" within "Test single choice name" "ion-item" in the app @@ -182,7 +182,7 @@ Feature: Test basic usage of choice activity in app And I press "Choice name" in the app Then I should find "Test choice description" in the app - When I press "Display options" in the app + When I press "Information" in the app And I press "Open in browser" in the app And I switch to the browser tab opened by the app And I log in as "teacher1" diff --git a/mod/course/tests/behat/app_basic_usage.feature b/mod/course/tests/behat/app_basic_usage.feature index 5ec5bab42..885796a8a 100755 --- a/mod/course/tests/behat/app_basic_usage.feature +++ b/mod/course/tests/behat/app_basic_usage.feature @@ -402,7 +402,6 @@ Feature: Test basic usage of one course in app Scenario: Self enrol Given I enter the course "Course 1" as "teacher1" in the app - And I press "Display options" in the app And I press "Course summary" in the app And I press "Open in browser" in the app And I switch to the browser tab opened by the app @@ -437,7 +436,6 @@ Feature: Test basic usage of one course in app Scenario: Guest access Given I enter the course "Course 1" as "teacher1" in the app - And I press "Display options" in the app And I press "Course summary" in the app And I press "Open in browser" in the app And I switch to the browser tab opened by the app @@ -451,7 +449,7 @@ Feature: Test basic usage of one course in app And I press "Site home" in the app And I press "Available courses" in the app And I press "Course 1" in the app - Then I should find "Download course" in the app + Then I should find "Course downloads" in the app And I should find "Course" in the app When I press "Course" "ion-button" in the app diff --git a/mod/courses/tests/behat/app_basic_usage.feature b/mod/courses/tests/behat/app_basic_usage.feature index df69a7f0d..7c3ea7d4f 100755 --- a/mod/courses/tests/behat/app_basic_usage.feature +++ b/mod/courses/tests/behat/app_basic_usage.feature @@ -103,12 +103,11 @@ Feature: Test basic usage of courses in app # Configure assignment as teacher When I enter the course "Course 1" as "teacher1" in the app And I press "assignment" in the app - And I press "Display options" in the app + And I press "Information" in the app And I press "Open in browser" in the app And I switch to the browser tab opened by the app And I log in as "teacher1" - And I press "Actions menu" - And I follow "Settings" + And I navigate to "Settings" in current page administration And I press "Expand all" And I click on "duedate[enabled]" "checkbox" And I click on "gradingduedate[enabled]" "checkbox" diff --git a/mod/forum/tests/behat/app_basic_usage.feature b/mod/forum/tests/behat/app_basic_usage.feature index 6e7a2b55d..032b16fd2 100755 --- a/mod/forum/tests/behat/app_basic_usage.feature +++ b/mod/forum/tests/behat/app_basic_usage.feature @@ -182,7 +182,7 @@ Feature: Test basic usage of forum activity in app Then I should find "Auto-test" in the app When I press the back button in the app - And I press "Display options" in the app + And I press "Course summary" in the app And I press "Course downloads" in the app And I press "Download" within "Test forum name" "ion-item" in the app And I press the back button in the app @@ -209,7 +209,7 @@ Feature: Test basic usage of forum activity in app Then I should find "Auto-test" in the app When I press the back button in the app - And I press "Display options" in the app + And I press "Course summary" in the app And I press "Course downloads" in the app And I press "Download" within "Test forum name" "ion-item" in the app And I press the back button in the app @@ -256,7 +256,7 @@ Feature: Test basic usage of forum activity in app And I switch offline mode to "true" And I press "None" near "test2" in the app And I press "0" near "Cancel" in the app - Then I should find "Data stored in the device because it couldn't be sent. It will be sent automatically later." in the app + Then I should find "Data stored in the device because it couldn't be sent. It will be sent automatically later." inside the toast in the app And I should find "Average of ratings: -" in the app And I should find "Average of ratings: 1" in the app @@ -264,7 +264,7 @@ Feature: Test basic usage of forum activity in app And I press the back button in the app Then I should find "This Forum has offline data to be synchronised." in the app - When I press "Display options" near "Test forum name" in the app + When I press "Information" near "Test forum name" in the app And I press "Synchronise now" in the app Then I should not find "This Forum has offline data to be synchronised." in the app @@ -288,7 +288,7 @@ Feature: Test basic usage of forum activity in app And I set the field "Message" to "DiscussionMessage" in the app And I press "Post to forum" in the app And I press the back button in the app - And I press "Display options" in the app + And I press "Course summary" in the app And I press "Course downloads" in the app And I press "Download" within "Test forum name" "ion-item" in the app And I press the back button in the app @@ -326,8 +326,8 @@ Feature: Test basic usage of forum activity in app When I switch offline mode to "false" And I press the back button in the app And I press "Test forum name" in the app - And I press "Display options" near "Test forum name" in the app - And I press "Refresh discussions" in the app + And I press "Information" near "Test forum name" in the app + And I press "Refresh" in the app And I press "DiscussionSubject" near "Sort by last post creation date in descending order" in the app Then I should find "DiscussionSubject" in the app And I should find "DiscussionMessage" in the app @@ -367,7 +367,7 @@ Feature: Test basic usage of forum activity in app Then I should find "DiscussionSubject 1" in the app When I press the back button in the app - And I press "Display options" in the app + And I press "Course summary" in the app And I press "Course downloads" in the app And I press "Download" within "Test forum name" "ion-item" in the app Then I should find "Downloaded" within "Test forum name" "ion-item" in the app diff --git a/mod/quiz/tests/behat/app_basic_usage.feature b/mod/quiz/tests/behat/app_basic_usage.feature index 493b7acf0..f9f654800 100755 --- a/mod/quiz/tests/behat/app_basic_usage.feature +++ b/mod/quiz/tests/behat/app_basic_usage.feature @@ -156,7 +156,7 @@ Feature: Attempt a quiz in app When I enter the course "Course 1" as "teacher1" in the app And I press "Quiz 1" in the app - And I press "Display options" in the app + And I press "Information" in the app And I press "Open in browser" in the app And I switch to the browser tab opened by the app And I log in as "teacher1" diff --git a/tests/behat/navigation_externallinks.feature b/tests/behat/navigation_externallinks.feature index febd346b5..4abf963fe 100644 --- a/tests/behat/navigation_externallinks.feature +++ b/tests/behat/navigation_externallinks.feature @@ -32,11 +32,12 @@ Feature: It opens external links properly. When I close the browser tab opened by the app And I press the back button in the app - And I press the page menu button in the app + And I press "Information" in the app And I press "Open in browser" in the app Then the app should have opened a browser tab When I close the browser tab opened by the app + When I close the popup in the app And I press "Forum topic" in the app And I press "moodle.org" in the app And I select "Don't show again." in the app diff --git a/tests/behat/settings_navigation.feature b/tests/behat/settings_navigation.feature index ae4779ae8..d5cc521e2 100644 --- a/tests/behat/settings_navigation.feature +++ b/tests/behat/settings_navigation.feature @@ -44,7 +44,7 @@ Feature: It navigates properly within settings. When I press the back button in the app And I press "Manage downloads" in the app - Then I should find "Total space usage" in the app + Then I should find "Total space used" in the app Scenario: Tablet navigation Given I enter the app @@ -79,4 +79,4 @@ Feature: It navigates properly within settings. When I press "Manage downloads" in the app Then "Manage downloads" should be selected in the app - And I should find "Total space usage" in the app + And I should find "Total space used" in the app