MOBILE-3810 behat: Remove legacy code
parent
0b774fc53c
commit
fc96a23e8a
|
@ -1,7 +1,7 @@
|
||||||
(function() {
|
(function() {
|
||||||
// Set up the M object - only pending_js is implemented.
|
// Set up the M object - only pending_js is implemented.
|
||||||
window.M = window.M ? window.M : {};
|
window.M = window.M ? window.M : {};
|
||||||
var M = window.M;
|
const M = window.M;
|
||||||
M.util = M.util ? M.util : {};
|
M.util = M.util ? M.util : {};
|
||||||
M.util.pending_js = M.util.pending_js ? M.util.pending_js : []; // eslint-disable-line camelcase
|
M.util.pending_js = M.util.pending_js ? M.util.pending_js : []; // eslint-disable-line camelcase
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@
|
||||||
*
|
*
|
||||||
* @param {string} text Information to log
|
* @param {string} text Information to log
|
||||||
*/
|
*/
|
||||||
var log = function() {
|
const log = function() {
|
||||||
var now = new Date();
|
const now = new Date();
|
||||||
var nowFormatted = String(now.getHours()).padStart(2, '0') + ':' +
|
const nowFormatted = String(now.getHours()).padStart(2, '0') + ':' +
|
||||||
String(now.getMinutes()).padStart(2, '0') + ':' +
|
String(now.getMinutes()).padStart(2, '0') + ':' +
|
||||||
String(now.getSeconds()).padStart(2, '0') + '.' +
|
String(now.getSeconds()).padStart(2, '0') + '.' +
|
||||||
String(now.getMilliseconds()).padStart(2, '0');
|
String(now.getMilliseconds()).padStart(2, '0');
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
* @param {function} target function to run
|
* @param {function} target function to run
|
||||||
* @param {number} count Number of times to do setTimeout (leave blank for 10)
|
* @param {number} count Number of times to do setTimeout (leave blank for 10)
|
||||||
*/
|
*/
|
||||||
var runAfterEverything = function(target, count) {
|
const runAfterEverything = function(target, count) {
|
||||||
if (count === undefined) {
|
if (count === undefined) {
|
||||||
count = 10;
|
count = 10;
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
*
|
*
|
||||||
* @param {string} key Key to add
|
* @param {string} key Key to add
|
||||||
*/
|
*/
|
||||||
var addPending = function(key) {
|
const addPending = function(key) {
|
||||||
// Add a special DELAY entry whenever another entry is added.
|
// Add a special DELAY entry whenever another entry is added.
|
||||||
if (window.M.util.pending_js.length == 0) {
|
if (window.M.util.pending_js.length == 0) {
|
||||||
window.M.util.pending_js.push('DELAY');
|
window.M.util.pending_js.push('DELAY');
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
*
|
*
|
||||||
* @param {string} key Key to remove
|
* @param {string} key Key to remove
|
||||||
*/
|
*/
|
||||||
var removePending = function(key) {
|
const removePending = function(key) {
|
||||||
// Remove the key immediately.
|
// Remove the key immediately.
|
||||||
window.M.util.pending_js = window.M.util.pending_js.filter(function(x) { // eslint-disable-line camelcase
|
window.M.util.pending_js = window.M.util.pending_js.filter(function(x) { // eslint-disable-line camelcase
|
||||||
return x !== key;
|
return x !== key;
|
||||||
|
@ -86,17 +86,17 @@
|
||||||
/**
|
/**
|
||||||
* Adds a pending key to the array, but removes it after some setTimeouts finish.
|
* Adds a pending key to the array, but removes it after some setTimeouts finish.
|
||||||
*/
|
*/
|
||||||
var addPendingDelay = function() {
|
const addPendingDelay = function() {
|
||||||
addPending('...');
|
addPending('...');
|
||||||
removePending('...');
|
removePending('...');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Override XMLHttpRequest to mark things pending while there is a request waiting.
|
// Override XMLHttpRequest to mark things pending while there is a request waiting.
|
||||||
var realOpen = XMLHttpRequest.prototype.open;
|
const realOpen = XMLHttpRequest.prototype.open;
|
||||||
var requestIndex = 0;
|
let requestIndex = 0;
|
||||||
XMLHttpRequest.prototype.open = function() {
|
XMLHttpRequest.prototype.open = function() {
|
||||||
var index = requestIndex++;
|
const index = requestIndex++;
|
||||||
var key = 'httprequest-' + index;
|
const key = 'httprequest-' + index;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Add to the list of pending requests.
|
// Add to the list of pending requests.
|
||||||
|
@ -108,20 +108,20 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
return realOpen.apply(this, arguments);
|
return realOpen.apply(this, arguments);
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
removePending(key);
|
removePending(key);
|
||||||
throw e;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var waitingBlocked = false;
|
let waitingBlocked = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a loading spinner is present and visible; if so, adds it to the pending array
|
* Checks if a loading spinner is present and visible; if so, adds it to the pending array
|
||||||
* (and if not, removes it).
|
* (and if not, removes it).
|
||||||
*/
|
*/
|
||||||
var checkUIBlocked = function() {
|
const checkUIBlocked = function() {
|
||||||
var blocked = document.querySelector('span.core-loading-spinner, ion-loading, .click-block-active');
|
const blocked = document.querySelector('span.core-loading-spinner, ion-loading, .click-block-active');
|
||||||
if (blocked && blocked.offsetParent) {
|
if (blocked && blocked.offsetParent) {
|
||||||
if (!waitingBlocked) {
|
if (!waitingBlocked) {
|
||||||
addPending('blocked');
|
addPending('blocked');
|
||||||
|
@ -142,8 +142,8 @@
|
||||||
// of the animations are set to 500ms so we allow it to continue from 500ms after any DOM
|
// of the animations are set to 500ms so we allow it to continue from 500ms after any DOM
|
||||||
// change.
|
// change.
|
||||||
|
|
||||||
var recentMutation = false;
|
let recentMutation = false;
|
||||||
var lastMutation;
|
let lastMutation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called from the mutation callback to remove the pending tag after 500ms if nothing else
|
* Called from the mutation callback to remove the pending tag after 500ms if nothing else
|
||||||
|
@ -152,7 +152,7 @@
|
||||||
* This will be called after 500ms, then every 100ms until there have been no mutation events
|
* This will be called after 500ms, then every 100ms until there have been no mutation events
|
||||||
* for 500ms.
|
* for 500ms.
|
||||||
*/
|
*/
|
||||||
var pollRecentMutation = function() {
|
const pollRecentMutation = function() {
|
||||||
if (Date.now() - lastMutation > 500) {
|
if (Date.now() - lastMutation > 500) {
|
||||||
recentMutation = false;
|
recentMutation = false;
|
||||||
removePending('dom-mutation');
|
removePending('dom-mutation');
|
||||||
|
@ -164,7 +164,7 @@
|
||||||
/**
|
/**
|
||||||
* Mutation callback, called whenever the DOM is mutated.
|
* Mutation callback, called whenever the DOM is mutated.
|
||||||
*/
|
*/
|
||||||
var mutationCallback = function() {
|
const mutationCallback = function() {
|
||||||
lastMutation = Date.now();
|
lastMutation = Date.now();
|
||||||
if (!recentMutation) {
|
if (!recentMutation) {
|
||||||
recentMutation = true;
|
recentMutation = true;
|
||||||
|
@ -176,7 +176,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set listener using the mutation callback.
|
// Set listener using the mutation callback.
|
||||||
var observer = new MutationObserver(mutationCallback);
|
const observer = new MutationObserver(mutationCallback);
|
||||||
observer.observe(document, {attributes: true, childList: true, subtree: true});
|
observer.observe(document, {attributes: true, childList: true, subtree: true});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -186,7 +186,7 @@
|
||||||
* @param {HTMLElement} container Container
|
* @param {HTMLElement} container Container
|
||||||
* @returns {boolean} Whether the element is visible or not
|
* @returns {boolean} Whether the element is visible or not
|
||||||
*/
|
*/
|
||||||
var isElementVisible = (element, container) => {
|
const isElementVisible = (element, container) => {
|
||||||
if (element.getAttribute('aria-hidden') === 'true' || getComputedStyle(element).display === 'none')
|
if (element.getAttribute('aria-hidden') === 'true' || getComputedStyle(element).display === 'none')
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -207,7 +207,7 @@
|
||||||
* @param {HTMLElement} container Container
|
* @param {HTMLElement} container Container
|
||||||
* @returns {boolean} Whether the element is selected or not
|
* @returns {boolean} Whether the element is selected or not
|
||||||
*/
|
*/
|
||||||
var isElementSelected = (element, container) => {
|
const isElementSelected = (element, container) => {
|
||||||
const ariaCurrent = element.getAttribute('aria-current');
|
const ariaCurrent = element.getAttribute('aria-current');
|
||||||
if (
|
if (
|
||||||
(ariaCurrent && ariaCurrent !== 'false') ||
|
(ariaCurrent && ariaCurrent !== 'false') ||
|
||||||
|
@ -223,31 +223,6 @@
|
||||||
return isElementSelected(parentElement, container);
|
return isElementSelected(parentElement, container);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic shared function to find possible xpath matches within the document, that are visible,
|
|
||||||
* and then process them using a callback function.
|
|
||||||
*
|
|
||||||
* @param {string} xpath Xpath to use
|
|
||||||
* @param {function} process Callback function that handles each matched node
|
|
||||||
*/
|
|
||||||
var findPossibleMatches = function(xpath, process) {
|
|
||||||
var select = 'ion-alert, ion-popover, ion-action-sheet, core-ion-tab.show-tab ion-page.show-page, ion-page.show-page, html';
|
|
||||||
var parent = document.querySelector(select);
|
|
||||||
var matches = document.evaluate(xpath, parent || document);
|
|
||||||
while (true) {
|
|
||||||
var match = matches.iterateNext();
|
|
||||||
if (!match) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Skip invisible text nodes.
|
|
||||||
if (!match.offsetParent) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
process(match);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds elements within a given container.
|
* Finds elements within a given container.
|
||||||
*
|
*
|
||||||
|
@ -255,7 +230,7 @@
|
||||||
* @param {string} text Text to look for
|
* @param {string} text Text to look for
|
||||||
* @return {HTMLElement} Elements containing the given text
|
* @return {HTMLElement} Elements containing the given text
|
||||||
*/
|
*/
|
||||||
var findElementsBasedOnTextWithin = (container, text) => {
|
const findElementsBasedOnTextWithin = (container, text) => {
|
||||||
const elements = [];
|
const elements = [];
|
||||||
const attributesSelector = `[aria-label*="${text}"], a[title*="${text}"], img[alt*="${text}"]`;
|
const attributesSelector = `[aria-label*="${text}"], a[title*="${text}"], img[alt*="${text}"]`;
|
||||||
|
|
||||||
|
@ -342,7 +317,7 @@
|
||||||
* @param {Array} elements Elements list.
|
* @param {Array} elements Elements list.
|
||||||
* @return {Array} Top ancestors.
|
* @return {Array} Top ancestors.
|
||||||
*/
|
*/
|
||||||
var getTopAncestors = function(elements) {
|
const getTopAncestors = function(elements) {
|
||||||
const uniqueElements = new Set(elements);
|
const uniqueElements = new Set(elements);
|
||||||
|
|
||||||
for (const element of uniqueElements) {
|
for (const element of uniqueElements) {
|
||||||
|
@ -366,7 +341,7 @@
|
||||||
* @param {HTMLElement} element Element.
|
* @param {HTMLElement} element Element.
|
||||||
* @return {HTMLElement} Parent element.
|
* @return {HTMLElement} Parent element.
|
||||||
*/
|
*/
|
||||||
var getParentElement = function(element) {
|
const getParentElement = function(element) {
|
||||||
return element.parentElement || (element.getRootNode() && element.getRootNode().host) || null;
|
return element.parentElement || (element.getRootNode() && element.getRootNode().host) || null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -376,7 +351,7 @@
|
||||||
* @param {object} locator Element locator.
|
* @param {object} locator Element locator.
|
||||||
* @return {HTMLElement} Found elements
|
* @return {HTMLElement} Found elements
|
||||||
*/
|
*/
|
||||||
var findElementsBasedOnText = function(locator) {
|
const findElementsBasedOnText = function(locator) {
|
||||||
const topContainer = document.querySelector('ion-alert, ion-popover, ion-action-sheet, core-ion-tab.show-tab ion-page.show-page, ion-page.show-page, html');
|
const topContainer = document.querySelector('ion-alert, ion-popover, ion-action-sheet, core-ion-tab.show-tab ion-page.show-page, ion-page.show-page, html');
|
||||||
let container = topContainer;
|
let container = topContainer;
|
||||||
|
|
||||||
|
@ -417,48 +392,39 @@
|
||||||
*
|
*
|
||||||
* @param {HTMLElement} element Element to press.
|
* @param {HTMLElement} element Element to press.
|
||||||
*/
|
*/
|
||||||
var pressElement = function(element) {
|
const pressElement = function(element) {
|
||||||
if (window.BehatMoodleAppLegacy) {
|
// Scroll the item into view.
|
||||||
var mainContent = getNavCtrl().getActive().contentRef().nativeElement;
|
element.scrollIntoView(false);
|
||||||
var rect = element.getBoundingClientRect();
|
|
||||||
|
|
||||||
// Scroll the item into view.
|
const rect = element.getBoundingClientRect();
|
||||||
mainContent.scrollTo(rect.x, rect.y);
|
|
||||||
|
|
||||||
// Simulate a mouse click on the button.
|
// Simulate a mouse click on the button.
|
||||||
var eventOptions = {
|
const eventOptions = {
|
||||||
clientX: rect.left + rect.width / 2,
|
clientX: rect.left + rect.width / 2,
|
||||||
clientY: rect.top + rect.height / 2,
|
clientY: rect.top + rect.height / 2,
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
view: window,
|
view: window,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
};
|
};
|
||||||
setTimeout(() => element.dispatchEvent(new MouseEvent('mousedown', eventOptions)), 0);
|
|
||||||
setTimeout(() => element.dispatchEvent(new MouseEvent('mouseup', eventOptions)), 0);
|
|
||||||
setTimeout(() => element.dispatchEvent(new MouseEvent('click', eventOptions)), 0);
|
|
||||||
} else {
|
|
||||||
// Scroll the item into view.
|
|
||||||
element.scrollIntoView();
|
|
||||||
|
|
||||||
// Events don't bubble up across Shadow DOM boundaries, and some buttons
|
// Events don't bubble up across Shadow DOM boundaries, and some buttons
|
||||||
// may not work without doing this.
|
// may not work without doing this.
|
||||||
const parentElement = getParentElement(element);
|
const parentElement = getParentElement(element);
|
||||||
|
|
||||||
if (parentElement && parentElement.matches('ion-button, ion-back-button')) {
|
if (parentElement && parentElement.matches('ion-button, ion-back-button')) {
|
||||||
element = parentElement;
|
element = parentElement;
|
||||||
}
|
|
||||||
|
|
||||||
// There are some buttons in the app that don't respond to click events, for example
|
|
||||||
// buttons using the core-supress-events directive. That's why we need to send both
|
|
||||||
// click and mouse events.
|
|
||||||
element.dispatchEvent(new MouseEvent('mousedown', eventOptions));
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
element.dispatchEvent(new MouseEvent('mouseup', eventOptions));
|
|
||||||
element.click();
|
|
||||||
}, 300);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There are some buttons in the app that don't respond to click events, for example
|
||||||
|
// buttons using the core-supress-events directive. That's why we need to send both
|
||||||
|
// click and mouse events.
|
||||||
|
element.dispatchEvent(new MouseEvent('mousedown', eventOptions));
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
element.dispatchEvent(new MouseEvent('mouseup', eventOptions));
|
||||||
|
element.click();
|
||||||
|
}, 300);
|
||||||
|
|
||||||
// Mark busy until the button click finishes processing.
|
// Mark busy until the button click finishes processing.
|
||||||
addPendingDelay();
|
addPendingDelay();
|
||||||
};
|
};
|
||||||
|
@ -469,69 +435,30 @@
|
||||||
* @param {string} button Type of button to press
|
* @param {string} button Type of button to press
|
||||||
* @return {string} OK if successful, or ERROR: followed by message
|
* @return {string} OK if successful, or ERROR: followed by message
|
||||||
*/
|
*/
|
||||||
var behatPressStandard = function(button) {
|
const behatPressStandard = function(button) {
|
||||||
log('Action - Click standard button: ' + button);
|
log('Action - Click standard button: ' + button);
|
||||||
|
|
||||||
// Find button
|
// Find button
|
||||||
var foundButton = null;
|
let foundButton = null;
|
||||||
|
|
||||||
if (window.BehatMoodleAppLegacy) {
|
switch (button) {
|
||||||
var selector;
|
case 'back':
|
||||||
switch (button) {
|
foundButton = findElementsBasedOnText({ text: 'Back' })[0];
|
||||||
case 'back' :
|
break;
|
||||||
selector = 'ion-navbar > button.back-button-md';
|
case 'main menu':
|
||||||
break;
|
foundButton = findElementsBasedOnText({
|
||||||
case 'main menu' :
|
text: 'More',
|
||||||
// Change in app version 3.8.
|
near: { text: 'Notifications' },
|
||||||
selector = 'page-core-mainmenu .tab-button > ion-icon[aria-label=more], ' +
|
})[0];
|
||||||
'page-core-mainmenu .tab-button > ion-icon[aria-label=menu]';
|
break;
|
||||||
break;
|
case 'accounts menu' :
|
||||||
case 'page menu' :
|
foundButton = findElementsBasedOnText({ text: 'Account' })[0];
|
||||||
// This lang string was changed in app version 3.6.
|
break;
|
||||||
selector = 'core-context-menu > button[aria-label=Info], ' +
|
case 'page menu':
|
||||||
'core-context-menu > button[aria-label=Information], ' +
|
foundButton = findElementsBasedOnText({ text: 'Display options' })[0];
|
||||||
'core-context-menu > button[aria-label="Display options"]';
|
break;
|
||||||
break;
|
default:
|
||||||
default:
|
return 'ERROR: Unsupported standard button type';
|
||||||
return 'ERROR: Unsupported standard button type';
|
|
||||||
}
|
|
||||||
var buttons = Array.from(document.querySelectorAll(selector));
|
|
||||||
var tooMany = false;
|
|
||||||
buttons.forEach(function(button) {
|
|
||||||
if (button.offsetParent) {
|
|
||||||
if (foundButton === null) {
|
|
||||||
foundButton = button;
|
|
||||||
} else {
|
|
||||||
tooMany = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!foundButton) {
|
|
||||||
return 'ERROR: Could not find button';
|
|
||||||
}
|
|
||||||
if (tooMany) {
|
|
||||||
return 'ERROR: Found too many buttons';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (button) {
|
|
||||||
case 'back':
|
|
||||||
foundButton = findElementsBasedOnText({ text: 'Back' })[0];
|
|
||||||
break;
|
|
||||||
case 'main menu':
|
|
||||||
foundButton = findElementsBasedOnText({
|
|
||||||
text: 'More',
|
|
||||||
near: { text: 'Notifications' },
|
|
||||||
})[0];
|
|
||||||
break;
|
|
||||||
case 'accounts menu' :
|
|
||||||
foundButton = findElementsBasedOnText({ text: 'Account' })[0];
|
|
||||||
break;
|
|
||||||
case 'page menu':
|
|
||||||
foundButton = findElementsBasedOnText({ text: 'Display options' })[0];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return 'ERROR: Unsupported standard button type';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click button
|
// Click button
|
||||||
|
@ -545,28 +472,22 @@
|
||||||
*
|
*
|
||||||
* @return {string} OK if successful, or ERROR: followed by message
|
* @return {string} OK if successful, or ERROR: followed by message
|
||||||
*/
|
*/
|
||||||
var behatClosePopup = function() {
|
const behatClosePopup = function() {
|
||||||
log('Action - Close popup');
|
log('Action - Close popup');
|
||||||
|
|
||||||
var backdrops = Array.from(document.querySelectorAll('ion-backdrop'));
|
let backdrops = Array.from(document.querySelectorAll('ion-backdrop'));
|
||||||
var found = null;
|
backdrops = backdrops.filter(function(backdrop) {
|
||||||
var tooMany = false;
|
return !!backdrop.offsetParent;
|
||||||
backdrops.forEach(function(backdrop) {
|
|
||||||
if (backdrop.offsetParent) {
|
|
||||||
if (found === null) {
|
|
||||||
found = backdrop;
|
|
||||||
} else {
|
|
||||||
tooMany = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
if (!found) {
|
|
||||||
|
if (!backdrops.length) {
|
||||||
return 'ERROR: Could not find backdrop';
|
return 'ERROR: Could not find backdrop';
|
||||||
}
|
}
|
||||||
if (tooMany) {
|
if (backdrops.length > 1) {
|
||||||
return 'ERROR: Found too many backdrops';
|
return 'ERROR: Found too many backdrops';
|
||||||
}
|
}
|
||||||
found.click();
|
const backdrop = backdrops[0];
|
||||||
|
backdrop.click();
|
||||||
|
|
||||||
// Mark busy until the click finishes processing.
|
// Mark busy until the click finishes processing.
|
||||||
addPendingDelay();
|
addPendingDelay();
|
||||||
|
@ -580,7 +501,7 @@
|
||||||
* @param {object} locator Element locator.
|
* @param {object} locator Element locator.
|
||||||
* @return {string} OK if successful, or ERROR: followed by message
|
* @return {string} OK if successful, or ERROR: followed by message
|
||||||
*/
|
*/
|
||||||
var behatFind = function(locator) {
|
const behatFind = function(locator) {
|
||||||
log('Action - Find', locator);
|
log('Action - Find', locator);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -596,30 +517,13 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get main navigation controller.
|
|
||||||
*
|
|
||||||
* @return {Object} main navigation controller.
|
|
||||||
*/
|
|
||||||
var getNavCtrl = function() {
|
|
||||||
var mainNav = window.appProvider.appCtrl.getRootNavs()[0].getActiveChildNav();
|
|
||||||
if (mainNav && mainNav.tabsIds.length && mainNav.firstSelectedTab) {
|
|
||||||
var tabPos = mainNav.tabsIds.indexOf(mainNav.firstSelectedTab);
|
|
||||||
if (tabPos !== -1 && mainNav._tabs && mainNav._tabs.length > tabPos) {
|
|
||||||
return mainNav._tabs[tabPos];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Fallback to return main nav - this will work but will overlay current tab.
|
|
||||||
return window.appProvider.appCtrl.getRootNavs()[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether an item is selected or not.
|
* Check whether an item is selected or not.
|
||||||
*
|
*
|
||||||
* @param {object} locator Element locator.
|
* @param {object} locator Element locator.
|
||||||
* @return {string} YES or NO if successful, or ERROR: followed by message
|
* @return {string} YES or NO if successful, or ERROR: followed by message
|
||||||
*/
|
*/
|
||||||
var behatIsSelected = function(locator) {
|
const behatIsSelected = function(locator) {
|
||||||
log('Action - Is Selected', locator);
|
log('Action - Is Selected', locator);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -637,10 +541,10 @@
|
||||||
* @param {object} locator Element locator.
|
* @param {object} locator Element locator.
|
||||||
* @return {string} OK if successful, or ERROR: followed by message
|
* @return {string} OK if successful, or ERROR: followed by message
|
||||||
*/
|
*/
|
||||||
var behatPress = function(locator) {
|
const behatPress = function(locator) {
|
||||||
log('Action - Press', locator);
|
log('Action - Press', locator);
|
||||||
|
|
||||||
var found;
|
let found;
|
||||||
try {
|
try {
|
||||||
found = findElementsBasedOnText(locator)[0];
|
found = findElementsBasedOnText(locator)[0];
|
||||||
|
|
||||||
|
@ -661,28 +565,22 @@
|
||||||
*
|
*
|
||||||
* @return {string} OK: followed by header text if successful, or ERROR: followed by message.
|
* @return {string} OK: followed by header text if successful, or ERROR: followed by message.
|
||||||
*/
|
*/
|
||||||
var behatGetHeader = function() {
|
const behatGetHeader = function() {
|
||||||
log('Action - Get header');
|
log('Action - Get header');
|
||||||
|
|
||||||
var result = null;
|
let titles = Array.from(document.querySelectorAll('ion-header ion-title, ion-header h1'));
|
||||||
var resultCount = 0;
|
titles = titles.filter(function(title) {
|
||||||
var titles = Array.from(document.querySelectorAll('ion-header ion-title, ion-header h1'));
|
return isElementVisible(title, document.body);
|
||||||
titles.forEach(function(title) {
|
|
||||||
if (
|
|
||||||
(window.BehatMoodleAppLegacy && title.offsetParent) ||
|
|
||||||
(!window.BehatMoodleAppLegacy && isElementVisible(title, document.body))
|
|
||||||
) {
|
|
||||||
result = title.innerText.trim();
|
|
||||||
resultCount++;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (resultCount > 1) {
|
if (titles.length > 1) {
|
||||||
return 'ERROR: Too many possible titles';
|
return 'ERROR: Too many possible titles';
|
||||||
} else if (!resultCount) {
|
} else if (!titles.length) {
|
||||||
return 'ERROR: No title found';
|
return 'ERROR: No title found';
|
||||||
} else {
|
} else {
|
||||||
return 'OK:' + result;
|
const title = titles[0].innerText.trim();
|
||||||
|
return 'OK:' + title;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -695,67 +593,18 @@
|
||||||
* @param {string} value New value
|
* @param {string} value New value
|
||||||
* @return {string} OK or ERROR: followed by message
|
* @return {string} OK or ERROR: followed by message
|
||||||
*/
|
*/
|
||||||
var behatSetField = function(field, value) {
|
const behatSetField = function(field, value) {
|
||||||
log('Action - Set field ' + field + ' to: ' + value);
|
log('Action - Set field ' + field + ' to: ' + value);
|
||||||
|
|
||||||
if (window.BehatMoodleAppLegacy) {
|
|
||||||
// Find input(s) with given placeholder.
|
const found = findElementsBasedOnText({ text: field, selector: 'input, textarea, [contenteditable="true"]' })[0];
|
||||||
var escapedText = field.replace('"', '""');
|
if (!found) {
|
||||||
var exactMatches = [];
|
return 'ERROR: No matches for text';
|
||||||
var anyMatches = [];
|
|
||||||
findPossibleMatches(
|
|
||||||
'//input[contains(@placeholder, "' + escapedText + '")] |' +
|
|
||||||
'//textarea[contains(@placeholder, "' + escapedText + '")] |' +
|
|
||||||
'//core-rich-text-editor/descendant::div[contains(@data-placeholder-text, "' +
|
|
||||||
escapedText + '")]', function(match) {
|
|
||||||
// Add to array depending on if it's an exact or partial match.
|
|
||||||
var placeholder;
|
|
||||||
if (match.nodeName === 'DIV') {
|
|
||||||
placeholder = match.getAttribute('data-placeholder-text');
|
|
||||||
} else {
|
|
||||||
placeholder = match.getAttribute('placeholder');
|
|
||||||
}
|
|
||||||
if (placeholder.trim() === field) {
|
|
||||||
exactMatches.push(match);
|
|
||||||
} else {
|
|
||||||
anyMatches.push(match);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Select the resulting match.
|
|
||||||
var found = null;
|
|
||||||
do {
|
|
||||||
// If there is an exact text match, use that (regardless of other matches).
|
|
||||||
if (exactMatches.length > 1) {
|
|
||||||
return 'ERROR: Too many exact placeholder matches for text';
|
|
||||||
} else if (exactMatches.length) {
|
|
||||||
found = exactMatches[0];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is one partial text match, use that.
|
|
||||||
if (anyMatches.length > 1) {
|
|
||||||
return 'ERROR: Too many partial placeholder matches for text';
|
|
||||||
} else if (anyMatches.length) {
|
|
||||||
found = anyMatches[0];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (false);
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
return 'ERROR: No matches for text';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
found = findElementsBasedOnText({ text: field, selector: 'input, textarea, [contenteditable="true"]' })[0];
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
return 'ERROR: No matches for text';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Functions to get/set value depending on field type.
|
// Functions to get/set value depending on field type.
|
||||||
var setValue;
|
let setValue;
|
||||||
var getValue;
|
let getValue;
|
||||||
switch (found.nodeName) {
|
switch (found.nodeName) {
|
||||||
case 'INPUT':
|
case 'INPUT':
|
||||||
case 'TEXTAREA':
|
case 'TEXTAREA':
|
||||||
|
@ -777,7 +626,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pretend we have cut and pasted the new text.
|
// Pretend we have cut and pasted the new text.
|
||||||
var event;
|
let event;
|
||||||
if (getValue() !== '') {
|
if (getValue() !== '') {
|
||||||
event = new InputEvent('input', {bubbles: true, view: window, cancelable: true,
|
event = new InputEvent('input', {bubbles: true, view: window, cancelable: true,
|
||||||
inputType: 'devareByCut'});
|
inputType: 'devareByCut'});
|
||||||
|
@ -805,7 +654,7 @@
|
||||||
* @param {string} className Constructor class name
|
* @param {string} className Constructor class name
|
||||||
* @return {object} Component instance
|
* @return {object} Component instance
|
||||||
*/
|
*/
|
||||||
var behatGetComponentInstance = function(selector, className) {
|
const behatGetComponentInstance = function(selector, className) {
|
||||||
const activeElement = Array.from(document.querySelectorAll(`.ion-page:not(.ion-page-hidden) ${selector}`)).pop();
|
const activeElement = Array.from(document.querySelectorAll(`.ion-page:not(.ion-page-hidden) ${selector}`)).pop();
|
||||||
|
|
||||||
if (!activeElement || !activeElement.__ngContext__) {
|
if (!activeElement || !activeElement.__ngContext__) {
|
||||||
|
|
|
@ -69,9 +69,6 @@ class behat_app extends behat_base {
|
||||||
/** @var bool Whether the app is running or not */
|
/** @var bool Whether the app is running or not */
|
||||||
protected $apprunning = false;
|
protected $apprunning = false;
|
||||||
|
|
||||||
/** @var bool Checks whether the app is runing a legacy version (ionic 3) */
|
|
||||||
protected $islegacy;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register listener.
|
* Register listener.
|
||||||
*
|
*
|
||||||
|
@ -432,13 +429,6 @@ class behat_app extends behat_base {
|
||||||
$this->ionicurl = $this->start_or_reuse_ionic();
|
$this->ionicurl = $this->start_or_reuse_ionic();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether the app is running a legacy version.
|
|
||||||
$json = @file_get_contents("{$this->ionicurl}/assets/env.json") ?: @file_get_contents("{$this->ionicurl}/config.json");
|
|
||||||
$data = json_decode($json);
|
|
||||||
$appversion = $data->build->version ?? str_replace('-dev', '', $data->versionname);
|
|
||||||
|
|
||||||
$this->islegacy = version_compare($appversion, '3.9.5', '<');
|
|
||||||
|
|
||||||
// Visit the Ionic URL.
|
// Visit the Ionic URL.
|
||||||
$this->getSession()->visit($this->ionicurl);
|
$this->getSession()->visit($this->ionicurl);
|
||||||
$this->notify_load();
|
$this->notify_load();
|
||||||
|
@ -453,10 +443,7 @@ class behat_app extends behat_base {
|
||||||
if ($title) {
|
if ($title) {
|
||||||
$text = $title->getHtml();
|
$text = $title->getHtml();
|
||||||
|
|
||||||
if (
|
if ($text === 'Moodle App') {
|
||||||
($this->islegacy && $text === 'Moodle Desktop') ||
|
|
||||||
(!$this->islegacy && $text === 'Moodle App')
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -465,8 +452,6 @@ class behat_app extends behat_base {
|
||||||
}, false, 60);
|
}, false, 60);
|
||||||
|
|
||||||
// Run the scripts to install Moodle 'pending' checks.
|
// Run the scripts to install Moodle 'pending' checks.
|
||||||
$islegacyboolean = $this->islegacy ? 'true' : 'false';
|
|
||||||
$this->execute_script("window.BehatMoodleAppLegacy = $islegacyboolean;");
|
|
||||||
$this->execute_script(file_get_contents(__DIR__ . '/app_behat_runtime.js'));
|
$this->execute_script(file_get_contents(__DIR__ . '/app_behat_runtime.js'));
|
||||||
|
|
||||||
if ($restart) {
|
if ($restart) {
|
||||||
|
@ -483,9 +468,7 @@ class behat_app extends behat_base {
|
||||||
// Wait for the onboarding modal to open, if any.
|
// Wait for the onboarding modal to open, if any.
|
||||||
$this->wait_for_pending_js();
|
$this->wait_for_pending_js();
|
||||||
|
|
||||||
$element = $this->islegacy
|
$element = $page->find('xpath', '//core-login-site-onboarding');
|
||||||
? $page->find('xpath', '//page-core-login-site-onboarding')
|
|
||||||
: $page->find('xpath', '//core-login-site-onboarding');
|
|
||||||
|
|
||||||
if ($element) {
|
if ($element) {
|
||||||
$this->i_press_in_the_app('"Skip"');
|
$this->i_press_in_the_app('"Skip"');
|
||||||
|
@ -516,8 +499,8 @@ class behat_app extends behat_base {
|
||||||
|
|
||||||
global $CFG;
|
global $CFG;
|
||||||
|
|
||||||
$this->i_set_the_field_in_the_app($this->islegacy ? 'campus.example.edu' : 'Your site', $CFG->wwwroot);
|
$this->i_set_the_field_in_the_app('Your site', $CFG->wwwroot);
|
||||||
$this->i_press_in_the_app($this->islegacy ? '"Connect!"' : '"Connect to your site"');
|
$this->i_press_in_the_app('"Connect to your site"');
|
||||||
$this->wait_for_pending_js();
|
$this->wait_for_pending_js();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -883,7 +866,7 @@ class behat_app extends behat_base {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Trigger Angular change detection
|
// Trigger Angular change detection
|
||||||
$session->executeScript($this->islegacy ? 'appRef.tick();' : 'ngZone.run(() => {});');
|
$session->executeScript('ngZone.run(() => {});');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -896,7 +879,7 @@ class behat_app extends behat_base {
|
||||||
|
|
||||||
$this->spin(
|
$this->spin(
|
||||||
function() use ($session) {
|
function() use ($session) {
|
||||||
$session->executeScript($this->islegacy ? 'appRef.tick();' : 'ngZone.run(() => {});');
|
$session->executeScript('ngZone.run(() => {});');
|
||||||
|
|
||||||
$nodes = $this->find_all('css', 'core-loading ion-spinner');
|
$nodes = $this->find_all('css', 'core-loading ion-spinner');
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue