command-wrapper.js
7.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
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);
};
})();