illuminator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/gem/README.md +37 -0
- data/gem/bin/illuminatorTestRunner.rb +22 -0
- data/gem/lib/illuminator.rb +171 -0
- data/gem/lib/illuminator/argument-parsing.rb +299 -0
- data/gem/lib/illuminator/automation-builder.rb +39 -0
- data/gem/lib/illuminator/automation-runner.rb +589 -0
- data/gem/lib/illuminator/build-artifacts.rb +118 -0
- data/gem/lib/illuminator/device-installer.rb +45 -0
- data/gem/lib/illuminator/host-utils.rb +42 -0
- data/gem/lib/illuminator/instruments-runner.rb +301 -0
- data/gem/lib/illuminator/javascript-runner.rb +98 -0
- data/gem/lib/illuminator/listeners/console-logger.rb +32 -0
- data/gem/lib/illuminator/listeners/full-output.rb +13 -0
- data/gem/lib/illuminator/listeners/instruments-listener.rb +22 -0
- data/gem/lib/illuminator/listeners/intermittent-failure-detector.rb +49 -0
- data/gem/lib/illuminator/listeners/pretty-output.rb +26 -0
- data/gem/lib/illuminator/listeners/saltinel-agent.rb +66 -0
- data/gem/lib/illuminator/listeners/saltinel-listener.rb +26 -0
- data/gem/lib/illuminator/listeners/start-detector.rb +52 -0
- data/gem/lib/illuminator/listeners/stop-detector.rb +46 -0
- data/gem/lib/illuminator/listeners/test-listener.rb +58 -0
- data/gem/lib/illuminator/listeners/trace-error-detector.rb +38 -0
- data/gem/lib/illuminator/options.rb +96 -0
- data/gem/lib/illuminator/resources/IlluminatorGeneratedEnvironment.erb +13 -0
- data/gem/lib/illuminator/resources/IlluminatorGeneratedRunnerForInstruments.erb +19 -0
- data/gem/lib/illuminator/test-definitions.rb +23 -0
- data/gem/lib/illuminator/test-suite.rb +155 -0
- data/gem/lib/illuminator/version.rb +3 -0
- data/gem/lib/illuminator/xcode-builder.rb +144 -0
- data/gem/lib/illuminator/xcode-utils.rb +219 -0
- data/gem/resources/BuildConfiguration.xcconfig +10 -0
- data/gem/resources/js/AppMap.js +767 -0
- data/gem/resources/js/Automator.js +1132 -0
- data/gem/resources/js/Base64.js +142 -0
- data/gem/resources/js/Bridge.js +102 -0
- data/gem/resources/js/Config.js +92 -0
- data/gem/resources/js/Extensions.js +2025 -0
- data/gem/resources/js/Illuminator.js +228 -0
- data/gem/resources/js/Preferences.js +24 -0
- data/gem/resources/scripts/UIAutomationBridge.rb +248 -0
- data/gem/resources/scripts/common.applescript +25 -0
- data/gem/resources/scripts/diff_png.sh +61 -0
- data/gem/resources/scripts/kill_all_sim_processes.sh +17 -0
- data/gem/resources/scripts/plist_to_json.sh +40 -0
- data/gem/resources/scripts/set_hardware_keyboard.applescript +0 -0
- metadata +225 -0
@@ -0,0 +1,1132 @@
|
|
1
|
+
// Automator.js
|
2
|
+
//
|
3
|
+
// creates 'automator' which can build and run scenarios
|
4
|
+
|
5
|
+
var debugAutomator = false;
|
6
|
+
|
7
|
+
(function () {
|
8
|
+
|
9
|
+
var root = this,
|
10
|
+
automator = null;
|
11
|
+
|
12
|
+
// put automator in namespace of importing code
|
13
|
+
if (typeof exports !== 'undefined') {
|
14
|
+
automator = exports;
|
15
|
+
} else {
|
16
|
+
automator = root.automator = {};
|
17
|
+
}
|
18
|
+
|
19
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
20
|
+
//
|
21
|
+
// Exception classes and helpers
|
22
|
+
//
|
23
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
24
|
+
|
25
|
+
automator.ScenarioSetupException = makeErrorClassWithGlobalLocator(__file__(), "ScenarioSetupException");
|
26
|
+
|
27
|
+
|
28
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
29
|
+
//
|
30
|
+
// Callbacks for test initialization - customizing Illuminator's behavior
|
31
|
+
//
|
32
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
33
|
+
|
34
|
+
// table of callbacks that are used by automator. sensible defaults.
|
35
|
+
automator.callback = {
|
36
|
+
onInit: function () { UIALogger.logDebug("Running default automator 'onInit' callback"); },
|
37
|
+
prepare: function () { UIALogger.logDebug("Running default automator 'prepare' callback"); },
|
38
|
+
preScenario: function (parm) { return UIALogger.logDebug("Returning true from default automator 'preScenario' callback " + JSON.stringify(parm)) || true; },
|
39
|
+
onScenarioPass: function (parm) { UIALogger.logDebug("Running default automator 'onScenarioPass' callback " + JSON.stringify(parm)); },
|
40
|
+
onScenarioFail: function (parm) { UIALogger.logDebug("Running default automator 'onScenarioFail' callback " + JSON.stringify(parm)); },
|
41
|
+
complete: function (parm) { UIALogger.logDebug("Running default automator 'complete' callback " + JSON.stringify(parm)); }
|
42
|
+
};
|
43
|
+
|
44
|
+
/**
|
45
|
+
* set the callback for Automator initialization, to be called only once -- after scenarios have been added
|
46
|
+
*
|
47
|
+
* The callback function takes an associative array with the following keys:
|
48
|
+
* - entryPoint
|
49
|
+
*
|
50
|
+
* @param fn the callback function, taking an associative array and whose return value is ignored
|
51
|
+
*/
|
52
|
+
automator.setCallbackOnInit = function (fn) {
|
53
|
+
automator.callback["onInit"] = fn;
|
54
|
+
};
|
55
|
+
|
56
|
+
/**
|
57
|
+
* set the callback for Automator run preparation, to be called only once -- before any scenarios execute
|
58
|
+
*
|
59
|
+
* This callback function will only be called if the automator's entry point requires tests to be run
|
60
|
+
*
|
61
|
+
* @param fn the callback function, taking no arguments and whose return value is ignored
|
62
|
+
*/
|
63
|
+
automator.setCallbackPrepare = function (fn) {
|
64
|
+
automator.callback["prepare"] = fn;
|
65
|
+
};
|
66
|
+
|
67
|
+
/**
|
68
|
+
* set the callback function for pre-scenario initialization -- called before each scenario run
|
69
|
+
*
|
70
|
+
* This callback function may return boolean false, indicating that the pre-scenario setup has failed.
|
71
|
+
* If the return value is false, Illuminator will restart Instruments (up to once per scenario) under
|
72
|
+
* the assumption that the application has reached a dead-end state and must be started fresh.
|
73
|
+
*
|
74
|
+
* @param fn the callback function, taking no arguments and returning false if setup was unsuccessful
|
75
|
+
*/
|
76
|
+
automator.setCallbackPreScenario = function (fn) {
|
77
|
+
automator.callback["preScenario"] = fn;
|
78
|
+
};
|
79
|
+
|
80
|
+
/**
|
81
|
+
* set the callback function for successful completion of a scenario
|
82
|
+
*
|
83
|
+
* The callback function takes an associative array with the following keys:
|
84
|
+
* - scenarioName
|
85
|
+
* - timeStarted
|
86
|
+
* - duration
|
87
|
+
*
|
88
|
+
* @param fn the callback function, taking an associative array and whose return value is ignored
|
89
|
+
*/
|
90
|
+
automator.setCallbackOnScenarioPass = function (fn) {
|
91
|
+
automator.callback["onScenarioPass"] = fn;
|
92
|
+
};
|
93
|
+
|
94
|
+
/**
|
95
|
+
* set the callback function for failed completion of a scenario
|
96
|
+
*
|
97
|
+
* The callback function takes an associative array with the following keys:
|
98
|
+
* - scenarioName
|
99
|
+
* - timeStarted
|
100
|
+
* - duration
|
101
|
+
*
|
102
|
+
* @param fn the callback function, taking an associative array and whose return value is ignored
|
103
|
+
*/
|
104
|
+
automator.setCallbackOnScenarioFail = function (fn) {
|
105
|
+
automator.callback["onScenarioFail"] = fn;
|
106
|
+
};
|
107
|
+
|
108
|
+
/**
|
109
|
+
* set the callback function for the conclusion of all scenarios
|
110
|
+
*
|
111
|
+
* The callback function takes an associative array with the following keys:
|
112
|
+
* - timeStarted
|
113
|
+
* - duration
|
114
|
+
*
|
115
|
+
* @param fn the callback function, taking and whose return value is ignored
|
116
|
+
*/
|
117
|
+
automator.setCallbackComplete = function (fn) {
|
118
|
+
automator.callback["complete"] = fn;
|
119
|
+
};
|
120
|
+
|
121
|
+
|
122
|
+
/**
|
123
|
+
* Safely execute a callback
|
124
|
+
*
|
125
|
+
* @param callbackName the string key into the callback array
|
126
|
+
* @param parameters the parameter array that should be passed to the callback
|
127
|
+
* @param doLogFail whether to log a failure message (i.e. whether we are currently in a test)
|
128
|
+
* @param doLogScreen whether to log the screen on a failure
|
129
|
+
* @return bool whether the callback was successful
|
130
|
+
*/
|
131
|
+
automator._executeCallback = function (callbackName, parameters, doLogFail, doLogScreen) {
|
132
|
+
try {
|
133
|
+
// call with parameters if supplied and return normally
|
134
|
+
var ret;
|
135
|
+
if (parameters === undefined) {
|
136
|
+
ret = automator.callback[callbackName]();
|
137
|
+
} else {
|
138
|
+
ret = automator.callback[callbackName](parameters);
|
139
|
+
}
|
140
|
+
|
141
|
+
// special case for preScenario callback. TODO: make this less special-casey
|
142
|
+
if ("preScenario" == callbackName && false === ret) {
|
143
|
+
notifyIlluminatorFramework("Request instruments restart");
|
144
|
+
}
|
145
|
+
|
146
|
+
return true;
|
147
|
+
} catch (e) {
|
148
|
+
var failMessage = "Callback '" + callbackName + "' failed: " + e;
|
149
|
+
|
150
|
+
// log info as requested
|
151
|
+
if (doLogScreen) {
|
152
|
+
automator.logScreenInfo();
|
153
|
+
}
|
154
|
+
automator.logStackInfo(e);
|
155
|
+
|
156
|
+
if (doLogFail) {
|
157
|
+
UIALogger.logFail(failMessage);
|
158
|
+
} else {
|
159
|
+
UIALogger.logError(failMessage);
|
160
|
+
}
|
161
|
+
return false;
|
162
|
+
}
|
163
|
+
};
|
164
|
+
|
165
|
+
|
166
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
167
|
+
//
|
168
|
+
// Functions to handle automator state -- ways for scenario steps to register side effects
|
169
|
+
//
|
170
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
171
|
+
automator._state = {};
|
172
|
+
automator._state.external = {};
|
173
|
+
|
174
|
+
/**
|
175
|
+
* Reset the automator state for a new test scenario to run
|
176
|
+
*/
|
177
|
+
automator._resetState = function () {
|
178
|
+
config.automatorModality = "reset";
|
179
|
+
automator._state.external = {};
|
180
|
+
automator._state.internal = {"deferredFailures": []};
|
181
|
+
};
|
182
|
+
|
183
|
+
/**
|
184
|
+
* Store a named state in automator
|
185
|
+
*
|
186
|
+
* @param key the name of the state
|
187
|
+
* @param value the value of the state
|
188
|
+
*/
|
189
|
+
automator.setState = function (key, value) {
|
190
|
+
automator._state.external[key] = value;
|
191
|
+
};
|
192
|
+
|
193
|
+
/**
|
194
|
+
* Predicate, whether there is a stored state for a key
|
195
|
+
*
|
196
|
+
* @param key the key to check
|
197
|
+
* @return bool whether there is a state with that key
|
198
|
+
*/
|
199
|
+
automator.hasState = function (key) {
|
200
|
+
return undefined !== automator._state.external[key];
|
201
|
+
};
|
202
|
+
|
203
|
+
/**
|
204
|
+
* Get the state with the given name. If it doesn't exist, return the default value
|
205
|
+
*
|
206
|
+
* @param key the name of the state
|
207
|
+
* @param defaultValue the value to return if key is undefined
|
208
|
+
*/
|
209
|
+
automator.getState = function (key, defaultValue) {
|
210
|
+
if (automator.hasState(key)) return automator._state.external[key];
|
211
|
+
|
212
|
+
UIALogger.logDebug("Automator state '" + key + "' not found, returning default");
|
213
|
+
return defaultValue;
|
214
|
+
};
|
215
|
+
|
216
|
+
/**
|
217
|
+
* Defer a failure until the end of the test scenario
|
218
|
+
*
|
219
|
+
* @param err the error object
|
220
|
+
*/
|
221
|
+
automator.deferFailure = function (err) {
|
222
|
+
UIALogger.logDebug("Deferring an error: " + err);
|
223
|
+
automator.logScreenInfo();
|
224
|
+
automator.logStackInfo(getStackTrace());
|
225
|
+
|
226
|
+
if (automator._state.internal["currentStepName"] && automator._state.internal["currentStepNumber"]) {
|
227
|
+
var msg = "Step " + automator._state.internal["currentStepNumber"];
|
228
|
+
msg += " (" + automator._state.internal["currentStepName"] + "): ";
|
229
|
+
automator._state.internal.deferredFailures.push(msg + err);
|
230
|
+
} else {
|
231
|
+
automator._state.internal.deferredFailures.push("<Undefined step>: " + err);
|
232
|
+
}
|
233
|
+
};
|
234
|
+
|
235
|
+
|
236
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
237
|
+
//
|
238
|
+
// Functions to build test scenarios
|
239
|
+
//
|
240
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
241
|
+
|
242
|
+
automator.allScenarios = []; // flat list of scenarios
|
243
|
+
automator.lastScenario = null; // state variable for building scenarios of steps
|
244
|
+
automator.allScenarioNames = {}; // for ensuring name uniqueness
|
245
|
+
|
246
|
+
// make a lookup array of characters that aren't allowed in tags
|
247
|
+
var disallowedTagChars = "!@#$%^&*()[]{}<>`~,'\"/\\+=;:";
|
248
|
+
automator.disallowedTagChars = {};
|
249
|
+
for (var i = 0; i < disallowedTagChars.length; ++i) {
|
250
|
+
automator.disallowedTagChars[disallowedTagChars[i]] = true;
|
251
|
+
}
|
252
|
+
|
253
|
+
/**
|
254
|
+
* Create an empty scenario with the given name and tags
|
255
|
+
*
|
256
|
+
* @param scenarioName the name for the scenario - must be unique
|
257
|
+
* @param tags array of tags for the scenario
|
258
|
+
* @return this
|
259
|
+
*/
|
260
|
+
automator.createScenario = function (scenarioName, tags) {
|
261
|
+
if (tags === undefined) tags = ["_untagged"]; // always have a tag
|
262
|
+
|
263
|
+
// check uniqueness
|
264
|
+
if (automator.allScenarioNames[scenarioName]) {
|
265
|
+
throw new automator.ScenarioSetupException("Can't create Scenario '" + scenarioName + "', because that name already exists");
|
266
|
+
}
|
267
|
+
automator.allScenarioNames[scenarioName] = true;
|
268
|
+
|
269
|
+
// check for disallowed characters in tag names
|
270
|
+
for (var i = 0; i < tags.length; ++i) {
|
271
|
+
var tag = tags[i];
|
272
|
+
for (var j = 0; j < tag.length; ++j) {
|
273
|
+
c = tag[j];
|
274
|
+
if (automator.disallowedTagChars[c]) {
|
275
|
+
throw new automator.ScenarioSetupException("Disallowed character '" + c + "' in tag '" + tag + "' in scenario '" + scenarioName + "'");
|
276
|
+
}
|
277
|
+
}
|
278
|
+
}
|
279
|
+
|
280
|
+
// create base object
|
281
|
+
automator.lastScenario = {
|
282
|
+
title: scenarioName,
|
283
|
+
steps: []
|
284
|
+
};
|
285
|
+
|
286
|
+
if (debugAutomator) {
|
287
|
+
UIALogger.logDebug(["Automator creating scenario '", scenarioName, "'",
|
288
|
+
" [", tags.join(", "), "]",
|
289
|
+
].join(""));
|
290
|
+
}
|
291
|
+
|
292
|
+
// add tags to objects
|
293
|
+
automator.lastScenario.tags_obj = {}; // convert tags to object
|
294
|
+
for (var i = 0; i < tags.length; ++i) {
|
295
|
+
var t = tags[i];
|
296
|
+
automator.lastScenario.tags_obj[t] = true;
|
297
|
+
}
|
298
|
+
|
299
|
+
// add information about where scenario was created (roughly)
|
300
|
+
var stack = getStackTrace();
|
301
|
+
for (var i = 0; i < stack.length; ++i) {
|
302
|
+
var l = stack[i];
|
303
|
+
if (!(l.nativeCode || l.file == "Automator.js")) {
|
304
|
+
automator.lastScenario.inFile = l.file;
|
305
|
+
automator.lastScenario.definedBy = l.functionName;
|
306
|
+
break;
|
307
|
+
}
|
308
|
+
}
|
309
|
+
|
310
|
+
|
311
|
+
// add new scenario to list
|
312
|
+
automator.allScenarios.push(automator.lastScenario);
|
313
|
+
|
314
|
+
return this;
|
315
|
+
};
|
316
|
+
|
317
|
+
|
318
|
+
/**
|
319
|
+
* Throw an exception if any parameters required for the screen action are not supplied
|
320
|
+
*
|
321
|
+
* @param screenAction an AppMap screen action
|
322
|
+
* @param suppliedParameters associative array of parameters
|
323
|
+
*/
|
324
|
+
automator._assertAllRequiredParameters = function (screenAction, suppliedParameters) {
|
325
|
+
for (var ap in screenAction.params) {
|
326
|
+
if (screenAction.params[ap].required && (undefined === suppliedParameters || undefined === suppliedParameters[ap])) {
|
327
|
+
failmsg = ["In scenario '",
|
328
|
+
automator.lastScenario.title,
|
329
|
+
"' in step ", automator.lastScenario.steps.length + 1,
|
330
|
+
" (", screenAction.name, ") ",
|
331
|
+
"missing required parameter '",
|
332
|
+
ap,
|
333
|
+
"'; ",
|
334
|
+
automator.paramsToString(screenAction.params)
|
335
|
+
].join("");
|
336
|
+
throw new automator.ScenarioSetupException(failmsg);
|
337
|
+
}
|
338
|
+
}
|
339
|
+
};
|
340
|
+
|
341
|
+
|
342
|
+
/**
|
343
|
+
* Throw an exception if any parameters supplied to the screen action are unrecognized
|
344
|
+
*
|
345
|
+
* @param screenAction an AppMap screen action
|
346
|
+
* @param suppliedParameters associative array of parameters
|
347
|
+
*/
|
348
|
+
automator._assertAllKnownParameters = function (screenAction, suppliedParameters) {
|
349
|
+
for (var p in suppliedParameters) {
|
350
|
+
if (undefined === screenAction.params[p]) {
|
351
|
+
failmsg = ["In scenario '",
|
352
|
+
automator.lastScenario.title,
|
353
|
+
"' in step ", automator.lastScenario.steps.length + 1,
|
354
|
+
" (", screenAction.name, ") ",
|
355
|
+
"received undefined parameter '",
|
356
|
+
p,
|
357
|
+
"'; ",
|
358
|
+
automator.paramsToString(screenAction.params)
|
359
|
+
].join("");
|
360
|
+
throw new automator.ScenarioSetupException(failmsg);
|
361
|
+
}
|
362
|
+
}
|
363
|
+
};
|
364
|
+
|
365
|
+
|
366
|
+
/**
|
367
|
+
* Add a step to the most recently created scenario
|
368
|
+
*
|
369
|
+
* @param screenAction an AppMap screen action
|
370
|
+
* @param desiredParameters associative array of parameters
|
371
|
+
* @return this
|
372
|
+
*/
|
373
|
+
automator.withStep = function (screenAction, desiredParameters) {
|
374
|
+
// generate a helpful error message if the screen action isn't defined
|
375
|
+
if (undefined === screenAction || typeof screenAction === 'string') {
|
376
|
+
var failmsg = ["withStep received an undefined screen action in scenario '",
|
377
|
+
automator.lastScenario.title,
|
378
|
+
"'"
|
379
|
+
];
|
380
|
+
var slength = automator.lastScenario.steps.length;
|
381
|
+
if (0 < slength) {
|
382
|
+
var goodAction = automator.lastScenario.steps[slength - 1].action;
|
383
|
+
failmsg.push(" after step " + goodAction.screenName + "." + goodAction.name);
|
384
|
+
}
|
385
|
+
throw new automator.ScenarioSetupException(failmsg.join(""));
|
386
|
+
}
|
387
|
+
|
388
|
+
// debug if necessary
|
389
|
+
if (debugAutomator) {
|
390
|
+
UIALogger.logDebug("screenAction is " + JSON.stringify(screenAction));
|
391
|
+
UIALogger.logDebug("screenAction.params is " + JSON.stringify(screenAction.params));
|
392
|
+
}
|
393
|
+
|
394
|
+
// create a step and check parameters
|
395
|
+
var step = {action: screenAction};
|
396
|
+
automator._assertAllRequiredParameters(screenAction, desiredParameters);
|
397
|
+
if (desiredParameters !== undefined) {
|
398
|
+
automator._assertAllKnownParameters(screenAction, desiredParameters);
|
399
|
+
step.parameters = desiredParameters;
|
400
|
+
}
|
401
|
+
|
402
|
+
// add step to scenario
|
403
|
+
automator.lastScenario.steps.push(step);
|
404
|
+
return this;
|
405
|
+
};
|
406
|
+
|
407
|
+
|
408
|
+
/**
|
409
|
+
* Add steps to the most recently created scenario by running a function that creates them
|
410
|
+
*
|
411
|
+
* @param stepGeneratorFn the function that will generate the steps
|
412
|
+
* @param desiredParameters associative array of parameters
|
413
|
+
* @return this
|
414
|
+
*/
|
415
|
+
automator.withGeneratedSteps = function(stepGeneratorFn, desiredParameters) {
|
416
|
+
stepGeneratorFn(desiredParameters);
|
417
|
+
return this;
|
418
|
+
};
|
419
|
+
|
420
|
+
/**
|
421
|
+
* Add a step to the most recently created scenario if the given condition is true at scenario creation time
|
422
|
+
*
|
423
|
+
* @param screenAction an AppMap screen action
|
424
|
+
* @param desiredParameters associative array of parameters
|
425
|
+
* @return this
|
426
|
+
*/
|
427
|
+
automator.withConditionalStep = function(condition, screenAction, desiredParameters) {
|
428
|
+
if(condition){
|
429
|
+
automator.withStep(screenAction, desiredParameters)
|
430
|
+
}
|
431
|
+
return this;
|
432
|
+
};
|
433
|
+
|
434
|
+
/**
|
435
|
+
* Add a repeated step to the most recently created scenario
|
436
|
+
*
|
437
|
+
* @param screenAction an AppMap screen action
|
438
|
+
* @param quantity the number of times that the step should be executed
|
439
|
+
* @param desiredParameters associative array of parameters, or a function taking 0-indexed run number that returns parameters
|
440
|
+
* @return this
|
441
|
+
*/
|
442
|
+
automator.withRepeatedStep = function(screenAction, quantity, desiredParameters) {
|
443
|
+
var mkParm;
|
444
|
+
|
445
|
+
// use the function they made, or make a function that returns the params they supplied
|
446
|
+
if ((typeof desiredParameters) == "function") {
|
447
|
+
mkParm = desiredParameters;
|
448
|
+
} else {
|
449
|
+
mkParm = function (_) {
|
450
|
+
return desiredParameters;
|
451
|
+
};
|
452
|
+
}
|
453
|
+
|
454
|
+
for (var i = 0; i < quantity; ++i) {
|
455
|
+
automator.withStep(screenAction, mkParm(i));
|
456
|
+
}
|
457
|
+
return this;
|
458
|
+
};
|
459
|
+
|
460
|
+
|
461
|
+
|
462
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
463
|
+
//
|
464
|
+
// Functions to run test scenarios
|
465
|
+
//
|
466
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
467
|
+
|
468
|
+
|
469
|
+
automator.lastRunScenario = null;
|
470
|
+
|
471
|
+
/**
|
472
|
+
* ENTRY POINT: Run tagged scenarios
|
473
|
+
*
|
474
|
+
* Run scenarios that match 3 sets of provided tags
|
475
|
+
*
|
476
|
+
* @param tagsAny array - any scenario with any matching tag will run (if tags=[], run all)
|
477
|
+
* @param tagsAll array - any scenario with AT LEAST the same tags will run
|
478
|
+
* @param tagsNone array - any scenario with NONE of these tags will run
|
479
|
+
* @param randomSeed integer - if provided, will be used to randomize the run order
|
480
|
+
*/
|
481
|
+
automator.runTaggedScenarios = function (tagsAny, tagsAll, tagsNone, randomSeed) {
|
482
|
+
UIALogger.logMessage("Automator running scenarios with tagsAny: [" + tagsAny.join(", ") + "]"
|
483
|
+
+ ", tagsAll: [" + tagsAll.join(", ") + "]"
|
484
|
+
+ ", tagsNone: [" + tagsNone.join(", ") + "]");
|
485
|
+
|
486
|
+
// filter the list by criteria
|
487
|
+
var onesToRun = [];
|
488
|
+
for (var i = 0; i < automator.allScenarios.length; ++i) {
|
489
|
+
var scenario = automator.allScenarios[i];
|
490
|
+
if (automator.scenarioMatchesCriteria(scenario, tagsAny, tagsAll, tagsNone)
|
491
|
+
&& automator.targetSupportsScenario(scenario)) {
|
492
|
+
onesToRun.push(scenario);
|
493
|
+
}
|
494
|
+
}
|
495
|
+
|
496
|
+
automator.runScenarioList(onesToRun, randomSeed);
|
497
|
+
};
|
498
|
+
|
499
|
+
/**
|
500
|
+
* ENTRY POINT: Run named scenarios
|
501
|
+
*
|
502
|
+
* Run scenarios that match the names of those provided
|
503
|
+
*
|
504
|
+
* @param scenarioNames array - the list of named scenarios to run
|
505
|
+
* @param randomSeed integer - if provided, will be used to randomize the run order
|
506
|
+
*/
|
507
|
+
automator.runNamedScenarios = function (scenarioNames, randomSeed) {
|
508
|
+
UIALogger.logMessage("Automator running " + scenarioNames.length + " scenarios by name");
|
509
|
+
|
510
|
+
// filter the list by name
|
511
|
+
var onesToRun = [];
|
512
|
+
// consider the full list of scenarios
|
513
|
+
for (var i = 0; i < automator.allScenarios.length; ++i) {
|
514
|
+
var scenario = automator.allScenarios[i];
|
515
|
+
// check whether any of the given scenario names match the scenario in the master list
|
516
|
+
for (var j = 0; j < scenarioNames.length; ++j) {
|
517
|
+
if (scenario.title == scenarioNames[j] && automator.targetSupportsScenario(scenario)) {
|
518
|
+
onesToRun.push(scenario);
|
519
|
+
}
|
520
|
+
}
|
521
|
+
}
|
522
|
+
|
523
|
+
automator.runScenarioList(onesToRun, randomSeed);
|
524
|
+
};
|
525
|
+
|
526
|
+
|
527
|
+
/**
|
528
|
+
* run a given list of scenarios, optionally in randomized order
|
529
|
+
*
|
530
|
+
* @param senarioList array of scenarios to run, in order
|
531
|
+
* @param ramdomSeed optional number, if provided the test run order will be randomized with this as a seed
|
532
|
+
*/
|
533
|
+
automator.runScenarioList = function (scenarioList, randomSeed) {
|
534
|
+
// randomize if asked
|
535
|
+
if (randomSeed !== undefined) {
|
536
|
+
UIALogger.logMessage("Automator RANDOMIZING scenarios with seed = " + randomSeed);
|
537
|
+
onesToRun = automator.shuffle(scenarioList, randomSeed);
|
538
|
+
}
|
539
|
+
|
540
|
+
// run initial callback and only continue on if it succeeds
|
541
|
+
if (!automator._executeCallback("prepare", undefined, false, false)) {
|
542
|
+
notifyIlluminatorFramework("Successful launch");
|
543
|
+
UIALogger.logMessage("Automator's 'prepare' callback failed, so halting.");
|
544
|
+
return;
|
545
|
+
}
|
546
|
+
|
547
|
+
// At this point, we consider the instruments/app launch to be a success
|
548
|
+
// this function will also serve as notification to the framework that we consider instruments to have started
|
549
|
+
automator.saveIntendedTestList(scenarioList);
|
550
|
+
var offset = config.automatorScenarioOffset;
|
551
|
+
|
552
|
+
var dt;
|
553
|
+
var t0 = getTime();
|
554
|
+
// iterate through scenarios and run them
|
555
|
+
UIALogger.logMessage(scenarioList.length + " scenarios to run");
|
556
|
+
for (var i = 0; i < scenarioList.length; i++) {
|
557
|
+
var message = "Running scenario " + (i + 1 + offset).toString() + " of " + (scenarioList.length + offset);
|
558
|
+
automator.runScenario(scenarioList[i], message);
|
559
|
+
}
|
560
|
+
dt = getTime() - t0;
|
561
|
+
UIALogger.logMessage("Completed running scenario list ("
|
562
|
+
+ scenarioList.length + " of "
|
563
|
+
+ (scenarioList.length + offset) + " total scenarios) "
|
564
|
+
+ " in " + secondsToHMS(dt));
|
565
|
+
|
566
|
+
// create a CSV report for the amount of time spent evaluating selectors
|
567
|
+
automator.saveSelectorReportCSV("selectorTimeCostReport");
|
568
|
+
|
569
|
+
// run completion callback
|
570
|
+
var info = {
|
571
|
+
scenarioCount: scenarioList.length,
|
572
|
+
timeStarted: t0,
|
573
|
+
duration: dt
|
574
|
+
};
|
575
|
+
automator._executeCallback("complete", info, false, false);
|
576
|
+
|
577
|
+
return this;
|
578
|
+
};
|
579
|
+
|
580
|
+
|
581
|
+
/**
|
582
|
+
* Save a JSON structure indicating the list of tests that will be run
|
583
|
+
*
|
584
|
+
* @param scenarioList an array of scenario objects
|
585
|
+
*/
|
586
|
+
automator.saveIntendedTestList = function (scenarioList) {
|
587
|
+
var names = [];
|
588
|
+
for (var i = 0; i < scenarioList.length; ++i) {
|
589
|
+
names.push(scenarioList[i].title);
|
590
|
+
}
|
591
|
+
|
592
|
+
var intendedListPath = config.buildArtifacts.intendedTestList;
|
593
|
+
if (!host().writeToFile(intendedListPath, JSON.stringify({scenarioNames: names}, null, " "))) {
|
594
|
+
throw new IlluminatorRuntimeFailureException("Could not save intended test list to " + intendedListPath);
|
595
|
+
}
|
596
|
+
|
597
|
+
notifyIlluminatorFramework("Saved intended test list to: " + intendedListPath);
|
598
|
+
};
|
599
|
+
|
600
|
+
/**
|
601
|
+
* Save a report to disk of the amount of time evaluating selectors (CSV)
|
602
|
+
*
|
603
|
+
* @param selectorReport the value of extensionProfiler.getCriteriaCost()
|
604
|
+
* @param reportName the basename of the report -- no path, no .csv extension
|
605
|
+
*/
|
606
|
+
automator.saveSelectorReportCSV = function (reportName) {
|
607
|
+
var totalSelectorTime = 0;
|
608
|
+
var selectorReportCsvPath = config.buildArtifacts.root + "/" + reportName + ".csv";
|
609
|
+
var csvLines = ["\"Total time (seconds)\",Count,\"Average time\",Selector"];
|
610
|
+
var selectorReport = extensionProfiler.getCriteriaCost();
|
611
|
+
for (var i = 0; i < selectorReport.length; ++i) {
|
612
|
+
var rec = selectorReport[i];
|
613
|
+
totalSelectorTime += rec.time;
|
614
|
+
csvLines.push(rec.time.toString() + "," + rec.hits + "," + (rec.time / rec.hits) + ",\"" + rec.criteria.replace(/"/g, '""') + '"');
|
615
|
+
}
|
616
|
+
if (host().writeToFile(selectorReportCsvPath, csvLines.join("\n"))) {
|
617
|
+
UIALogger.logMessage("Overall time spent evaluating soft selectors: " + secondsToHMS(totalSelectorTime)
|
618
|
+
+ " - full report at " + selectorReportCsvPath);
|
619
|
+
}
|
620
|
+
};
|
621
|
+
|
622
|
+
/**
|
623
|
+
* Run a single scenario and handle all its reporting callbacks
|
624
|
+
*
|
625
|
+
* @param scenario an automator scenario
|
626
|
+
* @param message string a message to print at the beginning of the test, immediately after the start
|
627
|
+
*/
|
628
|
+
automator.runScenario = function (scenario, message) {
|
629
|
+
var t1 = getTime();
|
630
|
+
var passed = automator._evaluateScenario(scenario, message);
|
631
|
+
var dt = getTime() - t1;
|
632
|
+
var info = {
|
633
|
+
scenarioName: scenario.title,
|
634
|
+
scenarioTags: Object.keys(scenario.tags_obj),
|
635
|
+
timeStarted: t1,
|
636
|
+
duration: dt
|
637
|
+
};
|
638
|
+
|
639
|
+
UIALogger.logDebug("Scenario completed in " + secondsToHMS(dt));
|
640
|
+
automator._executeCallback(passed ? "onScenarioPass" : "onScenarioFail", info, false, false);
|
641
|
+
};
|
642
|
+
|
643
|
+
|
644
|
+
/**
|
645
|
+
* Describe a scenario step (to the log)
|
646
|
+
*
|
647
|
+
* @param stepNumber the 1-indexed number of this step in the scenario
|
648
|
+
* @param totalSteps the total number of steps in this scenario
|
649
|
+
* @param step an automator scenario step
|
650
|
+
*/
|
651
|
+
automator._logScenarioStep = function (stepNumber, totalSteps, step) {
|
652
|
+
// build the parameter list to go in the step description
|
653
|
+
var parameters = step.parameters;
|
654
|
+
var parameters_arr = [];
|
655
|
+
var parameters_str = "";
|
656
|
+
for (var k in parameters) {
|
657
|
+
var v = parameters[k];
|
658
|
+
if (step.action.params[k].useInSummary && undefined !== v) {
|
659
|
+
parameters_arr.push(k.toString() + ": " + v.toString());
|
660
|
+
}
|
661
|
+
}
|
662
|
+
|
663
|
+
// make the descriptive parameter string
|
664
|
+
parameters_str = parameters_arr.length ? (" {" + parameters_arr.join(", ") + "}") : "";
|
665
|
+
|
666
|
+
// build the step description
|
667
|
+
UIALogger.logMessage(["STEP ", stepNumber, " of ", totalSteps, ": ",
|
668
|
+
"(", step.action.appName, ".", step.action.screenName, ".", step.action.name, ") ",
|
669
|
+
step.action.description,
|
670
|
+
parameters_str
|
671
|
+
].join(""));
|
672
|
+
};
|
673
|
+
|
674
|
+
|
675
|
+
/**
|
676
|
+
* Assert that an automator step is on the correct screen
|
677
|
+
*
|
678
|
+
* @param step an automator scenario step
|
679
|
+
*/
|
680
|
+
automator._assertCorrectScreen = function (step) {
|
681
|
+
// assert isCorrectScreen function exists
|
682
|
+
if (undefined === step.action.isCorrectScreen[config.implementation]) {
|
683
|
+
throw new IlluminatorSetupException(["No isCorrectScreen function defined for '",
|
684
|
+
step.action.screenName, ".", step.action.name,
|
685
|
+
"' on ", config.implementation].join(""));
|
686
|
+
}
|
687
|
+
|
688
|
+
// assert correct screen
|
689
|
+
if (!step.action.isCorrectScreen[config.implementation]()) {
|
690
|
+
throw new IlluminatorRuntimeVerificationException(["Failed assertion that '", step.action.screenName, "' is active"].join(""));
|
691
|
+
}
|
692
|
+
};
|
693
|
+
|
694
|
+
|
695
|
+
/**
|
696
|
+
* Extract the implementation-specific action from a step and execute it
|
697
|
+
*
|
698
|
+
* @param step an automator scenario step
|
699
|
+
*/
|
700
|
+
automator._executeStepAction = function (step) {
|
701
|
+
var actFn = step.action.actionFn["default"];
|
702
|
+
if (step.action.actionFn[config.implementation] !== undefined) actFn = step.action.actionFn[config.implementation];
|
703
|
+
|
704
|
+
// call step action with or without parameters, as appropriate
|
705
|
+
if (step.parameters !== undefined) {
|
706
|
+
actFn.call(this, step.parameters);
|
707
|
+
} else if (0 < Object.keys(step.action.params).length) {
|
708
|
+
actFn.call(this, {}); // ensure param'd functions always receive an argument
|
709
|
+
} else {
|
710
|
+
actFn.call(this);
|
711
|
+
}
|
712
|
+
};
|
713
|
+
|
714
|
+
|
715
|
+
/**
|
716
|
+
* Run a single scenario and return its pass/fail status
|
717
|
+
*
|
718
|
+
* @param scenario an automator scenario
|
719
|
+
* @param message string a message to print at the beginning of the test, immediately after the start
|
720
|
+
* @return boolean whether the scenario finished successfully
|
721
|
+
*/
|
722
|
+
automator._evaluateScenario = function (scenario, message) {
|
723
|
+
|
724
|
+
config.automatorModality = "initScenario";
|
725
|
+
var testname = scenario.title;
|
726
|
+
UIALogger.logDebug("###############################################################");
|
727
|
+
UIALogger.logStart(testname);
|
728
|
+
UIALogger.logMessage(["Scenario tags are [", Object.keys(scenario.tags_obj).join(", "), "]"].join(""));
|
729
|
+
if (undefined !== message) {
|
730
|
+
UIALogger.logMessage(message);
|
731
|
+
}
|
732
|
+
|
733
|
+
// print the previous scenario in case we are running with a randomizer
|
734
|
+
if (automator.lastRunScenario) {
|
735
|
+
UIALogger.logMessage("(Previous test was: " + automator.lastRunScenario + ")");
|
736
|
+
} else {
|
737
|
+
UIALogger.logDebug("(No previous test)");
|
738
|
+
}
|
739
|
+
automator.lastRunScenario = scenario.title;
|
740
|
+
|
741
|
+
// initialize the scenario
|
742
|
+
UIALogger.logDebug("----------------------------------------------------------------");
|
743
|
+
UIALogger.logMessage("STEP 0: Reset automator for new scenario");
|
744
|
+
automator._resetState();
|
745
|
+
if (!automator._executeCallback("preScenario",
|
746
|
+
{scenarioName: scenario.title, scenarioTags: Object.keys(scenario.tags_obj)},
|
747
|
+
true, true)) {
|
748
|
+
return false;
|
749
|
+
}
|
750
|
+
|
751
|
+
// wrap the iteration of the test steps in try/catch
|
752
|
+
var step = null;
|
753
|
+
try {
|
754
|
+
config.automatorModality = "executeScenario";
|
755
|
+
|
756
|
+
// if we iterate all steps without exception, test passes
|
757
|
+
for (var i = 0; i < scenario.steps.length; i++) {
|
758
|
+
var step = scenario.steps[i];
|
759
|
+
if (debugAutomator) {
|
760
|
+
UIALogger.logDebug(["DEBUG step ", i + 1, JSON.stringify(step)].join(""));
|
761
|
+
}
|
762
|
+
|
763
|
+
// set the current step name
|
764
|
+
automator._state.internal["currentStepName"] = step.action.screenName + "." + step.action.name;
|
765
|
+
automator._state.internal["currentStepNumber"] = i + 1;
|
766
|
+
|
767
|
+
// log this step to the console
|
768
|
+
UIALogger.logDebug("----------------------------------------------------------------");
|
769
|
+
automator._logScenarioStep(i + 1, scenario.steps.length, step);
|
770
|
+
|
771
|
+
// make sure the screen containing the action is active
|
772
|
+
automator._assertCorrectScreen(step);
|
773
|
+
|
774
|
+
// retrieve and execute the correct step action
|
775
|
+
automator._executeStepAction(step);
|
776
|
+
|
777
|
+
}
|
778
|
+
|
779
|
+
// check for any deferred errors
|
780
|
+
if (0 < automator._state.internal.deferredFailures.length) {
|
781
|
+
for (var i = 0; i < automator._state.internal.deferredFailures.length; ++i) {
|
782
|
+
UIALogger.logMessage("Deferred Failure " + (i + 1).toString() + ": " + automator._state.internal.deferredFailures[i]);
|
783
|
+
}
|
784
|
+
UIALogger.logFail(["The test completed all its steps, but",
|
785
|
+
automator._state.internal.deferredFailures.length.toString(),
|
786
|
+
"failures were deferred"].join(" "));
|
787
|
+
return false;
|
788
|
+
}
|
789
|
+
|
790
|
+
} catch (exception) {
|
791
|
+
config.automatorModality = "handleException";
|
792
|
+
var failmsg = exception.message ? exception.message : exception.toString();
|
793
|
+
var longmsg = (['Step ', i + 1, " of ", scenario.steps.length, " (",
|
794
|
+
step.action.screenName, ".", step.action.name,
|
795
|
+
') failed in scenario: "', scenario.title,
|
796
|
+
'" with message: ', failmsg].join(""));
|
797
|
+
|
798
|
+
UIALogger.logDebug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
|
799
|
+
UIALogger.logDebug(["FAILED:", failmsg].join(" "));
|
800
|
+
notifyIlluminatorFramework("Stack trace follows:");
|
801
|
+
automator.logScreenInfo();
|
802
|
+
automator.logStackInfo(exception);
|
803
|
+
UIATarget.localTarget().captureScreenWithName(step.name);
|
804
|
+
UIALogger.logDebug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
|
805
|
+
|
806
|
+
// check for any deferred errors
|
807
|
+
if (0 < automator._state.internal.deferredFailures.length) {
|
808
|
+
for (var i = 0; i < automator._state.internal.deferredFailures.length; ++i) {
|
809
|
+
UIALogger.logMessage("Deferred Failure " + (i + 1).toString() + ": " + automator._state.internal.deferredFailures[i]);
|
810
|
+
}
|
811
|
+
|
812
|
+
longmsg += [" ::", automator._state.internal.deferredFailures.length.toString(),
|
813
|
+
"other failures had been deferred"].join(" ");
|
814
|
+
}
|
815
|
+
UIALogger.logDebug(longmsg);
|
816
|
+
UIALogger.logFail(longmsg);
|
817
|
+
return false;
|
818
|
+
}
|
819
|
+
|
820
|
+
UIALogger.logPass(testname);
|
821
|
+
return true;
|
822
|
+
};
|
823
|
+
|
824
|
+
|
825
|
+
/**
|
826
|
+
* whether a given scenario is supported by the desired target implementation
|
827
|
+
*
|
828
|
+
* @param scenario an automator scenario
|
829
|
+
* @return bool
|
830
|
+
*/
|
831
|
+
automator.targetSupportsScenario = function (scenario) {
|
832
|
+
// if any actions are neither defined for the current target nor "default"
|
833
|
+
for (var i = 0; i < scenario.steps.length; ++i) {
|
834
|
+
var s = scenario.steps[i];
|
835
|
+
// target not defined
|
836
|
+
if (undefined === s.action.isCorrectScreen[config.implementation]) {
|
837
|
+
UIALogger.logDebug(["Skipping scenario '", scenario.title,
|
838
|
+
"' because screen '", s.action.screenName, "'",
|
839
|
+
" doesn't have a screenIsActive function for ", config.implementation].join(""));
|
840
|
+
return false;
|
841
|
+
}
|
842
|
+
|
843
|
+
// action not defined for target
|
844
|
+
if (s.action.actionFn["default"] === undefined && s.action.actionFn[config.implementation] === undefined) {
|
845
|
+
UIALogger.logDebug(["Skipping scenario '", scenario.title, "' because action '",
|
846
|
+
s.action.screenName, ".", s.action.name,
|
847
|
+
"' isn't suppored on ", config.implementation].join(""));
|
848
|
+
return false;
|
849
|
+
}
|
850
|
+
}
|
851
|
+
|
852
|
+
return true;
|
853
|
+
};
|
854
|
+
|
855
|
+
|
856
|
+
/**
|
857
|
+
* Whether a given scenario is a match for the given tags
|
858
|
+
*
|
859
|
+
* @param scenario an automator scenario
|
860
|
+
* @param tagsAny array - any scenario with any matching tag will run (if tags=[], run all)
|
861
|
+
* @param tagsAll array - any scenario with AT LEAST the same tags will run
|
862
|
+
* @param tagsNone array - any scenario with NONE of these tags will run
|
863
|
+
* @return bool
|
864
|
+
*/
|
865
|
+
automator.scenarioMatchesCriteria = function (scenario, tagsAny, tagsAll, tagsNone) {
|
866
|
+
// if any tagsAll are missing from scenario, fail
|
867
|
+
for (var i = 0; i < tagsAll.length; ++i) {
|
868
|
+
var t = tagsAll[i];
|
869
|
+
if (!(t in scenario.tags_obj)) return false;
|
870
|
+
}
|
871
|
+
|
872
|
+
// if any tagsNone are present in scenario, fail
|
873
|
+
for (var i = 0; i < tagsNone.length; ++i) {
|
874
|
+
var t = tagsNone[i];
|
875
|
+
if (t in scenario.tags_obj) return false;
|
876
|
+
}
|
877
|
+
|
878
|
+
// if no tagsAny specified, special case for ALL tags
|
879
|
+
if (0 == tagsAny.length) return true;
|
880
|
+
|
881
|
+
// if any tagsAny are present in scenario, pass
|
882
|
+
for (var i = 0; i < tagsAny.length; ++i) {
|
883
|
+
var t = tagsAny[i];
|
884
|
+
if (t in scenario.tags_obj) return true;
|
885
|
+
}
|
886
|
+
|
887
|
+
return false; // no tags matched
|
888
|
+
};
|
889
|
+
|
890
|
+
|
891
|
+
/**
|
892
|
+
* Shuffle an array - Knuth Shuffle implementation using a PRNG
|
893
|
+
*
|
894
|
+
* @param array the array to be shuffled
|
895
|
+
* @param seed number to seed the PRNG
|
896
|
+
*/
|
897
|
+
automator.shuffle = function (array, seed) {
|
898
|
+
var idx = array.length;
|
899
|
+
var tmp;
|
900
|
+
var rnd;
|
901
|
+
|
902
|
+
// count backwards from the end of the array, swapping the current element with a random one
|
903
|
+
while (0 !== idx) {
|
904
|
+
// randomize BEFORE decrement because we get a modded value
|
905
|
+
rnd = (Math.pow(2147483647, idx) + seed) % array.length; // use merseinne prime
|
906
|
+
idx -= 1;
|
907
|
+
|
908
|
+
// swap
|
909
|
+
tmp = array[idx];
|
910
|
+
array[idx] = array[rnd];
|
911
|
+
array[rnd] = tmp;
|
912
|
+
}
|
913
|
+
|
914
|
+
return array;
|
915
|
+
};
|
916
|
+
|
917
|
+
|
918
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
919
|
+
//
|
920
|
+
// Functions to describe the Automator
|
921
|
+
//
|
922
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
923
|
+
|
924
|
+
/**
|
925
|
+
* generate a readable description of the parameters that an action expects
|
926
|
+
*
|
927
|
+
* @param actionParams an associative array of parameters that an action defines
|
928
|
+
* @return string
|
929
|
+
*/
|
930
|
+
automator.paramsToString = function (actionParams) {
|
931
|
+
var param_list = [];
|
932
|
+
for (var p in actionParams) {
|
933
|
+
var pp = actionParams[p];
|
934
|
+
param_list.push([p,
|
935
|
+
" (",
|
936
|
+
pp.required ? "required" : "optional",
|
937
|
+
": ",
|
938
|
+
pp.description,
|
939
|
+
")"
|
940
|
+
].join(""));
|
941
|
+
}
|
942
|
+
|
943
|
+
return ["parameters are: [",
|
944
|
+
param_list.join(", "),
|
945
|
+
"]"].join("");
|
946
|
+
};
|
947
|
+
|
948
|
+
|
949
|
+
/**
|
950
|
+
* log some information about the automation environment
|
951
|
+
*/
|
952
|
+
automator.logInfo = function () {
|
953
|
+
UIALogger.logMessage("Target info: " +
|
954
|
+
"name='" + target().name() + "', " +
|
955
|
+
"model='" + target().model() + "', " +
|
956
|
+
"systemName='" + target().systemName() + "', " +
|
957
|
+
"systemVersion='" + target().systemVersion() + "', ");
|
958
|
+
|
959
|
+
var tags = {};
|
960
|
+
for (var s = 0; s < automator.allScenarios.length; ++s) {
|
961
|
+
var scenario = automator.allScenarios[s];
|
962
|
+
|
963
|
+
// get all tags
|
964
|
+
for (var k in scenario.tags_obj) {
|
965
|
+
tags[k] = 1;
|
966
|
+
}
|
967
|
+
}
|
968
|
+
|
969
|
+
var tagsArr = [];
|
970
|
+
for (var k in tags) {
|
971
|
+
tagsArr.push(k);
|
972
|
+
}
|
973
|
+
|
974
|
+
UIALogger.logMessage("Defined tags: '" + tagsArr.join("', '") + "'");
|
975
|
+
|
976
|
+
};
|
977
|
+
|
978
|
+
|
979
|
+
/**
|
980
|
+
* Log information about the currently-shown iOS screen
|
981
|
+
*
|
982
|
+
*/
|
983
|
+
automator.logScreenInfo = function () {
|
984
|
+
//UIATarget.localTarget().logElementTree(); // ugly
|
985
|
+
UIALogger.logDebug(target().elementReferenceDump("target()"));
|
986
|
+
UIALogger.logDebug(target().elementReferenceDump("target()", true));
|
987
|
+
};
|
988
|
+
|
989
|
+
/**
|
990
|
+
* Log information about the current stack
|
991
|
+
*
|
992
|
+
* @param mixed either an error object or a stack array
|
993
|
+
*/
|
994
|
+
automator.logStackInfo = function (mixed) {
|
995
|
+
var stack;
|
996
|
+
|
997
|
+
if (mixed instanceof Array) {
|
998
|
+
stack = mixed;
|
999
|
+
} else {
|
1000
|
+
var decoded = decodeStackTrace(mixed);
|
1001
|
+
|
1002
|
+
if (!decoded.isOK) {
|
1003
|
+
UIALogger.logMessage("Decoding stack trace didn't work: " + decoded.message);
|
1004
|
+
} else {
|
1005
|
+
UIALogger.logMessage("Stack trace from " + decoded.errorName + ":");
|
1006
|
+
}
|
1007
|
+
stack = decoded.stack;
|
1008
|
+
}
|
1009
|
+
|
1010
|
+
for (var i = 0; i < stack.length; ++i) {
|
1011
|
+
var l = stack[i];
|
1012
|
+
var position = " #" + i + ": ";
|
1013
|
+
var funcName = l.functionName === undefined ? "(anonymous)" : l.functionName;
|
1014
|
+
if (l.nativeCode) {
|
1015
|
+
UIALogger.logMessage(position + funcName + " from native code");
|
1016
|
+
} else {
|
1017
|
+
UIALogger.logMessage(position + funcName + " at " + l.file + " line " + l.line + " col " + l.column);
|
1018
|
+
}
|
1019
|
+
}
|
1020
|
+
};
|
1021
|
+
|
1022
|
+
|
1023
|
+
/**
|
1024
|
+
* Render the automator scenarios (and their steps, and parameters) to markdown
|
1025
|
+
*
|
1026
|
+
* @return string containing markdown
|
1027
|
+
*/
|
1028
|
+
automator.toMarkdown = function () {
|
1029
|
+
var ret = ["The following scenarios are defined in the Illuminator Automator:"];
|
1030
|
+
|
1031
|
+
var title = function (rank, text) {
|
1032
|
+
var total = 4;
|
1033
|
+
for (var i = 0; i <= (total - rank); ++i) {
|
1034
|
+
ret.push("");
|
1035
|
+
}
|
1036
|
+
|
1037
|
+
switch (rank) {
|
1038
|
+
case 1:
|
1039
|
+
ret.push(text);
|
1040
|
+
ret.push(Array(Math.max(10, text.length) + 1).join("="));
|
1041
|
+
break;
|
1042
|
+
case 2:
|
1043
|
+
ret.push(text);
|
1044
|
+
ret.push(Array(Math.max(10, text.length) + 1).join("-"));
|
1045
|
+
break;
|
1046
|
+
default:
|
1047
|
+
ret.push(Array(rank + 1).join("#") + " " + text);
|
1048
|
+
}
|
1049
|
+
};
|
1050
|
+
|
1051
|
+
title(1, "Automator Scenarios");
|
1052
|
+
// iterate over scenarios
|
1053
|
+
for (var i = 0; i < automator.allScenarios.length; ++i) {
|
1054
|
+
var scenario = automator.allScenarios[i];
|
1055
|
+
title(2, scenario.title);
|
1056
|
+
ret.push("Tags: `" + Object.keys(scenario.tags_obj).join("`, `") + "`");
|
1057
|
+
ret.push("");
|
1058
|
+
|
1059
|
+
// iterate over steps (actions)
|
1060
|
+
for (var j = 0; j < scenario.steps.length; ++j) {
|
1061
|
+
var step = scenario.steps[j];
|
1062
|
+
ret.push((j + 1).toString() + ". **" + step.action.screenName + "." + step.action.name + "**: " + step.action.description);
|
1063
|
+
|
1064
|
+
// iterate over parameters in the action
|
1065
|
+
for (var k in step.parameters) {
|
1066
|
+
var val = step.parameters[k];
|
1067
|
+
var v;
|
1068
|
+
|
1069
|
+
// formatting based on datatype of parameter
|
1070
|
+
switch ((typeof val).toString()) {
|
1071
|
+
case "number":
|
1072
|
+
case "boolean":
|
1073
|
+
v = val; // no change
|
1074
|
+
break;
|
1075
|
+
case "function":
|
1076
|
+
v = "\n\n```javascript\n" + val + "\n```";
|
1077
|
+
break;
|
1078
|
+
case "string":
|
1079
|
+
v = "`" + val + "`"; // backtick-quote
|
1080
|
+
break;
|
1081
|
+
default:
|
1082
|
+
v = "`" + JSON.stringify(val) + "`"; // stringify and annotate with type
|
1083
|
+
if (val instanceof Array) {
|
1084
|
+
v += " (Array)";
|
1085
|
+
} else {
|
1086
|
+
v += " (" + (typeof val) + ")";
|
1087
|
+
}
|
1088
|
+
}
|
1089
|
+
ret.push(" * `" + k + "` = " + v);
|
1090
|
+
}
|
1091
|
+
}
|
1092
|
+
}
|
1093
|
+
return ret.join("\n");
|
1094
|
+
};
|
1095
|
+
|
1096
|
+
|
1097
|
+
/**
|
1098
|
+
* Render the automator scenarios (tags and steps) to a javascript object
|
1099
|
+
*
|
1100
|
+
* @param includeSteps whether to include the list of test steps in the output
|
1101
|
+
* @return object
|
1102
|
+
*/
|
1103
|
+
automator.toScenarioObject = function (includeSteps) {
|
1104
|
+
var ret = {scenarios: []};
|
1105
|
+
|
1106
|
+
// iterate over scenarios
|
1107
|
+
for (var i = 0; i < automator.allScenarios.length; ++i) {
|
1108
|
+
var scenario = automator.allScenarios[i];
|
1109
|
+
var outScenario = {
|
1110
|
+
title: scenario.title,
|
1111
|
+
tags: Object.keys(scenario.tags_obj),
|
1112
|
+
inFile: scenario.inFile,
|
1113
|
+
definedBy: scenario.definedBy,
|
1114
|
+
};
|
1115
|
+
|
1116
|
+
if (includeSteps) {
|
1117
|
+
outScenario.steps = [];
|
1118
|
+
|
1119
|
+
// iterate over steps (actions)
|
1120
|
+
for (var j = 0; j < scenario.steps.length; ++j) {
|
1121
|
+
var step = scenario.steps[j];
|
1122
|
+
outScenario.steps.push(step.action.screenName + "." + step.action.name);
|
1123
|
+
}
|
1124
|
+
}
|
1125
|
+
ret.scenarios.push(outScenario);
|
1126
|
+
}
|
1127
|
+
|
1128
|
+
return ret;
|
1129
|
+
};
|
1130
|
+
|
1131
|
+
|
1132
|
+
}).call(this);
|