smart_monkey 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +21 -0
- data/README.md +77 -0
- data/Rakefile +57 -0
- data/Troubleshooting.md +61 -0
- data/VERSION +1 -0
- data/bin/smart_monkey +53 -0
- data/lib/bootstrap/css/bootstrap-responsive.css +1109 -0
- data/lib/bootstrap/css/bootstrap-responsive.min.css +9 -0
- data/lib/bootstrap/css/bootstrap.css +6167 -0
- data/lib/bootstrap/css/bootstrap.min.css +9 -0
- data/lib/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/lib/bootstrap/img/glyphicons-halflings.png +0 -0
- data/lib/bootstrap/js/bootstrap.js +2280 -0
- data/lib/bootstrap/js/bootstrap.min.js +6 -0
- data/lib/ios_device_log/deviceconsole +0 -0
- data/lib/smart_monkey.rb +2 -0
- data/lib/smart_monkey/command_helper.rb +71 -0
- data/lib/smart_monkey/monkey_runner.rb +549 -0
- data/lib/smart_monkey/templates/automation_result.xsl +61 -0
- data/lib/smart_monkey/templates/index.html.erb +77 -0
- data/lib/smart_monkey/templates/result.html.erb +110 -0
- data/lib/smart_monkey/templates/result_view.coffee +160 -0
- data/lib/smart_monkey/templates/result_view.js +250 -0
- data/lib/ui-auto-monkey/UIAutoMonkey.js +470 -0
- data/lib/ui-auto-monkey/custom.js +73 -0
- data/lib/ui-auto-monkey/handler/buttonHandler.js +111 -0
- data/lib/ui-auto-monkey/handler/wbScrollViewButtonHandler.js +114 -0
- data/lib/ui-auto-monkey/tuneup/LICENSE +20 -0
- data/lib/ui-auto-monkey/tuneup/assertions.js +402 -0
- data/lib/ui-auto-monkey/tuneup/image_asserter +26 -0
- data/lib/ui-auto-monkey/tuneup/image_assertion.js +65 -0
- data/lib/ui-auto-monkey/tuneup/image_assertion.rb +102 -0
- data/lib/ui-auto-monkey/tuneup/lang-ext.js +76 -0
- data/lib/ui-auto-monkey/tuneup/screen.js +11 -0
- data/lib/ui-auto-monkey/tuneup/test.js +71 -0
- data/lib/ui-auto-monkey/tuneup/test_runner/abbreviated_console_output.rb +38 -0
- data/lib/ui-auto-monkey/tuneup/test_runner/colored_console_output.rb +27 -0
- data/lib/ui-auto-monkey/tuneup/test_runner/console_output.rb +17 -0
- data/lib/ui-auto-monkey/tuneup/test_runner/preprocessor.rb +25 -0
- data/lib/ui-auto-monkey/tuneup/test_runner/run +343 -0
- data/lib/ui-auto-monkey/tuneup/test_runner/xunit_output.rb +114 -0
- data/lib/ui-auto-monkey/tuneup/tuneup.js +6 -0
- data/lib/ui-auto-monkey/tuneup/tuneup_js.podspec +52 -0
- data/lib/ui-auto-monkey/tuneup/uiautomation-ext.js +965 -0
- data/smart_monkey.gemspec +112 -0
- data/spec/spec_helper.rb +12 -0
- metadata +192 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
Pod::Spec.new do |spec|
|
2
|
+
spec.name = 'tuneup_js'
|
3
|
+
spec.version = '1.2.1'
|
4
|
+
spec.license = { :type => 'MIT' }
|
5
|
+
spec.homepage = 'http://www.tuneupjs.org'
|
6
|
+
spec.authors = {
|
7
|
+
'Alex Vollmer' => 'alex.vollmer@gmail.com',
|
8
|
+
'Aaron London' => 'github@postmechanical.com',
|
9
|
+
'Anders Pikas' => 'anders@pikas.se',
|
10
|
+
'BAM' => 'brianm@anytimefitness.com',
|
11
|
+
'BJ Homer' => 'bjhomer@gmail.com',
|
12
|
+
'Brandon Fosdick' => 'bfoz@bfoz.net',
|
13
|
+
'Bruce Gordon' => 'bruceg@yahoo-inc.com',
|
14
|
+
'Faisal Memon' => 'faisalmemonuk@yahoo.com',
|
15
|
+
'Genki Uehara' => 'uehara@cyberdom.co.jp',
|
16
|
+
'Ian Katz' => 'iakatz@paypal.com',
|
17
|
+
'Ian' => 'ifreecarve@gmail.com',
|
18
|
+
'Jerry Jaskierny' => 'jaskiern@channelsix.org',
|
19
|
+
'Kevin Connor' => 'kconnor@book.com',
|
20
|
+
'Thomas Deegan' => 'tdeegan@yahoo-inc.com',
|
21
|
+
'Luke Deniston' => 'luke@agrian.com',
|
22
|
+
'Max Metral' => 'mmetral@paypal.com',
|
23
|
+
'Max Rabin' => 'max@glidetalk.com',
|
24
|
+
'Michael Kamphausen' => 'michael.kamphausen@sinnerschrader.com',
|
25
|
+
'Mirko Beine' => 'mirko.beine@arsinventionis.de',
|
26
|
+
'Ondrej Benes' => 'ondrej.benes@cleverlance.com',
|
27
|
+
'PokerChang' => 'chang@pokerchang.com',
|
28
|
+
'Rob Booth' => 'rob.o.booth@gmail.com',
|
29
|
+
'Ross Sharrott' => 'ross@longweekendmobile.com',
|
30
|
+
'Sebastian Ludwig' => 'sebastian@lurado.de',
|
31
|
+
'Stefan Haubold' => 'stefan.haubold@hait.de',
|
32
|
+
'Steve Moser' => 'smoser2@illinois.edu',
|
33
|
+
'Tony Mann' => 'tmann@thefind.com',
|
34
|
+
'Werner Huber' => 'huber.werner@me.com',
|
35
|
+
'ap4y' => 'lod@pisem.net',
|
36
|
+
'giginet' => 'giginet.net@gmail.com',
|
37
|
+
'hborders' => 'hborders@mail.win.org',
|
38
|
+
'julienp' => 'julien@caffeine.lu',
|
39
|
+
'kimiyash' => 'kimiyash@gmail.com',
|
40
|
+
'kviksilver' => 'berceg@paypal.com',
|
41
|
+
'mah' => 'matthias.hochgatterer@runtastic.com',
|
42
|
+
'rpranata' => 'rendy.pranata@gmail.com',
|
43
|
+
'shifu' => 'chang@pokerchang.com'
|
44
|
+
}
|
45
|
+
spec.summary = 'A JavaScript library to ease automated iOS UI testing with UIAutomation and Instruments.'
|
46
|
+
spec.source = {
|
47
|
+
:git => 'https://github.com/alexvollmer/tuneup_js.git',
|
48
|
+
:tag => '1.2.1'
|
49
|
+
}
|
50
|
+
spec.source_files = '*.js', 'image_asserter', 'image_assertion.rb', 'test_runner/*'
|
51
|
+
spec.requires_arc = false
|
52
|
+
end
|
@@ -0,0 +1,965 @@
|
|
1
|
+
#import "assertions.js";
|
2
|
+
#import "lang-ext.js";
|
3
|
+
|
4
|
+
//We cannot instantiate a UIAElementNil and still get our extensions, so we hold onto one.
|
5
|
+
UIAElementNilSingleton = UIATarget.localTarget().frontMostApp().mainWindow().staticTexts().firstWithName("notFoundx123hfhfhfhfhfhed");
|
6
|
+
|
7
|
+
extend(UIATableView.prototype, {
|
8
|
+
/**
|
9
|
+
* A shortcut for:
|
10
|
+
* this.cells().firstWithName(name)
|
11
|
+
*/
|
12
|
+
cellNamed: function (name) {
|
13
|
+
return this.cells().firstWithName(name);
|
14
|
+
},
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Asserts that this table has a cell with the name (accessibility label)
|
18
|
+
* matching the given +name+ argument.
|
19
|
+
*/
|
20
|
+
assertCellNamed: function (name) {
|
21
|
+
assertNotNull(this.cellNamed(name), "No table cell found named '" + name + "'");
|
22
|
+
}
|
23
|
+
});
|
24
|
+
|
25
|
+
var isNotNil = function () {
|
26
|
+
var ret = undefined !== this && null != this && this.toString() != "[object UIAElementNil]";
|
27
|
+
return ret;
|
28
|
+
};
|
29
|
+
|
30
|
+
extend(UIAElementArray.prototype, {
|
31
|
+
/**
|
32
|
+
* Same as withName, but takes a regular expression
|
33
|
+
*/
|
34
|
+
withNameRegex: function(pattern) {
|
35
|
+
var ret = [];
|
36
|
+
for (var i = 0; i < this.length; ++i) {
|
37
|
+
var elem = this[i];
|
38
|
+
if (elem.isNotNil && elem.isNotNil() && elem.name().match(pattern) !== null) {
|
39
|
+
ret.push(elem);
|
40
|
+
}
|
41
|
+
}
|
42
|
+
return ret;
|
43
|
+
},
|
44
|
+
|
45
|
+
/**
|
46
|
+
* Same as firstWithName, but takes a regular expression
|
47
|
+
*/
|
48
|
+
firstWithNameRegex: function(pattern) {
|
49
|
+
for (var i = 0; i < this.length; ++i) {
|
50
|
+
var elem = this[i];
|
51
|
+
if (elem.isNotNil && elem.isNotNil() && elem.name().match(pattern) !== null) return elem;
|
52
|
+
}
|
53
|
+
return new UIAElementNil();
|
54
|
+
}
|
55
|
+
});
|
56
|
+
|
57
|
+
extend(UIAElement.prototype, {
|
58
|
+
|
59
|
+
/**
|
60
|
+
* Creates a screenshot of the UIElement and saves it to the log directory with the given name
|
61
|
+
*/
|
62
|
+
captureWithName: function(capture_name) {
|
63
|
+
var target = UIATarget.localTarget();
|
64
|
+
target.captureRectWithName(this.rect(), capture_name);
|
65
|
+
},
|
66
|
+
/**
|
67
|
+
* Equality operator
|
68
|
+
*
|
69
|
+
* Properly detects equality of 2 UIAElement objects
|
70
|
+
* - Can return false positives if 2 elements (and ancestors) have the same name, type, and rect()
|
71
|
+
*/
|
72
|
+
equals: function(elem2, maxRecursion) {
|
73
|
+
maxRecursion = maxRecursion === undefined ? -1 : maxRecursion;
|
74
|
+
if (this == elem2) return true; // shortcut when x == x
|
75
|
+
if (null === elem2) return false; // shortcut when one is nil
|
76
|
+
if (!this.isNotNil() || !elem2.isNotNil()) return !this.isNotNil() && !elem2.isNotNil(); // both nil or neither
|
77
|
+
if (this.toString() != elem2.toString()) return false; // element type
|
78
|
+
if (this.name() != elem2.name()) return false;
|
79
|
+
if (JSON.stringify(this.rect()) != JSON.stringify(elem2.rect())) return false; // possible false positives!
|
80
|
+
if (0 == maxRecursion) return true; // stop recursing?
|
81
|
+
if (-100 == maxRecursion) UIALogger.logWarning("Passed 100 recursions in UIAElement.equals");
|
82
|
+
return this.parent() === null || this.parent().equals(elem2.parent(), maxRecursion - 1); // check parent elem
|
83
|
+
},
|
84
|
+
|
85
|
+
/**
|
86
|
+
* General-purpose reduce function
|
87
|
+
*
|
88
|
+
* Applies the callback function to each node in the element tree starting from the current element.
|
89
|
+
*
|
90
|
+
* Callback function takes (previousValue, currentValue <UIAElement>, accessor_prefix, toplevel <UIAElement>)
|
91
|
+
* where previousValue is: initialValue (first time), otherwise the previous return from the callback
|
92
|
+
* currentValue is the UIAElement at the current location in the tree
|
93
|
+
* accessor_prefix is the code to access this element from the toplevel element
|
94
|
+
* toplevel is the top-level element on which this reduce function was called
|
95
|
+
*
|
96
|
+
* visibleOnly prunes the search tree to visible elements only
|
97
|
+
*/
|
98
|
+
_reduce: function(callback, initialValue, visibleOnly) {
|
99
|
+
var reduce_helper = function (elem, acc, prefix) {
|
100
|
+
var scalars = ["navigationBar", "popover", "tabBar", "toolbar"];
|
101
|
+
var vectors = ["activityIndicators", "buttons", "cells", "collectionViews", "images", "links", "navigationBars",
|
102
|
+
"pageIndicators", "pickers", "progressIndicators", "scrollViews", "searchBars",
|
103
|
+
"secureTextFields", "segmentedControls", "sliders", "staticTexts", "switches", "tabBars",
|
104
|
+
"tableViews", "textFields", "textViews", "toolbars", "webViews"];
|
105
|
+
|
106
|
+
// function to visit an element, and add it to an array of what was discovered
|
107
|
+
var accessed = [];
|
108
|
+
var visit = function(someElem, accessor, onlyConsiderNew) {
|
109
|
+
// filter invalid
|
110
|
+
if (undefined === someElem) return;
|
111
|
+
if (!someElem.isNotNil()) return;
|
112
|
+
|
113
|
+
// filter already visited (in cases where we care)
|
114
|
+
if (onlyConsiderNew) {
|
115
|
+
for (var i = 0; i < accessed.length; ++i) {
|
116
|
+
if (accessed[i].equals(someElem, 0)) return;
|
117
|
+
}
|
118
|
+
}
|
119
|
+
accessed.push(someElem);
|
120
|
+
|
121
|
+
// filter based on visibility
|
122
|
+
if (visibleOnly && !someElem.isVisible()) return;
|
123
|
+
acc = reduce_helper(someElem, callback(acc, someElem, accessor, this), accessor);
|
124
|
+
};
|
125
|
+
|
126
|
+
// try to access an element by name instead of number
|
127
|
+
var getNamedIndex = function(someArray, numericIndex) {
|
128
|
+
var e = someArray[numericIndex];
|
129
|
+
var name = e.name();
|
130
|
+
if (name !== null && e.equals(someArray.firstWithName(name), 0)) return '"' + name + '"';
|
131
|
+
return numericIndex;
|
132
|
+
}
|
133
|
+
|
134
|
+
// visit scalars
|
135
|
+
for (var i = 0; i < scalars.length; ++i) {
|
136
|
+
visit(elem[scalars[i]](), prefix + "." + scalars[i] + "()", false);
|
137
|
+
}
|
138
|
+
|
139
|
+
// visit the elements of the vectors
|
140
|
+
for (var i = 0; i < vectors.length; ++i) {
|
141
|
+
if (undefined === elem[vectors[i]]) continue;
|
142
|
+
var elemArray = elem[vectors[i]]();
|
143
|
+
if (undefined === elemArray) continue;
|
144
|
+
for (var j = 0; j < elemArray.length; ++j) {
|
145
|
+
var newElem = elemArray[j];
|
146
|
+
visit(newElem, prefix + "." + vectors[i] + "()[" + getNamedIndex(elemArray, j) + "]", false);
|
147
|
+
}
|
148
|
+
}
|
149
|
+
|
150
|
+
// visit any un-visited items
|
151
|
+
var elemArray = elem.elements()
|
152
|
+
for (var i = 0; i < elemArray.length; ++i) {
|
153
|
+
visit(elemArray[i], prefix + ".elements()[" + getNamedIndex(elemArray, i) + "]", true);
|
154
|
+
}
|
155
|
+
return acc;
|
156
|
+
};
|
157
|
+
|
158
|
+
UIATarget.localTarget().pushTimeout(0);
|
159
|
+
try {
|
160
|
+
return reduce_helper(this, initialValue, "");
|
161
|
+
} catch(e) {
|
162
|
+
throw e;
|
163
|
+
} finally {
|
164
|
+
UIATarget.localTarget().popTimeout();
|
165
|
+
}
|
166
|
+
|
167
|
+
},
|
168
|
+
|
169
|
+
/**
|
170
|
+
* Reduce function
|
171
|
+
*
|
172
|
+
* Applies the callback function to each node in the element tree starting from the current element.
|
173
|
+
*
|
174
|
+
* Callback function takes (previousValue, currentValue <UIAElement>, accessor_prefix, toplevel <UIAElement>)
|
175
|
+
* where previousValue is: initialValue (first time), otherwise the previous return from the callback
|
176
|
+
* currentValue is the UIAElement at the current location in the tree
|
177
|
+
* accessor_prefix is the code to access this element from the toplevel element
|
178
|
+
* toplevel is the top-level element on which this reduce function was called
|
179
|
+
*/
|
180
|
+
reduce: function(callback, initialValue) {
|
181
|
+
return this._reduce(callback, initialValue, false);
|
182
|
+
},
|
183
|
+
|
184
|
+
/**
|
185
|
+
* Reduce function
|
186
|
+
*
|
187
|
+
* Applies the callback function to each visible node in the element tree starting from the current element.
|
188
|
+
*
|
189
|
+
* Callback function takes (previousValue, currentValue <UIAElement>, accessor_prefix, toplevel <UIAElement>)
|
190
|
+
* where previousValue is: initialValue (first time), otherwise the previous return from the callback
|
191
|
+
* currentValue is the UIAElement at the current location in the tree
|
192
|
+
* accessor_prefix is the code to access this element from the toplevel element
|
193
|
+
* toplevel is the top-level element on which this reduce function was called
|
194
|
+
*/
|
195
|
+
reduceVisible: function(callback, initialValue) {
|
196
|
+
return this._reduce(callback, initialValue, true);
|
197
|
+
},
|
198
|
+
|
199
|
+
/**
|
200
|
+
* Find function
|
201
|
+
*
|
202
|
+
* Find elements by given criteria
|
203
|
+
*
|
204
|
+
* Return associative array {accessor: element} of results
|
205
|
+
*/
|
206
|
+
find: function(criteria, varName) {
|
207
|
+
if (criteria === undefined) {
|
208
|
+
UIALogger.logWarning("No criteria passed to find function, so assuming {} and returning all elements");
|
209
|
+
criteria = {};
|
210
|
+
}
|
211
|
+
varName = varName === undefined ? "<root element>" : varName;
|
212
|
+
var visibleOnly = criteria.isVisible === true;
|
213
|
+
|
214
|
+
var knownOptions = {UIAtype: 1, rect: 1, hasKeyboardFocus: 1, isEnabled: 1, isValid: 1,
|
215
|
+
label: 1, name: 1, nameRegex: 1, value: 1};
|
216
|
+
|
217
|
+
// helpful check, mostly catching capitalization errors
|
218
|
+
for (var k in criteria) {
|
219
|
+
if (knownOptions[k] === undefined) {
|
220
|
+
UIALogger.logWarning(this.toString() + ".find() received unknown criteria field '" + k + "' "
|
221
|
+
+ "(known fields are " + Object.keys(knownOptions).join(", ") + ")");
|
222
|
+
|
223
|
+
}
|
224
|
+
}
|
225
|
+
|
226
|
+
var c = criteria;
|
227
|
+
var collect_fn = function(acc, elem, prefix, _) {
|
228
|
+
if (c.UIAtype !== undefined && "[object " + c.UIAtype + "]" != elem.toString()) return acc;
|
229
|
+
if (c.rect !== undefined && JSON.stringify(c.rect) != JSON.stringify(elem.rect())) return acc;
|
230
|
+
if (c.hasKeyboardFocus !== undefined && c.hasKeyboardFocus != elem.hasKeyboardFocus()) return acc;
|
231
|
+
if (c.isEnabled !== undefined && c.isEnabled != elem.isEnabled()) return acc;
|
232
|
+
if (c.isValid !== undefined && c.isValid !== elem.isValid()) return acc;
|
233
|
+
if (c.label !== undefined && c.label != elem.label()) return acc;
|
234
|
+
if (c.name !== undefined && c.name != elem.name()) return acc;
|
235
|
+
if (c.nameRegex !== undefined && (elem.name() === null || elem.name().match(c.nameRegex) === null)) return acc;
|
236
|
+
if (c.value !== undefined && c.value != elem.value()) return acc;
|
237
|
+
|
238
|
+
acc[varName + prefix] = elem;
|
239
|
+
return acc;
|
240
|
+
}
|
241
|
+
|
242
|
+
return this._reduce(collect_fn, {}, visibleOnly);
|
243
|
+
},
|
244
|
+
|
245
|
+
/**
|
246
|
+
* Dump tree in .js format for copy/paste use in code
|
247
|
+
* varname is used as the first element in the canonical name
|
248
|
+
*/
|
249
|
+
elementAccessorDump: function(varName, visibleOnly) {
|
250
|
+
varName = varName === undefined ? "<root element>" : varName;
|
251
|
+
var title = "elementAccessorDump";
|
252
|
+
if (visibleOnly === true) {
|
253
|
+
title += " (of visible elements)";
|
254
|
+
if (!this.isVisible()) return title + ": <none, " + varName + " is not visible>";
|
255
|
+
}
|
256
|
+
|
257
|
+
var collect_fn = function (acc, _, prefix, __) {
|
258
|
+
acc.push(varName + prefix)
|
259
|
+
return acc;
|
260
|
+
};
|
261
|
+
|
262
|
+
return this._reduce(collect_fn, [title + " of " + varName + ":", varName], visibleOnly).join("\n");
|
263
|
+
},
|
264
|
+
|
265
|
+
/**
|
266
|
+
* Dump tree in json format for copy/paste use in AssertWindow and friends
|
267
|
+
*/
|
268
|
+
elementJSONDump: function (recursive, attributes, visibleOnly) {
|
269
|
+
if (visibleOnly && !this.isVisible()) {
|
270
|
+
return "";
|
271
|
+
}
|
272
|
+
|
273
|
+
if (!attributes) {
|
274
|
+
attributes = ["name", "label", "value", "isVisible"];
|
275
|
+
}
|
276
|
+
else if (attributes == 'ALL') {
|
277
|
+
attributes = ["name",
|
278
|
+
"label",
|
279
|
+
"value"
|
280
|
+
].concat(getMethods(this).filter(function (method) {
|
281
|
+
return method.match(/^(is|has)/)
|
282
|
+
}));
|
283
|
+
}
|
284
|
+
|
285
|
+
var jsonStr = "";
|
286
|
+
attributes.forEach(function (attr) {
|
287
|
+
try {
|
288
|
+
var value = this[attr]();
|
289
|
+
if (value != null) { //don't print null values
|
290
|
+
var valueType = typeof (value);
|
291
|
+
//quote strings and numbers. true/false unquoted.
|
292
|
+
if (valueType == "string" || valueType == "number") {
|
293
|
+
value = "'" + value + "'";
|
294
|
+
}
|
295
|
+
jsonStr += attr + ': ' + value + ',\n';
|
296
|
+
}
|
297
|
+
}
|
298
|
+
catch (e) {}
|
299
|
+
}, this);
|
300
|
+
|
301
|
+
if (recursive) {
|
302
|
+
var children = this.elements().toArray();
|
303
|
+
if (children.length > 0) {
|
304
|
+
var curType = null;
|
305
|
+
children.sort().forEach(function (child) {
|
306
|
+
|
307
|
+
function elementTypeToUIAGetter(elementType, parent) {
|
308
|
+
|
309
|
+
//almost all types follow a simple name to getter convention.
|
310
|
+
//UIAImage => images. UIAWindow => windows.
|
311
|
+
var getter = elementType.substring(3).lcfirst() + 's';
|
312
|
+
if (elementType == "UIACollectionCell" || elementType == "UIATableCell") {
|
313
|
+
getter = "cells";
|
314
|
+
}
|
315
|
+
if (parent && !eval('parent.' + getter)) {
|
316
|
+
//Note: we can't use introspection to list valid methods on the parents
|
317
|
+
//because they are all "native" methods and aren't visible.
|
318
|
+
//so the valid getter must be looked up in the documentation and mapped above
|
319
|
+
UIALogger.logError("elementTypeToUIAGetter could not determine getter for " + elementType);
|
320
|
+
}
|
321
|
+
return elementType.substring(3).lcfirst() + 's';
|
322
|
+
}
|
323
|
+
|
324
|
+
var objType = Object.prototype.toString.call(child); //[object UIAWindow]
|
325
|
+
objType = objType.substring(8, objType.length - 1); //UIAWindow
|
326
|
+
// there's a bug that causes leaf elements to have child references
|
327
|
+
// back up to UIAApplication, thus the check for that
|
328
|
+
// this means we can't dump from the "target" level - only mainWindow and below
|
329
|
+
// hopefully this bug goes away 2013-07-02
|
330
|
+
if (objType == "UIAApplication" || objType == "UIAElementNil" || (visibleOnly && !child.isVisible())) {
|
331
|
+
//skip this child
|
332
|
+
return;
|
333
|
+
}
|
334
|
+
|
335
|
+
if (objType == "UIACollectionCell" && !this.isVisible()) {
|
336
|
+
//elements() shows invisible cells that cells() does not
|
337
|
+
return;
|
338
|
+
}
|
339
|
+
if (curType && curType != objType) {
|
340
|
+
//close off open list
|
341
|
+
jsonStr += "],\n";
|
342
|
+
}
|
343
|
+
if (!curType || curType != objType) {
|
344
|
+
curType = objType;
|
345
|
+
//open a new list
|
346
|
+
jsonStr += elementTypeToUIAGetter(objType, this) + ": [\n";
|
347
|
+
}
|
348
|
+
|
349
|
+
var childJsonStr = child.elementJSONDump(true, attributes, visibleOnly);
|
350
|
+
if (childJsonStr) {
|
351
|
+
jsonStr += "{\n";
|
352
|
+
jsonStr += childJsonStr.replace(/^/gm, " ").replace(/ $/, '');
|
353
|
+
jsonStr += "},\n";
|
354
|
+
}
|
355
|
+
else {
|
356
|
+
//child has no attributes to report (all null)
|
357
|
+
jsonStr += " null,\n";
|
358
|
+
}
|
359
|
+
|
360
|
+
}, this);
|
361
|
+
if (curType) {
|
362
|
+
//close off open list
|
363
|
+
jsonStr += "],\n";
|
364
|
+
}
|
365
|
+
}
|
366
|
+
}
|
367
|
+
|
368
|
+
return jsonStr;
|
369
|
+
},
|
370
|
+
|
371
|
+
logElementJSON: function (attributes) {
|
372
|
+
//TODO dump the path to the object in the debug line
|
373
|
+
//ex: target.frontMostApp().mainWindow().toolbars()[0].buttons()["Library"]
|
374
|
+
UIALogger.logDebug("logElementJSON: " + (attributes ? "[" + attributes + "]" : '') + "\n" + this.elementJSONDump(false, attributes));
|
375
|
+
},
|
376
|
+
|
377
|
+
logVisibleElementJSON: function (attributes) {
|
378
|
+
//TODO dump the path to the object in the debug line
|
379
|
+
//ex: target.frontMostApp().mainWindow().toolbars()[0].buttons()["Library"]
|
380
|
+
UIALogger.logDebug("logVisibleElementJSON: " + (attributes ? "[" + attributes + "]" : '') + "\n" + this.elementJSONDump(false, attributes, true));
|
381
|
+
},
|
382
|
+
|
383
|
+
logElementTreeJSON: function (attributes) {
|
384
|
+
UIALogger.logDebug("logElementTreeJSON: " + (attributes ? "[" + attributes + "]" : '') + "\n" + this.elementJSONDump(true, attributes));
|
385
|
+
},
|
386
|
+
|
387
|
+
logVisibleElementTreeJSON: function (attributes) {
|
388
|
+
UIALogger.logDebug("logVisibleElementTreeJSON: " + (attributes ? "[" + attributes + "]" : '') + "\n" + this.elementJSONDump(true, attributes, true));
|
389
|
+
},
|
390
|
+
|
391
|
+
|
392
|
+
/**
|
393
|
+
* Poll till the item becomes visible, up to a specified timeout
|
394
|
+
*/
|
395
|
+
waitUntilVisible: function (timeoutInSeconds) {
|
396
|
+
this.waitUntil(function (element) {
|
397
|
+
return element;
|
398
|
+
}, function (element) {
|
399
|
+
return element.isVisible();
|
400
|
+
}, timeoutInSeconds, "to become visible");
|
401
|
+
},
|
402
|
+
|
403
|
+
/**
|
404
|
+
* Wait until element becomes invisible
|
405
|
+
*/
|
406
|
+
waitUntilInvisible: function (timeoutInSeconds) {
|
407
|
+
this.waitUntil(function (element) {
|
408
|
+
return element;
|
409
|
+
}, function (element) {
|
410
|
+
return !element.isVisible();
|
411
|
+
}, timeoutInSeconds, "to become invisible");
|
412
|
+
},
|
413
|
+
|
414
|
+
/**
|
415
|
+
* Wait until child element with name is added
|
416
|
+
*/
|
417
|
+
waitUntilFoundByName: function (name, timeoutInSeconds) {
|
418
|
+
this.waitUntil(function (element) {
|
419
|
+
return element.elements().firstWithName(name);
|
420
|
+
}, function (element) {
|
421
|
+
return element.isValid();
|
422
|
+
}, timeoutInSeconds, ["to become valid (with name '", name, "')"].join(""));
|
423
|
+
},
|
424
|
+
|
425
|
+
/**
|
426
|
+
* Wait until child element with name is removed
|
427
|
+
*/
|
428
|
+
waitUntilNotFoundByName: function (name, timeoutInSeconds) {
|
429
|
+
this.waitUntil(function (element) {
|
430
|
+
return element.elements().firstWithName(name);
|
431
|
+
}, function (element) {
|
432
|
+
return !element.isValid();
|
433
|
+
}, timeoutInSeconds, ["to become invalid (with name '", name, "'')"].join(""));
|
434
|
+
},
|
435
|
+
|
436
|
+
|
437
|
+
/**
|
438
|
+
* Wait until lookup_function(this) returns a valid lookup
|
439
|
+
* For convenience, return the element that was found
|
440
|
+
* Allow a label for more helpful error messages
|
441
|
+
*/
|
442
|
+
waitUntilAccessorSuccess: function (lookup_function, timeoutInSeconds, label) {
|
443
|
+
var isNotUseless = function (elem) {
|
444
|
+
return elem !== null && elem.isNotNil();
|
445
|
+
}
|
446
|
+
|
447
|
+
// this function will be referenced in waitUntil -- it supplies
|
448
|
+
// the name of what we are waiting for
|
449
|
+
var label_fn = function () {
|
450
|
+
return label;
|
451
|
+
}
|
452
|
+
|
453
|
+
if (!isNotUseless(this)) {
|
454
|
+
throw "waitUntilAccessorSuccess: won't work because the top element isn't valid";
|
455
|
+
}
|
456
|
+
|
457
|
+
this.waitUntil(function (element) {
|
458
|
+
// annotate the found elements with the label function if they are nil
|
459
|
+
try {
|
460
|
+
var possibleMatch = lookup_function(element);
|
461
|
+
if (!possibleMatch.isNotNil() && label !== undefined) possibleMatch.label = label_fn;
|
462
|
+
return possibleMatch;
|
463
|
+
}
|
464
|
+
catch (e) {
|
465
|
+
var fakeNil = new UIAElementNil();
|
466
|
+
if (label !== undefined) fakeNil.label = label_fn;
|
467
|
+
return fakeNil;
|
468
|
+
}
|
469
|
+
}, isNotUseless,
|
470
|
+
timeoutInSeconds, "to become an acceptable return value from the given function");
|
471
|
+
return lookup_function(this);
|
472
|
+
},
|
473
|
+
|
474
|
+
|
475
|
+
/**
|
476
|
+
* Wait until one lookup_function(this) in an associative array of lookup functions
|
477
|
+
* returns a valid lookup.
|
478
|
+
*
|
479
|
+
* Return an associative array of {key: <element found>, elem: <the element that was found>}
|
480
|
+
*/
|
481
|
+
waitUntilAccessorSelect: function (lookup_functions, timeoutInSeconds) {
|
482
|
+
var isNotUseless = function (elem) {
|
483
|
+
return elem !== null && elem.isNotNil();
|
484
|
+
}
|
485
|
+
|
486
|
+
if (!isNotUseless(this)) {
|
487
|
+
throw "waitUntilAccessorSelect: won't work because the top element isn't valid";
|
488
|
+
}
|
489
|
+
|
490
|
+
// composite find function
|
491
|
+
var find_any = function (element) {
|
492
|
+
for (var k in lookup_functions) {
|
493
|
+
var lookup_function = lookup_functions[k];
|
494
|
+
try {
|
495
|
+
var el = lookup_function(element);
|
496
|
+
if (isNotUseless(el)) return {key: k, elem: el};
|
497
|
+
}
|
498
|
+
catch (e) {
|
499
|
+
// ignore
|
500
|
+
}
|
501
|
+
}
|
502
|
+
return UIAElementNilSingleton; //do not create a new UIAElementNil as our prototype extensions are not on that object.
|
503
|
+
};
|
504
|
+
|
505
|
+
//cache find_any() results since we want to use the values later. We don't want to reinvokve find_any() because the UI might have changed since we last found something
|
506
|
+
//and if it no longer finds anything then find_any() will return UIAElementNil(), which we would then return, breaking the api contract to return a {key: elem:} object.
|
507
|
+
var successfulResult = null;
|
508
|
+
|
509
|
+
this.waitUntil(function (element) {
|
510
|
+
var result = find_any(element);
|
511
|
+
if (undefined !== result && result !== UIAElementNilSingleton) { //find_any() will return UIAElementNilSingleton if not found and we don't want to start processing that-especially not result["elem"]
|
512
|
+
successfulResult = result;
|
513
|
+
return result["elem"];
|
514
|
+
}
|
515
|
+
// we cannot support annotating the found elements with the label function if they are nil, because we reuse UIAElementNilSingleton
|
516
|
+
return UIAElementNilSingleton;
|
517
|
+
}, isNotUseless,
|
518
|
+
timeoutInSeconds, "to produce any acceptable return values");
|
519
|
+
return successfulResult;
|
520
|
+
},
|
521
|
+
|
522
|
+
|
523
|
+
/**
|
524
|
+
* Wait until the element has the given name
|
525
|
+
*/
|
526
|
+
waitUntilHasName: function (name, timeoutInSeconds) {
|
527
|
+
|
528
|
+
this.waitUntil(function (element) {
|
529
|
+
return element;
|
530
|
+
}, function (element) {
|
531
|
+
return element.name() == name;
|
532
|
+
}, timeoutInSeconds, "to have the name '" + name + "'");
|
533
|
+
|
534
|
+
},
|
535
|
+
|
536
|
+
|
537
|
+
/**
|
538
|
+
* Wait until element fulfills condition
|
539
|
+
*/
|
540
|
+
waitUntil: function (filterFunction, conditionFunction, timeoutInSeconds, description) {
|
541
|
+
timeoutInSeconds = timeoutInSeconds == null ? 5 : timeoutInSeconds;
|
542
|
+
var element = this;
|
543
|
+
var delay = 0.25;
|
544
|
+
UIATarget.localTarget().pushTimeout(0);
|
545
|
+
try {
|
546
|
+
retry(function () {
|
547
|
+
var filteredElement = filterFunction(element);
|
548
|
+
if (!conditionFunction(filteredElement)) {
|
549
|
+
if (!(filteredElement !== null && filteredElement.isNotNil())) {
|
550
|
+
var label = (filteredElement && filteredElement.label) ? filteredElement.label() : "Element";
|
551
|
+
// make simple error message if the element doesn't exist
|
552
|
+
throw ([label, "failed", description,
|
553
|
+
"within", timeoutInSeconds, "seconds."
|
554
|
+
].join(" "));
|
555
|
+
}
|
556
|
+
else {
|
557
|
+
// build a detailed error message with all available info on the current element
|
558
|
+
var elementDescription = filteredElement.toString();
|
559
|
+
if (filteredElement.name !== undefined) {
|
560
|
+
var elemName = filteredElement.name();
|
561
|
+
if (elemName !== null && elemName != "") {
|
562
|
+
elementDescription += " with name '" + elemName + "'";
|
563
|
+
}
|
564
|
+
}
|
565
|
+
throw (["Element", elementDescription,
|
566
|
+
"failed", description,
|
567
|
+
"within", timeoutInSeconds, "seconds."
|
568
|
+
].join(" "));
|
569
|
+
}
|
570
|
+
}
|
571
|
+
}, Math.max(1, timeoutInSeconds / delay), delay);
|
572
|
+
}
|
573
|
+
catch (e) {
|
574
|
+
throw e;
|
575
|
+
}
|
576
|
+
finally {
|
577
|
+
UIATarget.localTarget().popTimeout();
|
578
|
+
}
|
579
|
+
|
580
|
+
},
|
581
|
+
|
582
|
+
/**
|
583
|
+
* A shortcut for waiting an element to become visible and tap.
|
584
|
+
*/
|
585
|
+
vtap: function (timeout) {
|
586
|
+
if (undefined === timeout) timeout = 10;
|
587
|
+
this.waitUntilVisible(timeout);
|
588
|
+
this.tap();
|
589
|
+
},
|
590
|
+
|
591
|
+
/**
|
592
|
+
* A shortcut for scrolling to a visible item and and tap.
|
593
|
+
*/
|
594
|
+
svtap: function (timeout) {
|
595
|
+
if (undefined === timeout) timeout = 1;
|
596
|
+
try {
|
597
|
+
this.scrollToVisible();
|
598
|
+
} catch (e) {
|
599
|
+
// iOS 6 hack when no scrolling is needed
|
600
|
+
if (e.toString() != "scrollToVisible cannot be used on the element because it does not have a scrollable ancestor.") {
|
601
|
+
throw e;
|
602
|
+
}
|
603
|
+
}
|
604
|
+
//this.waitUntilVisible(timeout);
|
605
|
+
this.tap();
|
606
|
+
},
|
607
|
+
/**
|
608
|
+
* A shortcut for touching an element and waiting for it to disappear.
|
609
|
+
*/
|
610
|
+
tapAndWaitForInvalid: function () {
|
611
|
+
this.tap();
|
612
|
+
this.waitForInvalid();
|
613
|
+
},
|
614
|
+
|
615
|
+
/**
|
616
|
+
* verify that a text field is editable by tapping in it and waiting for a keyboard to appear.
|
617
|
+
*/
|
618
|
+
checkIsEditable: function () {
|
619
|
+
try {
|
620
|
+
var keyboardWasUp = target.frontMostApp().keyboard().isVisible();
|
621
|
+
|
622
|
+
// warn user if this is an object that might be destructively or oddly affected by this check
|
623
|
+
switch (this.toString()) {
|
624
|
+
case "[object UIAButton]":
|
625
|
+
case "[object UIALink]":
|
626
|
+
case "[object UIAActionSheet]":
|
627
|
+
case "[object UIAKey]":
|
628
|
+
case "[object UIAKeyboard]":
|
629
|
+
UIALogger.logWarning("checkIsEditable is going to tap() an object of type " + this.toString());
|
630
|
+
default:
|
631
|
+
this.tap();
|
632
|
+
}
|
633
|
+
|
634
|
+
// wait for keyboard to disappear if it was already active
|
635
|
+
if (keyboardWasUp) UIATarget.localTarget().delay(0.35);
|
636
|
+
target.frontMostApp().keyboard().waitUntilVisible(2);
|
637
|
+
return true;
|
638
|
+
} catch (e) {
|
639
|
+
return false;
|
640
|
+
}
|
641
|
+
},
|
642
|
+
|
643
|
+
isNotNil: isNotNil,
|
644
|
+
});
|
645
|
+
|
646
|
+
extend(UIAElementNil.prototype, {
|
647
|
+
isNotNil: function () {
|
648
|
+
return false;
|
649
|
+
},
|
650
|
+
isValid: function () {
|
651
|
+
return false;
|
652
|
+
},
|
653
|
+
isVisible: function () {
|
654
|
+
return false;
|
655
|
+
}
|
656
|
+
});
|
657
|
+
|
658
|
+
extend(UIAApplication.prototype, {
|
659
|
+
/**
|
660
|
+
* A shortcut for getting the current view controller's title from the
|
661
|
+
* navigation bar. If there is no navigation bar, this method returns null
|
662
|
+
*/
|
663
|
+
navigationTitle: function () {
|
664
|
+
navBar = this.mainWindow().navigationBar();
|
665
|
+
if (navBar) {
|
666
|
+
return navBar.name();
|
667
|
+
}
|
668
|
+
return null;
|
669
|
+
},
|
670
|
+
|
671
|
+
/**
|
672
|
+
* A shortcut for checking that the interface orientation in either
|
673
|
+
* portrait mode
|
674
|
+
*/
|
675
|
+
isPortraitOrientation: function () {
|
676
|
+
var orientation = this.interfaceOrientation();
|
677
|
+
return orientation == UIA_DEVICE_ORIENTATION_PORTRAIT ||
|
678
|
+
orientation == UIA_DEVICE_ORIENTATION_PORTRAIT_UPSIDEDOWN;
|
679
|
+
},
|
680
|
+
|
681
|
+
/**
|
682
|
+
* A shortcut for checking that the interface orientation in one of the
|
683
|
+
* landscape orientations.
|
684
|
+
*/
|
685
|
+
isLandscapeOrientation: function () {
|
686
|
+
var orientation = this.interfaceOrientation();
|
687
|
+
return orientation == UIA_DEVICE_ORIENTATION_LANDSCAPELEFT ||
|
688
|
+
orientation == UIA_DEVICE_ORIENTATION_LANDSCAPERIGHT;
|
689
|
+
}
|
690
|
+
});
|
691
|
+
|
692
|
+
extend(UIANavigationBar.prototype, {
|
693
|
+
/**
|
694
|
+
* Asserts that the left button's name matches the given +name+ argument
|
695
|
+
*/
|
696
|
+
assertLeftButtonNamed: function (name) {
|
697
|
+
assertEquals(name, this.leftButton().name());
|
698
|
+
},
|
699
|
+
|
700
|
+
/**
|
701
|
+
* Asserts that the right button's name matches the given +name+ argument
|
702
|
+
*/
|
703
|
+
assertRightButtonNamed: function (name) {
|
704
|
+
assertEquals(name, this.rightButton().name());
|
705
|
+
}
|
706
|
+
});
|
707
|
+
|
708
|
+
extend(UIATarget.prototype, {
|
709
|
+
/**
|
710
|
+
* A shortcut for checking that the interface orientation in either
|
711
|
+
* portrait mode
|
712
|
+
*/
|
713
|
+
isPortraitOrientation: function () {
|
714
|
+
var orientation = this.deviceOrientation();
|
715
|
+
return orientation == UIA_DEVICE_ORIENTATION_PORTRAIT ||
|
716
|
+
orientation == UIA_DEVICE_ORIENTATION_PORTRAIT_UPSIDEDOWN;
|
717
|
+
},
|
718
|
+
|
719
|
+
/**
|
720
|
+
* A shortcut for checking that the interface orientation in one of the
|
721
|
+
* landscape orientations.
|
722
|
+
*/
|
723
|
+
isLandscapeOrientation: function () {
|
724
|
+
var orientation = this.deviceOrientation();
|
725
|
+
return orientation == UIA_DEVICE_ORIENTATION_LANDSCAPELEFT ||
|
726
|
+
orientation == UIA_DEVICE_ORIENTATION_LANDSCAPERIGHT;
|
727
|
+
},
|
728
|
+
|
729
|
+
/**
|
730
|
+
* Determine if we are running on a simulator.
|
731
|
+
*/
|
732
|
+
isSimulator: function() {
|
733
|
+
return this.model().match(/Simulator/) !== null;
|
734
|
+
},
|
735
|
+
|
736
|
+
/**
|
737
|
+
* A convenience method for detecting that you're running on an iPad
|
738
|
+
*/
|
739
|
+
isDeviceiPad: function () {
|
740
|
+
//model is iPhone Simulator, even when running in iPad mode
|
741
|
+
return this.model().match(/^iPad/) !== null ||
|
742
|
+
this.name().match(/iPad Simulator/) !== null;
|
743
|
+
},
|
744
|
+
|
745
|
+
/**
|
746
|
+
* A convenience method for detecting that you're running on an
|
747
|
+
* iPhone or iPod touch
|
748
|
+
*/
|
749
|
+
isDeviceiPhone: function () {
|
750
|
+
return this.model().match(/^iPad/) === null &&
|
751
|
+
this.name().match(/^iPad Simulator$/) === null;
|
752
|
+
},
|
753
|
+
|
754
|
+
/**
|
755
|
+
* A shortcut for checking if target device is iPhone 5 (or iPod Touch
|
756
|
+
* 5th generation)
|
757
|
+
*/
|
758
|
+
isDeviceiPhone5: function () {
|
759
|
+
var isIphone = this.isDeviceiPhone();
|
760
|
+
var deviceScreen = this.rect();
|
761
|
+
return isIphone && deviceScreen.size.height == 568;
|
762
|
+
},
|
763
|
+
|
764
|
+
/**
|
765
|
+
* A convenience method for producing screenshots without status bar
|
766
|
+
*/
|
767
|
+
captureAppScreenWithName: function (imageName) {
|
768
|
+
var appRect = this.rect();
|
769
|
+
|
770
|
+
appRect.origin.y += 20.0;
|
771
|
+
appRect.size.height -= 20.0;
|
772
|
+
|
773
|
+
return this.captureRectWithName(appRect, imageName);
|
774
|
+
},
|
775
|
+
|
776
|
+
logDeviceInfo: function () {
|
777
|
+
UIALogger.logMessage("Dump Device:");
|
778
|
+
UIALogger.logMessage("DeviceInfo - model: " + this.model());
|
779
|
+
UIALogger.logMessage("DeviceInfo - rect: " + JSON.stringify(this.rect()));
|
780
|
+
UIALogger.logMessage("DeviceInfo - name: " + this.name());
|
781
|
+
UIALogger.logMessage("DeviceInfo - systemName: " + this.systemName());
|
782
|
+
UIALogger.logMessage("DeviceInfo - systemVersion: " + this.systemVersion());
|
783
|
+
}
|
784
|
+
});
|
785
|
+
extend(UIAKeyboard.prototype, {
|
786
|
+
KEYBOARD_TYPE_UNKNOWN: -1,
|
787
|
+
KEYBOARD_TYPE_ALPHA: 0,
|
788
|
+
KEYBOARD_TYPE_ALPHA_CAPS: 1,
|
789
|
+
KEYBOARD_TYPE_NUMBER_AND_PUNCTUATION: 2,
|
790
|
+
KEYBOARD_TYPE_NUMBER: 3,
|
791
|
+
keyboardType: function () {
|
792
|
+
if (this.keys().length < 12) {
|
793
|
+
return this.KEYBOARD_TYPE_NUMBER;
|
794
|
+
}
|
795
|
+
else if (this.keys().firstWithName("a").isNotNil()) {
|
796
|
+
return this.KEYBOARD_TYPE_ALPHA;
|
797
|
+
}
|
798
|
+
else if (this.keys().firstWithName("A").isNotNil()) {
|
799
|
+
return this.KEYBOARD_TYPE_ALPHA_CAPS;
|
800
|
+
}
|
801
|
+
else if (this.keys().firstWithName("1").isNotNil()) {
|
802
|
+
return this.KEYBOARD_TYPE_NUMBER_AND_PUNCTUATION;
|
803
|
+
}
|
804
|
+
else {
|
805
|
+
return this.KEYBOARD_TYPE_UNKNOWN;
|
806
|
+
}
|
807
|
+
}
|
808
|
+
});
|
809
|
+
|
810
|
+
var typeString = function (pstrString, pbClear) {
|
811
|
+
pstrString = pstrString.toString();
|
812
|
+
// handle keyboard not being focused
|
813
|
+
if (!this.hasKeyboardFocus()) {
|
814
|
+
this.tap();
|
815
|
+
}
|
816
|
+
var kb, db; // keyboard, deleteButton
|
817
|
+
var seconds = 2;
|
818
|
+
var waitTime = 0.25;
|
819
|
+
var maxAttempts = seconds / waitTime;
|
820
|
+
var noSuccess = true;
|
821
|
+
var failMsg = null;
|
822
|
+
|
823
|
+
// attempt to get a successful keypress several times -- using the first character
|
824
|
+
// this is a hack for iOS 6.x where the keyboard is sometimes "visible" before usable
|
825
|
+
while ((pbClear || noSuccess) && 0 < maxAttempts--) {
|
826
|
+
try {
|
827
|
+
kb = target.frontMostApp().keyboard();
|
828
|
+
// handle clearing
|
829
|
+
if (pbClear) {
|
830
|
+
db = kb.buttons()["Delete"];
|
831
|
+
if (!db.isNotNil()) db = kb.keys()["Delete"]; // compatibilty hack
|
832
|
+
|
833
|
+
// touchAndHold doesn't work without this next line... not sure why :(
|
834
|
+
db.tap();
|
835
|
+
pbClear = false; // prevent clear on next iteration
|
836
|
+
db.touchAndHold(3.7);
|
837
|
+
|
838
|
+
}
|
839
|
+
|
840
|
+
if (pstrString.length !== 0) {
|
841
|
+
kb.typeString(pstrString.charAt(0));
|
842
|
+
}
|
843
|
+
|
844
|
+
noSuccess = false; // here + no error caught means done
|
845
|
+
}
|
846
|
+
catch (e) {
|
847
|
+
failMsg = e;
|
848
|
+
UIATarget.localTarget().delay(waitTime);
|
849
|
+
}
|
850
|
+
}
|
851
|
+
|
852
|
+
// report any errors that prevented success
|
853
|
+
if (0 > maxAttempts && null !== failMsg) throw "typeString caught error: " + failMsg.toString();
|
854
|
+
|
855
|
+
// now type the rest of the string
|
856
|
+
try {
|
857
|
+
if (pstrString.length > 0) kb.typeString(pstrString.substr(1));
|
858
|
+
} catch (e) {
|
859
|
+
if (-1 == e.toString().indexOf(" failed to tap ")) throw e;
|
860
|
+
|
861
|
+
UIALogger.logDebug("Retrying keyboard action, typing slower this time");
|
862
|
+
this.typeString("", true);
|
863
|
+
kb.setInterKeyDelay(0.2);
|
864
|
+
kb.typeString(pstrString);
|
865
|
+
}
|
866
|
+
|
867
|
+
};
|
868
|
+
|
869
|
+
extend(UIATextField.prototype, {
|
870
|
+
typeString: typeString,
|
871
|
+
clear: function () {
|
872
|
+
this.typeString("", true);
|
873
|
+
}
|
874
|
+
});
|
875
|
+
|
876
|
+
extend(UIATextView.prototype, {
|
877
|
+
typeString: typeString,
|
878
|
+
clear: function () {
|
879
|
+
this.typeString("", true);
|
880
|
+
}
|
881
|
+
});
|
882
|
+
|
883
|
+
extend(UIAPickerWheel.prototype, {
|
884
|
+
|
885
|
+
/*
|
886
|
+
* Better implementation than UIAPickerWheel.selectValue
|
887
|
+
* Works also for texts
|
888
|
+
* Poorly works not for UIDatePickers -> because .values() which get all values of wheel does not work :(
|
889
|
+
* I think this is a bug in UIAutomation!
|
890
|
+
*/
|
891
|
+
scrollToValue: function (valueToSelect) {
|
892
|
+
|
893
|
+
var element = this;
|
894
|
+
|
895
|
+
var values = this.values();
|
896
|
+
var pickerValue = element.value();
|
897
|
+
|
898
|
+
// convert to string
|
899
|
+
valueToSelect = valueToSelect + "";
|
900
|
+
|
901
|
+
// some wheels return for .value() "17. 128 of 267" ?? don't know why
|
902
|
+
// throw away all after "." but be careful lastIndexOf is used because the value can
|
903
|
+
// also have "." in it!! e.g.: "1.2. 13 of 27"
|
904
|
+
if (pickerValue.lastIndexOf(".") != -1) {
|
905
|
+
var currentValue = pickerValue.substr(0, pickerValue.lastIndexOf("."));
|
906
|
+
}
|
907
|
+
else {
|
908
|
+
var currentValue = element.value();
|
909
|
+
}
|
910
|
+
|
911
|
+
var currentValueIndex = values.indexOf(currentValue);
|
912
|
+
var valueToSelectIndex = values.indexOf(valueToSelect);
|
913
|
+
|
914
|
+
if (valueToSelectIndex == -1) {
|
915
|
+
fail("value: " + valueToSelect + " not found in Wheel!");
|
916
|
+
}
|
917
|
+
|
918
|
+
var elementsToScroll = valueToSelectIndex - currentValueIndex;
|
919
|
+
|
920
|
+
UIALogger.logDebug("number of elements to scroll: " + elementsToScroll);
|
921
|
+
if (elementsToScroll > 0) {
|
922
|
+
|
923
|
+
for (i = 0; i < elementsToScroll; i++) {
|
924
|
+
element.tapWithOptions({
|
925
|
+
tapOffset: {
|
926
|
+
x: 0.35,
|
927
|
+
y: 0.67
|
928
|
+
}
|
929
|
+
});
|
930
|
+
target.delay(0.7);
|
931
|
+
}
|
932
|
+
|
933
|
+
}
|
934
|
+
else {
|
935
|
+
|
936
|
+
for (i = 0; i > elementsToScroll; i--) {
|
937
|
+
element.tapWithOptions({
|
938
|
+
tapOffset: {
|
939
|
+
x: 0.35,
|
940
|
+
y: 0.31
|
941
|
+
}
|
942
|
+
});
|
943
|
+
target.delay(0.7);
|
944
|
+
}
|
945
|
+
}
|
946
|
+
},
|
947
|
+
|
948
|
+
/*
|
949
|
+
* Wheels filled with values return for .value() "17. 128 of 267"
|
950
|
+
* ?? don't know why -> for comparisons this is unuseful!!
|
951
|
+
* If you want to check a value of a wheel this function is very helpful
|
952
|
+
*/
|
953
|
+
realValue: function () {
|
954
|
+
|
955
|
+
// current value of wheel
|
956
|
+
var pickerValue = this.value();
|
957
|
+
|
958
|
+
// throw away all after "." but be careful lastIndexOf is used because the value can
|
959
|
+
if (pickerValue.lastIndexOf(".") != -1) {
|
960
|
+
return pickerValue.substr(0, pickerValue.lastIndexOf("."));
|
961
|
+
}
|
962
|
+
|
963
|
+
return this.value();
|
964
|
+
}
|
965
|
+
});
|