Files
tasks/tests/node_modules/nightwatch/lib/page-object/command-wrapper.js

239 lines
7.8 KiB
JavaScript

module.exports = new (function() {
/**
* Given an element name, returns that element object
*
* @param {Object} parent The parent page or section
* @param {string} elementName Name of element
* @returns {Object} The element object
*/
function getElement(parent, elementName) {
elementName = elementName.substring(1);
if (!(elementName in parent.elements)) {
throw new Error(elementName + ' was not found in "' + parent.name +
'". Available elements: ' + Object.keys(parent.elements));
}
return parent.elements[elementName];
}
/**
* Given a section name, returns that section object
*
* @param {Object} parent The parent page or section
* @param {string} sectionName Name of section
* @returns {Object} The section object
*/
function getSection(parent, sectionName) {
sectionName = sectionName.substring(1);
if (!(sectionName in parent.section)) {
throw new Error(sectionName + ' was not found in "' + parent.name +
'". Available sections: ' + Object.keys(parent.sections));
}
return parent.section[sectionName];
}
/**
* Calls use(Css|Xpath|Recursion) command
*
* Uses `useXpath`, `useCss`, and `useRecursion` commands.
*
* @param {Object} client The Nightwatch instance
* @param {string} desiredStrategy (css selector|xpath|recursion)
* @returns {null}
*/
function setLocateStrategy(client, desiredStrategy) {
var methodMap = {
xpath : 'useXpath',
'css selector' : 'useCss',
recursion : 'useRecursion'
};
if (desiredStrategy in methodMap) {
client.api[methodMap[desiredStrategy]]();
}
}
/**
* Creates a closure that enables calling commands and assertions on the page or section.
* For all element commands and assertions, it fetches element's selector and locate strategy
* For elements nested under sections, it sets 'recursion' as the locate strategy and passes as its first argument to the command an array of its ancestors + self
* If the command or assertion is not on an element, it calls it with the untouched passed arguments
*
* @param {Object} parent The parent page or section
* @param {function} commandFn The actual command function
* @param {string} commandName The name of the command ("click", "containsText", etc)
* @param {Boolean} [isChaiAssertion]
* @returns {function}
*/
function makeWrappedCommand(parent, commandFn, commandName, isChaiAssertion) {
return function() {
var args = Array.prototype.slice.call(arguments);
var prevLocateStrategy = parent.client.locateStrategy;
var elementCommand = isElementCommand(args);
if (elementCommand) {
var firstArg;
var desiredStrategy;
var callbackIndex;
var originalCallback;
var elementOrSectionName = args.shift();
var getter = (isChaiAssertion && commandName === 'section') ? getSection : getElement;
var elementOrSection = getter(parent, elementOrSectionName);
var ancestors = getAncestorsWithElement(elementOrSection);
if (ancestors.length === 1) {
firstArg = elementOrSection.selector;
desiredStrategy = elementOrSection.locateStrategy;
} else {
firstArg = ancestors;
desiredStrategy = 'recursion';
}
setLocateStrategy(parent.client, desiredStrategy);
args.unshift(firstArg);
// if a callback is being used with this command, wrap it in
// a function that allows us to restore the locate strategy
// to its original value before the callback is called
callbackIndex = findCallbackIndex(args);
if (callbackIndex !== -1) {
originalCallback = args[callbackIndex];
args[callbackIndex] = function callbackWrapper() {
// restore the locate strategy directly through client.locateStrategy.
// setLocateStrategy() can't be used since it uses the api commands
// which get added to the command queue and will not update the
// strategy in time for the callback which is getting immediately
// called after
parent.client.locateStrategy = prevLocateStrategy;
return originalCallback.apply(parent.client.api, arguments);
};
}
}
var c = commandFn.apply(parent.client, args);
if (elementCommand) {
setLocateStrategy(parent.client, prevLocateStrategy);
}
return (isChaiAssertion ? c : parent);
};
}
/**
*
* @param {Array} args
* @return {boolean}
*/
function isElementCommand(args) {
return (args.length > 0) && (args[0].toString().indexOf('@') === 0);
}
/**
* Identifies the location of a callback function within an arguments array.
*
* @param {Array} args Arguments array in which to find the location of a callback.
* @returns {number} Index location of the callback in the args array. If not found, -1 is returned.
*/
function findCallbackIndex(args) {
if (args.length === 0) {
return -1;
}
// callbacks will usually be the last argument. waitFor methods allow an additional
// message argument to follow the callback which will also need to be checked for.
// last argument
var index = args.length - 1;
if (typeof args[index] === 'function') {
return index;
}
// second to last argument (waitfor calls)
index--;
if (typeof args[index] === 'function') {
return index;
}
return -1;
}
/**
* Retrieves an array of ancestors of the supplied element. The last element in the array is the element object itself
*
* @param {Object} element The element
* @returns {Array}
*/
function getAncestorsWithElement(element) {
var elements = [];
function addElement(e) {
elements.unshift(e);
if (e.parent && e.parent.selector) {
addElement(e.parent);
}
}
addElement(element);
return elements;
}
/**
* Adds commands (elements commands, assertions, etc) to the page or section
*
* @param {Object} parent The parent page or section
* @param {Object} target What the command is added to (parent|section or assertion object on parent|section)
* @param {Object} commands
* @returns {null}
*/
function applyCommandsToTarget(parent, target, commands) {
Object.keys(commands).forEach(function(commandName) {
if (isValidAssertion(commandName)) {
target[commandName] = target[commandName] || {};
var isChaiAssertion = commandName === 'expect';
var assertions = commands[commandName];
Object.keys(assertions).forEach(function(assertionName) {
target[commandName][assertionName] = addCommand(target[commandName], assertions[assertionName], assertionName, parent, isChaiAssertion);
});
} else {
target[commandName] = addCommand(target, commands[commandName], commandName, parent, false);
}
});
}
function addCommand(target, commandFn, commandName, parent, isChaiAssertion) {
if (target[commandName]) {
parent.client.results.errors++;
var error = new Error('The command "' + commandName + '" is already defined!');
parent.client.errors.push(error.stack);
throw error;
}
return makeWrappedCommand(parent, commandFn, commandName, isChaiAssertion);
}
function isValidAssertion(commandName) {
return ['assert', 'verify', 'expect'].indexOf(commandName) > -1;
}
/**
* Entrypoint to add commands (elements commands, assertions, etc) to the page or section
*
* @param {Object} parent The parent page or section
* @param {function} commandLoader function that retrieves commands
* @returns {null}
*/
this.addWrappedCommands = function (parent, commandLoader) {
var commands = {};
commands = commandLoader(commands);
applyCommandsToTarget(parent, parent, commands);
};
})();