illuminator 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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);
|