Improve location of elements and containers

This commit is contained in:
Pau Ferrer Ocaña 2022-02-22 13:45:37 +01:00
parent 00f7cda5bf
commit 77c5418f0c
2 changed files with 109 additions and 26 deletions

View File

@ -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;

View File

@ -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');